Skip to content

Early previewhamur is not on npm yet. The components are being built in the open, follow along on GitHub.

CompareInputs

Tree Select in five frameworks

The same example, live in each framework. One core underneath, so they should behave the same.

Back to the Tree Select docs

Live

React

Loading19.3.1

Loading React…

No event yet

Vue

Loading3.5.44

Loading Vue…

No event yet

Svelte

Loading5.57.3

Loading Svelte…

No event yet

Solid

Loading2.0.0-rc.21

Loading Solid…

No event yet

Angular

Loading22.2.4

Loading Angular…

No event yet

The attributes each framework puts on the open popup, read from the page.

AttributeReactVueSvelteSolidAngular
roleClosedClosedClosedClosedClosed
aria-modalClosedClosedClosedClosedClosed
aria-labelledbyClosedClosedClosedClosedClosed
data-openClosedClosedClosedClosedClosed

Nothing open. Open a tile to fill its column.

Each tile sets modal to false, so five popups can be open at once; that is why aria-modal reads absent.

The same example, in each framework

Each tile above runs this file.

TreeSelect.tsx
import type * as React from 'react'
import { TreeSelect } from '@hamur/react/tree-select'

const labels: Record<string, string> = {
  documents: 'Documents',
  work: 'Work',
  reports: 'Reports',
  slides: 'Slides',
  personal: 'Personal',
  pictures: 'Pictures',
  music: 'Music',
}
const children: Record<string, string[]> = {
  documents: ['work', 'personal'],
  work: ['reports', 'slides'],
}
const roots = ['documents', 'pictures', 'music']

function renderNodes(values: string[]): React.ReactNode {
  return values.map((value) => (
    <TreeSelect.Item key={value} value={value} className="tree-item">
      <TreeSelect.ItemContent className="tree-row">
        <TreeSelect.ItemToggle keepMounted className="tree-toggle">
          <svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true">
            <path d="M6 4l4 4-4 4" />
          </svg>
        </TreeSelect.ItemToggle>
        <TreeSelect.ItemText className="tree-text">
          {labels[value]}
        </TreeSelect.ItemText>
      </TreeSelect.ItemContent>
      {children[value] ? (
        <TreeSelect.Group className="tree-group">
          {renderNodes(children[value])}
        </TreeSelect.Group>
      ) : null}
    </TreeSelect.Item>
  ))
}

export default function TreeSelectExample() {
  return (
    // The whole tree as data, so the trigger can name the value while the popup is closed.
    <TreeSelect.Root
      items={roots}
      getItemChildren={(value: string) => children[value]}
      itemToStringLabel={(value: string) => labels[value]}
      defaultExpanded={['documents']}
    >
      <div className="field">
        <div id="tree-select-label" className="select-label">
          Folder
        </div>
        <TreeSelect.Trigger
          className="select"
          aria-labelledby="tree-select-label"
        >
          <TreeSelect.Value
            className="select-value"
            placeholder="Choose a folder"
          />
          <TreeSelect.Icon className="select-icon" />
        </TreeSelect.Trigger>
      </div>
      <TreeSelect.Portal>
        <TreeSelect.Positioner className="select-positioner" sideOffset={6}>
          <TreeSelect.Popup className="select-popup tree-select-popup">
            <TreeSelect.Tree
              className="tree tree-select-tree"
              aria-label="Folders"
            >
              {renderNodes(roots)}
            </TreeSelect.Tree>
          </TreeSelect.Popup>
        </TreeSelect.Positioner>
      </TreeSelect.Portal>
    </TreeSelect.Root>
  )
}

Measured, per framework

The frameworks against each other only, on the same machine and the same example.

FrameworkTestsSizeOpenCloseMemory
ReactNot measured yetNot measured yetNot measured yetNot measured yetNot measured yet
VueNot measured yetNot measured yetNot measured yetNot measured yetNot measured yet
SvelteNot measured yetNot measured yetNot measured yetNot measured yetNot measured yet
SolidNot measured yetNot measured yetNot measured yetNot measured yetNot measured yet
Angular2Not measured yetNot measured yetNot measured yetNot measured yet

Size is the gzip of the component's entry with core and dom. Open, Close and Memory are medians in Chromium 156 on Apple M3 Max; Memory is what one open and close leaves on the heap. Tests are Tree Select's passing tests in each framework's suites. [N] is a value not measured yet.

Same props, five spellings

The TreeSelect.Root names in each framework's own syntax.

PropReactVueSvelteSolidAngular
childrenchildren#defaultchildrenchildrenng-content
ididididid[id]
valuevaluevaluevaluevalue[value]
defaultValuedefaultValuedefault-valuedefaultValuedefaultValue[defaultValue]
onValueChangeeventonValueChange@value-changeonValueChangeonValueChange(valueChange)
multiplemultiplemultiplemultiplemultiple[multiple]
cascadecascadecascadecascadecascade[cascade]
cascadeValuecascadeValuecascade-valuecascadeValuecascadeValue[cascadeValue]
selectableselectableselectableselectableselectable[selectable]
disallowEmptySelectiondisallowEmptySelectiondisallow-empty-selectiondisallowEmptySelectiondisallowEmptySelection[disallowEmptySelection]
expandedexpandedexpandedexpandedexpanded[expanded]
defaultExpandeddefaultExpandeddefault-expandeddefaultExpandeddefaultExpanded[defaultExpanded]
onExpandedChangeeventonExpandedChange@expanded-changeonExpandedChangeonExpandedChange(expandedChange)
expandOnPressexpandOnPressexpand-on-pressexpandOnPressexpandOnPress[expandOnPress]
loadChildrenloadChildrenload-childrenloadChildrenloadChildren[loadChildren]
onLoadChildrenErroreventonLoadChildrenError@load-children-erroronLoadChildrenErroronLoadChildrenError(loadChildrenError)
itemsitemsitemsitemsitems[items]
getItemChildrengetItemChildrenget-item-childrengetItemChildrengetItemChildren[getItemChildren]
virtualizedvirtualizedvirtualizedvirtualizedvirtualized[virtualized]
onItemHighlightedeventonItemHighlighted@item-highlightedonItemHighlightedonItemHighlighted(itemHighlighted)
itemToStringLabelitemToStringLabelitem-to-string-labelitemToStringLabelitemToStringLabel[itemToStringLabel]
itemToStringValueitemToStringValueitem-to-string-valueitemToStringValueitemToStringValue[itemToStringValue]
isItemEqualToValueisItemEqualToValueis-item-equal-to-valueisItemEqualToValueisItemEqualToValue[isItemEqualToValue]
isItemDisabledisItemDisabledis-item-disabledisItemDisabledisItemDisabled[isItemDisabled]
onHighlightChangeeventonHighlightChange@highlight-changeonHighlightChangeonHighlightChange(highlightChange)
loopFocusloopFocusloop-focusloopFocusloopFocus[loopFocus]
openopenopenopenopen[open]
defaultOpendefaultOpendefault-opendefaultOpendefaultOpen[defaultOpen]
onOpenChangeeventonOpenChange@open-changeonOpenChangeonOpenChange(openChange)
onOpenChangeCompleteeventonOpenChangeComplete@open-change-completeonOpenChangeCompleteonOpenChangeComplete(openChangeComplete)
actionsRefactionsRefactions-refactionsRefactionsRef[actionsRef]
modalmodalmodalmodalmodal[modal]
inputValueinputValueinput-valueinputValueinputValue[inputValue]
defaultInputValuedefaultInputValuedefault-input-valuedefaultInputValuedefaultInputValue[defaultInputValue]
onInputValueChangeeventonInputValueChange@input-value-changeonInputValueChangeonInputValueChange(inputValueChange)
filterfilterfilterfilterfilter[filter]
disableddisableddisableddisableddisabled[disabled]
readOnlyreadOnlyread-onlyreadOnlyreadOnly[readOnly]
requiredrequiredrequiredrequiredrequired[required]
namenamenamenamename[name]
formformformformform[form]
inputRefinputRefinput-refinputRefinputRef[inputRef]
labelslabelslabelslabelslabels[labels]