@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/meter/meter.tsx"],"sourcesContent":["import \"@smwb/ui-styles/less/tokens.entry.less\";\nimport \"@smwb/ui-styles/less/components/meter/meter.entry.less\";\nimport { type JSX, Show, children, mergeProps, splitProps } from \"solid-js\";\nimport cn from \"clsx\";\nimport { mergeRefs, type Ref } from \"../../../primitives/mergeRefs\";\n\nexport type MeterColor = \"neutral\" | \"primary\" | \"info\" | \"success\" | \"warning\" | \"error\";\n\n/** From this share of `max` (0–1) on, the meter takes `color`. */\nexport interface MeterThreshold {\n from: number;\n color: MeterColor;\n}\n\n/** Info while under way, success when complete. */\nexport const DEFAULT_METER_THRESHOLDS: readonly MeterThreshold[] = [\n { from: 0, color: \"info\" },\n { from: 1, color: \"success\" },\n];\n\nexport interface MeterProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>, \"role\"> {\n /** How many are done (X of Y). */\n value: number;\n /** How many there are in all (X of Y). */\n max: number;\n /** What is measured — the accessible name, e.g. \"Requirements accepted\". */\n label: string;\n /** Visible text instead of \"X of Y\": markup, or a function of (value, max). */\n text?: JSX.Element | ((value: number, max: number) => JSX.Element);\n /** The word between the figures in the default text and in aria-valuetext. */\n ofLabel?: string;\n /** Colour by share of `max`: the last threshold the share reaches wins. */\n thresholds?: readonly MeterThreshold[];\n /** A fixed colour, instead of the thresholds. */\n color?: MeterColor;\n /** The bar only; the text stays available to assistive technology. */\n hideText?: boolean;\n /** Test id of the root `<div>`; `meter` unless given. */\n \"data-testid\"?: string | undefined;\n ref?: Ref<HTMLDivElement>;\n}\n\n/** The colour for a share of the whole: the last threshold (by `from`) the share reaches. */\nexport function meterColor(share: number, thresholds: readonly MeterThreshold[]): MeterColor {\n let color: MeterColor = \"neutral\";\n let from = -Infinity;\n for (const threshold of thresholds) {\n if (share >= threshold.from && threshold.from >= from) {\n color = threshold.color;\n from = threshold.from;\n }\n }\n return color;\n}\n\n/** A share of a whole, \"X of Y\": a bar and the figures beside it, coloured by thresholds. */\nexport function Meter(props: MeterProps): JSX.Element {\n const merged = mergeProps({ ofLabel: \"of\", thresholds: DEFAULT_METER_THRESHOLDS }, props);\n const [local, rest] = splitProps(merged, [\n \"class\",\n \"value\",\n \"max\",\n \"label\",\n \"text\",\n \"ofLabel\",\n \"thresholds\",\n \"color\",\n \"hideText\",\n \"ref\",\n \"data-testid\",\n ]);\n\n const share = (): number => (local.max > 0 ? Math.min(1, Math.max(0, local.value / local.max)) : 0);\n const color = (): MeterColor => local.color ?? meterColor(share(), local.thresholds);\n // Read the text once: markup passed as a prop is created anew on every read,\n // and during hydration a copy made only to test it has no server DOM node. A\n // function of (value, max) is kept as it is and called where the text renders.\n const custom = children(() => local.text as JSX.Element);\n const text = (): JSX.Element => {\n const resolved = custom() as JSX.Element | ((value: number, max: number) => JSX.Element);\n return typeof resolved === \"function\" ? resolved(local.value, local.max) : resolved;\n };\n\n return (\n <div\n {...rest}\n ref={mergeRefs(local.ref)}\n role=\"meter\"\n aria-label={local.label}\n aria-valuenow={local.value}\n aria-valuemin={0}\n aria-valuemax={local.max}\n aria-valuetext={`${local.value} ${local.ofLabel} ${local.max}`}\n data-testid={local[\"data-testid\"] ?? \"meter\"}\n class={cn(\"smwb-meter\"
|
|
1
|
+
{"version":3,"sources":["../../../../../src/components/dataDisplay/meter/meter.tsx"],"sourcesContent":["import \"@smwb/ui-styles/less/tokens.entry.less\";\nimport \"@smwb/ui-styles/less/components/meter/meter.entry.less\";\nimport { type JSX, Show, children, mergeProps, splitProps } from \"solid-js\";\nimport cn from \"clsx\";\nimport { mergeRefs, type Ref } from \"../../../primitives/mergeRefs\";\n\nexport type MeterColor = \"neutral\" | \"primary\" | \"info\" | \"success\" | \"warning\" | \"error\";\nexport type MeterTextPosition = \"after\" | \"before\";\nexport type MeterSize = \"medium\" | \"compact\";\n\n/** From this share of `max` (0–1) on, the meter takes `color`. */\nexport interface MeterThreshold {\n from: number;\n color: MeterColor;\n}\n\n/** Info while under way, success when complete. */\nexport const DEFAULT_METER_THRESHOLDS: readonly MeterThreshold[] = [\n { from: 0, color: \"info\" },\n { from: 1, color: \"success\" },\n];\n\nexport interface MeterProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>, \"role\"> {\n /** How many are done (X of Y). */\n value: number;\n /** How many there are in all (X of Y). */\n max: number;\n /** What is measured — the accessible name, e.g. \"Requirements accepted\". */\n label: string;\n /** Visible text instead of \"X of Y\": markup, or a function of (value, max). */\n text?: JSX.Element | ((value: number, max: number) => JSX.Element);\n /** The word between the figures in the default text and in aria-valuetext. */\n ofLabel?: string;\n /** Colour by share of `max`: the last threshold the share reaches wins. */\n thresholds?: readonly MeterThreshold[];\n /** A fixed colour, instead of the thresholds. */\n color?: MeterColor;\n /** The bar only; the text stays available to assistive technology. */\n hideText?: boolean;\n /** Where the text sits against the bar: `after` it (the default) or `before` it. */\n textPosition?: MeterTextPosition;\n /**\n * `compact`: a fixed-width bar and small secondary text, and the block shrinks to its\n * content instead of filling the parent. `medium` (the default) fills the parent.\n */\n size?: MeterSize;\n /**\n * What assistive technology reads as the value, e.g. the text you format for the\n * eye (\"$4.18 / $20\"); \"X of Y\" built from `value`, `ofLabel` and `max` unless given.\n */\n \"aria-valuetext\"?: string | undefined;\n /** Test id of the root `<div>`; `meter` unless given. */\n \"data-testid\"?: string | undefined;\n ref?: Ref<HTMLDivElement>;\n}\n\n/** The colour for a share of the whole: the last threshold (by `from`) the share reaches. */\nexport function meterColor(share: number, thresholds: readonly MeterThreshold[]): MeterColor {\n let color: MeterColor = \"neutral\";\n let from = -Infinity;\n for (const threshold of thresholds) {\n if (share >= threshold.from && threshold.from >= from) {\n color = threshold.color;\n from = threshold.from;\n }\n }\n return color;\n}\n\n/** A share of a whole, \"X of Y\": a bar and the figures beside it, coloured by thresholds. */\nexport function Meter(props: MeterProps): JSX.Element {\n const merged = mergeProps({ ofLabel: \"of\", thresholds: DEFAULT_METER_THRESHOLDS }, props);\n const [local, rest] = splitProps(merged, [\n \"class\",\n \"value\",\n \"max\",\n \"label\",\n \"text\",\n \"ofLabel\",\n \"thresholds\",\n \"color\",\n \"hideText\",\n \"textPosition\",\n \"size\",\n \"aria-valuetext\",\n \"ref\",\n \"data-testid\",\n ]);\n\n const share = (): number => (local.max > 0 ? Math.min(1, Math.max(0, local.value / local.max)) : 0);\n const color = (): MeterColor => local.color ?? meterColor(share(), local.thresholds);\n // Read the text once: markup passed as a prop is created anew on every read,\n // and during hydration a copy made only to test it has no server DOM node. A\n // function of (value, max) is kept as it is and called where the text renders.\n const custom = children(() => local.text as JSX.Element);\n const text = (): JSX.Element => {\n const resolved = custom() as JSX.Element | ((value: number, max: number) => JSX.Element);\n return typeof resolved === \"function\" ? resolved(local.value, local.max) : resolved;\n };\n\n return (\n <div\n {...rest}\n ref={mergeRefs(local.ref)}\n role=\"meter\"\n aria-label={local.label}\n aria-valuenow={local.value}\n aria-valuemin={0}\n aria-valuemax={local.max}\n aria-valuetext={local[\"aria-valuetext\"] ?? `${local.value} ${local.ofLabel} ${local.max}`}\n data-testid={local[\"data-testid\"] ?? \"meter\"}\n class={cn(\n \"smwb-meter\",\n `smwb-meter_color-${color()}`,\n {\n \"smwb-meter_bar-only\": local.hideText,\n \"smwb-meter_text-before\": local.textPosition === \"before\",\n \"smwb-meter_compact\": local.size === \"compact\",\n },\n local.class\n )}\n >\n <span class=\"smwb-meter__track\" aria-hidden=\"true\">\n <span class=\"smwb-meter__fill\" style={{ width: `${share() * 100}%` }} />\n </span>\n <Show when={!local.hideText}>\n <span class=\"smwb-meter__text\" aria-hidden=\"true\">\n <Show\n when={custom() !== undefined}\n fallback={\n <>\n <b class=\"smwb-meter__value\">{local.value}</b>\n <span class=\"smwb-meter__of\">\n {\" \"}\n {local.ofLabel} {local.max}\n </span>\n </>\n }\n >\n {text()}\n </Show>\n </span>\n </Show>\n </div>\n );\n}\n\nexport default Meter;\n"],"mappings":"AAAA,OAAO;AACP,OAAO;AACP,SAAmB,MAAM,UAAU,YAAY,kBAAkB;AACjE,OAAO,QAAQ;AACf,SAAS,iBAA2B;AAa7B,MAAM,2BAAsD;AAAA,EACjE,EAAE,MAAM,GAAG,OAAO,OAAO;AAAA,EACzB,EAAE,MAAM,GAAG,OAAO,UAAU;AAC9B;AAqCO,SAAS,WAAW,OAAe,YAAmD;AAC3F,MAAI,QAAoB;AACxB,MAAI,OAAO;AACX,aAAW,aAAa,YAAY;AAClC,QAAI,SAAS,UAAU,QAAQ,UAAU,QAAQ,MAAM;AACrD,cAAQ,UAAU;AAClB,aAAO,UAAU;AAAA,IACnB;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,MAAM,OAAgC;AACpD,QAAM,SAAS,WAAW,EAAE,SAAS,MAAM,YAAY,yBAAyB,GAAG,KAAK;AACxF,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,EACF,CAAC;AAED,QAAM,QAAQ,MAAe,MAAM,MAAM,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,MAAM,QAAQ,MAAM,GAAG,CAAC,IAAI;AACjG,QAAM,QAAQ,MAAkB,MAAM,SAAS,WAAW,MAAM,GAAG,MAAM,UAAU;AAInF,QAAM,SAAS,SAAS,MAAM,MAAM,IAAmB;AACvD,QAAM,OAAO,MAAmB;AAC9B,UAAM,WAAW,OAAO;AACxB,WAAO,OAAO,aAAa,aAAa,SAAS,MAAM,OAAO,MAAM,GAAG,IAAI;AAAA,EAC7E;AAEA,SACE,CAAC;AAAA,QACK;AAAA,IACJ,KAAK,UAAU,MAAM,GAAG;AAAA,IACxB,KAAK;AAAA,IACL,YAAY,MAAM;AAAA,IAClB,eAAe,MAAM;AAAA,IACrB,eAAe;AAAA,IACf,eAAe,MAAM;AAAA,IACrB,gBAAgB,MAAM,gBAAgB,KAAK,GAAG,MAAM,KAAK,IAAI,MAAM,OAAO,IAAI,MAAM,GAAG;AAAA,IACvF,aAAa,MAAM,aAAa,KAAK;AAAA,IACrC,OAAO;AAAA,MACL;AAAA,MACA,oBAAoB,MAAM,CAAC;AAAA,MAC3B;AAAA,QACE,uBAAuB,MAAM;AAAA,QAC7B,0BAA0B,MAAM,iBAAiB;AAAA,QACjD,sBAAsB,MAAM,SAAS;AAAA,MACvC;AAAA,MACA,MAAM;AAAA,IACR;AAAA,GACD;AAAA,MACC,CAAC,KAAK,MAAM,oBAAoB,YAAY,OAAO;AAAA,QACjD,CAAC,KAAK,MAAM,mBAAmB,OAAO,EAAE,OAAO,GAAG,MAAM,IAAI,GAAG,IAAI,GAAG,EAAE;AAAA,MAC1E,EAFC,KAEM;AAAA,MACP,CAAC,KAAK,MAAM,CAAC,MAAM,UAAU;AAAA,QAC3B,CAAC,KAAK,MAAM,mBAAmB,YAAY,OAAO;AAAA,UAChD,CAAC;AAAA,IACC,MAAM,OAAO,MAAM;AAAA,IACnB,UACE,EAAE;AAAA,gBACA,CAAC,EAAE,MAAM,qBAAqB,MAAM,MAAM,EAAzC,EAA6C;AAAA,gBAC9C,CAAC,KAAK,MAAM,iBAAiB;AAAA,mBAC1B,IAAI;AAAA,mBACJ,MAAM,QAAQ,EAAE,MAAM,IAAI;AAAA,gBAC7B,EAHC,KAGM;AAAA,cACT;AAAA,GAEH;AAAA,aACE,KAAK,EAAE;AAAA,UACV,EAbC,KAaM;AAAA,QACT,EAfC,KAeM;AAAA,MACT,EAjBC,KAiBM;AAAA,IACT,EA1CC;AA4CL;AAEA,IAAO,gBAAQ;","names":[],"file":"meter.jsx"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/components/dataDisplay/statusDot/index.ts"],
|
|
4
|
-
"sourcesContent": ["export { StatusDot, default } from \"./statusDot\";\nexport type { StatusDotProps, StatusDotStatus, StatusDotColor, StatusDotSize } from \"./statusDot\";\n"],
|
|
4
|
+
"sourcesContent": ["export { StatusDot, default } from \"./statusDot\";\nexport type { StatusDotProps, StatusDotStatus, StatusDotColor, StatusDotSize, StatusDotShape } from \"./statusDot\";\n"],
|
|
5
5
|
"mappings": "AAAA,SAAS,WAAW,WAAAA,gBAAe;",
|
|
6
6
|
"names": ["default"]
|
|
7
7
|
}
|
|
@@ -4,7 +4,17 @@ import { splitProps } from "solid-js";
|
|
|
4
4
|
import cn from "clsx";
|
|
5
5
|
import { mergeRefs } from "../../../primitives/mergeRefs";
|
|
6
6
|
function StatusDot(props) {
|
|
7
|
-
const [local, rest] = splitProps(props, [
|
|
7
|
+
const [local, rest] = splitProps(props, [
|
|
8
|
+
"class",
|
|
9
|
+
"status",
|
|
10
|
+
"color",
|
|
11
|
+
"size",
|
|
12
|
+
"shape",
|
|
13
|
+
"pulse",
|
|
14
|
+
"label",
|
|
15
|
+
"ref",
|
|
16
|
+
"data-testid"
|
|
17
|
+
]);
|
|
8
18
|
const shown = () => local.status ?? (local.color === void 0 ? "unknown" : void 0);
|
|
9
19
|
return <span
|
|
10
20
|
{...rest}
|
|
@@ -15,6 +25,8 @@ function StatusDot(props) {
|
|
|
15
25
|
class={cn(
|
|
16
26
|
"smwb-status-dot",
|
|
17
27
|
`smwb-status-dot_size-${local.size ?? "md"}`,
|
|
28
|
+
// Only the diamond carries a class: a dot is the shape the base rule already draws.
|
|
29
|
+
local.shape === "diamond" && "smwb-status-dot_shape-diamond",
|
|
18
30
|
shown() && `smwb-status-dot_status-${shown()}`,
|
|
19
31
|
local.color && `smwb-status-dot_color-${local.color}`,
|
|
20
32
|
{ "smwb-status-dot_pulse": local.pulse ?? local.status === "active" },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/components/dataDisplay/statusDot/statusDot.tsx"],"sourcesContent":["import \"@smwb/ui-styles/less/tokens.entry.less\";\nimport \"@smwb/ui-styles/less/components/statusDot/statusDot.entry.less\";\nimport type { JSX } from \"solid-js\";\nimport { splitProps } from \"solid-js\";\nimport cn from \"clsx\";\nimport { mergeRefs, type Ref } from \"../../../primitives/mergeRefs\";\n\n/** What the marker says: `active` is under way, `attention` waits for someone, `idle` rests, `done` is finished. */\nexport type StatusDotStatus = \"active\" | \"attention\" | \"idle\" | \"done\" | \"error\" | \"unknown\";\n\n/** The theme colour names `Chip` takes; `default` is the neutral on-surface colour. */\nexport type StatusDotColor = \"default\" | \"primary\" | \"secondary\" | \"error\" | \"success\" | \"warning\" | \"info\";\n\nexport type StatusDotSize = \"sm\" | \"md\";\n\nexport interface StatusDotProps extends Omit<JSX.HTMLAttributes<HTMLSpanElement>, \"role\" | \"children\" | \"aria-label\"> {\n /**\n * What the dot says, and so its colour: `active` info, `attention` warning, `idle` neutral, `done` success,\n * `error` error. `unknown` is a ring in the outline colour. Without `status` (and without `color`) the dot is\n * `unknown`.\n */\n status?: StatusDotStatus | undefined;\n /**\n * A theme colour instead of the status's own, or on its own with no `status` (a plain disc). Given with a\n * `status`, the status still sets the shape.\n */\n color?: StatusDotColor | undefined;\n /** `md` (12px) unless given; `sm` is 8px. */\n size?: StatusDotSize | undefined;\n /**\n * A ring spreads from the dot. On unless given for `active`, off for every other status. Never drawn while the\n * user prefers reduced motion.\n */\n pulse?: boolean | undefined;\n /** What the dot means in words, e.g. \"Working\": the accessible name, read by assistive technology. */\n label: string;\n /** Test id of the root; `status-dot` unless given. */\n \"data-testid\"?: string | undefined;\n ref?: Ref<HTMLSpanElement>;\n}\n\n/** A small status marker: a dot
|
|
1
|
+
{"version":3,"sources":["../../../../../src/components/dataDisplay/statusDot/statusDot.tsx"],"sourcesContent":["import \"@smwb/ui-styles/less/tokens.entry.less\";\nimport \"@smwb/ui-styles/less/components/statusDot/statusDot.entry.less\";\nimport type { JSX } from \"solid-js\";\nimport { splitProps } from \"solid-js\";\nimport cn from \"clsx\";\nimport { mergeRefs, type Ref } from \"../../../primitives/mergeRefs\";\n\n/** What the marker says: `active` is under way, `attention` waits for someone, `idle` rests, `done` is finished. */\nexport type StatusDotStatus = \"active\" | \"attention\" | \"idle\" | \"done\" | \"error\" | \"unknown\";\n\n/** The theme colour names `Chip` takes; `default` is the neutral on-surface colour. */\nexport type StatusDotColor = \"default\" | \"primary\" | \"secondary\" | \"error\" | \"success\" | \"warning\" | \"info\";\n\nexport type StatusDotSize = \"sm\" | \"md\";\n\n/** The marker's outline, chosen by the caller and not by `status`: `dot` is a disc, `diamond` a square turned corner-up. */\nexport type StatusDotShape = \"dot\" | \"diamond\";\n\nexport interface StatusDotProps extends Omit<JSX.HTMLAttributes<HTMLSpanElement>, \"role\" | \"children\" | \"aria-label\"> {\n /**\n * What the dot says, and so its colour: `active` info, `attention` warning, `idle` neutral, `done` success,\n * `error` error. `unknown` is a ring in the outline colour. Without `status` (and without `color`) the dot is\n * `unknown`.\n */\n status?: StatusDotStatus | undefined;\n /**\n * A theme colour instead of the status's own, or on its own with no `status` (a plain disc). Given with a\n * `status`, the status still sets the shape.\n */\n color?: StatusDotColor | undefined;\n /** `md` (12px) unless given; `sm` is 8px. */\n size?: StatusDotSize | undefined;\n /**\n * The marker's outline, `dot` unless given: `diamond` turns the same square corner-up. The shape is the caller's\n * choice, not the status's — a diamond takes its colour from `color` or `status`, its size from `size` and its\n * pulse from `pulse` exactly as the dot does, so `unknown` is a ring and `active` pulses in either shape. Its box\n * is the box of a dot of the same `size`, so the two swap places without moving a row.\n */\n shape?: StatusDotShape | undefined;\n /**\n * A ring spreads from the dot. On unless given for `active`, off for every other status. Never drawn while the\n * user prefers reduced motion.\n */\n pulse?: boolean | undefined;\n /** What the dot means in words, e.g. \"Working\": the accessible name, read by assistive technology. */\n label: string;\n /** Test id of the root; `status-dot` unless given. */\n \"data-testid\"?: string | undefined;\n ref?: Ref<HTMLSpanElement>;\n}\n\n/** A small status marker: a dot or a diamond, coloured by `status` or `color`, named by `label`. */\nexport function StatusDot(props: StatusDotProps): JSX.Element {\n const [local, rest] = splitProps(props, [\n \"class\",\n \"status\",\n \"color\",\n \"size\",\n \"shape\",\n \"pulse\",\n \"label\",\n \"ref\",\n \"data-testid\",\n ]);\n\n // A bare colour is a plain disc; with neither, the dot is \"unknown\".\n const shown = (): StatusDotStatus | undefined => local.status ?? (local.color === undefined ? \"unknown\" : undefined);\n\n return (\n <span\n {...rest}\n ref={mergeRefs(local.ref)}\n role=\"img\"\n aria-label={local.label}\n data-testid={local[\"data-testid\"] ?? \"status-dot\"}\n class={cn(\n \"smwb-status-dot\",\n `smwb-status-dot_size-${local.size ?? \"md\"}`,\n // Only the diamond carries a class: a dot is the shape the base rule already draws.\n local.shape === \"diamond\" && \"smwb-status-dot_shape-diamond\",\n shown() && `smwb-status-dot_status-${shown()}`,\n local.color && `smwb-status-dot_color-${local.color}`,\n { \"smwb-status-dot_pulse\": local.pulse ?? local.status === \"active\" },\n local.class\n )}\n />\n );\n}\n\nexport default StatusDot;\n"],"mappings":"AAAA,OAAO;AACP,OAAO;AAEP,SAAS,kBAAkB;AAC3B,OAAO,QAAQ;AACf,SAAS,iBAA2B;AA+C7B,SAAS,UAAU,OAAoC;AAC5D,QAAM,CAAC,OAAO,IAAI,IAAI,WAAW,OAAO;AAAA,IACtC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAGD,QAAM,QAAQ,MAAmC,MAAM,WAAW,MAAM,UAAU,SAAY,YAAY;AAE1G,SACE,CAAC;AAAA,QACK;AAAA,IACJ,KAAK,UAAU,MAAM,GAAG;AAAA,IACxB,KAAK;AAAA,IACL,YAAY,MAAM;AAAA,IAClB,aAAa,MAAM,aAAa,KAAK;AAAA,IACrC,OAAO;AAAA,MACL;AAAA,MACA,wBAAwB,MAAM,QAAQ,IAAI;AAAA;AAAA,MAE1C,MAAM,UAAU,aAAa;AAAA,MAC7B,MAAM,KAAK,0BAA0B,MAAM,CAAC;AAAA,MAC5C,MAAM,SAAS,yBAAyB,MAAM,KAAK;AAAA,MACnD,EAAE,yBAAyB,MAAM,SAAS,MAAM,WAAW,SAAS;AAAA,MACpE,MAAM;AAAA,IACR;AAAA,EACF;AAEJ;AAEA,IAAO,oBAAQ;","names":[],"file":"statusDot.jsx"}
|
|
@@ -63,6 +63,12 @@ function TreeNode(props) {
|
|
|
63
63
|
const isOpen = () => ctx.expandedIds().includes(nodeId);
|
|
64
64
|
const isSelected = () => ctx.selectedIds().includes(nodeId);
|
|
65
65
|
const isLoading = () => ctx.loadingIds().includes(nodeId);
|
|
66
|
+
const nodeIconName = () => {
|
|
67
|
+
const icons = ctx.nodeIcon;
|
|
68
|
+
if (icons === false) return void 0;
|
|
69
|
+
return isOpen() ? icons.opened ?? "folder_open" : icons.closed ?? "folder";
|
|
70
|
+
};
|
|
71
|
+
const listIconName = () => ctx.listIcon === false ? void 0 : ctx.listIcon;
|
|
66
72
|
const toggleNode = () => {
|
|
67
73
|
if (!isOpen()) ctx.ensureLoaded(item(), nodeId);
|
|
68
74
|
ctx.toggleExpanded(nodeId);
|
|
@@ -118,8 +124,11 @@ function TreeNode(props) {
|
|
|
118
124
|
</Show>
|
|
119
125
|
<span class="smwb-tree__node__content" onClick={onContentClick} onKeyDown={onKeyDown} tabindex={0} role="button">
|
|
120
126
|
<span class="smwb-tree__node__label" id={labelId}>
|
|
121
|
-
<Show
|
|
122
|
-
|
|
127
|
+
<Show
|
|
128
|
+
when={hasChildNodes()}
|
|
129
|
+
fallback={<Show when={listIconName()}>{(name) => <Icon name={name()} size={20} />}</Show>}
|
|
130
|
+
>
|
|
131
|
+
<Show when={nodeIconName()}>{(name) => <Icon name={name()} fill={1} size={20} />}</Show>
|
|
123
132
|
</Show>
|
|
124
133
|
{labelSlot()}
|
|
125
134
|
</span>
|
|
@@ -150,6 +159,8 @@ function TreeView(props) {
|
|
|
150
159
|
{
|
|
151
160
|
listIcon: "draft",
|
|
152
161
|
nodeIcon: { closed: "folder", opened: "folder_open" },
|
|
162
|
+
dense: false,
|
|
163
|
+
guides: true,
|
|
153
164
|
data: [],
|
|
154
165
|
defaultExpanded: [],
|
|
155
166
|
defaultSelected: [],
|
|
@@ -163,6 +174,8 @@ function TreeView(props) {
|
|
|
163
174
|
"class",
|
|
164
175
|
"listIcon",
|
|
165
176
|
"nodeIcon",
|
|
177
|
+
"dense",
|
|
178
|
+
"guides",
|
|
166
179
|
"data",
|
|
167
180
|
"expanded",
|
|
168
181
|
"defaultExpanded",
|
|
@@ -256,7 +269,7 @@ function TreeView(props) {
|
|
|
256
269
|
data-testid={local["data-testid"] ?? "tree-view"}
|
|
257
270
|
ref={mergeRefs(local.ref)}
|
|
258
271
|
role="tree"
|
|
259
|
-
class={cn("smwb-tree", local.class)}
|
|
272
|
+
class={cn("smwb-tree", { "smwb-tree_dense": local.dense, "smwb-tree_no-guides": !local.guides }, local.class)}
|
|
260
273
|
>
|
|
261
274
|
<TreeNodes items={local.data} path="" wrap />
|
|
262
275
|
</div>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/components/dataDisplay/treeView/treeView.tsx"],"sourcesContent":["import \"@smwb/ui-styles/less/tokens.entry.less\";\nimport \"@smwb/ui-styles/less/components/treeView/treeView.entry.less\";\nimport {\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 \"@smwb/ui-styles/less/tokens.entry.less\";\nimport \"@smwb/ui-styles/less/components/treeView/treeView.entry.less\";\nimport {\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"}
|
|
@@ -8,6 +8,16 @@ import { mergeRefs } from "../../../primitives/mergeRefs";
|
|
|
8
8
|
import { useButtonGroup } from "../buttonGroups/buttonGroupContext";
|
|
9
9
|
import { callHandler } from "../../../primitives/callHandler";
|
|
10
10
|
import { resolveLinkRel, resolveLinkTarget } from "../../../utils/linkAttrs";
|
|
11
|
+
const ICON_ONLY_NAME = "`iconOnly` has no text, so it needs an `aria-label` (or `aria-labelledby`) to be named.";
|
|
12
|
+
const ICON_ONLY_LABEL = "`iconOnly` is a square for one icon: a `label` is clipped. Drop it and name the button with `aria-label`.";
|
|
13
|
+
const ICON_ONLY_CHILDREN = "`iconOnly` is a square for one icon: children are clipped. Drop them and name the button with `aria-label`.";
|
|
14
|
+
const ICON_ONLY_ICON = "`iconOnly` without an `icon` draws an empty square.";
|
|
15
|
+
const warnedIconOnly = /* @__PURE__ */ new Set();
|
|
16
|
+
function warnIconOnly(kind, message) {
|
|
17
|
+
if (warnedIconOnly.has(kind)) return;
|
|
18
|
+
warnedIconOnly.add(kind);
|
|
19
|
+
console.warn(`[smwb] Button: ${message}`);
|
|
20
|
+
}
|
|
11
21
|
function Button(props) {
|
|
12
22
|
const group = useButtonGroup();
|
|
13
23
|
const merged = mergeProps(
|
|
@@ -28,6 +38,7 @@ function Button(props) {
|
|
|
28
38
|
"label",
|
|
29
39
|
"variant",
|
|
30
40
|
"icon",
|
|
41
|
+
"iconOnly",
|
|
31
42
|
"iconProps",
|
|
32
43
|
"href",
|
|
33
44
|
"target",
|
|
@@ -60,7 +71,8 @@ function Button(props) {
|
|
|
60
71
|
local.class,
|
|
61
72
|
{
|
|
62
73
|
"smwb-disabled": disabled(),
|
|
63
|
-
"smwb-full-width": fullWidth()
|
|
74
|
+
"smwb-full-width": fullWidth(),
|
|
75
|
+
"smwb-button_icon-only": local.iconOnly
|
|
64
76
|
}
|
|
65
77
|
);
|
|
66
78
|
const onPointerDown = (event) => {
|
|
@@ -81,6 +93,19 @@ function Button(props) {
|
|
|
81
93
|
<Ripple actionRef={(actions) => rippleActions = actions} center={local.rippleCenter} />
|
|
82
94
|
</Show>
|
|
83
95
|
</>;
|
|
96
|
+
if (local.iconOnly) {
|
|
97
|
+
if (!rest["aria-label"] && !rest["aria-labelledby"]) {
|
|
98
|
+
warnIconOnly("name", ICON_ONLY_NAME);
|
|
99
|
+
}
|
|
100
|
+
if (local.label) {
|
|
101
|
+
warnIconOnly("label", ICON_ONLY_LABEL);
|
|
102
|
+
} else if ("children" in local) {
|
|
103
|
+
warnIconOnly("children", ICON_ONLY_CHILDREN);
|
|
104
|
+
}
|
|
105
|
+
if (!icon()) {
|
|
106
|
+
warnIconOnly("icon", ICON_ONLY_ICON);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
84
109
|
return <Show
|
|
85
110
|
when={local.href}
|
|
86
111
|
fallback={<button
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/components/inputs/button/button.tsx"],"sourcesContent":["import \"@smwb/ui-styles/less/tokens.entry.less\";\nimport \"@smwb/ui-styles/less/components/button/button.entry.less\";\nimport { 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 \"@smwb/ui-styles/less/tokens.entry.less\";\nimport \"@smwb/ui-styles/less/components/button/button.entry.less\";\nimport { 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"}
|
|
@@ -23,6 +23,7 @@ function Toggle(props) {
|
|
|
23
23
|
"color",
|
|
24
24
|
"helperText",
|
|
25
25
|
"error",
|
|
26
|
+
"compact",
|
|
26
27
|
"onChange",
|
|
27
28
|
"aria-describedby",
|
|
28
29
|
"ref"
|
|
@@ -40,13 +41,19 @@ function Toggle(props) {
|
|
|
40
41
|
local.onChange?.(event, event.currentTarget.checked, props);
|
|
41
42
|
};
|
|
42
43
|
return <TextFieldWrapper
|
|
43
|
-
helperText={helper.text()}
|
|
44
|
+
helperText={local.compact && !helper.text() ? null : helper.text()}
|
|
44
45
|
helperTextId={helper.id}
|
|
45
46
|
disabled={local.disabled}
|
|
46
47
|
error={local.error}
|
|
47
48
|
class="smwb-text-field-checkbox"
|
|
48
49
|
>
|
|
49
|
-
<span
|
|
50
|
+
<span
|
|
51
|
+
class={cn(
|
|
52
|
+
"smwb-toggle__wrapper",
|
|
53
|
+
local.labelPlacement && `smwb-toggle__wrapper_label-${local.labelPlacement}`,
|
|
54
|
+
local.compact && "smwb-toggle__wrapper_compact"
|
|
55
|
+
)}
|
|
56
|
+
>
|
|
50
57
|
<Show when={local.label}>
|
|
51
58
|
<label for={id} class="smwb-toggle__label">
|
|
52
59
|
{local.label}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/components/inputs/toggle/toggle.tsx"],"sourcesContent":["import \"@smwb/ui-styles/less/tokens.entry.less\";\nimport \"@smwb/ui-styles/less/components/toggle/toggle.entry.less\";\nimport { 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 \"@smwb/ui-styles/less/tokens.entry.less\";\nimport \"@smwb/ui-styles/less/components/toggle/toggle.entry.less\";\nimport { 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,OAAO;AACP,OAAO;AACP,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,12 +1,13 @@
|
|
|
1
1
|
import "@smwb/ui-styles/less/tokens.entry.less";
|
|
2
2
|
import "@smwb/ui-styles/less/components/appLayout/appLayout.entry.less";
|
|
3
|
-
import { Show, children, mergeProps, splitProps } from "solid-js";
|
|
3
|
+
import { Show, children, createEffect, mergeProps, onCleanup, splitProps } from "solid-js";
|
|
4
4
|
import cn from "clsx";
|
|
5
5
|
import { Icon } from "../../base/icon";
|
|
6
6
|
import { createControllableState } from "../../../primitives/createControllableState";
|
|
7
7
|
import { createId } from "../../../primitives/createId";
|
|
8
8
|
import { mergeRefs } from "../../../primitives/mergeRefs";
|
|
9
9
|
import { mergeStyle } from "../../../primitives/mergeStyle";
|
|
10
|
+
import { publishHeaderOffset } from "../../../primitives/publishHeaderOffset";
|
|
10
11
|
const len = (v) => typeof v === "number" ? `${v}px` : v;
|
|
11
12
|
function AppLayout(props) {
|
|
12
13
|
const merged = mergeProps(
|
|
@@ -65,7 +66,18 @@ function AppLayout(props) {
|
|
|
65
66
|
style={style()}
|
|
66
67
|
>
|
|
67
68
|
<Show when={headerSlot()}>
|
|
68
|
-
<div
|
|
69
|
+
<div
|
|
70
|
+
class="smwb-app-layout__header"
|
|
71
|
+
ref={(el) => (
|
|
72
|
+
// Created with the header, so it ends with it. A header that scrolls away leaves no fixed edge for a
|
|
73
|
+
// sheet to start under.
|
|
74
|
+
createEffect(() => {
|
|
75
|
+
if (local.stickyHeader) onCleanup(publishHeaderOffset(el));
|
|
76
|
+
})
|
|
77
|
+
)}
|
|
78
|
+
>
|
|
79
|
+
{headerSlot()}
|
|
80
|
+
</div>
|
|
69
81
|
</Show>
|
|
70
82
|
<div class="smwb-app-layout__body">
|
|
71
83
|
<Show when={sidebarSlot()}>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/components/layout/appLayout/appLayout.tsx"],"sourcesContent":["import \"@smwb/ui-styles/less/tokens.entry.less\";\nimport \"@smwb/ui-styles/less/components/appLayout/appLayout.entry.less\";\nimport { 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 \"@smwb/ui-styles/less/tokens.entry.less\";\nimport \"@smwb/ui-styles/less/components/appLayout/appLayout.entry.less\";\nimport { 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":"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","names":[],"file":"appLayout.jsx"}
|