@smwb/ui-solid 1.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +115 -0
- package/dist/assets/styles/less/components/appBar/appBar.entry.css +1 -1
- package/dist/assets/styles/less/components/avatar/avatar.entry.css +1 -1
- package/dist/assets/styles/less/components/badge/badge.entry.css +1 -1
- package/dist/assets/styles/less/components/breadcrumbs/breadcrumbs.entry.css +1 -1
- package/dist/assets/styles/less/components/button/button.entry.css +1 -1
- package/dist/assets/styles/less/components/buttonGroup/buttonGroup.entry.css +1 -1
- package/dist/assets/styles/less/components/dagView/dagView.entry.css +1 -1
- package/dist/assets/styles/less/components/meter/meter.entry.css +1 -1
- package/dist/assets/styles/less/components/screenDivider/screenDivider.entry.css +1 -1
- package/dist/assets/styles/less/components/sheet/sheet.entry.css +1 -1
- package/dist/assets/styles/less/components/statusDot/statusDot.entry.css +1 -1
- package/dist/assets/styles/less/components/tabGroups/tabGroups.entry.css +1 -1
- package/dist/assets/styles/less/components/tabs/tab/tab.entry.css +1 -1
- package/dist/assets/styles/less/components/toggle/toggle.entry.css +1 -1
- package/dist/assets/styles/less/components/treeView/treeView.entry.css +1 -1
- package/dist/assets/styles/less/tokens.entry.css +1 -1
- package/dist/components/dataDisplay/avatar/avatar.d.ts +5 -2
- package/dist/components/dataDisplay/avatar/avatar.js +9 -9
- package/dist/components/dataDisplay/avatar/avatar.js.map +1 -1
- package/dist/components/dataDisplay/badge/badge.d.ts +2 -1
- package/dist/components/dataDisplay/badge/badge.js.map +1 -1
- package/dist/components/dataDisplay/chart/chart.d.ts +16 -8
- package/dist/components/dataDisplay/chart/chartFull.d.ts +14 -0
- package/dist/components/dataDisplay/chart/core.d.ts +19 -0
- package/dist/components/dataDisplay/chart/index.d.ts +1 -1
- package/dist/components/dataDisplay/dagView/dagView.d.ts +4 -0
- package/dist/components/dataDisplay/dagView/dagView.js +124 -123
- package/dist/components/dataDisplay/dagView/dagView.js.map +1 -1
- package/dist/components/dataDisplay/dagView/index.d.ts +1 -1
- package/dist/components/dataDisplay/meter/index.d.ts +1 -1
- package/dist/components/dataDisplay/meter/meter.d.ts +14 -0
- package/dist/components/dataDisplay/meter/meter.js +32 -30
- package/dist/components/dataDisplay/meter/meter.js.map +1 -1
- package/dist/components/dataDisplay/statusDot/index.d.ts +1 -1
- package/dist/components/dataDisplay/statusDot/statusDot.d.ts +10 -1
- package/dist/components/dataDisplay/statusDot/statusDot.js +20 -11
- package/dist/components/dataDisplay/statusDot/statusDot.js.map +1 -1
- package/dist/components/dataDisplay/treeView/treeView.d.ts +18 -2
- package/dist/components/dataDisplay/treeView/treeView.js +140 -121
- package/dist/components/dataDisplay/treeView/treeView.js.map +1 -1
- package/dist/components/inputs/button/button.d.ts +7 -0
- package/dist/components/inputs/button/button.js +57 -51
- package/dist/components/inputs/button/button.js.map +1 -1
- package/dist/components/inputs/toggle/toggle.d.ts +6 -0
- package/dist/components/inputs/toggle/toggle.js +23 -23
- package/dist/components/inputs/toggle/toggle.js.map +1 -1
- package/dist/components/layout/appLayout/appLayout.d.ts +6 -1
- package/dist/components/layout/appLayout/appLayout.js +49 -42
- package/dist/components/layout/appLayout/appLayout.js.map +1 -1
- package/dist/components/layout/screenDivider/screenDivider.d.ts +12 -0
- package/dist/components/layout/screenDivider/screenDivider.js +87 -46
- package/dist/components/layout/screenDivider/screenDivider.js.map +1 -1
- package/dist/components/layout/sheet/sheet.d.ts +11 -0
- package/dist/components/layout/sheet/sheet.js +122 -102
- package/dist/components/layout/sheet/sheet.js.map +1 -1
- package/dist/components/layout/tabGroups/index.d.ts +1 -1
- package/dist/components/layout/tabGroups/tabGroups.d.ts +44 -1
- package/dist/components/layout/tabGroups/tabGroups.js +500 -460
- package/dist/components/layout/tabGroups/tabGroups.js.map +1 -1
- package/dist/components/layout/tabGroups/tabGroupsFocus.d.ts +16 -0
- package/dist/components/layout/tabGroups/tabGroupsFocus.js +102 -70
- package/dist/components/layout/tabGroups/tabGroupsFocus.js.map +1 -1
- package/dist/components/navigation/appBar/appBar.d.ts +12 -0
- package/dist/components/navigation/appBar/appBar.js.map +1 -1
- package/dist/components/navigation/breadcrumbs/breadcrumbs.d.ts +9 -0
- package/dist/components/navigation/breadcrumbs/breadcrumbs.js +47 -31
- package/dist/components/navigation/breadcrumbs/breadcrumbs.js.map +1 -1
- package/dist/components/navigation/tabs/tab/tab.js +71 -68
- package/dist/components/navigation/tabs/tab/tab.js.map +1 -1
- package/dist/components/navigation/tabs/tabs.d.ts +15 -1
- package/dist/components/navigation/tabs/tabs.js +19 -16
- package/dist/components/navigation/tabs/tabs.js.map +1 -1
- package/dist/components/navigation/tabs/tabsContext.d.ts +2 -0
- package/dist/components/navigation/tabs/tabsContext.js.map +1 -1
- package/dist/headless/components/dataDisplay/avatar/avatar.js +9 -9
- package/dist/headless/components/dataDisplay/avatar/avatar.js.map +1 -1
- package/dist/headless/components/dataDisplay/badge/badge.js.map +1 -1
- package/dist/headless/components/dataDisplay/dagView/dagView.js +133 -132
- package/dist/headless/components/dataDisplay/dagView/dagView.js.map +1 -1
- package/dist/headless/components/dataDisplay/meter/meter.js +32 -30
- package/dist/headless/components/dataDisplay/meter/meter.js.map +1 -1
- package/dist/headless/components/dataDisplay/statusDot/statusDot.js +19 -10
- package/dist/headless/components/dataDisplay/statusDot/statusDot.js.map +1 -1
- package/dist/headless/components/dataDisplay/treeView/treeView.js +139 -120
- package/dist/headless/components/dataDisplay/treeView/treeView.js.map +1 -1
- package/dist/headless/components/inputs/button/button.js +57 -51
- package/dist/headless/components/inputs/button/button.js.map +1 -1
- package/dist/headless/components/inputs/toggle/toggle.js +23 -23
- package/dist/headless/components/inputs/toggle/toggle.js.map +1 -1
- package/dist/headless/components/layout/appLayout/appLayout.js +52 -45
- package/dist/headless/components/layout/appLayout/appLayout.js.map +1 -1
- package/dist/headless/components/layout/screenDivider/screenDivider.js +88 -47
- package/dist/headless/components/layout/screenDivider/screenDivider.js.map +1 -1
- package/dist/headless/components/layout/sheet/sheet.js +124 -104
- package/dist/headless/components/layout/sheet/sheet.js.map +1 -1
- package/dist/headless/components/layout/tabGroups/tabGroups.js +500 -460
- package/dist/headless/components/layout/tabGroups/tabGroups.js.map +1 -1
- package/dist/headless/components/layout/tabGroups/tabGroupsFocus.js +102 -70
- package/dist/headless/components/layout/tabGroups/tabGroupsFocus.js.map +1 -1
- package/dist/headless/components/navigation/appBar/appBar.js.map +1 -1
- package/dist/headless/components/navigation/breadcrumbs/breadcrumbs.js +46 -30
- package/dist/headless/components/navigation/breadcrumbs/breadcrumbs.js.map +1 -1
- package/dist/headless/components/navigation/tabs/tab/tab.js +70 -67
- package/dist/headless/components/navigation/tabs/tab/tab.js.map +1 -1
- package/dist/headless/components/navigation/tabs/tabs.js +19 -16
- package/dist/headless/components/navigation/tabs/tabs.js.map +1 -1
- package/dist/headless/components/navigation/tabs/tabsContext.js.map +1 -1
- package/dist/headless/primitives/publishHeaderOffset.js +19 -0
- package/dist/headless/primitives/publishHeaderOffset.js.map +1 -0
- package/dist/headless/source/components/dataDisplay/avatar/avatar.jsx +1 -0
- package/dist/headless/source/components/dataDisplay/avatar/avatar.jsx.map +1 -1
- package/dist/headless/source/components/dataDisplay/badge/badge.jsx.map +1 -1
- package/dist/headless/source/components/dataDisplay/chart/chart.jsx +14 -12
- package/dist/headless/source/components/dataDisplay/chart/chart.jsx.map +1 -1
- package/dist/headless/source/components/dataDisplay/chart/chartFull.js +12 -0
- package/dist/headless/source/components/dataDisplay/chart/chartFull.js.map +7 -0
- package/dist/headless/source/components/dataDisplay/chart/core.js +11 -0
- package/dist/headless/source/components/dataDisplay/chart/core.js.map +7 -0
- package/dist/headless/source/components/dataDisplay/chart/index.js +1 -1
- package/dist/headless/source/components/dataDisplay/chart/index.js.map +1 -1
- package/dist/headless/source/components/dataDisplay/dagView/dagView.jsx +2 -1
- package/dist/headless/source/components/dataDisplay/dagView/dagView.jsx.map +1 -1
- package/dist/headless/source/components/dataDisplay/dagView/index.js.map +2 -2
- package/dist/headless/source/components/dataDisplay/meter/index.js.map +1 -1
- package/dist/headless/source/components/dataDisplay/meter/meter.jsx +14 -2
- package/dist/headless/source/components/dataDisplay/meter/meter.jsx.map +1 -1
- package/dist/headless/source/components/dataDisplay/statusDot/index.js.map +1 -1
- package/dist/headless/source/components/dataDisplay/statusDot/statusDot.jsx +13 -1
- package/dist/headless/source/components/dataDisplay/statusDot/statusDot.jsx.map +1 -1
- package/dist/headless/source/components/dataDisplay/treeView/treeView.jsx +16 -3
- package/dist/headless/source/components/dataDisplay/treeView/treeView.jsx.map +1 -1
- package/dist/headless/source/components/inputs/button/button.jsx +26 -1
- package/dist/headless/source/components/inputs/button/button.jsx.map +1 -1
- package/dist/headless/source/components/inputs/toggle/toggle.jsx +9 -2
- package/dist/headless/source/components/inputs/toggle/toggle.jsx.map +1 -1
- package/dist/headless/source/components/layout/appLayout/appLayout.jsx +14 -2
- package/dist/headless/source/components/layout/appLayout/appLayout.jsx.map +1 -1
- package/dist/headless/source/components/layout/screenDivider/screenDivider.jsx +102 -18
- package/dist/headless/source/components/layout/screenDivider/screenDivider.jsx.map +1 -1
- package/dist/headless/source/components/layout/sheet/sheet.jsx +14 -1
- package/dist/headless/source/components/layout/sheet/sheet.jsx.map +1 -1
- package/dist/headless/source/components/layout/tabGroups/index.js.map +1 -1
- package/dist/headless/source/components/layout/tabGroups/tabGroups.jsx +55 -1
- package/dist/headless/source/components/layout/tabGroups/tabGroups.jsx.map +1 -1
- package/dist/headless/source/components/layout/tabGroups/tabGroupsFocus.js +42 -0
- package/dist/headless/source/components/layout/tabGroups/tabGroupsFocus.js.map +2 -2
- package/dist/headless/source/components/navigation/appBar/appBar.jsx.map +1 -1
- package/dist/headless/source/components/navigation/breadcrumbs/breadcrumbs.jsx +39 -4
- package/dist/headless/source/components/navigation/breadcrumbs/breadcrumbs.jsx.map +1 -1
- package/dist/headless/source/components/navigation/tabs/tab/tab.jsx +7 -1
- package/dist/headless/source/components/navigation/tabs/tab/tab.jsx.map +1 -1
- package/dist/headless/source/components/navigation/tabs/tabs.jsx +5 -1
- package/dist/headless/source/components/navigation/tabs/tabs.jsx.map +1 -1
- package/dist/headless/source/components/navigation/tabs/tabsContext.js.map +2 -2
- package/dist/headless/source/index.js +8 -1
- package/dist/headless/source/index.js.map +2 -2
- package/dist/headless/source/primitives/publishHeaderOffset.js +34 -0
- package/dist/headless/source/primitives/publishHeaderOffset.js.map +7 -0
- package/dist/index.d.ts +5 -5
- package/dist/primitives/publishHeaderOffset.d.ts +7 -0
- package/dist/primitives/publishHeaderOffset.js +19 -0
- package/dist/primitives/publishHeaderOffset.js.map +1 -0
- package/dist/source/components/dataDisplay/avatar/avatar.jsx +1 -0
- package/dist/source/components/dataDisplay/avatar/avatar.jsx.map +1 -1
- package/dist/source/components/dataDisplay/badge/badge.jsx.map +1 -1
- package/dist/source/components/dataDisplay/chart/chart.jsx +14 -12
- package/dist/source/components/dataDisplay/chart/chart.jsx.map +1 -1
- package/dist/source/components/dataDisplay/chart/chartFull.js +12 -0
- package/dist/source/components/dataDisplay/chart/chartFull.js.map +7 -0
- package/dist/source/components/dataDisplay/chart/core.js +11 -0
- package/dist/source/components/dataDisplay/chart/core.js.map +7 -0
- package/dist/source/components/dataDisplay/chart/index.js +1 -1
- package/dist/source/components/dataDisplay/chart/index.js.map +1 -1
- package/dist/source/components/dataDisplay/dagView/dagView.jsx +2 -1
- package/dist/source/components/dataDisplay/dagView/dagView.jsx.map +1 -1
- package/dist/source/components/dataDisplay/dagView/index.js.map +2 -2
- package/dist/source/components/dataDisplay/meter/index.js.map +1 -1
- package/dist/source/components/dataDisplay/meter/meter.jsx +14 -2
- package/dist/source/components/dataDisplay/meter/meter.jsx.map +1 -1
- package/dist/source/components/dataDisplay/statusDot/index.js.map +1 -1
- package/dist/source/components/dataDisplay/statusDot/statusDot.jsx +13 -1
- package/dist/source/components/dataDisplay/statusDot/statusDot.jsx.map +1 -1
- package/dist/source/components/dataDisplay/treeView/treeView.jsx +16 -3
- package/dist/source/components/dataDisplay/treeView/treeView.jsx.map +1 -1
- package/dist/source/components/inputs/button/button.jsx +26 -1
- package/dist/source/components/inputs/button/button.jsx.map +1 -1
- package/dist/source/components/inputs/toggle/toggle.jsx +9 -2
- package/dist/source/components/inputs/toggle/toggle.jsx.map +1 -1
- package/dist/source/components/layout/appLayout/appLayout.jsx +14 -2
- package/dist/source/components/layout/appLayout/appLayout.jsx.map +1 -1
- package/dist/source/components/layout/screenDivider/screenDivider.jsx +102 -18
- package/dist/source/components/layout/screenDivider/screenDivider.jsx.map +1 -1
- package/dist/source/components/layout/sheet/sheet.jsx +14 -1
- package/dist/source/components/layout/sheet/sheet.jsx.map +1 -1
- package/dist/source/components/layout/tabGroups/index.js.map +1 -1
- package/dist/source/components/layout/tabGroups/tabGroups.jsx +55 -1
- package/dist/source/components/layout/tabGroups/tabGroups.jsx.map +1 -1
- package/dist/source/components/layout/tabGroups/tabGroupsFocus.js +42 -0
- package/dist/source/components/layout/tabGroups/tabGroupsFocus.js.map +2 -2
- package/dist/source/components/navigation/appBar/appBar.jsx.map +1 -1
- package/dist/source/components/navigation/breadcrumbs/breadcrumbs.jsx +39 -4
- package/dist/source/components/navigation/breadcrumbs/breadcrumbs.jsx.map +1 -1
- package/dist/source/components/navigation/tabs/tab/tab.jsx +7 -1
- package/dist/source/components/navigation/tabs/tab/tab.jsx.map +1 -1
- package/dist/source/components/navigation/tabs/tabs.jsx +5 -1
- package/dist/source/components/navigation/tabs/tabs.jsx.map +1 -1
- package/dist/source/components/navigation/tabs/tabsContext.js.map +2 -2
- package/dist/source/index.js +8 -1
- package/dist/source/index.js.map +2 -2
- package/dist/source/primitives/publishHeaderOffset.js +34 -0
- package/dist/source/primitives/publishHeaderOffset.js.map +7 -0
- package/package.json +9 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../src/components/dataDisplay/treeView/treeView.tsx"],"sourcesContent":["import {\n type Accessor,\n type JSX,\n For,\n Show,\n createContext,\n createMemo,\n createSignal,\n createUniqueId,\n mergeProps,\n splitProps,\n useContext,\n} from \"solid-js\";\nimport cn from \"clsx\";\nimport { Icon } from \"../../base/icon\";\nimport { createControllableState } from \"../../../primitives/createControllableState\";\nimport { mergeRefs, type Ref } from \"../../../primitives/mergeRefs\";\nimport { isOwnClick } from \"../../../utils/ownClick\";\n\nexport interface ITreeNodeItem {\n /** Stable identifier. Falls back to the positional path when omitted. */\n id?: string;\n label: string;\n children?: ITreeNodeItem[];\n /** Hint that the node is expandable even before its children are loaded. */\n hasChildren?: boolean;\n}\n\nexport interface TreeViewProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n nodeIcon?: { opened?: string; closed?: string };\n listIcon?: string;\n data: ITreeNodeItem[];\n expanded?: string[];\n defaultExpanded?: string[];\n onExpandedChange?: (expanded: string[]) => void;\n selected?: string[];\n defaultSelected?: string[];\n /** Reports the full selection plus, for user toggles, the node that just changed. */\n onSelectedChange?: (selected: string[], changed?: { id: string; selected: boolean }) => void;\n /**\n * Enables row selection: a click, Enter or Space on a row toggles its selection. The chevron and the\n * Left/Right keys never change it.\n */\n selectable?: boolean;\n /** Renders a checkbox per node bound to selection. */\n checkboxes?: boolean;\n /**\n * Whether a click, Enter or Space on a row also expands or collapses its children. The chevron and the\n * Right/Left keys always do. Defaults to `true` while the tree is not `selectable` (a row has nothing else to\n * do) and to `false` otherwise, so that selecting a row leaves it open or closed.\n */\n toggleOnRowClick?: boolean;\n /** Custom node renderer. */\n renderLabel?: (item: ITreeNodeItem, nodeId: string) => JSX.Element;\n /**\n * Per-node actions (buttons, a menu trigger), drawn at the end of the row. They sit beside the row's\n * button, not inside it, so they take their own clicks, Enter and Space, focus and accessible names.\n */\n renderActions?: (item: ITreeNodeItem, nodeId: string) => JSX.Element;\n /** Text shown while `loadChildren` is pending; \"Loading…\" unless given. */\n loadingText?: string;\n /** Lazily resolves children the first time a node is expanded. */\n loadChildren?: (item: ITreeNodeItem, nodeId: string) => Promise<ITreeNodeItem[]>;\n /** Test id of the root `<div>`; `tree-view` unless given. */\n \"data-testid\"?: string | undefined;\n ref?: Ref<HTMLDivElement>;\n}\n\ninterface TreeContextValue {\n expandedIds: Accessor<string[]>;\n selectedIds: Accessor<string[]>;\n loadedChildren: Accessor<Record<string, ITreeNodeItem[]>>;\n loadingIds: Accessor<string[]>;\n toggleExpanded: (id: string) => void;\n setOpen: (id: string, open: boolean) => void;\n toggleSelected: (id: string) => void;\n ensureLoaded: (item: ITreeNodeItem, nodeId: string) => void;\n selectable: boolean;\n checkboxes: boolean;\n toggleOnRowClick: boolean;\n loadingText: string;\n nodeIcon: { opened?: string; closed?: string };\n listIcon: string;\n renderLabel: ((item: ITreeNodeItem, nodeId: string) => JSX.Element) | undefined;\n renderActions: ((item: ITreeNodeItem, nodeId: string) => JSX.Element) | undefined;\n hasLoader: boolean;\n}\n\nconst TreeContext = createContext<TreeContextValue>();\n\ninterface TreeEntry {\n id: string;\n item: ITreeNodeItem;\n path: string;\n}\n\n/**\n * Whether two items are the same as far as a row's own markup goes. A caller that refetches hands the tree\n * brand-new objects for the same nodes, and comparing those by identity would rebuild the `renderLabel` /\n * `renderActions` output on every refetch: Solid replaces a dynamic child, so the elements the caller drew\n * (and the focus in them, an open menu anchored to one, a caret) would be thrown away while the row around\n * them survives. Children are compared one level deep — a row draws that it has them, not their subtrees.\n */\nfunction sameItem(a: ITreeNodeItem, b: ITreeNodeItem): boolean {\n if (a === b) return true;\n if (a.id !== b.id || a.label !== b.label || a.hasChildren !== b.hasChildren) return false;\n const left = a.children;\n const right = b.children;\n if (left === right) return true;\n if (!left || !right || left.length !== right.length) return false;\n return left.every((child, idx) => {\n const other = right[idx];\n return !!other && child.id === other.id && child.label === other.label && child.hasChildren === other.hasChildren;\n });\n}\n\n/**\n * The rows of one level, keyed by node id. `For` keys by reference, so it would rebuild every row (and drop\n * the focus in it) whenever the caller hands in new objects for the same nodes. It is given the ids instead:\n * equal strings are the same row, and a row reads its item from the current data.\n */\nfunction TreeNodes(props: { items: ITreeNodeItem[]; path: string; wrap?: boolean }): JSX.Element {\n const entries = createMemo<TreeEntry[]>(() =>\n props.items.map((item, idx) => {\n const path = props.path === \"\" ? `${idx}` : `${props.path}.${idx}`;\n return { id: item.id ?? path, item, path };\n })\n );\n const byId = createMemo(() => new Map(entries().map((entry) => [entry.id, entry])));\n const ids = createMemo(() => entries().map((entry) => entry.id));\n\n return (\n <For each={ids()}>\n {(id) => {\n // A removed row is disposed after the lookup recomputes, so keep the last entry for that pass.\n let last = byId().get(id)!;\n const entry = (): TreeEntry => (last = byId().get(id) ?? last);\n return props.wrap ? (\n <div class=\"smwb-tree__wrapper\" role=\"presentation\">\n <TreeNode entry={entry} />\n </div>\n ) : (\n <TreeNode entry={entry} />\n );\n }}\n </For>\n );\n}\n\nfunction TreeNode(props: { entry: Accessor<TreeEntry> }): JSX.Element {\n const ctx = useContext(TreeContext)!;\n // The id is the row's identity and never changes while the row lives.\n const nodeId = props.entry().id;\n const item = (): ITreeNodeItem => props.entry().item;\n // Names the treeitem: its own content would drag the actions and the checkbox into the node's name.\n const labelId = `${createUniqueId()}-label`;\n // What the caller's renderers see. Held by value, not by identity, so a refetch that hands in new\n // objects for the same nodes does not rebuild the slots below (see sameItem).\n const slotItem = createMemo(() => item(), undefined, { equals: sameItem });\n const labelSlot = createMemo<JSX.Element>(() =>\n ctx.renderLabel ? ctx.renderLabel(slotItem(), nodeId) : slotItem().label\n );\n const resolvedChildren = (): ITreeNodeItem[] | undefined => item().children ?? ctx.loadedChildren()[nodeId];\n const isLazy = (): boolean => ctx.hasLoader && (!!item().hasChildren || !!ctx.loadedChildren()[nodeId]);\n const hasChildNodes = (): boolean => (!!resolvedChildren() && (resolvedChildren()?.length ?? 0) > 0) || isLazy();\n const isOpen = (): boolean => ctx.expandedIds().includes(nodeId);\n const isSelected = (): boolean => ctx.selectedIds().includes(nodeId);\n const isLoading = (): boolean => ctx.loadingIds().includes(nodeId);\n\n const toggleNode = (): void => {\n if (!isOpen()) ctx.ensureLoaded(item(), nodeId);\n ctx.toggleExpanded(nodeId);\n };\n\n const activateRow = (): void => {\n if (ctx.toggleOnRowClick && hasChildNodes()) toggleNode();\n if (ctx.selectable) ctx.toggleSelected(nodeId);\n };\n\n const onKeyDown: JSX.EventHandler<HTMLSpanElement, KeyboardEvent> = (event) => {\n // Keys from controls a custom label draws are theirs.\n if (event.target !== event.currentTarget) return;\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n activateRow();\n } else if (event.key === \"ArrowRight\" && hasChildNodes()) {\n ctx.ensureLoaded(item(), nodeId);\n ctx.setOpen(nodeId, true);\n } else if (event.key === \"ArrowLeft\" && hasChildNodes()) {\n ctx.setOpen(nodeId, false);\n }\n };\n\n const onContentClick: JSX.EventHandler<HTMLSpanElement, MouseEvent> = (event) => {\n if (isOwnClick(event)) activateRow();\n };\n\n return (\n <div\n class={cn(\"smwb-tree__node\", { \"smwb-tree__node_selected\": isSelected() })}\n role=\"treeitem\"\n aria-labelledby={labelId}\n aria-expanded={hasChildNodes() ? isOpen() : undefined}\n aria-selected={ctx.selectable || ctx.checkboxes ? isSelected() : undefined}\n >\n <div class=\"smwb-tree__node__row\">\n {/*\n A pointer shortcut: the keyboard expands with ArrowRight/ArrowLeft on the row, so the chevron is\n decorative, unnamed and no Tab stop. If it ever becomes a real <button>, `aria-hidden` must go with\n it (a focusable element inside `aria-hidden` is an ARIA violation) and it then needs a name plus\n `aria-expanded`.\n */}\n <span class=\"smwb-tree__node__toggle\" aria-hidden=\"true\" onClick={() => hasChildNodes() && toggleNode()}>\n <Show when={hasChildNodes()}>\n <Icon name={isOpen() ? \"expand_more\" : \"chevron_right\"} size={20} />\n </Show>\n </span>\n <Show when={ctx.checkboxes}>\n <input\n type=\"checkbox\"\n class=\"smwb-tree__node__checkbox\"\n checked={isSelected()}\n onChange={() => ctx.toggleSelected(nodeId)}\n aria-label={item().label}\n />\n </Show>\n <span class=\"smwb-tree__node__content\" onClick={onContentClick} onKeyDown={onKeyDown} tabindex={0} role=\"button\">\n <span class=\"smwb-tree__node__label\" id={labelId}>\n <Show when={hasChildNodes()} fallback={<Icon name={ctx.listIcon} size={20} />}>\n <Icon name={isOpen() ? ctx.nodeIcon.opened ?? \"folder_open\" : ctx.nodeIcon.closed ?? \"folder\"} fill={1} size={20} />\n </Show>\n {labelSlot()}\n </span>\n </span>\n <Show when={ctx.renderActions}>\n {(renderActions) => {\n // Created inside the branch that draws it, so SSR and hydration agree on which keys exist.\n const actionsSlot = createMemo<JSX.Element>(() => renderActions()(slotItem(), nodeId));\n return <span class=\"smwb-tree__node__actions\">{actionsSlot()}</span>;\n }}\n </Show>\n </div>\n <Show when={hasChildNodes() && isOpen()}>\n <div role=\"group\">\n <Show\n when={!(isLoading() && !resolvedChildren())}\n fallback={\n <div class=\"smwb-tree__node__loading\" role=\"status\">\n {ctx.loadingText}\n </div>\n }\n >\n <TreeNodes items={resolvedChildren() ?? []} path={props.entry().path} />\n </Show>\n </div>\n </Show>\n </div>\n );\n}\n\nexport function TreeView(props: TreeViewProps): JSX.Element {\n const merged = mergeProps(\n {\n listIcon: \"draft\",\n nodeIcon: { closed: \"folder\", opened: \"folder_open\" } as { opened?: string; closed?: string },\n data: [] as ITreeNodeItem[],\n defaultExpanded: [] as string[],\n defaultSelected: [] as string[],\n selectable: false,\n checkboxes: false,\n loadingText: \"Loading…\",\n },\n props\n );\n const [local, rest] = splitProps(merged, [\n \"class\",\n \"listIcon\",\n \"nodeIcon\",\n \"data\",\n \"expanded\",\n \"defaultExpanded\",\n \"onExpandedChange\",\n \"selected\",\n \"defaultSelected\",\n \"onSelectedChange\",\n \"selectable\",\n \"checkboxes\",\n \"toggleOnRowClick\",\n \"renderLabel\",\n \"renderActions\",\n \"loadingText\",\n \"loadChildren\",\n \"ref\",\n \"data-testid\",\n ]);\n\n const [loadedChildren, setLoadedChildren] = createSignal<Record<string, ITreeNodeItem[]>>({});\n const [loadingIds, setLoadingIds] = createSignal<string[]>([]);\n const [expandedIds, setExpandedIds] = createControllableState<string[]>({\n value: () => local.expanded,\n defaultValue: local.defaultExpanded,\n onChange: (next) => local.onExpandedChange?.(next),\n });\n let lastToggled: { id: string; selected: boolean } | undefined;\n const [selectedIds, setSelectedIds] = createControllableState<string[]>({\n value: () => local.selected,\n defaultValue: local.defaultSelected,\n onChange: (next) => {\n // createControllableState calls onChange synchronously from the setter,\n // so lastToggled still refers to the toggle that caused this change.\n const changed = lastToggled;\n lastToggled = undefined;\n local.onSelectedChange?.(next, changed);\n },\n });\n\n const toggleExpanded = (id: string): void =>\n setExpandedIds((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]));\n\n const setOpen = (id: string, open: boolean): void =>\n setExpandedIds((prev) => {\n const has = prev.includes(id);\n if (open && !has) return [...prev, id];\n if (!open && has) return prev.filter((x) => x !== id);\n return prev;\n });\n\n const toggleSelected = (id: string): void => {\n lastToggled = { id, selected: !selectedIds().includes(id) };\n setSelectedIds((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]));\n };\n\n const ensureLoaded = (item: ITreeNodeItem, nodeId: string): void => {\n if (!local.loadChildren || loadedChildren()[nodeId] || (item.children && item.children.length > 0)) return;\n setLoadingIds((prev) => (prev.includes(nodeId) ? prev : [...prev, nodeId]));\n void local\n .loadChildren(item, nodeId)\n .then((children) => setLoadedChildren((prev) => ({ ...prev, [nodeId]: children })))\n .finally(() => setLoadingIds((prev) => prev.filter((x) => x !== nodeId)));\n };\n\n const contextValue: TreeContextValue = {\n expandedIds,\n selectedIds,\n loadedChildren,\n loadingIds,\n toggleExpanded,\n setOpen,\n toggleSelected,\n ensureLoaded,\n get selectable() {\n return local.selectable;\n },\n get checkboxes() {\n return local.checkboxes;\n },\n get toggleOnRowClick() {\n return local.toggleOnRowClick ?? !local.selectable;\n },\n get loadingText() {\n return local.loadingText;\n },\n get nodeIcon() {\n return local.nodeIcon;\n },\n get listIcon() {\n return local.listIcon;\n },\n get renderLabel() {\n return local.renderLabel;\n },\n get renderActions() {\n return local.renderActions;\n },\n get hasLoader() {\n return !!local.loadChildren;\n },\n };\n\n return (\n <TreeContext.Provider value={contextValue}>\n <div\n {...rest}\n data-testid={local[\"data-testid\"] ?? \"tree-view\"}\n ref={mergeRefs(local.ref)}\n role=\"tree\"\n class={cn(\"smwb-tree\", local.class)}\n >\n <TreeNodes items={local.data} path=\"\" wrap />\n </div>\n </TreeContext.Provider>\n );\n}\n\nexport default TreeView;\n"],"mappings":"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","names":[],"file":"treeView.jsx"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../src/components/dataDisplay/treeView/treeView.tsx"],"sourcesContent":["import {\n type Accessor,\n type JSX,\n For,\n Show,\n createContext,\n createMemo,\n createSignal,\n createUniqueId,\n mergeProps,\n splitProps,\n useContext,\n} from \"solid-js\";\nimport cn from \"clsx\";\nimport { Icon } from \"../../base/icon\";\nimport { createControllableState } from \"../../../primitives/createControllableState\";\nimport { mergeRefs, type Ref } from \"../../../primitives/mergeRefs\";\nimport { isOwnClick } from \"../../../utils/ownClick\";\n\nexport interface ITreeNodeItem {\n /** Stable identifier. Falls back to the positional path when omitted. */\n id?: string;\n label: string;\n children?: ITreeNodeItem[];\n /** Hint that the node is expandable even before its children are loaded. */\n hasChildren?: boolean;\n}\n\nexport interface TreeViewProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n /**\n * Icons of the nodes that have children, by state; `{ closed: \"folder\", opened: \"folder_open\" }` unless given.\n * `false` draws none, and the label starts where the icon did.\n */\n nodeIcon?: { opened?: string; closed?: string } | false;\n /** Icon of a node without children; \"draft\" unless given. `false` draws none. */\n listIcon?: string | false;\n /**\n * Compact look: the row is as tall as the small control (`--smui-control-h-sm`, 32px) instead of the medium one\n * (40px), and a level steps 16px (`@treeViewIndentDense`) instead of 40px. A tree with `--smwb-tree-row-h` or\n * `--smwb-tree-indent` set on it (the `style` prop) takes those values, e.g. a 26px row.\n */\n dense?: boolean;\n /**\n * Whether the vertical lines and corners that join a node to its parent are drawn. They are by default; turning\n * them off leaves the levels where they were.\n */\n guides?: boolean;\n data: ITreeNodeItem[];\n expanded?: string[];\n defaultExpanded?: string[];\n onExpandedChange?: (expanded: string[]) => void;\n selected?: string[];\n defaultSelected?: string[];\n /** Reports the full selection plus, for user toggles, the node that just changed. */\n onSelectedChange?: (selected: string[], changed?: { id: string; selected: boolean }) => void;\n /**\n * Enables row selection: a click, Enter or Space on a row toggles its selection. The chevron and the\n * Left/Right keys never change it.\n */\n selectable?: boolean;\n /** Renders a checkbox per node bound to selection. */\n checkboxes?: boolean;\n /**\n * Whether a click, Enter or Space on a row also expands or collapses its children. The chevron and the\n * Right/Left keys always do. Defaults to `true` while the tree is not `selectable` (a row has nothing else to\n * do) and to `false` otherwise, so that selecting a row leaves it open or closed.\n */\n toggleOnRowClick?: boolean;\n /** Custom node renderer. */\n renderLabel?: (item: ITreeNodeItem, nodeId: string) => JSX.Element;\n /**\n * Per-node actions (buttons, a menu trigger), drawn at the end of the row. They sit beside the row's\n * button, not inside it, so they take their own clicks, Enter and Space, focus and accessible names.\n */\n renderActions?: (item: ITreeNodeItem, nodeId: string) => JSX.Element;\n /** Text shown while `loadChildren` is pending; \"Loading…\" unless given. */\n loadingText?: string;\n /** Lazily resolves children the first time a node is expanded. */\n loadChildren?: (item: ITreeNodeItem, nodeId: string) => Promise<ITreeNodeItem[]>;\n /** Test id of the root `<div>`; `tree-view` unless given. */\n \"data-testid\"?: string | undefined;\n ref?: Ref<HTMLDivElement>;\n}\n\ninterface TreeContextValue {\n expandedIds: Accessor<string[]>;\n selectedIds: Accessor<string[]>;\n loadedChildren: Accessor<Record<string, ITreeNodeItem[]>>;\n loadingIds: Accessor<string[]>;\n toggleExpanded: (id: string) => void;\n setOpen: (id: string, open: boolean) => void;\n toggleSelected: (id: string) => void;\n ensureLoaded: (item: ITreeNodeItem, nodeId: string) => void;\n selectable: boolean;\n checkboxes: boolean;\n toggleOnRowClick: boolean;\n loadingText: string;\n nodeIcon: { opened?: string; closed?: string } | false;\n listIcon: string | false;\n renderLabel: ((item: ITreeNodeItem, nodeId: string) => JSX.Element) | undefined;\n renderActions: ((item: ITreeNodeItem, nodeId: string) => JSX.Element) | undefined;\n hasLoader: boolean;\n}\n\nconst TreeContext = createContext<TreeContextValue>();\n\ninterface TreeEntry {\n id: string;\n item: ITreeNodeItem;\n path: string;\n}\n\n/**\n * Whether two items are the same as far as a row's own markup goes. A caller that refetches hands the tree\n * brand-new objects for the same nodes, and comparing those by identity would rebuild the `renderLabel` /\n * `renderActions` output on every refetch: Solid replaces a dynamic child, so the elements the caller drew\n * (and the focus in them, an open menu anchored to one, a caret) would be thrown away while the row around\n * them survives. Children are compared one level deep — a row draws that it has them, not their subtrees.\n */\nfunction sameItem(a: ITreeNodeItem, b: ITreeNodeItem): boolean {\n if (a === b) return true;\n if (a.id !== b.id || a.label !== b.label || a.hasChildren !== b.hasChildren) return false;\n const left = a.children;\n const right = b.children;\n if (left === right) return true;\n if (!left || !right || left.length !== right.length) return false;\n return left.every((child, idx) => {\n const other = right[idx];\n return !!other && child.id === other.id && child.label === other.label && child.hasChildren === other.hasChildren;\n });\n}\n\n/**\n * The rows of one level, keyed by node id. `For` keys by reference, so it would rebuild every row (and drop\n * the focus in it) whenever the caller hands in new objects for the same nodes. It is given the ids instead:\n * equal strings are the same row, and a row reads its item from the current data.\n */\nfunction TreeNodes(props: { items: ITreeNodeItem[]; path: string; wrap?: boolean }): JSX.Element {\n const entries = createMemo<TreeEntry[]>(() =>\n props.items.map((item, idx) => {\n const path = props.path === \"\" ? `${idx}` : `${props.path}.${idx}`;\n return { id: item.id ?? path, item, path };\n })\n );\n const byId = createMemo(() => new Map(entries().map((entry) => [entry.id, entry])));\n const ids = createMemo(() => entries().map((entry) => entry.id));\n\n return (\n <For each={ids()}>\n {(id) => {\n // A removed row is disposed after the lookup recomputes, so keep the last entry for that pass.\n let last = byId().get(id)!;\n const entry = (): TreeEntry => (last = byId().get(id) ?? last);\n return props.wrap ? (\n <div class=\"smwb-tree__wrapper\" role=\"presentation\">\n <TreeNode entry={entry} />\n </div>\n ) : (\n <TreeNode entry={entry} />\n );\n }}\n </For>\n );\n}\n\nfunction TreeNode(props: { entry: Accessor<TreeEntry> }): JSX.Element {\n const ctx = useContext(TreeContext)!;\n // The id is the row's identity and never changes while the row lives.\n const nodeId = props.entry().id;\n const item = (): ITreeNodeItem => props.entry().item;\n // Names the treeitem: its own content would drag the actions and the checkbox into the node's name.\n const labelId = `${createUniqueId()}-label`;\n // What the caller's renderers see. Held by value, not by identity, so a refetch that hands in new\n // objects for the same nodes does not rebuild the slots below (see sameItem).\n const slotItem = createMemo(() => item(), undefined, { equals: sameItem });\n const labelSlot = createMemo<JSX.Element>(() =>\n ctx.renderLabel ? ctx.renderLabel(slotItem(), nodeId) : slotItem().label\n );\n const resolvedChildren = (): ITreeNodeItem[] | undefined => item().children ?? ctx.loadedChildren()[nodeId];\n const isLazy = (): boolean => ctx.hasLoader && (!!item().hasChildren || !!ctx.loadedChildren()[nodeId]);\n const hasChildNodes = (): boolean => (!!resolvedChildren() && (resolvedChildren()?.length ?? 0) > 0) || isLazy();\n const isOpen = (): boolean => ctx.expandedIds().includes(nodeId);\n const isSelected = (): boolean => ctx.selectedIds().includes(nodeId);\n const isLoading = (): boolean => ctx.loadingIds().includes(nodeId);\n // The names of the node's icon and of the leaf's, `undefined` when `nodeIcon` / `listIcon` is `false`.\n const nodeIconName = (): string | undefined => {\n const icons = ctx.nodeIcon;\n if (icons === false) return undefined;\n return isOpen() ? (icons.opened ?? \"folder_open\") : (icons.closed ?? \"folder\");\n };\n const listIconName = (): string | undefined => (ctx.listIcon === false ? undefined : ctx.listIcon);\n\n const toggleNode = (): void => {\n if (!isOpen()) ctx.ensureLoaded(item(), nodeId);\n ctx.toggleExpanded(nodeId);\n };\n\n const activateRow = (): void => {\n if (ctx.toggleOnRowClick && hasChildNodes()) toggleNode();\n if (ctx.selectable) ctx.toggleSelected(nodeId);\n };\n\n const onKeyDown: JSX.EventHandler<HTMLSpanElement, KeyboardEvent> = (event) => {\n // Keys from controls a custom label draws are theirs.\n if (event.target !== event.currentTarget) return;\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n activateRow();\n } else if (event.key === \"ArrowRight\" && hasChildNodes()) {\n ctx.ensureLoaded(item(), nodeId);\n ctx.setOpen(nodeId, true);\n } else if (event.key === \"ArrowLeft\" && hasChildNodes()) {\n ctx.setOpen(nodeId, false);\n }\n };\n\n const onContentClick: JSX.EventHandler<HTMLSpanElement, MouseEvent> = (event) => {\n if (isOwnClick(event)) activateRow();\n };\n\n return (\n <div\n class={cn(\"smwb-tree__node\", { \"smwb-tree__node_selected\": isSelected() })}\n role=\"treeitem\"\n aria-labelledby={labelId}\n aria-expanded={hasChildNodes() ? isOpen() : undefined}\n aria-selected={ctx.selectable || ctx.checkboxes ? isSelected() : undefined}\n >\n <div class=\"smwb-tree__node__row\">\n {/*\n A pointer shortcut: the keyboard expands with ArrowRight/ArrowLeft on the row, so the chevron is\n decorative, unnamed and no Tab stop. If it ever becomes a real <button>, `aria-hidden` must go with\n it (a focusable element inside `aria-hidden` is an ARIA violation) and it then needs a name plus\n `aria-expanded`.\n */}\n <span class=\"smwb-tree__node__toggle\" aria-hidden=\"true\" onClick={() => hasChildNodes() && toggleNode()}>\n <Show when={hasChildNodes()}>\n <Icon name={isOpen() ? \"expand_more\" : \"chevron_right\"} size={20} />\n </Show>\n </span>\n <Show when={ctx.checkboxes}>\n <input\n type=\"checkbox\"\n class=\"smwb-tree__node__checkbox\"\n checked={isSelected()}\n onChange={() => ctx.toggleSelected(nodeId)}\n aria-label={item().label}\n />\n </Show>\n <span class=\"smwb-tree__node__content\" onClick={onContentClick} onKeyDown={onKeyDown} tabindex={0} role=\"button\">\n <span class=\"smwb-tree__node__label\" id={labelId}>\n <Show\n when={hasChildNodes()}\n fallback={<Show when={listIconName()}>{(name) => <Icon name={name()} size={20} />}</Show>}\n >\n <Show when={nodeIconName()}>{(name) => <Icon name={name()} fill={1} size={20} />}</Show>\n </Show>\n {labelSlot()}\n </span>\n </span>\n <Show when={ctx.renderActions}>\n {(renderActions) => {\n // Created inside the branch that draws it, so SSR and hydration agree on which keys exist.\n const actionsSlot = createMemo<JSX.Element>(() => renderActions()(slotItem(), nodeId));\n return <span class=\"smwb-tree__node__actions\">{actionsSlot()}</span>;\n }}\n </Show>\n </div>\n <Show when={hasChildNodes() && isOpen()}>\n <div role=\"group\">\n <Show\n when={!(isLoading() && !resolvedChildren())}\n fallback={\n <div class=\"smwb-tree__node__loading\" role=\"status\">\n {ctx.loadingText}\n </div>\n }\n >\n <TreeNodes items={resolvedChildren() ?? []} path={props.entry().path} />\n </Show>\n </div>\n </Show>\n </div>\n );\n}\n\nexport function TreeView(props: TreeViewProps): JSX.Element {\n const merged = mergeProps(\n {\n listIcon: \"draft\" as string | false,\n nodeIcon: { closed: \"folder\", opened: \"folder_open\" } as { opened?: string; closed?: string } | false,\n dense: false,\n guides: true,\n data: [] as ITreeNodeItem[],\n defaultExpanded: [] as string[],\n defaultSelected: [] as string[],\n selectable: false,\n checkboxes: false,\n loadingText: \"Loading…\",\n },\n props\n );\n const [local, rest] = splitProps(merged, [\n \"class\",\n \"listIcon\",\n \"nodeIcon\",\n \"dense\",\n \"guides\",\n \"data\",\n \"expanded\",\n \"defaultExpanded\",\n \"onExpandedChange\",\n \"selected\",\n \"defaultSelected\",\n \"onSelectedChange\",\n \"selectable\",\n \"checkboxes\",\n \"toggleOnRowClick\",\n \"renderLabel\",\n \"renderActions\",\n \"loadingText\",\n \"loadChildren\",\n \"ref\",\n \"data-testid\",\n ]);\n\n const [loadedChildren, setLoadedChildren] = createSignal<Record<string, ITreeNodeItem[]>>({});\n const [loadingIds, setLoadingIds] = createSignal<string[]>([]);\n const [expandedIds, setExpandedIds] = createControllableState<string[]>({\n value: () => local.expanded,\n defaultValue: local.defaultExpanded,\n onChange: (next) => local.onExpandedChange?.(next),\n });\n let lastToggled: { id: string; selected: boolean } | undefined;\n const [selectedIds, setSelectedIds] = createControllableState<string[]>({\n value: () => local.selected,\n defaultValue: local.defaultSelected,\n onChange: (next) => {\n // createControllableState calls onChange synchronously from the setter,\n // so lastToggled still refers to the toggle that caused this change.\n const changed = lastToggled;\n lastToggled = undefined;\n local.onSelectedChange?.(next, changed);\n },\n });\n\n const toggleExpanded = (id: string): void =>\n setExpandedIds((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]));\n\n const setOpen = (id: string, open: boolean): void =>\n setExpandedIds((prev) => {\n const has = prev.includes(id);\n if (open && !has) return [...prev, id];\n if (!open && has) return prev.filter((x) => x !== id);\n return prev;\n });\n\n const toggleSelected = (id: string): void => {\n lastToggled = { id, selected: !selectedIds().includes(id) };\n setSelectedIds((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]));\n };\n\n const ensureLoaded = (item: ITreeNodeItem, nodeId: string): void => {\n if (!local.loadChildren || loadedChildren()[nodeId] || (item.children && item.children.length > 0)) return;\n setLoadingIds((prev) => (prev.includes(nodeId) ? prev : [...prev, nodeId]));\n void local\n .loadChildren(item, nodeId)\n .then((children) => setLoadedChildren((prev) => ({ ...prev, [nodeId]: children })))\n .finally(() => setLoadingIds((prev) => prev.filter((x) => x !== nodeId)));\n };\n\n const contextValue: TreeContextValue = {\n expandedIds,\n selectedIds,\n loadedChildren,\n loadingIds,\n toggleExpanded,\n setOpen,\n toggleSelected,\n ensureLoaded,\n get selectable() {\n return local.selectable;\n },\n get checkboxes() {\n return local.checkboxes;\n },\n get toggleOnRowClick() {\n return local.toggleOnRowClick ?? !local.selectable;\n },\n get loadingText() {\n return local.loadingText;\n },\n get nodeIcon() {\n return local.nodeIcon;\n },\n get listIcon() {\n return local.listIcon;\n },\n get renderLabel() {\n return local.renderLabel;\n },\n get renderActions() {\n return local.renderActions;\n },\n get hasLoader() {\n return !!local.loadChildren;\n },\n };\n\n return (\n <TreeContext.Provider value={contextValue}>\n <div\n {...rest}\n data-testid={local[\"data-testid\"] ?? \"tree-view\"}\n ref={mergeRefs(local.ref)}\n role=\"tree\"\n class={cn(\"smwb-tree\", { \"smwb-tree_dense\": local.dense, \"smwb-tree_no-guides\": !local.guides }, local.class)}\n >\n <TreeNodes items={local.data} path=\"\" wrap />\n </div>\n </TreeContext.Provider>\n );\n}\n\nexport default TreeView;\n"],"mappings":"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","names":[],"file":"treeView.jsx"}
|
|
@@ -6,6 +6,16 @@ import { mergeRefs } from "../../../primitives/mergeRefs";
|
|
|
6
6
|
import { useButtonGroup } from "../buttonGroups/buttonGroupContext";
|
|
7
7
|
import { callHandler } from "../../../primitives/callHandler";
|
|
8
8
|
import { resolveLinkRel, resolveLinkTarget } from "../../../utils/linkAttrs";
|
|
9
|
+
const ICON_ONLY_NAME = "`iconOnly` has no text, so it needs an `aria-label` (or `aria-labelledby`) to be named.";
|
|
10
|
+
const ICON_ONLY_LABEL = "`iconOnly` is a square for one icon: a `label` is clipped. Drop it and name the button with `aria-label`.";
|
|
11
|
+
const ICON_ONLY_CHILDREN = "`iconOnly` is a square for one icon: children are clipped. Drop them and name the button with `aria-label`.";
|
|
12
|
+
const ICON_ONLY_ICON = "`iconOnly` without an `icon` draws an empty square.";
|
|
13
|
+
const warnedIconOnly = /* @__PURE__ */ new Set();
|
|
14
|
+
function warnIconOnly(kind, message) {
|
|
15
|
+
if (warnedIconOnly.has(kind)) return;
|
|
16
|
+
warnedIconOnly.add(kind);
|
|
17
|
+
console.warn(`[smwb] Button: ${message}`);
|
|
18
|
+
}
|
|
9
19
|
function Button(props) {
|
|
10
20
|
const group = useButtonGroup();
|
|
11
21
|
const merged = mergeProps(
|
|
@@ -26,6 +36,7 @@ function Button(props) {
|
|
|
26
36
|
"label",
|
|
27
37
|
"variant",
|
|
28
38
|
"icon",
|
|
39
|
+
"iconOnly",
|
|
29
40
|
"iconProps",
|
|
30
41
|
"href",
|
|
31
42
|
"target",
|
|
@@ -58,7 +69,8 @@ function Button(props) {
|
|
|
58
69
|
local.class,
|
|
59
70
|
{
|
|
60
71
|
"smwb-disabled": disabled(),
|
|
61
|
-
"smwb-full-width": fullWidth()
|
|
72
|
+
"smwb-full-width": fullWidth(),
|
|
73
|
+
"smwb-button_icon-only": local.iconOnly
|
|
62
74
|
}
|
|
63
75
|
);
|
|
64
76
|
const onPointerDown = (event) => {
|
|
@@ -79,6 +91,19 @@ function Button(props) {
|
|
|
79
91
|
<Ripple actionRef={(actions) => rippleActions = actions} center={local.rippleCenter} />
|
|
80
92
|
</Show>
|
|
81
93
|
</>;
|
|
94
|
+
if (local.iconOnly) {
|
|
95
|
+
if (!rest["aria-label"] && !rest["aria-labelledby"]) {
|
|
96
|
+
warnIconOnly("name", ICON_ONLY_NAME);
|
|
97
|
+
}
|
|
98
|
+
if (local.label) {
|
|
99
|
+
warnIconOnly("label", ICON_ONLY_LABEL);
|
|
100
|
+
} else if ("children" in local) {
|
|
101
|
+
warnIconOnly("children", ICON_ONLY_CHILDREN);
|
|
102
|
+
}
|
|
103
|
+
if (!icon()) {
|
|
104
|
+
warnIconOnly("icon", ICON_ONLY_ICON);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
82
107
|
return <Show
|
|
83
108
|
when={local.href}
|
|
84
109
|
fallback={<button
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../src/components/inputs/button/button.tsx"],"sourcesContent":["import { type JSX, type ParentProps, Show, children, mergeProps, splitProps } from \"solid-js\";\nimport cn from \"clsx\";\nimport { Ripple, type RippleActionsRef } from \"../../base/ripple\";\nimport { Icon, type IconProps } from \"../../base/icon\";\nimport { mergeRefs, type Ref } from \"../../../primitives/mergeRefs\";\nimport { useButtonGroup } from \"../buttonGroups/buttonGroupContext\";\nimport { callHandler } from \"../../../primitives/callHandler\";\nimport { resolveLinkRel, resolveLinkTarget } from \"../../../utils/linkAttrs\";\n\nexport interface ButtonProps extends JSX.HTMLAttributes<HTMLButtonElement | HTMLAnchorElement> {\n /** Button text. */\n label?: string;\n /** Visual style of the button. */\n variant?: \"contained\" | \"text\" | \"outlined\";\n /** Button size. */\n size?: \"small\" | \"medium\" | \"large\";\n /** Icon as an element (e.g. <Icon/>) or a string (icon name / url / base64). */\n icon?: JSX.Element | string;\n /** Props forwarded to the <Icon/> when `icon` is a string. */\n iconProps?: Omit<IconProps, \"name\">;\n /** @deprecated use `color`. Kept for backward compatibility. */\n colorTheme?: \"primary\";\n /** Semantic color, resolved through `--smwb-*Color*` tokens. */\n color?: \"primary\" | \"secondary\" | \"error\" | \"success\" | \"warning\" | \"info\";\n /** When set, the component renders as an <a>. */\n href?: string;\n /**\n * Where a link (with `href`) opens. By default `\"_blank\"`: a new tab. `null` leaves the `target`\n * attribute out, so the link opens in place, like an internal one; an empty string does the same, and\n * any other string is used as is.\n */\n target?: string | null | undefined;\n /**\n * The `rel` of a link (with `href`). By default `\"noreferrer\"` for a link that opens another tab or\n * window, and none for one that opens in place. Your own value replaces the default, so add `noreferrer` yourself\n * if you still want it; an empty string drops the attribute.\n */\n rel?: string | undefined;\n disabled?: boolean;\n type?: \"button\" | \"submit\" | \"reset\";\n /** Stretch to the full width of the container. */\n fullWidth?: boolean;\n /** Center the ripple effect. */\n rippleCenter?: boolean;\n /** Disable the ripple effect. */\n disableRipple?: boolean;\n /** Test id of the root; `button`, or `button-link` for a link, unless given. */\n \"data-testid\"?: string | undefined;\n ref?: Ref<HTMLButtonElement | HTMLAnchorElement>;\n}\n\nexport function Button(props: ParentProps<ButtonProps>): JSX.Element {\n const group = useButtonGroup();\n const merged = mergeProps(\n {\n get variant(): NonNullable<ButtonProps[\"variant\"]> {\n return group?.variant ?? \"contained\";\n },\n get size(): NonNullable<ButtonProps[\"size\"]> {\n return group?.size ?? \"medium\";\n },\n get colorTheme(): NonNullable<ButtonProps[\"colorTheme\"]> {\n return group?.colorTheme ?? \"primary\";\n },\n },\n props\n );\n const [local, rest] = splitProps(merged, [\n \"label\",\n \"variant\",\n \"icon\",\n \"iconProps\",\n \"href\",\n \"target\",\n \"rel\",\n \"class\",\n \"disabled\",\n \"children\",\n \"fullWidth\",\n \"rippleCenter\",\n \"disableRipple\",\n // The button sets this on its root too; its own handler calls the caller's first.\n \"onPointerDown\",\n \"size\",\n \"colorTheme\",\n \"color\",\n \"ref\",\n \"data-testid\",\n ]);\n\n const linkTarget = (): string | undefined => resolveLinkTarget(local.target);\n const color = (): string => local.color ?? group?.color ?? local.colorTheme;\n const disabled = (): boolean | undefined => local.disabled ?? group?.disabled;\n const fullWidth = (): boolean | undefined => local.fullWidth ?? group?.fullWidth;\n let rippleActions: RippleActionsRef | undefined;\n\n const classes = (): string =>\n cn(\n \"smwb-button\",\n `smwb-${local.variant}`,\n `smwb-${local.size}`,\n color() === \"primary\" ? \"smwb-primary\" : `smwb-button_color-${color()}`,\n group && \"smwb-button-group__grouped\",\n local.class,\n {\n \"smwb-disabled\": disabled(),\n \"smwb-full-width\": fullWidth(),\n }\n );\n\n // Ripple on pointerdown so it fires for touch and pen, not just mouse.\n const onPointerDown: JSX.EventHandler<HTMLButtonElement | HTMLAnchorElement, PointerEvent> = (event) => {\n callHandler(local.onPointerDown, event);\n if (!disabled()) {\n rippleActions?.onRipple(event);\n }\n };\n\n // Read the icon once: markup passed as a prop is created anew on every read,\n // and during hydration the copy made for a <Show> test has no server DOM node.\n const icon = children(() => local.icon);\n const iconElement = (): JSX.Element => {\n const resolved = icon();\n return typeof resolved === \"string\" ? <Icon {...(local.iconProps ?? {})} name={resolved} /> : resolved;\n };\n\n const content = (): JSX.Element => (\n <>\n <Show when={icon()}>{iconElement()}</Show>\n {local.label ? local.label : local.children}\n <Show when={!local.disableRipple}>\n <Ripple actionRef={(actions) => (rippleActions = actions)} center={local.rippleCenter} />\n </Show>\n </>\n );\n\n return (\n <Show\n when={local.href}\n fallback={\n <button\n data-testid={local[\"data-testid\"] ?? \"button\"}\n {...rest}\n ref={mergeRefs(local.ref)}\n class={classes()}\n onPointerDown={onPointerDown}\n disabled={!!disabled()}\n >\n {content()}\n </button>\n }\n >\n <a\n data-testid={local[\"data-testid\"] ?? \"button-link\"}\n {...rest}\n ref={mergeRefs(local.ref)}\n class={classes()}\n href={local.href}\n target={linkTarget()}\n rel={resolveLinkRel(local.rel, linkTarget())}\n onPointerDown={onPointerDown}\n >\n {content()}\n </a>\n </Show>\n );\n}\n\nexport default Button;\n"],"mappings":"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","names":[],"file":"button.jsx"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../src/components/inputs/button/button.tsx"],"sourcesContent":["import { type JSX, type ParentProps, Show, children, mergeProps, splitProps } from \"solid-js\";\nimport cn from \"clsx\";\nimport { Ripple, type RippleActionsRef } from \"../../base/ripple\";\nimport { Icon, type IconProps } from \"../../base/icon\";\nimport { mergeRefs, type Ref } from \"../../../primitives/mergeRefs\";\nimport { useButtonGroup } from \"../buttonGroups/buttonGroupContext\";\nimport { callHandler } from \"../../../primitives/callHandler\";\nimport { resolveLinkRel, resolveLinkTarget } from \"../../../utils/linkAttrs\";\n\nexport interface ButtonProps extends JSX.HTMLAttributes<HTMLButtonElement | HTMLAnchorElement> {\n /** Button text. */\n label?: string;\n /** Visual style of the button. */\n variant?: \"contained\" | \"text\" | \"outlined\";\n /** Button size. */\n size?: \"small\" | \"medium\" | \"large\";\n /** Icon as an element (e.g. <Icon/>) or a string (icon name / url / base64). */\n icon?: JSX.Element | string;\n /**\n * Icon-only button: a square of its size's control height (`--smui-control-h-sm/md/lg`), with no\n * `min-width` and no padding. Works for every `variant` and for a link (`href`). It has no text, so its\n * name comes from `aria-label` (or `aria-labelledby`); without one the component warns in the console.\n * Apart from `fullWidth`: a square does not stretch.\n */\n iconOnly?: boolean;\n /** Props forwarded to the <Icon/> when `icon` is a string. */\n iconProps?: Omit<IconProps, \"name\">;\n /** @deprecated use `color`. Kept for backward compatibility. */\n colorTheme?: \"primary\";\n /** Semantic color, resolved through `--smwb-*Color*` tokens. */\n color?: \"primary\" | \"secondary\" | \"error\" | \"success\" | \"warning\" | \"info\";\n /** When set, the component renders as an <a>. */\n href?: string;\n /**\n * Where a link (with `href`) opens. By default `\"_blank\"`: a new tab. `null` leaves the `target`\n * attribute out, so the link opens in place, like an internal one; an empty string does the same, and\n * any other string is used as is.\n */\n target?: string | null | undefined;\n /**\n * The `rel` of a link (with `href`). By default `\"noreferrer\"` for a link that opens another tab or\n * window, and none for one that opens in place. Your own value replaces the default, so add `noreferrer` yourself\n * if you still want it; an empty string drops the attribute.\n */\n rel?: string | undefined;\n disabled?: boolean;\n type?: \"button\" | \"submit\" | \"reset\";\n /** Stretch to the full width of the container. */\n fullWidth?: boolean;\n /** Center the ripple effect. */\n rippleCenter?: boolean;\n /** Disable the ripple effect. */\n disableRipple?: boolean;\n /** Test id of the root; `button`, or `button-link` for a link, unless given. */\n \"data-testid\"?: string | undefined;\n ref?: Ref<HTMLButtonElement | HTMLAnchorElement>;\n}\n\nconst ICON_ONLY_NAME = \"`iconOnly` has no text, so it needs an `aria-label` (or `aria-labelledby`) to be named.\";\nconst ICON_ONLY_LABEL =\n \"`iconOnly` is a square for one icon: a `label` is clipped. Drop it and name the button with `aria-label`.\";\nconst ICON_ONLY_CHILDREN =\n \"`iconOnly` is a square for one icon: children are clipped. Drop them and name the button with `aria-label`.\";\nconst ICON_ONLY_ICON = \"`iconOnly` without an `icon` draws an empty square.\";\n\nconst warnedIconOnly = new Set<string>();\n\n// Warns unconditionally, once per kind of mistake, like the rest of ui-solid (icon.tsx).\nfunction warnIconOnly(kind: \"name\" | \"label\" | \"children\" | \"icon\", message: string): void {\n if (warnedIconOnly.has(kind)) return;\n warnedIconOnly.add(kind);\n console.warn(`[smwb] Button: ${message}`);\n}\n\nexport function Button(props: ParentProps<ButtonProps>): JSX.Element {\n const group = useButtonGroup();\n const merged = mergeProps(\n {\n get variant(): NonNullable<ButtonProps[\"variant\"]> {\n return group?.variant ?? \"contained\";\n },\n get size(): NonNullable<ButtonProps[\"size\"]> {\n return group?.size ?? \"medium\";\n },\n get colorTheme(): NonNullable<ButtonProps[\"colorTheme\"]> {\n return group?.colorTheme ?? \"primary\";\n },\n },\n props\n );\n const [local, rest] = splitProps(merged, [\n \"label\",\n \"variant\",\n \"icon\",\n \"iconOnly\",\n \"iconProps\",\n \"href\",\n \"target\",\n \"rel\",\n \"class\",\n \"disabled\",\n \"children\",\n \"fullWidth\",\n \"rippleCenter\",\n \"disableRipple\",\n // The button sets this on its root too; its own handler calls the caller's first.\n \"onPointerDown\",\n \"size\",\n \"colorTheme\",\n \"color\",\n \"ref\",\n \"data-testid\",\n ]);\n\n const linkTarget = (): string | undefined => resolveLinkTarget(local.target);\n const color = (): string => local.color ?? group?.color ?? local.colorTheme;\n const disabled = (): boolean | undefined => local.disabled ?? group?.disabled;\n const fullWidth = (): boolean | undefined => local.fullWidth ?? group?.fullWidth;\n let rippleActions: RippleActionsRef | undefined;\n\n const classes = (): string =>\n cn(\n \"smwb-button\",\n `smwb-${local.variant}`,\n `smwb-${local.size}`,\n color() === \"primary\" ? \"smwb-primary\" : `smwb-button_color-${color()}`,\n group && \"smwb-button-group__grouped\",\n local.class,\n {\n \"smwb-disabled\": disabled(),\n \"smwb-full-width\": fullWidth(),\n \"smwb-button_icon-only\": local.iconOnly,\n }\n );\n\n // Ripple on pointerdown so it fires for touch and pen, not just mouse.\n const onPointerDown: JSX.EventHandler<HTMLButtonElement | HTMLAnchorElement, PointerEvent> = (event) => {\n callHandler(local.onPointerDown, event);\n if (!disabled()) {\n rippleActions?.onRipple(event);\n }\n };\n\n // Read the icon once: markup passed as a prop is created anew on every read,\n // and during hydration the copy made for a <Show> test has no server DOM node.\n const icon = children(() => local.icon);\n const iconElement = (): JSX.Element => {\n const resolved = icon();\n return typeof resolved === \"string\" ? <Icon {...(local.iconProps ?? {})} name={resolved} /> : resolved;\n };\n\n const content = (): JSX.Element => (\n <>\n <Show when={icon()}>{iconElement()}</Show>\n {local.label ? local.label : local.children}\n <Show when={!local.disableRipple}>\n <Ripple actionRef={(actions) => (rippleActions = actions)} center={local.rippleCenter} />\n </Show>\n </>\n );\n\n // Checked once, here: `children` is only looked up by key, because reading it would create its nodes.\n if (local.iconOnly) {\n if (!rest[\"aria-label\"] && !rest[\"aria-labelledby\"]) {\n warnIconOnly(\"name\", ICON_ONLY_NAME);\n }\n if (local.label) {\n warnIconOnly(\"label\", ICON_ONLY_LABEL);\n } else if (\"children\" in local) {\n warnIconOnly(\"children\", ICON_ONLY_CHILDREN);\n }\n if (!icon()) {\n warnIconOnly(\"icon\", ICON_ONLY_ICON);\n }\n }\n\n return (\n <Show\n when={local.href}\n fallback={\n <button\n data-testid={local[\"data-testid\"] ?? \"button\"}\n {...rest}\n ref={mergeRefs(local.ref)}\n class={classes()}\n onPointerDown={onPointerDown}\n disabled={!!disabled()}\n >\n {content()}\n </button>\n }\n >\n <a\n data-testid={local[\"data-testid\"] ?? \"button-link\"}\n {...rest}\n ref={mergeRefs(local.ref)}\n class={classes()}\n href={local.href}\n target={linkTarget()}\n rel={resolveLinkRel(local.rel, linkTarget())}\n onPointerDown={onPointerDown}\n >\n {content()}\n </a>\n </Show>\n );\n}\n\nexport default Button;\n"],"mappings":"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","names":[],"file":"button.jsx"}
|
|
@@ -21,6 +21,7 @@ function Toggle(props) {
|
|
|
21
21
|
"color",
|
|
22
22
|
"helperText",
|
|
23
23
|
"error",
|
|
24
|
+
"compact",
|
|
24
25
|
"onChange",
|
|
25
26
|
"aria-describedby",
|
|
26
27
|
"ref"
|
|
@@ -38,13 +39,19 @@ function Toggle(props) {
|
|
|
38
39
|
local.onChange?.(event, event.currentTarget.checked, props);
|
|
39
40
|
};
|
|
40
41
|
return <TextFieldWrapper
|
|
41
|
-
helperText={helper.text()}
|
|
42
|
+
helperText={local.compact && !helper.text() ? null : helper.text()}
|
|
42
43
|
helperTextId={helper.id}
|
|
43
44
|
disabled={local.disabled}
|
|
44
45
|
error={local.error}
|
|
45
46
|
class="smwb-text-field-checkbox"
|
|
46
47
|
>
|
|
47
|
-
<span
|
|
48
|
+
<span
|
|
49
|
+
class={cn(
|
|
50
|
+
"smwb-toggle__wrapper",
|
|
51
|
+
local.labelPlacement && `smwb-toggle__wrapper_label-${local.labelPlacement}`,
|
|
52
|
+
local.compact && "smwb-toggle__wrapper_compact"
|
|
53
|
+
)}
|
|
54
|
+
>
|
|
48
55
|
<Show when={local.label}>
|
|
49
56
|
<label for={id} class="smwb-toggle__label">
|
|
50
57
|
{local.label}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../src/components/inputs/toggle/toggle.tsx"],"sourcesContent":["import { type JSX, Show, mergeProps, splitProps } from \"solid-js\";\nimport cn from \"clsx\";\nimport { TextFieldWrapper } from \"../textField/textFieldWrapper\";\nimport { createHelperText } from \"../../../primitives/createHelperText\";\nimport { createId } from \"../../../primitives/createId\";\nimport { mergeRefs, type Ref } from \"../../../primitives/mergeRefs\";\nimport { callHandler } from \"../../../primitives/callHandler\";\nimport type { SelectionChangeEvent, SelectionControlColor, SelectionControlSize } from \"../checkbox/checkbox\";\n\nexport interface ToggleProps extends Omit<JSX.InputHTMLAttributes<HTMLInputElement>, \"onChange\" | \"size\"> {\n disabled?: boolean;\n /**\n * Keeps the switch as it is: clicks on it or its label and Space change nothing, and `onChange` is not called.\n * It stays focusable, and its input tells assistive technology it is read-only (`aria-readonly`).\n */\n readOnly?: boolean;\n required?: boolean;\n checked?: boolean;\n id?: string;\n label?: string;\n labelPlacement?: \"top\" | \"left\" | \"right\";\n size?: SelectionControlSize;\n color?: SelectionControlColor;\n /** Unified handler: `(event, checked, data)`. */\n onChange?: (event: SelectionChangeEvent, checked: boolean, data: ToggleProps) => void;\n helperText?: JSX.Element;\n error?: boolean;\n ref?: Ref<HTMLInputElement>;\n}\n\nexport function Toggle(props: ToggleProps): JSX.Element {\n const merged = mergeProps({ size: \"medium\" as SelectionControlSize, color: \"primary\" as SelectionControlColor }, props);\n const [local, args] = splitProps(merged, [\n \"disabled\",\n \"readOnly\",\n \"onClick\",\n \"required\",\n \"checked\",\n \"class\",\n \"id\",\n \"label\",\n \"labelPlacement\",\n \"size\",\n \"color\",\n \"helperText\",\n \"error\",\n \"onChange\",\n \"aria-describedby\",\n \"ref\",\n ]);\n const id = createId(local.id);\n const helper = createHelperText(() => local.helperText, { describedBy: () => local[\"aria-describedby\"] });\n\n // `readonly` means nothing to a checkbox input: a click on it or on its label, and Space, still\n // toggle it. Cancel the click; the browser then puts the previous state back.\n const handleClick: JSX.EventHandler<HTMLInputElement, MouseEvent> = (event) => {\n callHandler(local.onClick, event);\n if (local.readOnly) event.preventDefault();\n };\n\n const handleChange: JSX.EventHandler<HTMLInputElement, Event> = (event) => {\n if (local.readOnly) {\n return;\n }\n local.onChange?.(event, event.currentTarget.checked, props);\n };\n\n return (\n <TextFieldWrapper\n helperText={helper.text()}\n helperTextId={helper.id}\n disabled={local.disabled}\n error={local.error}\n class=\"smwb-text-field-checkbox\"\n >\n <span
|
|
1
|
+
{"version":3,"sources":["../../../../../../src/components/inputs/toggle/toggle.tsx"],"sourcesContent":["import { type JSX, Show, mergeProps, splitProps } from \"solid-js\";\nimport cn from \"clsx\";\nimport { TextFieldWrapper } from \"../textField/textFieldWrapper\";\nimport { createHelperText } from \"../../../primitives/createHelperText\";\nimport { createId } from \"../../../primitives/createId\";\nimport { mergeRefs, type Ref } from \"../../../primitives/mergeRefs\";\nimport { callHandler } from \"../../../primitives/callHandler\";\nimport type { SelectionChangeEvent, SelectionControlColor, SelectionControlSize } from \"../checkbox/checkbox\";\n\nexport interface ToggleProps extends Omit<JSX.InputHTMLAttributes<HTMLInputElement>, \"onChange\" | \"size\"> {\n disabled?: boolean;\n /**\n * Keeps the switch as it is: clicks on it or its label and Space change nothing, and `onChange` is not called.\n * It stays focusable, and its input tells assistive technology it is read-only (`aria-readonly`).\n */\n readOnly?: boolean;\n required?: boolean;\n checked?: boolean;\n id?: string;\n label?: string;\n labelPlacement?: \"top\" | \"left\" | \"right\";\n size?: SelectionControlSize;\n color?: SelectionControlColor;\n /** Unified handler: `(event, checked, data)`. */\n onChange?: (event: SelectionChangeEvent, checked: boolean, data: ToggleProps) => void;\n helperText?: JSX.Element;\n error?: boolean;\n /**\n * A compact look for a status row or a toolbar: the label shares the line with the switch (\"top\", the default,\n * counts as \"left\"), no line is kept for helper text unless `helperText` is set, and the row is the theme's small\n * control height, at most 28px. The default look is unchanged.\n */\n compact?: boolean;\n ref?: Ref<HTMLInputElement>;\n}\n\nexport function Toggle(props: ToggleProps): JSX.Element {\n const merged = mergeProps({ size: \"medium\" as SelectionControlSize, color: \"primary\" as SelectionControlColor }, props);\n const [local, args] = splitProps(merged, [\n \"disabled\",\n \"readOnly\",\n \"onClick\",\n \"required\",\n \"checked\",\n \"class\",\n \"id\",\n \"label\",\n \"labelPlacement\",\n \"size\",\n \"color\",\n \"helperText\",\n \"error\",\n \"compact\",\n \"onChange\",\n \"aria-describedby\",\n \"ref\",\n ]);\n const id = createId(local.id);\n const helper = createHelperText(() => local.helperText, { describedBy: () => local[\"aria-describedby\"] });\n\n // `readonly` means nothing to a checkbox input: a click on it or on its label, and Space, still\n // toggle it. Cancel the click; the browser then puts the previous state back.\n const handleClick: JSX.EventHandler<HTMLInputElement, MouseEvent> = (event) => {\n callHandler(local.onClick, event);\n if (local.readOnly) event.preventDefault();\n };\n\n const handleChange: JSX.EventHandler<HTMLInputElement, Event> = (event) => {\n if (local.readOnly) {\n return;\n }\n local.onChange?.(event, event.currentTarget.checked, props);\n };\n\n return (\n <TextFieldWrapper\n helperText={local.compact && !helper.text() ? null : helper.text()}\n helperTextId={helper.id}\n disabled={local.disabled}\n error={local.error}\n class=\"smwb-text-field-checkbox\"\n >\n <span\n class={cn(\n \"smwb-toggle__wrapper\",\n local.labelPlacement && `smwb-toggle__wrapper_label-${local.labelPlacement}`,\n local.compact && \"smwb-toggle__wrapper_compact\"\n )}\n >\n <Show when={local.label}>\n <label for={id} class=\"smwb-toggle__label\">\n {local.label}\n </label>\n </Show>\n <label\n class={cn(\"smwb-toggle\", `smwb-toggle_size-${local.size}`, `smwb-toggle_color-${local.color}`, local.class, {\n \"smwb-disabled\": local.disabled,\n \"smwb-readonly\": local.readOnly,\n })}\n >\n <input\n type=\"checkbox\"\n id={id}\n class=\"smwb-toggle__checkbox\"\n aria-checked={local.checked}\n aria-invalid={local.error || undefined}\n aria-required={local.required || undefined}\n aria-describedby={helper.describedBy()}\n role=\"switch\"\n checked={local.checked}\n disabled={local.disabled}\n readonly={local.readOnly}\n required={local.required}\n ref={mergeRefs(local.ref)}\n {...args}\n aria-readonly={local.readOnly || undefined}\n onChange={handleChange}\n onClick={handleClick}\n />\n <span class=\"smwb-toggle__track\" />\n <span class=\"smwb-toggle__thumb\" />\n </label>\n </span>\n </TextFieldWrapper>\n );\n}\n\nexport default Toggle;\n"],"mappings":"AAAA,SAAmB,MAAM,YAAY,kBAAkB;AACvD,OAAO,QAAQ;AACf,SAAS,wBAAwB;AACjC,SAAS,wBAAwB;AACjC,SAAS,gBAAgB;AACzB,SAAS,iBAA2B;AACpC,SAAS,mBAAmB;AA8BrB,SAAS,OAAO,OAAiC;AACtD,QAAM,SAAS,WAAW,EAAE,MAAM,UAAkC,OAAO,UAAmC,GAAG,KAAK;AACtH,QAAM,CAAC,OAAO,IAAI,IAAI,WAAW,QAAQ;AAAA,IACvC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,QAAM,KAAK,SAAS,MAAM,EAAE;AAC5B,QAAM,SAAS,iBAAiB,MAAM,MAAM,YAAY,EAAE,aAAa,MAAM,MAAM,kBAAkB,EAAE,CAAC;AAIxG,QAAM,cAA8D,CAAC,UAAU;AAC7E,gBAAY,MAAM,SAAS,KAAK;AAChC,QAAI,MAAM,SAAU,OAAM,eAAe;AAAA,EAC3C;AAEA,QAAM,eAA0D,CAAC,UAAU;AACzE,QAAI,MAAM,UAAU;AAClB;AAAA,IACF;AACA,UAAM,WAAW,OAAO,MAAM,cAAc,SAAS,KAAK;AAAA,EAC5D;AAEA,SACE,CAAC;AAAA,IACC,YAAY,MAAM,WAAW,CAAC,OAAO,KAAK,IAAI,OAAO,OAAO,KAAK;AAAA,IACjE,cAAc,OAAO;AAAA,IACrB,UAAU,MAAM;AAAA,IAChB,OAAO,MAAM;AAAA,IACb,MAAM;AAAA,GACP;AAAA,MACC,CAAC;AAAA,IACC,OAAO;AAAA,MACL;AAAA,MACA,MAAM,kBAAkB,8BAA8B,MAAM,cAAc;AAAA,MAC1E,MAAM,WAAW;AAAA,IACnB;AAAA,GACD;AAAA,QACC,CAAC,KAAK,MAAM,MAAM,OAAO;AAAA,UACvB,CAAC,MAAM,KAAK,IAAI,MAAM,qBAAqB;AAAA,aACxC,MAAM,MAAM;AAAA,UACf,EAFC,MAEO;AAAA,QACV,EAJC,KAIM;AAAA,QACP,CAAC;AAAA,IACC,OAAO,GAAG,eAAe,oBAAoB,MAAM,IAAI,IAAI,qBAAqB,MAAM,KAAK,IAAI,MAAM,OAAO;AAAA,MAC1G,iBAAiB,MAAM;AAAA,MACvB,iBAAiB,MAAM;AAAA,IACzB,CAAC;AAAA,GACF;AAAA,UACC,CAAC;AAAA,IACC,KAAK;AAAA,IACL,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,cAAc,MAAM;AAAA,IACpB,cAAc,MAAM,SAAS;AAAA,IAC7B,eAAe,MAAM,YAAY;AAAA,IACjC,kBAAkB,OAAO,YAAY;AAAA,IACrC,KAAK;AAAA,IACL,SAAS,MAAM;AAAA,IACf,UAAU,MAAM;AAAA,IAChB,UAAU,MAAM;AAAA,IAChB,UAAU,MAAM;AAAA,IAChB,KAAK,UAAU,MAAM,GAAG;AAAA,QACpB;AAAA,IACJ,eAAe,MAAM,YAAY;AAAA,IACjC,UAAU;AAAA,IACV,SAAS;AAAA,EACX,EAAE;AAAA,UACF,CAAC,KAAK,MAAM,qBAAqB,EAAE;AAAA,UACnC,CAAC,KAAK,MAAM,qBAAqB,EAAE;AAAA,QACrC,EA3BC,MA2BO;AAAA,MACV,EAxCC,KAwCM;AAAA,IACT,EAhDC;AAkDL;AAEA,IAAO,iBAAQ;","names":[],"file":"toggle.jsx"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { Show, children, mergeProps, splitProps } from "solid-js";
|
|
1
|
+
import { Show, children, createEffect, mergeProps, onCleanup, splitProps } from "solid-js";
|
|
2
2
|
import cn from "clsx";
|
|
3
3
|
import { Icon } from "../../base/icon";
|
|
4
4
|
import { createControllableState } from "../../../primitives/createControllableState";
|
|
5
5
|
import { createId } from "../../../primitives/createId";
|
|
6
6
|
import { mergeRefs } from "../../../primitives/mergeRefs";
|
|
7
7
|
import { mergeStyle } from "../../../primitives/mergeStyle";
|
|
8
|
+
import { publishHeaderOffset } from "../../../primitives/publishHeaderOffset";
|
|
8
9
|
const len = (v) => typeof v === "number" ? `${v}px` : v;
|
|
9
10
|
function AppLayout(props) {
|
|
10
11
|
const merged = mergeProps(
|
|
@@ -63,7 +64,18 @@ function AppLayout(props) {
|
|
|
63
64
|
style={style()}
|
|
64
65
|
>
|
|
65
66
|
<Show when={headerSlot()}>
|
|
66
|
-
<div
|
|
67
|
+
<div
|
|
68
|
+
class="smwb-app-layout__header"
|
|
69
|
+
ref={(el) => (
|
|
70
|
+
// Created with the header, so it ends with it. A header that scrolls away leaves no fixed edge for a
|
|
71
|
+
// sheet to start under.
|
|
72
|
+
createEffect(() => {
|
|
73
|
+
if (local.stickyHeader) onCleanup(publishHeaderOffset(el));
|
|
74
|
+
})
|
|
75
|
+
)}
|
|
76
|
+
>
|
|
77
|
+
{headerSlot()}
|
|
78
|
+
</div>
|
|
67
79
|
</Show>
|
|
68
80
|
<div class="smwb-app-layout__body">
|
|
69
81
|
<Show when={sidebarSlot()}>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../src/components/layout/appLayout/appLayout.tsx"],"sourcesContent":["import { type JSX, type ParentProps, Show, children, mergeProps, splitProps } from \"solid-js\";\nimport cn from \"clsx\";\nimport { Icon } from \"../../base/icon\";\nimport { createControllableState } from \"../../../primitives/createControllableState\";\nimport { createId } from \"../../../primitives/createId\";\nimport { mergeRefs, type Ref } from \"../../../primitives/mergeRefs\";\nimport { mergeStyle } from \"../../../primitives/mergeStyle\";\n\nexport interface AppLayoutProps extends JSX.HTMLAttributes<HTMLDivElement> {\n /** Top bar slot (e.g. <AppBar />). Spans full width. */\n header?: JSX.Element;\n /** Side navigation slot (e.g. <Sidebar />). */\n sidebar?: JSX.Element;\n /** Footer slot, below the content. */\n footer?: JSX.Element;\n /** Sidebar width (px or any CSS length). */\n sidebarWidth?: number | string;\n /** Controlled collapsed state of the sidebar rail. */\n sidebarCollapsed?: boolean;\n /** Initial collapsed state (uncontrolled). */\n defaultSidebarCollapsed?: boolean;\n /** Called whenever the collapsed state changes. */\n onSidebarCollapsedChange?: (collapsed: boolean) => void;\n /** Renders a built-in toggle button to collapse/expand the sidebar. */\n collapsible?: boolean;\n /** Custom toggle node replacing the default icon button. */\n collapseToggle?: JSX.Element;\n /** Accessible label for the default toggle button. */\n collapseToggleAriaLabel?: string;\n /** Sidebar collapsed width. */\n sidebarCollapsedWidth?: number | string;\n
|
|
1
|
+
{"version":3,"sources":["../../../../../../src/components/layout/appLayout/appLayout.tsx"],"sourcesContent":["import { type JSX, type ParentProps, Show, children, createEffect, mergeProps, onCleanup, splitProps } from \"solid-js\";\nimport cn from \"clsx\";\nimport { Icon } from \"../../base/icon\";\nimport { createControllableState } from \"../../../primitives/createControllableState\";\nimport { createId } from \"../../../primitives/createId\";\nimport { mergeRefs, type Ref } from \"../../../primitives/mergeRefs\";\nimport { mergeStyle } from \"../../../primitives/mergeStyle\";\nimport { publishHeaderOffset } from \"../../../primitives/publishHeaderOffset\";\n\nexport interface AppLayoutProps extends JSX.HTMLAttributes<HTMLDivElement> {\n /** Top bar slot (e.g. <AppBar />). Spans full width. */\n header?: JSX.Element;\n /** Side navigation slot (e.g. <Sidebar />). */\n sidebar?: JSX.Element;\n /** Footer slot, below the content. */\n footer?: JSX.Element;\n /** Sidebar width (px or any CSS length). */\n sidebarWidth?: number | string;\n /** Controlled collapsed state of the sidebar rail. */\n sidebarCollapsed?: boolean;\n /** Initial collapsed state (uncontrolled). */\n defaultSidebarCollapsed?: boolean;\n /** Called whenever the collapsed state changes. */\n onSidebarCollapsedChange?: (collapsed: boolean) => void;\n /** Renders a built-in toggle button to collapse/expand the sidebar. */\n collapsible?: boolean;\n /** Custom toggle node replacing the default icon button. */\n collapseToggle?: JSX.Element;\n /** Accessible label for the default toggle button. */\n collapseToggleAriaLabel?: string;\n /** Sidebar collapsed width. */\n sidebarCollapsedWidth?: number | string;\n /**\n * Keep the header pinned while content scrolls. A pinned header's height is published as `--smui-sheet-offset-top`\n * on the document element, so a `Sheet` opens below it and leaves it visible and clickable (a `Sheet` with its own\n * `offsetTop` ignores it). The height is taken from the top of the window, so the layout is assumed to start\n * there; with several layouts the most recent one wins.\n */\n stickyHeader?: boolean;\n /** Test id of the root `<div>`; `app-layout` unless given. */\n \"data-testid\"?: string | undefined;\n ref?: Ref<HTMLDivElement>;\n}\n\nconst len = (v: number | string): string => (typeof v === \"number\" ? `${v}px` : v);\n\nexport function AppLayout(props: ParentProps<AppLayoutProps>): JSX.Element {\n const merged = mergeProps(\n {\n sidebarWidth: 260 as number | string,\n defaultSidebarCollapsed: false,\n collapsible: false,\n collapseToggleAriaLabel: \"Toggle navigation\",\n sidebarCollapsedWidth: 72 as number | string,\n stickyHeader: true,\n },\n props\n );\n const [local, rest] = splitProps(merged, [\n \"header\",\n \"sidebar\",\n \"footer\",\n \"sidebarWidth\",\n \"sidebarCollapsed\",\n \"defaultSidebarCollapsed\",\n \"onSidebarCollapsedChange\",\n \"collapsible\",\n \"collapseToggle\",\n \"collapseToggleAriaLabel\",\n \"sidebarCollapsedWidth\",\n \"stickyHeader\",\n \"class\",\n \"style\",\n \"children\",\n \"ref\",\n \"data-testid\",\n ]);\n\n const [collapsed, setCollapsed] = createControllableState<boolean>({\n value: () => local.sidebarCollapsed,\n defaultValue: local.defaultSidebarCollapsed,\n onChange: (next) => local.onSidebarCollapsedChange?.(next),\n });\n\n const sidebarId = createId();\n\n // Resolve slot props through `children()` so each is created once, in THIS\n // component's reactive context. A pre-created element passed straight into a\n // <Show> gets its hydration key at the caller's position but renders at the\n // Show's, so the hydration walk can't find it (\"Unable to find DOM nodes for\n // key\"). Resolving here keeps the key sequence aligned on server and client.\n const headerSlot = children(() => local.header);\n const sidebarSlot = children(() => local.sidebar);\n const footerSlot = children(() => local.footer);\n // The toggle is drawn only on a collapsible layout, so it is read only then.\n const collapseToggleSlot = children(() => (local.collapsible ? local.collapseToggle : undefined));\n\n // The caller's style goes last: its own --smui-app-sidebar-w wins over the width props.\n const style = (): JSX.CSSProperties | string | undefined =>\n mergeStyle(\n { \"--smui-app-sidebar-w\": collapsed() ? len(local.sidebarCollapsedWidth) : len(local.sidebarWidth) },\n local.style\n );\n\n return (\n <div\n {...rest}\n ref={mergeRefs(local.ref)}\n data-testid={local[\"data-testid\"] ?? \"app-layout\"}\n class={cn(\n \"smwb-app-layout\",\n { \"smwb-app-layout_sticky-header\": local.stickyHeader, \"smwb-app-layout_collapsed\": collapsed() },\n local.class\n )}\n style={style()}\n >\n <Show when={headerSlot()}>\n <div\n class=\"smwb-app-layout__header\"\n ref={(el) =>\n // Created with the header, so it ends with it. A header that scrolls away leaves no fixed edge for a\n // sheet to start under.\n createEffect(() => {\n if (local.stickyHeader) onCleanup(publishHeaderOffset(el));\n })\n }\n >\n {headerSlot()}\n </div>\n </Show>\n <div class=\"smwb-app-layout__body\">\n <Show when={sidebarSlot()}>\n <aside id={sidebarId} class=\"smwb-app-layout__sidebar\">\n <Show when={local.collapsible}>\n <div class=\"smwb-app-layout__collapse\">\n <Show\n when={collapseToggleSlot()}\n fallback={\n <button\n type=\"button\"\n class=\"smwb-app-layout__collapse-btn\"\n aria-label={local.collapseToggleAriaLabel}\n aria-expanded={!collapsed()}\n aria-controls={sidebarId}\n onClick={() => setCollapsed(!collapsed())}\n >\n <Icon name={collapsed() ? \"chevron_right\" : \"chevron_left\"} size={20} />\n </button>\n }\n >\n {collapseToggleSlot()}\n </Show>\n </div>\n </Show>\n {sidebarSlot()}\n </aside>\n </Show>\n <div class=\"smwb-app-layout__content\">\n {local.children}\n <Show when={footerSlot()}>\n <div class=\"smwb-app-layout__footer\">{footerSlot()}</div>\n </Show>\n </div>\n </div>\n </div>\n );\n}\n\nexport default AppLayout;\n"],"mappings":"AAAA,SAAqC,MAAM,UAAU,cAAc,YAAY,WAAW,kBAAkB;AAC5G,OAAO,QAAQ;AACf,SAAS,YAAY;AACrB,SAAS,+BAA+B;AACxC,SAAS,gBAAgB;AACzB,SAAS,iBAA2B;AACpC,SAAS,kBAAkB;AAC3B,SAAS,2BAA2B;AAqCpC,MAAM,MAAM,CAAC,MAAgC,OAAO,MAAM,WAAW,GAAG,CAAC,OAAO;AAEzE,SAAS,UAAU,OAAiD;AACzE,QAAM,SAAS;AAAA,IACb;AAAA,MACE,cAAc;AAAA,MACd,yBAAyB;AAAA,MACzB,aAAa;AAAA,MACb,yBAAyB;AAAA,MACzB,uBAAuB;AAAA,MACvB,cAAc;AAAA,IAChB;AAAA,IACA;AAAA,EACF;AACA,QAAM,CAAC,OAAO,IAAI,IAAI,WAAW,QAAQ;AAAA,IACvC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,QAAM,CAAC,WAAW,YAAY,IAAI,wBAAiC;AAAA,IACjE,OAAO,MAAM,MAAM;AAAA,IACnB,cAAc,MAAM;AAAA,IACpB,UAAU,CAAC,SAAS,MAAM,2BAA2B,IAAI;AAAA,EAC3D,CAAC;AAED,QAAM,YAAY,SAAS;AAO3B,QAAM,aAAa,SAAS,MAAM,MAAM,MAAM;AAC9C,QAAM,cAAc,SAAS,MAAM,MAAM,OAAO;AAChD,QAAM,aAAa,SAAS,MAAM,MAAM,MAAM;AAE9C,QAAM,qBAAqB,SAAS,MAAO,MAAM,cAAc,MAAM,iBAAiB,MAAU;AAGhG,QAAM,QAAQ,MACZ;AAAA,IACE,EAAE,wBAAwB,UAAU,IAAI,IAAI,MAAM,qBAAqB,IAAI,IAAI,MAAM,YAAY,EAAE;AAAA,IACnG,MAAM;AAAA,EACR;AAEF,SACE,CAAC;AAAA,QACK;AAAA,IACJ,KAAK,UAAU,MAAM,GAAG;AAAA,IACxB,aAAa,MAAM,aAAa,KAAK;AAAA,IACrC,OAAO;AAAA,MACL;AAAA,MACA,EAAE,iCAAiC,MAAM,cAAc,6BAA6B,UAAU,EAAE;AAAA,MAChG,MAAM;AAAA,IACR;AAAA,IACA,OAAO,MAAM;AAAA,GACd;AAAA,MACC,CAAC,KAAK,MAAM,WAAW,GAAG;AAAA,QACxB,CAAC;AAAA,IACC,MAAM;AAAA,IACN,KAAK,CAAC;AAAA;AAAA;AAAA,MAGJ,aAAa,MAAM;AACjB,YAAI,MAAM,aAAc,WAAU,oBAAoB,EAAE,CAAC;AAAA,MAC3D,CAAC;AAAA;AAAA,GAEJ;AAAA,WACE,WAAW,EAAE;AAAA,QAChB,EAXC,IAWK;AAAA,MACR,EAbC,KAaM;AAAA,MACP,CAAC,IAAI,MAAM,wBAAwB;AAAA,QACjC,CAAC,KAAK,MAAM,YAAY,GAAG;AAAA,UACzB,CAAC,MAAM,IAAI,WAAW,MAAM,2BAA2B;AAAA,YACrD,CAAC,KAAK,MAAM,MAAM,aAAa;AAAA,cAC7B,CAAC,IAAI,MAAM,4BAA4B;AAAA,gBACrC,CAAC;AAAA,IACC,MAAM,mBAAmB;AAAA,IACzB,UACE,CAAC;AAAA,MACC,KAAK;AAAA,MACL,MAAM;AAAA,MACN,YAAY,MAAM;AAAA,MAClB,eAAe,CAAC,UAAU;AAAA,MAC1B,eAAe;AAAA,MACf,SAAS,MAAM,aAAa,CAAC,UAAU,CAAC;AAAA,KACzC;AAAA,sBACC,CAAC,KAAK,MAAM,UAAU,IAAI,kBAAkB,gBAAgB,MAAM,IAAI,EAAE;AAAA,oBAC1E,EATC;AAAA,GAWJ;AAAA,mBACE,mBAAmB,EAAE;AAAA,gBACxB,EAhBC,KAgBM;AAAA,cACT,EAlBC,IAkBK;AAAA,YACR,EApBC,KAoBM;AAAA,aACN,YAAY,EAAE;AAAA,UACjB,EAvBC,MAuBO;AAAA,QACV,EAzBC,KAyBM;AAAA,QACP,CAAC,IAAI,MAAM,2BAA2B;AAAA,WACnC,MAAM,SAAS;AAAA,UAChB,CAAC,KAAK,MAAM,WAAW,GAAG;AAAA,YACxB,CAAC,IAAI,MAAM,2BAA2B,WAAW,EAAE,EAAlD,IAAwD;AAAA,UAC3D,EAFC,KAEM;AAAA,QACT,EALC,IAKK;AAAA,MACR,EAjCC,IAiCK;AAAA,IACR,EA3DC;AA6DL;AAEA,IAAO,oBAAQ;","names":[],"file":"appLayout.jsx"}
|
|
@@ -10,6 +10,29 @@ import { isServer } from "solid-js/web";
|
|
|
10
10
|
import cn from "clsx";
|
|
11
11
|
import { throttle } from "../../../utils/throttle";
|
|
12
12
|
import { mergeRefs } from "../../../primitives/mergeRefs";
|
|
13
|
+
const MIN_SIZE = /^(\d*\.?\d+)(px|%)?$/i;
|
|
14
|
+
const parseMinSize = (min) => {
|
|
15
|
+
if (min === void 0) return null;
|
|
16
|
+
if (typeof min === "number") return Number.isFinite(min) && min > 0 ? { value: min, unit: "px" } : null;
|
|
17
|
+
const match = MIN_SIZE.exec(min.trim());
|
|
18
|
+
if (!match) return null;
|
|
19
|
+
const [, digits = "", unit] = match;
|
|
20
|
+
const value = parseFloat(digits);
|
|
21
|
+
return value > 0 ? { value, unit: unit === "%" ? "%" : "px" } : null;
|
|
22
|
+
};
|
|
23
|
+
const minToPercent = (min, available) => {
|
|
24
|
+
const parsed = parseMinSize(min);
|
|
25
|
+
if (!parsed) return 0;
|
|
26
|
+
if (parsed.unit === "%") return parsed.value;
|
|
27
|
+
return available > 0 ? parsed.value / available * 100 : 0;
|
|
28
|
+
};
|
|
29
|
+
const getBounds = (minFirst, minSecond, available) => {
|
|
30
|
+
const low = Math.min(100, minToPercent(minFirst, available));
|
|
31
|
+
const high = Math.max(low, 100 - minToPercent(minSecond, available));
|
|
32
|
+
return [low, high];
|
|
33
|
+
};
|
|
34
|
+
const minimumsClash = (minFirst, minSecond, available) => Math.min(100, minToPercent(minFirst, available)) + minToPercent(minSecond, available) > 100;
|
|
35
|
+
const clampDivider = (value, [low, high]) => Math.max(low, Math.min(high, value));
|
|
13
36
|
const getInitialDivider = (sizeFirst, sizeSecond) => {
|
|
14
37
|
if (!sizeFirst && sizeSecond) {
|
|
15
38
|
return 100 - sizeSecond;
|
|
@@ -33,28 +56,62 @@ function ScreenDivider(props) {
|
|
|
33
56
|
"onSizeChange",
|
|
34
57
|
"keyboardStep",
|
|
35
58
|
"resizerAriaLabel",
|
|
59
|
+
"minSizeFirst",
|
|
60
|
+
"minSizeSecond",
|
|
61
|
+
"collapsed",
|
|
36
62
|
"ref"
|
|
37
63
|
]);
|
|
38
64
|
let wrapperEl;
|
|
65
|
+
let resizerEl;
|
|
66
|
+
let lastBar = 0;
|
|
39
67
|
const [divider, setDivider] = createSignal(getInitialDivider(local.sizeFirst, local.sizeSecond));
|
|
40
68
|
const [isDragging, setIsDragging] = createSignal(false);
|
|
69
|
+
const [available, setAvailable] = createSignal(0);
|
|
70
|
+
const vertical = () => local.orientation === "vertical";
|
|
71
|
+
const measureAvailable = () => {
|
|
72
|
+
const main = (el) => {
|
|
73
|
+
const rect = el?.getBoundingClientRect();
|
|
74
|
+
return rect ? vertical() ? rect.height : rect.width : 0;
|
|
75
|
+
};
|
|
76
|
+
const bar = main(resizerEl);
|
|
77
|
+
if (bar > 0) lastBar = bar;
|
|
78
|
+
return Math.max(0, main(wrapperEl) - (bar > 0 ? bar : lastBar));
|
|
79
|
+
};
|
|
80
|
+
createEffect(() => {
|
|
81
|
+
if (isServer) return;
|
|
82
|
+
if (!parseMinSize(local.minSizeFirst) && !parseMinSize(local.minSizeSecond)) return;
|
|
83
|
+
void local.orientation;
|
|
84
|
+
void local.collapsed;
|
|
85
|
+
const update = () => {
|
|
86
|
+
setAvailable(measureAvailable());
|
|
87
|
+
};
|
|
88
|
+
update();
|
|
89
|
+
if (!wrapperEl || typeof ResizeObserver === "undefined") return;
|
|
90
|
+
const observer = new ResizeObserver(update);
|
|
91
|
+
observer.observe(wrapperEl);
|
|
92
|
+
onCleanup(() => observer.disconnect());
|
|
93
|
+
});
|
|
41
94
|
createEffect(() => {
|
|
42
95
|
setDivider(getInitialDivider(local.sizeFirst, local.sizeSecond));
|
|
43
96
|
});
|
|
97
|
+
const bounds = () => getBounds(local.minSizeFirst, local.minSizeSecond, available());
|
|
98
|
+
const shown = () => clampDivider(divider(), bounds());
|
|
44
99
|
createEffect(() => {
|
|
45
|
-
local.onSizeChange?.(
|
|
100
|
+
local.onSizeChange?.(shown());
|
|
46
101
|
});
|
|
47
102
|
const resolved = resolveChildren(() => local.children);
|
|
48
103
|
const child = () => resolved.toArray();
|
|
49
104
|
const dragOver = throttle((event) => {
|
|
50
|
-
if (!isDragging()) return;
|
|
105
|
+
if (!isDragging() || local.collapsed) return;
|
|
51
106
|
const wrapper = wrapperEl;
|
|
52
107
|
if (!wrapper) return;
|
|
53
108
|
const rect = wrapper.getBoundingClientRect();
|
|
54
|
-
const total =
|
|
55
|
-
const cursor =
|
|
109
|
+
const total = vertical() ? rect.height : rect.width;
|
|
110
|
+
const cursor = vertical() ? event.clientY - rect.top : event.clientX - rect.left;
|
|
56
111
|
const clamped = Math.max(5, Math.min(total - 5, cursor));
|
|
57
|
-
setDivider(
|
|
112
|
+
setDivider(
|
|
113
|
+
clampDivider(clamped / total * 100, getBounds(local.minSizeFirst, local.minSizeSecond, measureAvailable()))
|
|
114
|
+
);
|
|
58
115
|
}, 1e3 / 60);
|
|
59
116
|
const handleDragStart = () => {
|
|
60
117
|
setIsDragging(true);
|
|
@@ -77,46 +134,73 @@ function ScreenDivider(props) {
|
|
|
77
134
|
});
|
|
78
135
|
});
|
|
79
136
|
const onResizerKeyDown = (event) => {
|
|
80
|
-
const decreaseKey =
|
|
81
|
-
const increaseKey =
|
|
137
|
+
const decreaseKey = vertical() ? "ArrowUp" : "ArrowLeft";
|
|
138
|
+
const increaseKey = vertical() ? "ArrowDown" : "ArrowRight";
|
|
139
|
+
const keyBounds = getBounds(local.minSizeFirst, local.minSizeSecond, measureAvailable());
|
|
82
140
|
if (event.key === decreaseKey) {
|
|
83
141
|
event.preventDefault();
|
|
84
|
-
setDivider((
|
|
142
|
+
setDivider(clampDivider(Math.max(0, shown() - local.keyboardStep), keyBounds));
|
|
85
143
|
} else if (event.key === increaseKey) {
|
|
86
144
|
event.preventDefault();
|
|
87
|
-
setDivider((
|
|
145
|
+
setDivider(clampDivider(Math.min(100, shown() + local.keyboardStep), keyBounds));
|
|
88
146
|
} else if (event.key === "Home") {
|
|
89
147
|
event.preventDefault();
|
|
90
|
-
setDivider(0);
|
|
148
|
+
setDivider(clampDivider(0, keyBounds));
|
|
91
149
|
} else if (event.key === "End") {
|
|
92
150
|
event.preventDefault();
|
|
93
|
-
setDivider(100);
|
|
151
|
+
setDivider(clampDivider(100, keyBounds));
|
|
94
152
|
}
|
|
95
153
|
};
|
|
96
|
-
const
|
|
97
|
-
|
|
154
|
+
const sizeOf = (pane) => {
|
|
155
|
+
const share = pane === "first" ? shown() : 100 - shown();
|
|
156
|
+
return local.collapsed ? local.collapsed === pane ? 0 : 100 : share;
|
|
157
|
+
};
|
|
158
|
+
const paneStyle = (pane) => {
|
|
159
|
+
const style = vertical() ? { height: `${sizeOf(pane)}%` } : { width: `${sizeOf(pane)}%` };
|
|
160
|
+
const min = parseMinSize(pane === "first" ? local.minSizeFirst : local.minSizeSecond);
|
|
161
|
+
const gives = pane === "second" && minimumsClash(local.minSizeFirst, local.minSizeSecond, available());
|
|
162
|
+
if (!local.collapsed && !gives && min?.unit === "px")
|
|
163
|
+
style[vertical() ? "min-height" : "min-width"] = `${min.value}px`;
|
|
164
|
+
return style;
|
|
165
|
+
};
|
|
98
166
|
return <div
|
|
99
167
|
class={cn("smwb-screen-divider__wrapper", `smwb-${local.orientation}`, local.class)}
|
|
100
168
|
ref={mergeRefs(local.ref, (el) => wrapperEl = el)}
|
|
101
169
|
data-testid="screen-divider"
|
|
102
170
|
>
|
|
103
|
-
<div
|
|
171
|
+
<div
|
|
172
|
+
class={cn(
|
|
173
|
+
"smwb-screen-divider__first",
|
|
174
|
+
`smwb-${local.orientation}`,
|
|
175
|
+
local.collapsed === "first" && "smwb-collapsed"
|
|
176
|
+
)}
|
|
177
|
+
style={paneStyle("first")}
|
|
178
|
+
>
|
|
104
179
|
{child()[0] ?? ""}
|
|
105
180
|
</div>
|
|
106
181
|
<span
|
|
182
|
+
ref={(el) => resizerEl = el}
|
|
107
183
|
class="smwb-screen-divider__resizer"
|
|
184
|
+
hidden={local.collapsed !== void 0}
|
|
108
185
|
onPointerDown={handleDragStart}
|
|
109
186
|
draggable={false}
|
|
110
187
|
role="separator"
|
|
111
188
|
aria-orientation={local.orientation === "vertical" ? "horizontal" : "vertical"}
|
|
112
|
-
aria-valuenow={Math.round(
|
|
113
|
-
aria-valuemin={0}
|
|
114
|
-
aria-valuemax={
|
|
189
|
+
aria-valuenow={Math.round(shown())}
|
|
190
|
+
aria-valuemin={Math.round(bounds()[0])}
|
|
191
|
+
aria-valuemax={Math.round(bounds()[1])}
|
|
115
192
|
aria-label={local.resizerAriaLabel}
|
|
116
193
|
tabindex={0}
|
|
117
194
|
onKeyDown={onResizerKeyDown}
|
|
118
195
|
/>
|
|
119
|
-
<div
|
|
196
|
+
<div
|
|
197
|
+
class={cn(
|
|
198
|
+
"smwb-screen-divider__second",
|
|
199
|
+
`smwb-${local.orientation}`,
|
|
200
|
+
local.collapsed === "second" && "smwb-collapsed"
|
|
201
|
+
)}
|
|
202
|
+
style={paneStyle("second")}
|
|
203
|
+
>
|
|
120
204
|
{child()[1] ?? ""}
|
|
121
205
|
</div>
|
|
122
206
|
</div>;
|