@weasel-js/ui 1.7.1 → 1.7.3
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/dist/chunks/{BandEditor-Dd0egAgE.js → BandEditor-B4jUAHQ8.js} +13 -13
- package/dist/chunks/BandEditor-B4jUAHQ8.js.map +1 -0
- package/dist/chunks/{ColorField-BLs9fbeq.js → ColorField-BwlelY26.js} +4 -4
- package/dist/chunks/ColorField-BwlelY26.js.map +1 -0
- package/dist/chunks/{ComboBox-CuTLRmUg.js → ComboBox-DKln_mOd.js} +11 -11
- package/dist/chunks/ComboBox-DKln_mOd.js.map +1 -0
- package/dist/chunks/{CurveEditor-jVBEqqdv.js → CurveEditor-0EE00D6s.js} +2 -2
- package/dist/chunks/{CurveEditor-jVBEqqdv.js.map → CurveEditor-0EE00D6s.js.map} +1 -1
- package/dist/chunks/{DataGrid-BklgBqS0.js → DataGrid-CmHJe57v.js} +22 -17
- package/dist/chunks/DataGrid-CmHJe57v.js.map +1 -0
- package/dist/chunks/DetailList-BpU50XVe.js +54 -0
- package/dist/chunks/DetailList-BpU50XVe.js.map +1 -0
- package/dist/chunks/{DetentSlider-Bh7EpMqT.js → DetentSlider-DNTAk8xP.js} +2 -2
- package/dist/chunks/{DetentSlider-Bh7EpMqT.js.map → DetentSlider-DNTAk8xP.js.map} +1 -1
- package/dist/chunks/{FallthroughDiagram-C7BUe8eU.js → FallthroughDiagram-CS1IhQaU.js} +14 -14
- package/dist/chunks/FallthroughDiagram-CS1IhQaU.js.map +1 -0
- package/dist/chunks/{Field-DsBlnFKf.js → Field-DiX_WByH.js} +2 -2
- package/dist/chunks/{Field-DsBlnFKf.js.map → Field-DiX_WByH.js.map} +1 -1
- package/dist/chunks/{FillStrokeSwatch-CgsYQgew.js → FillStrokeSwatch-C4h3rUFM.js} +2 -2
- package/dist/chunks/FillStrokeSwatch-C4h3rUFM.js.map +1 -0
- package/dist/chunks/{FontFamilySelect-CGJ5OIOy.js → FontFamilySelect-DEvMXyZd.js} +2 -2
- package/dist/chunks/{FontFamilySelect-CGJ5OIOy.js.map → FontFamilySelect-DEvMXyZd.js.map} +1 -1
- package/dist/chunks/{FontWeightSelect-RbcXYJPS.js → FontWeightSelect-BLQ_LMJF.js} +2 -2
- package/dist/chunks/{FontWeightSelect-RbcXYJPS.js.map → FontWeightSelect-BLQ_LMJF.js.map} +1 -1
- package/dist/chunks/{GradientEditor-DypXzMc-.js → GradientEditor-DD2OjQtn.js} +4 -4
- package/dist/chunks/{GradientEditor-DypXzMc-.js.map → GradientEditor-DD2OjQtn.js.map} +1 -1
- package/dist/chunks/{InlineRange-P01ABSzo.js → InlineRange-DPjIsJB3.js} +3 -3
- package/dist/chunks/InlineRange-DPjIsJB3.js.map +1 -0
- package/dist/chunks/{Input-CzwvCZs_.js → Input-D7SDKbST.js} +2 -2
- package/dist/chunks/{Input-CzwvCZs_.js.map → Input-D7SDKbST.js.map} +1 -1
- package/dist/chunks/{Jog-DfNUYejm.js → Jog-BrkzdGV2.js} +6 -6
- package/dist/chunks/Jog-BrkzdGV2.js.map +1 -0
- package/dist/chunks/{LayerList-CsDqwYh-.js → LayerList-BqeWFuGc.js} +3 -3
- package/dist/chunks/{LayerList-CsDqwYh-.js.map → LayerList-BqeWFuGc.js.map} +1 -1
- package/dist/chunks/{ListEditor-BnGzqERm.js → ListEditor-OQDzG4A5.js} +2 -2
- package/dist/chunks/{ListEditor-BnGzqERm.js.map → ListEditor-OQDzG4A5.js.map} +1 -1
- package/dist/chunks/{MenuButton-L9f5vido.js → MenuButton-TAFg88Lp.js} +2 -2
- package/dist/chunks/{MenuButton-L9f5vido.js.map → MenuButton-TAFg88Lp.js.map} +1 -1
- package/dist/chunks/{MeshEditor-GbqqiPk8.js → MeshEditor-DWpvg8H6.js} +2 -2
- package/dist/chunks/{MeshEditor-GbqqiPk8.js.map → MeshEditor-DWpvg8H6.js.map} +1 -1
- package/dist/chunks/{NumberField-DLXXgh48.js → NumberField-DS0WSiin.js} +7 -7
- package/dist/chunks/NumberField-DS0WSiin.js.map +1 -0
- package/dist/chunks/{PaintField-C0hoXc0Y.js → PaintField-BdmQM53v.js} +2 -2
- package/dist/chunks/{PaintField-C0hoXc0Y.js.map → PaintField-BdmQM53v.js.map} +1 -1
- package/dist/chunks/{PaintInput-jQRxM95Y.js → PaintInput-DaQMeBsI.js} +4 -4
- package/dist/chunks/{PaintInput-jQRxM95Y.js.map → PaintInput-DaQMeBsI.js.map} +1 -1
- package/dist/chunks/{PatternPicker-CaMYXzhF.js → PatternPicker-B3HcMZWg.js} +2 -2
- package/dist/chunks/{PatternPicker-CaMYXzhF.js.map → PatternPicker-B3HcMZWg.js.map} +1 -1
- package/dist/chunks/{Plot2D-BHNbgCAl.js → Plot2D-BrvQS0dh.js} +7 -7
- package/dist/chunks/Plot2D-BrvQS0dh.js.map +1 -0
- package/dist/chunks/{PointPlotter-BZ_vk6gV.js → PointPlotter-BBXpQ_QD.js} +2 -2
- package/dist/chunks/{PointPlotter-BZ_vk6gV.js.map → PointPlotter-BBXpQ_QD.js.map} +1 -1
- package/dist/chunks/{Prefs-CkyldX-e.js → Prefs-BnVEyfWO.js} +76 -66
- package/dist/chunks/Prefs-BnVEyfWO.js.map +1 -0
- package/dist/chunks/{Properties-D15c8NP-.js → Properties-BoG2MMHR.js} +24 -24
- package/dist/chunks/{Properties-D15c8NP-.js.map → Properties-BoG2MMHR.js.map} +1 -1
- package/dist/chunks/{PropertyField-CV_Gw57S.js → PropertyField-PMrqBA0n.js} +243 -186
- package/dist/chunks/PropertyField-PMrqBA0n.js.map +1 -0
- package/dist/chunks/{RadioGroup-4dbzqvGy.js → RadioGroup-B70_M-gT.js} +2 -2
- package/dist/chunks/{RadioGroup-4dbzqvGy.js.map → RadioGroup-B70_M-gT.js.map} +1 -1
- package/dist/chunks/{RangeSlider-BxokZViJ.js → RangeSlider-DO5TZVk7.js} +8 -8
- package/dist/chunks/RangeSlider-DO5TZVk7.js.map +1 -0
- package/dist/chunks/{Select-BppLsJTz.js → Select-ouV8O6cp.js} +19 -19
- package/dist/chunks/Select-ouV8O6cp.js.map +1 -0
- package/dist/chunks/{SelectionPanel-Cm1qu-ZX.js → SelectionPanel-COZ6z9_P.js} +2 -2
- package/dist/chunks/{SelectionPanel-Cm1qu-ZX.js.map → SelectionPanel-COZ6z9_P.js.map} +1 -1
- package/dist/chunks/{Slider-Cl5RCeZ7.js → Slider-DSrlMEq8.js} +19 -19
- package/dist/chunks/Slider-DSrlMEq8.js.map +1 -0
- package/dist/chunks/{StatusBar-Ca9Dvhhq.js → StatusBar-BEOfOX0D.js} +6 -6
- package/dist/chunks/StatusBar-BEOfOX0D.js.map +1 -0
- package/dist/chunks/{SwatchStrip-Cw0l8Om7.js → SwatchStrip-Bi9iPLoh.js} +2 -2
- package/dist/chunks/{SwatchStrip-Cw0l8Om7.js.map → SwatchStrip-Bi9iPLoh.js.map} +1 -1
- package/dist/chunks/{Timeline-pqnYk5cV.js → Timeline-sMCH2fah.js} +99 -84
- package/dist/chunks/Timeline-sMCH2fah.js.map +1 -0
- package/dist/chunks/{TokenPanel-DAkL6vwQ.js → TokenPanel-WtKGY2Db.js} +29 -29
- package/dist/chunks/TokenPanel-WtKGY2Db.js.map +1 -0
- package/dist/chunks/{ToolOptionsBar-COsTJ1cu.js → ToolOptionsBar-BJKwnemU.js} +2 -2
- package/dist/chunks/{ToolOptionsBar-COsTJ1cu.js.map → ToolOptionsBar-BJKwnemU.js.map} +1 -1
- package/dist/chunks/{createKeyframeLayer-BTa4LyJE.js → createKeyframeLayer-DSVf13Su.js} +2 -2
- package/dist/chunks/{createKeyframeLayer-BTa4LyJE.js.map → createKeyframeLayer-DSVf13Su.js.map} +1 -1
- package/dist/chunks/field.module-B27pWv0i.js +15 -0
- package/dist/chunks/{field.module-Bh7BTKMh.js.map → field.module-B27pWv0i.js.map} +1 -1
- package/dist/chunks/{model-B1-gMBIW.js → model-B6zuQg6b.js} +2 -2
- package/dist/chunks/{model-B1-gMBIW.js.map → model-B6zuQg6b.js.map} +1 -1
- package/dist/components/BandEditor/index.js +1 -1
- package/dist/components/ColorField/index.js +1 -1
- package/dist/components/ComboBox/index.js +1 -1
- package/dist/components/CurveEditor/index.js +2 -2
- package/dist/components/DataGrid/DataGrid.d.ts +3 -0
- package/dist/components/DataGrid/DataGrid.d.ts.map +1 -1
- package/dist/components/DataGrid/index.js +1 -1
- package/dist/components/DetailList/DetailList.d.ts +1 -1
- package/dist/components/DetailList/DetailList.d.ts.map +1 -1
- package/dist/components/DetailList/index.js +1 -1
- package/dist/components/DetentSlider/index.js +1 -1
- package/dist/components/FallthroughDiagram/index.js +1 -1
- package/dist/components/Field/index.js +1 -1
- package/dist/components/FillStrokeSwatch/FillStrokeSwatch.d.ts +5 -2
- package/dist/components/FillStrokeSwatch/FillStrokeSwatch.d.ts.map +1 -1
- package/dist/components/FillStrokeSwatch/index.js +1 -1
- package/dist/components/FontFamilySelect/index.js +1 -1
- package/dist/components/FontWeightSelect/index.js +1 -1
- package/dist/components/GradientEditor/index.js +1 -1
- package/dist/components/InlineRange/index.js +1 -1
- package/dist/components/Input/index.js +1 -1
- package/dist/components/Jog/index.js +1 -1
- package/dist/components/LayerList/index.js +1 -1
- package/dist/components/ListEditor/index.js +1 -1
- package/dist/components/MenuButton/index.js +1 -1
- package/dist/components/MeshEditor/index.js +1 -1
- package/dist/components/NumberField/index.js +1 -1
- package/dist/components/PaintField/index.js +1 -1
- package/dist/components/PaintInput/index.js +1 -1
- package/dist/components/PatternPicker/index.js +1 -1
- package/dist/components/Plot2D/index.js +1 -1
- package/dist/components/PointPlotter/index.js +1 -1
- package/dist/components/Prefs/PrefsForm.d.ts +14 -0
- package/dist/components/Prefs/PrefsForm.d.ts.map +1 -1
- package/dist/components/Prefs/PrefsRow.d.ts +4 -0
- package/dist/components/Prefs/PrefsRow.d.ts.map +1 -1
- package/dist/components/Prefs/index.js +1 -1
- package/dist/components/Properties/PropertyField.d.ts.map +1 -1
- package/dist/components/Properties/PropertyPanel.d.ts +18 -1
- package/dist/components/Properties/PropertyPanel.d.ts.map +1 -1
- package/dist/components/Properties/index.d.ts +2 -2
- package/dist/components/Properties/index.d.ts.map +1 -1
- package/dist/components/Properties/index.js +3 -3
- package/dist/components/RadioGroup/index.js +1 -1
- package/dist/components/RangeSlider/index.js +1 -1
- package/dist/components/Select/index.js +1 -1
- package/dist/components/SelectionPanel/index.js +2 -2
- package/dist/components/Slider/index.js +1 -1
- package/dist/components/StatusBar/StatusBar.d.ts +1 -1
- package/dist/components/StatusBar/index.js +1 -1
- package/dist/components/SwatchStrip/index.js +1 -1
- package/dist/components/Timeline/Transport.d.ts.map +1 -1
- package/dist/components/Timeline/index.js +1 -1
- package/dist/components/TokenPanel/index.js +1 -1
- package/dist/components/ToolOptionsBar/index.js +1 -1
- package/dist/index.js +75 -75
- package/dist/index.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +9 -9
- package/dist/chunks/BandEditor-Dd0egAgE.js.map +0 -1
- package/dist/chunks/ColorField-BLs9fbeq.js.map +0 -1
- package/dist/chunks/ComboBox-CuTLRmUg.js.map +0 -1
- package/dist/chunks/DataGrid-BklgBqS0.js.map +0 -1
- package/dist/chunks/DetailList-DFtqdOJj.js +0 -52
- package/dist/chunks/DetailList-DFtqdOJj.js.map +0 -1
- package/dist/chunks/FallthroughDiagram-C7BUe8eU.js.map +0 -1
- package/dist/chunks/FillStrokeSwatch-CgsYQgew.js.map +0 -1
- package/dist/chunks/InlineRange-P01ABSzo.js.map +0 -1
- package/dist/chunks/Jog-DfNUYejm.js.map +0 -1
- package/dist/chunks/NumberField-DLXXgh48.js.map +0 -1
- package/dist/chunks/Plot2D-BHNbgCAl.js.map +0 -1
- package/dist/chunks/Prefs-CkyldX-e.js.map +0 -1
- package/dist/chunks/PropertyField-CV_Gw57S.js.map +0 -1
- package/dist/chunks/RangeSlider-BxokZViJ.js.map +0 -1
- package/dist/chunks/Select-BppLsJTz.js.map +0 -1
- package/dist/chunks/Slider-Cl5RCeZ7.js.map +0 -1
- package/dist/chunks/StatusBar-Ca9Dvhhq.js.map +0 -1
- package/dist/chunks/Timeline-pqnYk5cV.js.map +0 -1
- package/dist/chunks/TokenPanel-DAkL6vwQ.js.map +0 -1
- package/dist/chunks/field.module-Bh7BTKMh.js +0 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weasel-js/ui",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.3",
|
|
4
4
|
"description": "UI chrome primitives for weasel apps (properties panel, etc.)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -23,17 +23,17 @@
|
|
|
23
23
|
"./package.json": "./package.json"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@weasel-js/font": "1.7.
|
|
27
|
-
"@weasel-js/modes": "1.7.
|
|
28
|
-
"@weasel-js/quantity": "1.7.
|
|
29
|
-
"@weasel-js/svg": "1.7.
|
|
30
|
-
"@weasel-js/theme": "1.7.
|
|
26
|
+
"@weasel-js/font": "1.7.3",
|
|
27
|
+
"@weasel-js/modes": "1.7.3",
|
|
28
|
+
"@weasel-js/quantity": "1.7.3",
|
|
29
|
+
"@weasel-js/svg": "1.7.3",
|
|
30
|
+
"@weasel-js/theme": "1.7.3",
|
|
31
31
|
"react-aria-components": "^1.5.0"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"react": ">=18",
|
|
35
35
|
"react-dom": ">=18",
|
|
36
|
-
"@weasel-js/core": "1.7.
|
|
36
|
+
"@weasel-js/core": "1.7.3"
|
|
37
37
|
},
|
|
38
38
|
"author": "orochi235",
|
|
39
39
|
"homepage": "https://orochi235.github.io/weasel/",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"provenance": true
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
|
-
"@weasel-js/cursor": "1.7.
|
|
65
|
-
"@weasel-js/geom": "1.7.
|
|
64
|
+
"@weasel-js/cursor": "1.7.3",
|
|
65
|
+
"@weasel-js/geom": "1.7.3"
|
|
66
66
|
}
|
|
67
67
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"BandEditor-Dd0egAgE.js","names":["s","Fragment","snapToNearest","Fragment$1","BandEditor_module_default","clamp01","u","linearScale","max","min","value","unit","logScale","lo","hi","warnedNonPositiveMin","isDev","resolveScale","requested","scale","normalizeBands","bands","b","bandBounds","index","seamBounds","clampSeamTo","to","setSeam","next","unitEdges","edges","clampBandShift","isFirst","isLast","shift","moveBandEdges","from","splitBands","at","makeData","mergeBand","i","SEAM_DISPLAY","decimal","untag","band","amount","retagBands","firstTagged","input","source","retag","KEY_STEP","pct","keepData","isTextEntry","target","tag","BandEditor","tagged","props","snap","splitBand","trackRef","useRef","sessionRef","labelId","useId","draft","setDraft","useState","retagged","onInput","onChange","committed","shown","sc","toUnit","v","fromUnit","trackWidth","unitAt","rect","clientX","snapped","altKey","ticks","width","tick","drag","openPointerSession","down","onEnd","onSeamPointerDown","e","base","latest","ev","onSeamKeyDown","delta","onBandPointerDown","selectedIndex","onSelect","startUnit","onBandFocus","className","label","qty","isSelected","renderBand","display"],"sources":["../../src/components/BandEditor/BandEditor.module.css","../../src/components/BandEditor/scale.ts","../../src/components/BandEditor/bands.ts","../../src/components/BandEditor/BandEditor.tsx"],"sourcesContent":[".root {\n position: relative;\n user-select: none;\n -webkit-user-select: none;\n touch-action: none;\n display: flex;\n flex-direction: column;\n gap: var(--wzl-space-1);\n}\n\n.label {\n font: var(--wzl-font-weight-medium) var(--wzl-font-size-sm) / var(--wzl-leading-tight) var(--wzl-font-ui);\n color: var(--wzl-fg-muted);\n}\n\n/* The ruler carries the ticks and is the split affordance: a click anywhere\n * on it divides the band underneath. The band strip below owns selection and\n * body drags, so neither gesture has to guess which the pointer meant. */\n.ruler {\n position: relative;\n height: var(--be-ruler-height, 20px);\n cursor: crosshair;\n}\n\n.tick {\n position: absolute;\n left: var(--be-at);\n bottom: 0;\n width: 1px;\n height: 6px;\n background: var(--wzl-fg-subtle);\n pointer-events: none;\n}\n\n.tickLabel {\n position: absolute;\n bottom: 7px;\n left: 0;\n transform: translateX(-50%);\n font: var(--wzl-font-weight-medium) var(--wzl-font-size-xs) / var(--wzl-leading-tight) var(--wzl-font-mono);\n color: var(--wzl-fg-muted);\n white-space: nowrap;\n}\n\n.bands {\n position: relative;\n height: var(--be-band-height, 34px);\n}\n\n.band {\n position: absolute;\n top: 0;\n bottom: 0;\n left: var(--be-from);\n width: calc(var(--be-to) - var(--be-from));\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n padding: 0 var(--wzl-space-2);\n margin: 0;\n background: var(--wzl-surface-raised);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n color: var(--wzl-fg);\n font: var(--wzl-font-weight-medium) var(--wzl-font-size-sm) / var(--wzl-leading-tight) var(--wzl-font-ui);\n cursor: grab;\n}\n\n.band:hover {\n background: var(--wzl-surface-hover);\n}\n\n.band:active {\n cursor: grabbing;\n}\n\n.band:focus-visible {\n outline: 2px solid var(--wzl-accent);\n outline-offset: -2px;\n}\n\n/* First and last bands are pinned to `min` / `max`, so their bodies do not\n * translate — the cursor should not promise otherwise. */\n.band:first-of-type,\n.band:last-of-type {\n cursor: default;\n}\n\n/* Accent mixed toward transparent rather than a flat token, so the tint\n reads over whatever surface the consumer's theme puts behind it. */\n.selected {\n background: color-mix(in srgb, var(--wzl-accent) 22%, transparent);\n border-color: var(--wzl-accent);\n box-shadow: inset 0 0 0 1px var(--wzl-accent);\n color: var(--wzl-fg);\n}\n\n.bandContent {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n pointer-events: none;\n}\n\n/* Where a dragged band or seam started, held until the drag ends. */\n.bandGhost {\n position: absolute;\n top: 0;\n bottom: 0;\n left: var(--be-from);\n width: calc(var(--be-to) - var(--be-from));\n border: 1px dashed var(--wzl-border-strong);\n border-radius: var(--wzl-radius-sm);\n pointer-events: none;\n}\n\n.seamGhost {\n position: absolute;\n left: var(--be-at);\n top: -3px;\n bottom: -3px;\n width: 0;\n border-left: 1px dashed var(--wzl-border-strong);\n pointer-events: none;\n}\n\n/* Wider than the line it draws: a 1px seam is unhittable. */\n.seam {\n position: absolute;\n left: var(--be-at);\n top: -3px;\n bottom: -3px;\n width: 11px;\n margin-left: -5px;\n z-index: 1;\n cursor: ew-resize;\n background: none;\n border: none;\n}\n\n.seam::before {\n content: '';\n position: absolute;\n top: 0;\n bottom: 0;\n left: 5px;\n width: 1px;\n background: var(--wzl-border-strong);\n}\n\n.seam:hover::before,\n.seam:focus-visible::before {\n background: var(--wzl-accent);\n width: 2px;\n}\n\n.seam:focus-visible {\n outline: 2px solid var(--wzl-accent);\n outline-offset: 0;\n border-radius: var(--wzl-radius-sm);\n}\n","/** Maps a domain value onto its position along the track, and back. */\nexport interface BandScale {\n /** Domain → position in [0,1]. Must be monotonic increasing. */\n toUnit(value: number, min: number, max: number): number;\n /** Inverse of `toUnit`. */\n fromUnit(unit: number, min: number, max: number): number;\n}\n\nexport function clamp01(u: number): number {\n return u < 0 ? 0 : u > 1 ? 1 : u;\n}\n\n/** Even domain steps take even track steps. */\nexport const linearScale: BandScale = {\n toUnit: (value, min, max) => (max === min ? 0 : (value - min) / (max - min)),\n fromUnit: (unit, min, max) => min + unit * (max - min),\n};\n\n/** Even *ratios* take even track steps. Requires `min > 0`. */\nexport const logScale: BandScale = {\n toUnit: (value, min, max) => {\n const lo = Math.log(min);\n const hi = Math.log(max);\n return hi === lo ? 0 : (Math.log(value) - lo) / (hi - lo);\n },\n fromUnit: (unit, min, max) => {\n const lo = Math.log(min);\n return Math.exp(lo + unit * (Math.log(max) - lo));\n },\n};\n\nlet warnedNonPositiveMin = false;\n\nfunction isDev(): boolean {\n return typeof process !== 'undefined' ? process.env.NODE_ENV !== 'production' : true;\n}\n\n/**\n * Resolves the `scale` prop, substituting `linearScale` for a `logScale`\n * that `min` would send to `NaN`.\n */\nexport function resolveScale(\n scale: 'linear' | 'log' | BandScale | undefined,\n min: number,\n): BandScale {\n const requested =\n scale === undefined || scale === 'log'\n ? logScale\n : scale === 'linear'\n ? linearScale\n : scale;\n\n if (requested !== logScale || min > 0) return requested;\n\n if (isDev() && !warnedNonPositiveMin) {\n warnedNonPositiveMin = true;\n console.warn(\n `BandEditor: a log scale needs min > 0, but min is ${min}. Falling back to a linear scale. ` +\n `Pass scale=\"linear\" to silence this.`,\n );\n }\n return linearScale;\n}\n","import type { Quantity } from '@weasel-js/quantity';\nimport type { BandScale } from './scale';\nimport { clamp01 } from './scale';\n\n/** One band. `from` may be a tagged quantity, which keeps its display and\n * unit through every edit: see `BandEditor`. */\nexport interface Band<T, F extends Quantity = number> {\n /** Domain value where this band starts. The first band's is normalized to `min`. */\n from: F;\n data: T;\n}\n\n/**\n * A band list covers `[min, max]` exactly: N bands, N−1 interior seams, no\n * gaps and no overlaps. Seam *k* is `bands[k + 1].from`, and lives between\n * `bands[k].from` and the start of the band after next (or `max`).\n *\n * Every function here preserves that. Each returns the array it was given\n * when the edit would be a no-op, so callers can skip a callback with `===`.\n */\n\n/** Pins the first band's start to `min`; leaves the rest alone. */\nexport function normalizeBands<T>(value: readonly Band<T>[], min: number): Band<T>[] {\n const bands = value.map((b) => ({ ...b }));\n if (bands.length > 0) bands[0].from = min;\n return bands;\n}\n\n/** `[from, to]` of band `index` in domain space. */\nexport function bandBounds<T>(\n bands: readonly Band<T>[],\n index: number,\n min: number,\n max: number,\n): [number, number] {\n const from = index === 0 ? min : bands[index].from;\n const to = index === bands.length - 1 ? max : bands[index + 1].from;\n return [from, to];\n}\n\n/** How far seam `index` can travel before it would cross a neighbour. */\nexport function seamBounds<T>(\n bands: readonly Band<T>[],\n index: number,\n min: number,\n max: number,\n): [number, number] {\n const lo = index === 0 ? min : bands[index].from;\n const hi = index + 2 <= bands.length - 1 ? bands[index + 2].from : max;\n return [lo, hi];\n}\n\n/** Clamps a target position for seam `index` to its neighbours, inclusive. */\nexport function clampSeamTo<T>(\n bands: readonly Band<T>[],\n index: number,\n to: number,\n min: number,\n max: number,\n): number {\n const [lo, hi] = seamBounds(bands, index, min, max);\n return Math.max(lo, Math.min(hi, to));\n}\n\nexport function setSeam<T>(bands: readonly Band<T>[], index: number, to: number): Band<T>[] {\n if (bands[index + 1] === undefined || bands[index + 1].from === to) return bands as Band<T>[];\n const next = bands.map((b) => ({ ...b }));\n next[index + 1].from = to;\n return next;\n}\n\n/** Band edges as track positions: `length + 1` values from 0 to 1. */\nexport function unitEdges<T>(\n bands: readonly Band<T>[],\n scale: BandScale,\n min: number,\n max: number,\n): number[] {\n const edges = bands.map((b, i) => (i === 0 ? 0 : clamp01(scale.toUnit(b.from, min, max))));\n edges.push(1);\n return edges;\n}\n\n/**\n * Largest part of `shift` band `index` can take without pushing a neighbour's\n * far edge past it. Zero for the first and last band, whose outer edges are\n * `min` and `max` and do not move.\n */\nexport function clampBandShift(edges: readonly number[], index: number, shift: number): number {\n const isFirst = index === 0;\n const isLast = index === edges.length - 2;\n if (isFirst || isLast) return 0;\n const lo = edges[index - 1] - edges[index];\n const hi = edges[index + 2] - edges[index + 1];\n return Math.max(lo, Math.min(hi, shift));\n}\n\n/** Moves both edges of band `index`, which is the same as moving two seams. */\nexport function moveBandEdges<T>(\n bands: readonly Band<T>[],\n index: number,\n from: number,\n to: number,\n): Band<T>[] {\n const next = bands.map((b) => ({ ...b }));\n next[index].from = from;\n if (next[index + 1] !== undefined) next[index + 1].from = to;\n return next;\n}\n\n/**\n * Splits the band containing `at` in two, the new band starting there.\n * A position already on a seam or on `min` / `max` splits nothing.\n */\nexport function splitBands<T>(\n bands: readonly Band<T>[],\n at: number,\n min: number,\n max: number,\n makeData: (at: number, from: T) => T,\n): Band<T>[] {\n if (bands.length === 0 || at <= min || at >= max) return bands as Band<T>[];\n let index = -1;\n for (let i = 0; i < bands.length; i++) {\n const [from, to] = bandBounds(bands, i, min, max);\n if (at > from && at < to) {\n index = i;\n break;\n }\n }\n if (index < 0) return bands as Band<T>[];\n const next = bands.map((b) => ({ ...b }));\n next.splice(index + 1, 0, { from: at, data: makeData(at, bands[index].data) });\n return next;\n}\n\n/**\n * Merges band `index` into its left neighbour, which keeps its payload and\n * absorbs the span. The first band has no left neighbour and cannot be\n * merged away — a partition always has at least one part.\n */\nexport function mergeBand<T>(bands: readonly Band<T>[], index: number): Band<T>[] {\n if (index <= 0 || index >= bands.length) return bands as Band<T>[];\n return bands.filter((_, i) => i !== index).map((b) => ({ ...b }));\n}\n","import {\n Fragment,\n useEffect,\n useId,\n useRef,\n useState,\n type CSSProperties,\n type KeyboardEvent as ReactKeyboardEvent,\n type PointerEvent as ReactPointerEvent,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport { openPointerSession, type PointerSession } from '@weasel-js/core';\nimport { amount, decimal, qty, retag, type Display, type Quantity } from '@weasel-js/quantity';\nimport s from './BandEditor.module.css';\nimport { SNAP_RADIUS_PX, snapToNearest } from '../../snap';\nimport { clamp01, resolveScale, type BandScale } from './scale';\nimport {\n bandBounds,\n clampBandShift,\n clampSeamTo,\n mergeBand,\n moveBandEdges,\n normalizeBands,\n seamBounds,\n setSeam,\n splitBands,\n unitEdges,\n type Band,\n} from './bands';\n\nexport type { Band, BandScale };\n\n/** Props for {@link BandEditor}. */\nexport interface BandEditorProps<T, F extends Quantity = number> {\n /** Ascending by `from`. `value[0].from` is normalized to `min` on read. A\n * tagged `from` stays tagged through every edit, with its display and unit. */\n value: Band<T, F>[];\n /** Live during a drag — wire for preview, do not write to history. */\n onInput?: (next: Band<T, F>[]) => void;\n /** Committed at gesture end: one call per gesture. */\n onChange: (next: Band<T, F>[]) => void;\n min: number;\n max: number;\n /** Default `'log'`. */\n scale?: 'linear' | 'log' | BandScale;\n /** A tick with no `label` of its own is labeled through `display`, when given. */\n ticks?: { at: number; label?: ReactNode }[];\n /**\n * How a seam's value shows and is spoken — `fraction()` makes a seam at\n * 1/12 announce `1 over 12` rather than `0.08333333333333333`. A tagged\n * `from` uses its own display instead. Default: three decimals.\n */\n display?: Display;\n /** Snap a dragged seam to a tick within ~6px. Default true; `alt` defeats it per-drag. */\n snap?: boolean;\n renderBand?: (band: Band<T, F>, index: number) => ReactNode;\n selectedIndex?: number | null;\n onSelect?: (index: number) => void;\n /** Payload for a band split off an existing one. Default duplicates `from`. */\n splitBand?: (at: number, from: T) => T;\n label?: ReactNode;\n className?: string;\n}\n\n/** A drag under way: the bands as the pointer has them, and which edges it took from where. */\ntype Draft<T> = { bands: Band<T>[]; ghost: { seam: number } | { band: number } };\n\nconst SEAM_DISPLAY = decimal();\n\nfunction untag<T, F extends Quantity>(band: Band<T, F>): Band<T> {\n return typeof band.from === 'number' ? (band as Band<T>) : { from: amount(band.from), data: band.data };\n}\n\n/**\n * `next` with each `from` in the shape the consumer gave it. A band keeps the\n * tag of the band at its index when the count is unchanged; after a split or\n * a merge it takes the tag of the input band carrying the same payload, else\n * the first tagged one — a split band inherits its parent's presentation.\n */\nfunction retagBands<T, F extends Quantity>(input: readonly Band<T, F>[], next: readonly Band<T>[]): Band<T, F>[] {\n const firstTagged = input.find((b) => typeof b.from !== 'number');\n if (!firstTagged) return next as Band<T, F>[];\n return next.map((band, i) => {\n const source =\n (next.length === input.length ? input[i] : input.find((b) => b.data === band.data)) ?? firstTagged;\n return { from: retag(source.from, band.from), data: band.data };\n });\n}\n\n/** One arrow-key step, as a fraction of the track. */\nconst KEY_STEP = 0.01;\n\nfunction pct(unit: number): string {\n return `${clamp01(unit) * 100}%`;\n}\n\nfunction keepData<T>(_at: number, from: T): T {\n return from;\n}\n\nfunction isTextEntry(target: EventTarget | null): boolean {\n if (!(target instanceof HTMLElement)) return false;\n if (target.isContentEditable) return true;\n const tag = target.tagName;\n return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT';\n}\n\n/**\n * Divides a numeric axis into contiguous bands and lets you drag the seams\n * between them. Each band carries a payload the consumer supplies and\n * renders through `renderBand`; the control itself knows nothing about what\n * a band means.\n *\n * The axis is always fully covered — N bands, N−1 interior seams, no gaps and\n * no overlaps — so editing is editing a sorted list of seam positions. Seams\n * clamp at their neighbours rather than crossing, which means a drag can\n * never destroy a band: removal is only ever the explicit `x` / `Delete`\n * merge. The first band is doubly exceptional, its left edge pinned to `min`\n * and its body immovable, and it has no left neighbour to merge into.\n *\n * | Gesture | Effect |\n * |---|---|\n * | drag a seam | resize the two bands either side |\n * | drag a band body | move both its seams, preserving its span |\n * | click the ruler | split the band under the pointer |\n * | `x` / `Delete` | merge the selected band into its left neighbour |\n * | click a band | select it |\n * | `←` `→` on a focused seam | move it by one step; `shift` for ten |\n *\n * `scale` defaults to `'log'`, because the interesting part of a width axis\n * is usually its narrow end.\n */\nexport function BandEditor<T, F extends Quantity = number>(props: BandEditorProps<T, F>): ReactElement {\n const {\n value: tagged,\n display = SEAM_DISPLAY,\n min,\n max,\n scale,\n ticks,\n renderBand,\n selectedIndex,\n onSelect,\n label,\n className,\n } = props;\n const snap = props.snap ?? true;\n const splitBand = props.splitBand ?? keepData;\n\n const trackRef = useRef<HTMLDivElement | null>(null);\n const sessionRef = useRef<PointerSession | null>(null);\n useEffect(() => () => { sessionRef.current?.cancel(); }, []);\n const labelId = useId();\n const [draft, setDraft] = useState<Draft<T> | null>(null);\n const value = tagged.map(untag);\n const retagged = (next: Band<T>[]): Band<T, F>[] => retagBands(tagged, next);\n const onInput = props.onInput && ((next: Band<T>[]) => props.onInput?.(retagged(next)));\n const onChange = (next: Band<T>[]) => props.onChange(retagged(next));\n const committed = normalizeBands(value, min);\n // Drawn from the drag while one is under way, so the control moves with the pointer whether or not the consumer\n // wires `onInput`. Every edit reads `committed`.\n const bands = draft?.bands ?? committed;\n const shown = retagged(bands);\n const sc = resolveScale(scale, min);\n const toUnit = (v: number): number => clamp01(sc.toUnit(v, min, max));\n const fromUnit = (u: number): number => sc.fromUnit(clamp01(u), min, max);\n\n const trackWidth = (): number => trackRef.current?.getBoundingClientRect().width ?? 0;\n\n const unitAt = (clientX: number): number => {\n const rect = trackRef.current?.getBoundingClientRect();\n if (!rect || rect.width === 0) return 0;\n return clamp01((clientX - rect.left) / rect.width);\n };\n\n const snapped = (unit: number, altKey: boolean): number => {\n if (!snap || altKey || !ticks || ticks.length === 0) return unit;\n const width = trackWidth();\n if (width === 0) return unit;\n return snapToNearest(unit, ticks.map((tick) => toUnit(tick.at)), SNAP_RADIUS_PX / width);\n };\n\n // No pointer capture: a band body is a `<button>` whose content the consumer\n // renders, and capture would retarget pointerup and kill the click on it.\n // A drag that ends without a release commits nothing.\n const drag = (\n down: ReactPointerEvent<HTMLElement>,\n onMove: (ev: PointerEvent) => void,\n onEnd: () => void,\n ): void => {\n sessionRef.current?.cancel();\n sessionRef.current = openPointerSession(down.currentTarget, down, {\n onMove,\n onEnd: () => { sessionRef.current = null; setDraft(null); onEnd(); },\n onCancel: () => { sessionRef.current = null; setDraft(null); },\n }, { capture: false });\n };\n\n const onSeamPointerDown = (index: number) => (e: ReactPointerEvent<HTMLDivElement>): void => {\n if (typeof e.button === 'number' && e.button > 0) return;\n e.preventDefault();\n e.stopPropagation();\n const base = committed;\n let latest: Band<T>[] | null = null;\n drag(\n e,\n (ev) => {\n const to = clampSeamTo(base, index, fromUnit(snapped(unitAt(ev.clientX), ev.altKey)), min, max);\n latest = setSeam(base, index, to);\n setDraft({ bands: latest, ghost: { seam: index } });\n onInput?.(latest);\n },\n () => {\n if (latest) onChange(latest);\n },\n );\n };\n\n const onSeamKeyDown = (index: number) => (e: ReactKeyboardEvent<HTMLDivElement>): void => {\n const [lo, hi] = seamBounds(committed, index, min, max);\n let target: number;\n if (e.key === 'Home') target = lo;\n else if (e.key === 'End') target = hi;\n else {\n let delta = 0;\n if (e.key === 'ArrowRight' || e.key === 'ArrowUp') delta = e.shiftKey ? KEY_STEP * 10 : KEY_STEP;\n else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') delta = e.shiftKey ? -KEY_STEP * 10 : -KEY_STEP;\n else return;\n target = fromUnit(toUnit(committed[index + 1].from) + delta);\n }\n e.preventDefault();\n const next = setSeam(committed, index, clampSeamTo(committed, index, target, min, max));\n if (next !== committed) onChange(next);\n };\n\n const onBandPointerDown = (index: number) => (e: ReactPointerEvent<HTMLButtonElement>): void => {\n if (typeof e.button === 'number' && e.button > 0) return;\n if (index !== selectedIndex) onSelect?.(index);\n // The first and last bands' outer edges are `min` and `max`, which do not\n // move, so their bodies have nothing to translate.\n if (index === 0 || index === committed.length - 1) return;\n const base = committed;\n const edges = unitEdges(base, sc, min, max);\n const startUnit = unitAt(e.clientX);\n let latest: Band<T>[] | null = null;\n drag(\n e,\n (ev) => {\n const shift = clampBandShift(edges, index, unitAt(ev.clientX) - startUnit);\n latest = moveBandEdges(\n base,\n index,\n fromUnit(edges[index] + shift),\n fromUnit(edges[index + 1] + shift),\n );\n setDraft({ bands: latest, ghost: { band: index } });\n onInput?.(latest);\n },\n () => {\n if (latest) onChange(latest);\n },\n );\n };\n\n const onBandFocus = (index: number) => (): void => {\n if (index !== selectedIndex) onSelect?.(index);\n };\n\n const onTrackPointerDown = (e: ReactPointerEvent<HTMLDivElement>): void => {\n if (typeof e.button === 'number' && e.button > 0) return;\n e.preventDefault();\n const next = splitBands(committed, fromUnit(unitAt(e.clientX)), min, max, splitBand);\n if (next !== committed) onChange(next);\n };\n\n const onRootKeyDown = (e: ReactKeyboardEvent<HTMLDivElement>): void => {\n if (e.key !== 'x' && e.key !== 'Delete') return;\n // `x` is a bare letter and Cmd/Ctrl+X is cut: neither may be swallowed\n // when the keystroke belongs to something a consumer put in a band.\n if (e.metaKey || e.ctrlKey || e.altKey) return;\n if (isTextEntry(e.target)) return;\n if (selectedIndex === null || selectedIndex === undefined) return;\n const next = mergeBand(committed, selectedIndex);\n if (next === committed) return;\n e.preventDefault();\n onChange(next);\n onSelect?.(selectedIndex - 1);\n };\n\n return (\n <div\n className={[s.root, className].filter(Boolean).join(' ')}\n role=\"group\"\n aria-label={typeof label === 'string' ? label : undefined}\n aria-labelledby={label !== undefined && typeof label !== 'string' ? labelId : undefined}\n onKeyDown={onRootKeyDown}\n >\n {label !== undefined && <div id={labelId} className={s.label}>{label}</div>}\n <div className={s.ruler} ref={trackRef} data-band-ruler=\"\" onPointerDown={onTrackPointerDown}>\n {ticks?.map((tick, i) => (\n <span\n key={i}\n className={s.tick}\n data-tick-at={tick.at}\n style={{ '--be-at': pct(toUnit(tick.at)) } as CSSProperties}\n >\n {(tick.label !== undefined || props.display !== undefined) && (\n <span className={s.tickLabel}>{tick.label ?? qty(tick.at, props.display).text}</span>\n )}\n </span>\n ))}\n </div>\n <div className={s.bands}>\n {bands.map((_, i) => {\n const [from, to] = bandBounds(bands, i, min, max);\n const isSelected = selectedIndex === i;\n return (\n <Fragment key={i}>\n <button\n type=\"button\"\n className={[s.band, isSelected ? s.selected : null].filter(Boolean).join(' ')}\n style={{ '--be-from': pct(toUnit(from)), '--be-to': pct(toUnit(to)) } as CSSProperties}\n aria-label={`Band ${i + 1}`}\n aria-pressed={isSelected}\n data-band-index={i}\n onPointerDown={onBandPointerDown(i)}\n onFocus={onBandFocus(i)}\n >\n <span className={s.bandContent}>{renderBand?.(shown[i]!, i)}</span>\n </button>\n {i < bands.length - 1 && (\n <div\n role=\"slider\"\n tabIndex={0}\n className={s.seam}\n style={{ '--be-at': pct(toUnit(bands[i + 1].from)) } as CSSProperties}\n aria-label={`Seam ${i + 1}`}\n aria-orientation=\"horizontal\"\n aria-valuemin={seamBounds(bands, i, min, max)[0]}\n aria-valuemax={seamBounds(bands, i, min, max)[1]}\n aria-valuenow={bands[i + 1].from}\n aria-valuetext={qty(shown[i + 1]!.from, display).spoken}\n data-seam-index={i}\n onPointerDown={onSeamPointerDown(i)}\n onKeyDown={onSeamKeyDown(i)}\n />\n )}\n </Fragment>\n );\n })}\n {draft && 'band' in draft.ghost && (\n <div\n className={s.bandGhost}\n data-band-ghost=\"\"\n aria-hidden=\"true\"\n style={{\n '--be-from': pct(toUnit(bandBounds(committed, draft.ghost.band, min, max)[0])),\n '--be-to': pct(toUnit(bandBounds(committed, draft.ghost.band, min, max)[1])),\n } as CSSProperties}\n />\n )}\n {draft && 'seam' in draft.ghost && (\n <div\n className={s.seamGhost}\n data-band-ghost=\"\"\n aria-hidden=\"true\"\n style={{ '--be-at': pct(toUnit(committed[draft.ghost.seam + 1].from)) } as CSSProperties}\n />\n )}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;ACQA,SAAgBK,EAAQ,GAAmB;CACzC,OAAOC,IAAI,IAAI,IAAIA,IAAI,IAAI,IAAIA;AACjC;AAGA,IAAaC,IAAyB;CACpC,SAAS,GAAO,GAAK,MAASC,MAAQC,IAAM,KAAKC,IAAQD,MAAQD,IAAMC;CACvE,WAAW,GAAM,GAAK,MAAQA,IAAME,KAAQH,IAAMC;AACpD,GAGaG,IAAsB;CACjC,SAAS,GAAO,GAAK,MAAQ;EAC3B,IAAMC,IAAK,KAAK,IAAIJ,CAAG,GACjBK,IAAK,KAAK,IAAIN,CAAG;EACvB,OAAOM,MAAOD,IAAK,KAAK,KAAK,IAAIH,CAAK,IAAIG,MAAOC,IAAKD;CACxD;CACA,WAAW,GAAM,GAAK,MAAQ;EAC5B,IAAMA,IAAK,KAAK,IAAIJ,CAAG;EACvB,OAAO,KAAK,IAAII,IAAKF,KAAQ,KAAK,IAAIH,CAAG,IAAIK,EAAG;CAClD;AACF,GAEIE,IAAuB;AAE3B,SAASC,IAAiB;CACxB,OAAO,OAAO,UAAY,MAAA,QAAA,IAAA,aAAuC,eAAe;AAClF;AAMA,SAAgBC,EACd,GACA,GACW;CACX,IAAMC,IACJC,MAAU,KAAA,KAAaA,MAAU,QAC7BP,IACAO,MAAU,WACRZ,IACAY;CAWR,OATID,MAAcN,KAAYH,IAAM,IAAUS,KAE1CF,EAAM,KAAK,CAACD,MACd,IAAuB,IACvB,QAAQ,KACN,qDAAqDN,EAAI,uEAE3D,IAEKF;AACT;;;ACxCA,SAAgBa,EAAkB,GAA2B,GAAwB;CACnF,IAAMC,IAAQX,EAAM,KAAK,OAAO,EAAE,GAAGY,EAAE,EAAE;CAEzC,OADID,EAAM,SAAS,MAAG,EAAM,EAAE,CAAC,OAAOZ,IAC/BY;AACT;AAGA,SAAgBE,EACd,GACA,GACA,GACA,GACkB;CAGlB,OAAO,CAFMC,MAAU,IAAIf,IAAMY,EAAMG,EAAM,CAAC,MACnCA,MAAUH,EAAM,SAAS,IAAIb,IAAMa,EAAMG,IAAQ,EAAE,CAAC,IAC/C;AAClB;AAGA,SAAgBC,EACd,GACA,GACA,GACA,GACkB;CAGlB,OAAO,CAFID,MAAU,IAAIf,IAAMY,EAAMG,EAAM,CAAC,MACjCA,IAAQ,KAAKH,EAAM,SAAS,IAAIA,EAAMG,IAAQ,EAAE,CAAC,OAAOhB,CACrD;AAChB;AAGA,SAAgBkB,EACd,GACA,GACA,GACA,GACA,GACQ;CACR,IAAM,CAACb,GAAIC,KAAMW,EAAWJ,GAAOG,GAAOf,GAAKD,CAAG;CAClD,OAAO,KAAK,IAAIK,GAAI,KAAK,IAAIC,GAAIa,CAAE,CAAC;AACtC;AAEA,SAAgBC,EAAW,GAA2B,GAAe,GAAuB;CAC1F,IAAIP,EAAMG,IAAQ,OAAO,KAAA,KAAaH,EAAMG,IAAQ,EAAE,CAAC,SAASG,GAAI,OAAON;CAC3E,IAAMQ,IAAOR,EAAM,KAAK,OAAO,EAAE,GAAGC,EAAE,EAAE;CAExC,OADA,EAAKE,IAAQ,EAAE,CAAC,OAAOG,GAChBE;AACT;AAGA,SAAgBC,GACd,GACA,GACA,GACA,GACU;CACV,IAAMC,IAAQV,EAAM,KAAK,GAAG,MAAO,MAAM,IAAI,IAAIhB,EAAQc,EAAM,OAAOG,EAAE,MAAMb,GAAKD,CAAG,CAAC,CAAE;CAEzF,OADAuB,EAAM,KAAK,CAAC,GACLA;AACT;AAOA,SAAgBC,GAAe,GAA0B,GAAe,GAAuB;CAC7F,IAAMC,IAAUT,MAAU,GACpBU,IAASV,MAAUO,EAAM,SAAS;CACxC,IAAIE,KAAWC,GAAQ,OAAO;CAC9B,IAAMrB,IAAKkB,EAAMP,IAAQ,KAAKO,EAAMP,IAC9BV,IAAKiB,EAAMP,IAAQ,KAAKO,EAAMP,IAAQ;CAC5C,OAAO,KAAK,IAAIX,GAAI,KAAK,IAAIC,GAAIqB,CAAK,CAAC;AACzC;AAGA,SAAgBC,GACd,GACA,GACA,GACA,GACW;CACX,IAAMP,IAAOR,EAAM,KAAK,OAAO,EAAE,GAAGC,EAAE,EAAE;CAGxC,OAFA,EAAKE,EAAM,CAAC,OAAOa,GACfR,EAAKL,IAAQ,OAAO,KAAA,MAAW,EAAKA,IAAQ,EAAE,CAAC,OAAOG,IACnDE;AACT;AAMA,SAAgBS,GACd,GACA,GACA,GACA,GACA,GACW;CACX,IAAIjB,EAAM,WAAW,KAAKkB,KAAM9B,KAAO8B,KAAM/B,GAAK,OAAOa;CACzD,IAAIG,IAAQ;CACZ,KAAK,IAAI,IAAI,GAAG,IAAIH,EAAM,QAAQ,KAAK;EACrC,IAAM,CAACgB,GAAMV,KAAMJ,EAAWF,GAAO,GAAGZ,GAAKD,CAAG;EAChD,IAAI+B,IAAKF,KAAQE,IAAKZ,GAAI;GACxB,IAAQ;GACR;EACF;CACF;CACA,IAAIH,IAAQ,GAAG,OAAOH;CACtB,IAAMQ,IAAOR,EAAM,KAAK,OAAO,EAAE,GAAGC,EAAE,EAAE;CAExC,OADAO,EAAK,OAAOL,IAAQ,GAAG,GAAG;EAAE,MAAMe;EAAI,MAAMC,EAASD,GAAIlB,EAAMG,EAAM,CAAC,IAAI;CAAE,CAAC,GACtEK;AACT;AAOA,SAAgBY,GAAa,GAA2B,GAA0B;CAEhF,OADIjB,KAAS,KAAKA,KAASH,EAAM,SAAeA,IACzCA,EAAM,QAAQ,GAAG,MAAMqB,MAAMlB,CAAK,CAAC,CAAC,KAAK,OAAO,EAAE,GAAGF,EAAE,EAAE;AAClE;;;AC5EA,IAAMqB,IAAeC,EAAQ;AAE7B,SAASC,EAA6B,GAA2B;CAC/D,OAAO,OAAOC,EAAK,QAAS,WAAYA,IAAmB;EAAE,MAAMC,EAAOD,EAAK,IAAI;EAAG,MAAMA,EAAK;CAAK;AACxG;AAQA,SAASE,EAAkC,GAA8B,GAAwC;CAC/G,IAAMC,IAAcC,EAAM,MAAM,MAAM,OAAO5B,EAAE,QAAS,QAAQ;CAEhE,OADK2B,IACEpB,EAAK,KAAK,GAAM,MAAM;EAC3B,IAAMsB,KACHtB,EAAK,WAAWqB,EAAM,SAASA,EAAM,KAAKA,EAAM,MAAM,MAAM5B,EAAE,SAASwB,EAAK,IAAI,MAAMG;EACzF,OAAO;GAAE,MAAMG,EAAMD,EAAO,MAAML,EAAK,IAAI;GAAG,MAAMA,EAAK;EAAK;CAChE,CAAC,IALwBjB;AAM3B;AAGA,IAAMwB,IAAW;AAEjB,SAASC,EAAI,GAAsB;CACjC,OAAO,GAAGjD,EAAQM,CAAI,IAAI,IAAI;AAChC;AAEA,SAAS4C,EAAY,GAAa,GAAY;CAC5C,OAAOlB;AACT;AAEA,SAASmB,GAAY,GAAqC;CACxD,IAAI,EAAEC,aAAkB,cAAc,OAAO;CAC7C,IAAIA,EAAO,mBAAmB,OAAO;CACrC,IAAMC,IAAMD,EAAO;CACnB,OAAOC,MAAQ,WAAWA,MAAQ,cAAcA,MAAQ;AAC1D;AA2BA,SAAgBC,EAA2C,GAA4C;CACrG,IAAM,EACJ,OAAOC,GACP,aAAUjB,GACV,QACA,QACA,UACA,UACA,eACA,kBACA,aACA,UACA,iBACEkB,GACEC,IAAOD,EAAM,QAAQ,IACrBE,KAAYF,EAAM,aAAaN,GAE/BS,IAAWC,EAA8B,IAAI,GAC7CC,IAAaD,EAA8B,IAAI;CACrD,cAAsB;EAAE,EAAW,SAAS,OAAO;CAAG,GAAG,CAAC,CAAC;CAC3D,IAAME,IAAUC,EAAM,GAChB,CAACC,GAAOC,KAAYC,EAA0B,IAAI,GAClD7D,IAAQkD,EAAO,IAAIf,CAAK,GACxB2B,KAAY,MAAkCxB,EAAWY,GAAQ/B,CAAI,GACrE4C,IAAUZ,EAAM,aAAa,MAAoBA,EAAM,UAAUW,EAAS3C,CAAI,CAAC,IAC/E6C,KAAY,MAAoBb,EAAM,SAASW,EAAS3C,CAAI,CAAC,GAC7D8C,IAAYvD,EAAeV,GAAOD,CAAG,GAGrCY,IAAQgD,GAAO,SAASM,GACxBC,IAAQJ,EAASnD,CAAK,GACtBwD,IAAK5D,EAAaE,GAAOV,CAAG,GAC5BqE,KAAU,MAAsBzE,EAAQwE,EAAG,OAAOE,GAAGtE,GAAKD,CAAG,CAAC,GAC9DwE,KAAY,MAAsBH,EAAG,SAASxE,EAAQC,CAAC,GAAGG,GAAKD,CAAG,GAElEyE,WAA2BjB,EAAS,SAAS,sBAAsB,CAAC,CAAC,SAAS,GAE9EkB,KAAU,MAA4B;EAC1C,IAAMC,IAAOnB,EAAS,SAAS,sBAAsB;EAErD,OADI,CAACmB,KAAQA,EAAK,UAAU,IAAU,IAC/B9E,GAAS+E,IAAUD,EAAK,QAAQA,EAAK,KAAK;CACnD,GAEME,MAAW,GAAc,MAA4B;EACzD,IAAI,CAACvB,KAAQwB,KAAU,CAACC,KAASA,EAAM,WAAW,GAAG,OAAO5E;EAC5D,IAAM6E,IAAQP,GAAW;EAEzB,OADIO,MAAU,IAAU7E,IACjBT,EAAcS,GAAM4E,EAAM,KAAK,MAAST,EAAOW,EAAK,EAAE,CAAC,GAAA,IAAoBD,CAAK;CACzF,GAKME,KACJ,GACA,GACA,MACS;EAET,AADAxB,EAAW,SAAS,OAAO,GAC3B,EAAW,UAAUyB,EAAmBC,EAAK,eAAeA,GAAM;GAChE;GACA,aAAa;IAA6C,AAA3C,EAAW,UAAU,MAAMtB,EAAS,IAAI,GAAGuB,EAAM;GAAG;GACnE,gBAAgB;IAA6B,AAA3B,EAAW,UAAU,MAAMvB,EAAS,IAAI;GAAG;EAC/D,GAAG,EAAE,SAAS,GAAM,CAAC;CACvB,GAEMwB,MAAqB,OAAmB,MAA+C;EAC3F,IAAI,OAAOC,EAAE,UAAW,YAAYA,EAAE,SAAS,GAAG;EAElD,AADAA,EAAE,eAAe,GACjBA,EAAE,gBAAgB;EAClB,IAAMC,IAAOrB,GACTsB,IAA2B;EAC/B,EACEF,IACC,MAAO;GACN,IAAMpE,IAAKD,EAAYsE,GAAMxE,GAAOwD,EAASK,GAAQH,EAAOgB,EAAG,OAAO,GAAGA,EAAG,MAAM,CAAC,GAAGzF,GAAKD,CAAG;GAG9F,AAFA,IAASoB,EAAQoE,GAAMxE,GAAOG,CAAE,GAChC2C,EAAS;IAAE,OAAO2B;IAAQ,OAAO,EAAE,MAAMzE,EAAM;GAAE,CAAC,GAClDiD,IAAUwB,CAAM;EAClB,SACM;GACJ,AAAIA,KAAQvB,EAASuB,CAAM;EAC7B,CACF;CACF,GAEME,MAAiB,OAAmB,MAAgD;EACxF,IAAM,CAACtF,GAAIC,KAAMW,EAAWkD,GAAWnD,GAAOf,GAAKD,CAAG,GAClDiD;EACJ,IAAIsC,EAAE,QAAQ,QAAQ,IAASlF;OAC1B,IAAIkF,EAAE,QAAQ,OAAO,IAASjF;OAC9B;GACH,IAAIsF,IAAQ;GACZ,IAAIL,EAAE,QAAQ,gBAAgBA,EAAE,QAAQ,WAAW,IAAQA,EAAE,WAAW1C,IAAW,KAAKA;QACnF,IAAI0C,EAAE,QAAQ,eAAeA,EAAE,QAAQ,aAAa,IAAQA,EAAE,WAAW,MAAiB;QAC1F;GACL,IAASf,EAASF,EAAOH,EAAUnD,IAAQ,EAAE,CAAC,IAAI,IAAI4E,CAAK;EAC7D;EACA,EAAE,eAAe;EACjB,IAAMvE,IAAOD,EAAQ+C,GAAWnD,GAAOE,EAAYiD,GAAWnD,GAAOiC,GAAQhD,GAAKD,CAAG,CAAC;EACtF,AAAIqB,MAAS8C,KAAWD,EAAS7C,CAAI;CACvC,GAEMwE,MAAqB,OAAmB,MAAkD;EAK9F,IAJI,OAAON,EAAE,UAAW,YAAYA,EAAE,SAAS,MAC3CvE,MAAU8E,KAAeC,IAAW/E,CAAK,GAGzCA,MAAU,KAAKA,MAAUmD,EAAU,SAAS,IAAG;EACnD,IAAMqB,IAAOrB,GACP5C,IAAQD,GAAUkE,GAAMnB,GAAIpE,GAAKD,CAAG,GACpCgG,IAAYtB,EAAOa,EAAE,OAAO,GAC9BE,IAA2B;EAC/B,EACEF,IACC,MAAO;GACN,IAAM5D,IAAQH,GAAeD,GAAOP,GAAO0D,EAAOgB,EAAG,OAAO,IAAIM,CAAS;GAQzE,AAPA,IAASpE,GACP4D,GACAxE,GACAwD,EAASjD,EAAMP,KAASW,CAAK,GAC7B6C,EAASjD,EAAMP,IAAQ,KAAKW,CAAK,CACnC,GACAmC,EAAS;IAAE,OAAO2B;IAAQ,OAAO,EAAE,MAAMzE,EAAM;GAAE,CAAC,GAClDiD,IAAUwB,CAAM;EAClB,SACM;GACJ,AAAIA,KAAQvB,EAASuB,CAAM;EAC7B,CACF;CACF,GAEMQ,MAAe,YAA8B;EACjD,AAAIjF,MAAU8E,KAAeC,IAAW/E,CAAK;CAC/C;CAuBA,OACE,kBAAC,OAAD;EACE,WAAW,CAACxB,EAAE,MAAM0G,CAAS,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;EACvD,MAAK;EACL,cAAY,OAAOC,KAAU,WAAWA,IAAQ,KAAA;EAChD,mBAAiBA,MAAU,KAAA,KAAa,OAAOA,KAAU,WAAWxC,IAAU,KAAA;EAC9E,YApBmB,MAAgD;GAMrE,IALI,EAAE,QAAQ,OAAO,EAAE,QAAQ,YAG3B,EAAE,WAAW,EAAE,WAAW,EAAE,UAC5BX,GAAY,EAAE,MAAM,KACpB8C,KAAkB,MAAqC;GAC3D,IAAMzE,IAAOY,GAAUkC,GAAW2B,CAAa;GAC/C,AAAIzE,MAAS8C,MACb,EAAE,eAAe,GACjBD,EAAS7C,CAAI,GACb0E,IAAWD,IAAgB,CAAC;EAC9B;EAGE,UAAA;GAOGK,MAAU,KAAA,KAAa,kBAAC,OAAD;IAAK,IAAIxC;IAAS,WAAWnE,EAAE;IAAQ,UAAA2G;GAAW,CAAA;GAC1E,kBAAC,OAAD;IAAK,WAAW3G,EAAE;IAAO,KAAKgE;IAAU,mBAAgB;IAAG,gBA9BnC,MAA+C;KACzE,IAAI,OAAO,EAAE,UAAW,YAAY,EAAE,SAAS,GAAG;KAClD,EAAE,eAAe;KACjB,IAAMnC,IAAOS,GAAWqC,GAAWK,EAASE,EAAO,EAAE,OAAO,CAAC,GAAGzE,GAAKD,GAAKuD,EAAS;KACnF,AAAIlC,MAAS8C,KAAWD,EAAS7C,CAAI;IACvC;IA0BO,UAAA0D,GAAO,KAAK,GAAM,MACjB,kBAAC,QAAD;KAEE,WAAWvF,EAAE;KACb,gBAAcyF,EAAK;KACnB,OAAO,EAAE,WAAWnC,EAAIwB,EAAOW,EAAK,EAAE,CAAC,EAAE;KAEvC,WAAAA,EAAK,UAAU,KAAA,KAAa5B,EAAM,YAAY,KAAA,MAC9C,kBAAC,QAAD;MAAM,WAAW7D,EAAE;MAAY,UAAAyF,EAAK,SAASmB,EAAInB,EAAK,IAAI5B,EAAM,OAAO,CAAC,CAAC;KAAW,CAAA;IAElF,GARCnB,CAQD,CACP;GACE,CAAA;GACL,kBAAC,OAAD;IAAK,WAAW1C,EAAE;IAAlB,UAAA;KACGqB,EAAM,KAAK,GAAG,MAAM;MACnB,IAAM,CAACgB,GAAMV,KAAMJ,EAAWF,GAAOqB,GAAGjC,GAAKD,CAAG,GAC1CqG,IAAaP,MAAkB5D;MACrC,OACE,kBAACzC,GAAD,EAAA,UAAA,CACE,kBAAC,UAAD;OACE,MAAK;OACL,WAAW,CAACD,EAAE,MAAM6G,IAAa7G,EAAE,WAAW,IAAI,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;OAC5E,OAAO;QAAE,aAAasD,EAAIwB,EAAOzC,CAAI,CAAC;QAAG,WAAWiB,EAAIwB,EAAOnD,CAAE,CAAC;OAAE;OACpE,cAAY,QAAQe,IAAI;OACxB,gBAAcmE;OACd,mBAAiBnE;OACjB,eAAe2D,GAAkB3D,CAAC;OAClC,SAAS+D,GAAY/D,CAAC;OAEtB,UAAA,kBAAC,QAAD;QAAM,WAAW1C,EAAE;QAAc,UAAA8G,IAAalC,EAAMlC,IAAKA,CAAC;OAAQ,CAAA;MAC5D,CAAA,GACPA,IAAIrB,EAAM,SAAS,KAClB,kBAAC,OAAD;OACE,MAAK;OACL,UAAU;OACV,WAAWrB,EAAE;OACb,OAAO,EAAE,WAAWsD,EAAIwB,EAAOzD,EAAMqB,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE;OACnD,cAAY,QAAQA,IAAI;OACxB,oBAAiB;OACjB,iBAAejB,EAAWJ,GAAOqB,GAAGjC,GAAKD,CAAG,CAAC,CAAC;OAC9C,iBAAeiB,EAAWJ,GAAOqB,GAAGjC,GAAKD,CAAG,CAAC,CAAC;OAC9C,iBAAea,EAAMqB,IAAI,EAAE,CAAC;OAC5B,kBAAgBkE,EAAIhC,EAAMlC,IAAI,EAAE,CAAE,MAAMqE,CAAO,CAAC,CAAC;OACjD,mBAAiBrE;OACjB,eAAeoD,GAAkBpD,CAAC;OAClC,WAAWyD,GAAczD,CAAC;MAC3B,CAAA,CAEK,EAAA,GA9BKA,CA8BL;KAEd,CAAC;KACA2B,KAAS,UAAUA,EAAM,SACxB,kBAAC,OAAD;MACE,WAAWrE,EAAE;MACb,mBAAgB;MAChB,eAAY;MACZ,OAAO;OACL,aAAasD,EAAIwB,EAAOvD,EAAWoD,GAAWN,EAAM,MAAM,MAAM5D,GAAKD,CAAG,CAAC,CAAC,EAAE,CAAC;OAC7E,WAAW8C,EAAIwB,EAAOvD,EAAWoD,GAAWN,EAAM,MAAM,MAAM5D,GAAKD,CAAG,CAAC,CAAC,EAAE,CAAC;MAC7E;KACD,CAAA;KAEF6D,KAAS,UAAUA,EAAM,SACxB,kBAAC,OAAD;MACE,WAAWrE,EAAE;MACb,mBAAgB;MAChB,eAAY;MACZ,OAAO,EAAE,WAAWsD,EAAIwB,EAAOH,EAAUN,EAAM,MAAM,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE;KACvE,CAAA;IAEA;;EACF;;AAET"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ColorField-BLs9fbeq.js","names":["_Fragment","s","shared","s","useRecordRecentColor","range_module_default","InlineRange","OpacityRange_module_default","OpacityRange","props","label","draft","setDraft","useState","pct","value","commit","onChange","Fragment","next","onInput","ColorField_module_default","ColorField","hex8","toHex8","rgb6","alpha01","getAlpha01","alphaPct","colorDraft","setColorDraft","alphaDraft","setAlphaDraft","visibleRgb","visibleAlphaPct","compose","alpha","withAlpha01","rgb","a01","alphaLocked","mixed","record","swatch","useRef","latestCommit","useEffect","el","onClose","className","alphaDisabled"],"sources":["../../src/components/ColorField/OpacityRange.module.css","../../src/components/ColorField/OpacityRange.tsx","../../src/components/ColorField/ColorField.module.css","../../src/components/ColorField/ColorField.tsx"],"sourcesContent":["/* `InlineRange` owns the track; this only says how it shares the row. */\n.range {\n flex: 1;\n}\n\n.readout {\n min-inline-size: 3ch;\n text-align: end;\n font-size: var(--wzl-font-size-sm);\n opacity: 0.7;\n}\n","import { useState, type ReactElement } from 'react';\nimport { InlineRange } from '../InlineRange';\nimport shared from '../range.module.css';\nimport s from './OpacityRange.module.css';\n\n/** Props for {@link OpacityRange}. Alphas are 0..1 at 1% resolution. */\nexport interface OpacityRangeProps {\n value: number;\n disabled?: boolean;\n /** Names what the opacity belongs to; the slider is `<label> opacity`. */\n 'aria-label'?: string;\n onInput?: (alpha01: number) => void;\n /** One call per gesture, and none for a gesture that moved nothing. */\n onChange: (alpha01: number) => void;\n}\n\n/**\n * The opacity slider and its percent readout, as two inline items for the\n * caller's own row. `ColorField` puts it beside a swatch; `PaintInput` puts it\n * under a paint with no single color to carry an alpha.\n */\nexport function OpacityRange(props: OpacityRangeProps): ReactElement {\n const { value, disabled = false, onInput, onChange } = props;\n const label = props['aria-label'];\n // Tracks the thumb during a gesture — the committed prop only updates after\n // onChange — then resets to follow the prop again.\n const [draft, setDraft] = useState<number | null>(null);\n const pct = draft ?? Math.round(value * 100);\n\n const commit = (): void => {\n if (draft === null) return;\n onChange(draft / 100);\n setDraft(null);\n };\n\n return (\n <>\n <InlineRange\n className={`${s.range} ${shared.alpha}`}\n min={0}\n max={100}\n step={1}\n value={pct}\n disabled={disabled}\n aria-label={label ? `${label} opacity` : 'Opacity'}\n onInput={(e) => {\n const next = Number((e.target as HTMLInputElement).value);\n setDraft(next);\n onInput?.(next / 100);\n }}\n onPointerUp={commit}\n onPointerCancel={commit}\n onKeyUp={commit}\n onBlur={commit}\n />\n <span className={s.readout} aria-hidden=\"true\">{pct}%</span>\n </>\n );\n}\n",".root {\n display: inline-flex;\n align-items: center;\n gap: var(--wzl-space-3);\n}\n\n.chip {\n position: relative;\n display: inline-flex;\n border-radius: var(--wzl-radius-sm);\n overflow: hidden;\n}\n\n.color {\n inline-size: 28px;\n block-size: var(--wzl-control-h-sm);\n padding: 0;\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n background: none;\n cursor: pointer;\n}\n\n.color:focus-visible {\n outline: none;\n border-color: var(--wzl-focus-ring);\n box-shadow: 0 0 0 1px var(--wzl-focus-ring);\n}\n\n/* Indeterminate (mixed) presentation: checkerboard overlay atop the\n * swatch. pointer-events: none so the input stays clickable. */\n.root[data-mixed] .chip::after {\n content: '';\n position: absolute;\n inset: 1px;\n border-radius: var(--wzl-radius-sm);\n pointer-events: none;\n background: repeating-conic-gradient(\n var(--wzl-checker-a) 0% 25%,\n var(--wzl-checker-b) 0% 50%\n )\n 50% / var(--wzl-checker-size) var(--wzl-checker-size);\n}\n","import { useEffect, useRef, useState } from 'react';\nimport { toHex8, getAlpha01, withAlpha01 } from '@weasel-js/core';\nimport { useRecordRecentColor } from '../RecentColors/RecentColorsProvider';\nimport { OpacityRange } from './OpacityRange';\nimport s from './ColorField.module.css';\n\n/**\n * Props for {@link ColorField}. `onInput` fires throughout a gesture and\n * `onChange` once at its end.\n */\nexport interface ColorFieldProps {\n /** Current color, `#rrggbb` or `#rrggbbaa`. Omit when `mixed`. */\n value?: string;\n /** Indeterminate presentation (multi-selection with differing colors):\n * checkered chip, empty value. The first color edit produces a real\n * value; the opacity slider stays disabled until then (an alpha-only\n * edit would otherwise commit a value composed against the\n * placeholder black). */\n mixed?: boolean;\n /** Show the opacity slider; emitted values are `#rrggbbaa`. */\n alpha?: boolean;\n /** Draw the opacity slider inert, for a consumer that drops alpha. */\n alphaDisabled?: boolean;\n /** Live value during interaction (picker drag, slider drag). Optional —\n * wire it for live preview; omit it for commit-only consumers. */\n onInput?: (hex: string) => void;\n /** Committed value — picker close or blur, or slider release. One call\n * per user gesture; pair with an undoable write. */\n onChange: (hex: string) => void;\n /** Id for the color input, for a `<label htmlFor>` outside it. */\n id?: string;\n 'aria-label'?: string;\n className?: string;\n}\n\n/**\n * Compact color editor: native color input + optional opacity slider.\n * Commit semantics are gesture-based (see `onChange`) because native\n * color/range inputs fire `change` continuously during interaction —\n * commit-on-change would emit one undo entry per tick.\n */\nexport function ColorField(props: ColorFieldProps) {\n const { value, mixed = false, alpha = false, alphaDisabled = false, onInput, onChange, id, className } = props;\n const hex8 = toHex8(value ?? '#000000');\n const rgb6 = hex8.slice(0, 7);\n const alpha01 = getAlpha01(hex8);\n const alphaPct = Math.round(alpha01 * 100);\n\n // Drafts track the control during a gesture (the committed prop only\n // updates after onChange), then reset to follow the prop again.\n const [colorDraft, setColorDraft] = useState<string | null>(null);\n const [alphaDraft, setAlphaDraft] = useState<number | null>(null);\n const visibleRgb = colorDraft ?? rgb6;\n const visibleAlphaPct = alphaDraft ?? alphaPct;\n\n const compose = (rgb: string, a01: number): string =>\n alpha ? withAlpha01(rgb, a01) : rgb;\n\n // Mixed with no color edit yet: the alpha slider has nothing real to\n // compose against (rgb6 is the placeholder black), so keep it disabled\n // until the first color edit lands.\n const alphaLocked = mixed && colorDraft === null;\n\n // Commit only when a gesture actually changed something — blur with no\n // preceding input must not emit (it would create a no-op undo entry).\n const record = useRecordRecentColor();\n const commit = (rgb: string, a01: number): void => {\n if (colorDraft === null && alphaDraft === null) return;\n const next = compose(rgb, a01);\n onChange(next);\n record(next);\n setColorDraft(null);\n setAlphaDraft(null);\n };\n\n // The picker closing fires a native `change`, which React's `onChange` does\n // not deliver apart from `input`; the input can keep focus after it closes,\n // so waiting for blur alone would hold the commit until the next click.\n const swatch = useRef<HTMLInputElement>(null);\n const latestCommit = useRef(() => {});\n useEffect(() => {\n latestCommit.current = () => commit(visibleRgb, visibleAlphaPct / 100);\n });\n useEffect(() => {\n const el = swatch.current;\n if (!el) return;\n const onClose = () => latestCommit.current();\n el.addEventListener('change', onClose);\n return () => el.removeEventListener('change', onClose);\n }, []);\n\n return (\n <span\n className={[s.root, className].filter(Boolean).join(' ')}\n {...(mixed && colorDraft === null ? { 'data-mixed': '' } : {})}\n >\n <span className={s.chip}>\n <input\n ref={swatch}\n id={id}\n className={s.color}\n type=\"color\"\n value={visibleRgb}\n aria-label={props['aria-label'] ?? 'Color'}\n onInput={(e) => {\n const rgb = (e.target as HTMLInputElement).value;\n setColorDraft(rgb);\n onInput?.(compose(rgb, visibleAlphaPct / 100));\n }}\n onBlur={() => commit(visibleRgb, visibleAlphaPct / 100)}\n />\n </span>\n {alpha && (\n <OpacityRange\n value={visibleAlphaPct / 100}\n disabled={alphaLocked || alphaDisabled}\n aria-label={props['aria-label']}\n onInput={(a01) => {\n setAlphaDraft(Math.round(a01 * 100));\n onInput?.(compose(visibleRgb, a01));\n }}\n onChange={(a01) => commit(visibleRgb, a01)}\n />\n )}\n </span>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ComboBox-CuTLRmUg.js","names":["ComboBox","RACComboBox","f","s","RACPopover","RACListBox","listbox","RACInput","RACButton","RACListBoxItem","useOverlayPortal","listbox_module_default","ComboBox_module_default","admitEverything","ComboBox$1","rest","props","portalContainer","ComboBox","filter","onSelectionChange","k","onCommit","field_module_default","width","orientation","className","anchor","label","Label","ComboBoxFrame","placeholder","optionLabels","options","children","l","i","description","Text","FieldError","errorMessage","Popover","portalProps","ListBox","loadError","emptyLabel","errorLabel","ComboBoxItem","o","state","useContext","ComboBoxStateContext","Input","isLoading","onCommitText","text","Button","Children","isValidElement","c","ListBoxItem"],"sources":["../../src/components/ComboBox/ComboBox.module.css","../../src/components/ComboBox/ComboBox.tsx"],"sourcesContent":[".frame {\n display: flex;\n align-items: stretch;\n overflow: hidden;\n}\n\n.frame input {\n padding: 0 var(--wzl-field-pad-x);\n}\n\n/* A request is out: the rows on screen are the previous query's, so mark them\n stale rather than clearing the list out from under the arrow keys. */\n.frame:has(input[aria-busy='true'])::after {\n content: '';\n align-self: center;\n flex: none;\n width: 10px;\n height: 10px;\n margin-right: var(--wzl-space-2);\n border: 1.5px solid var(--wzl-fg-subtle);\n border-top-color: transparent;\n border-radius: 50%;\n animation: spin 700ms linear infinite;\n}\n\n@keyframes spin {\n to {\n transform: rotate(1turn);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .frame:has(input[aria-busy='true'])::after {\n animation: none;\n border-top-color: var(--wzl-fg-subtle);\n opacity: 0.5;\n }\n}\n\n.openButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--wzl-icon-button-size);\n background: transparent;\n border: none;\n border-left: 1px solid var(--wzl-border);\n color: var(--wzl-fg-muted);\n cursor: pointer;\n padding: 0;\n}\n\n.openButton[data-hovered]:not([data-disabled]) {\n color: var(--wzl-fg);\n}\n\n.openButton svg {\n width: 10px;\n height: 10px;\n}\n\n.popover {\n min-width: var(--trigger-width);\n outline: none;\n}\n\n.option {\n display: flex;\n align-items: center;\n padding: var(--wzl-space-2) var(--wzl-space-4);\n border-radius: var(--wzl-radius-sm);\n cursor: pointer;\n color: var(--wzl-fg);\n outline: none;\n}\n\n.option[data-focused] {\n background: var(--wzl-surface-hover);\n}\n\n.option[data-selected] {\n font-weight: var(--wzl-font-weight-medium);\n}\n\n.option[data-disabled] {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n.empty {\n padding: var(--wzl-space-3) var(--wzl-space-4);\n color: var(--wzl-fg-subtle);\n font-size: var(--wzl-font-size-sm);\n}\n\n.error {\n padding: var(--wzl-space-3) var(--wzl-space-4);\n color: var(--wzl-danger);\n font-size: var(--wzl-font-size-sm);\n}\n\n/* `width='fit'`: a hidden stack of every option label shares the input's grid\n cell, so the field is wide enough for whichever option is chosen and takes\n no more of the row than that. */\n.fit .frame {\n display: grid;\n grid-template-columns: auto auto;\n width: fit-content;\n max-width: 100%;\n}\n\n/* `width: 0` keeps the input's 20-character intrinsic width out of the track\n sizing — the sizer is what the column measures — and `min-width` then\n stretches it back across whatever that came to. */\n.fit .frame input {\n grid-area: 1 / 1;\n width: 0;\n min-width: 100%;\n}\n\n.fit .sizer {\n grid-area: 1 / 1;\n}\n\n.fit .openButton {\n grid-area: 1 / 2;\n}\n\n.sizer {\n display: grid;\n height: 0;\n overflow: hidden;\n visibility: hidden;\n white-space: nowrap;\n padding: 0 var(--wzl-field-pad-x);\n}\n\n.sizer > * {\n grid-area: 1 / 1;\n}\n","import { Children, isValidElement, useContext, type KeyboardEvent, type ReactNode } from 'react';\nimport {\n ComboBox as RACComboBox,\n ComboBoxStateContext,\n Label,\n Input as RACInput,\n Button as RACButton,\n Popover as RACPopover,\n ListBox as RACListBox,\n ListBoxItem as RACListBoxItem,\n Text,\n FieldError,\n type ComboBoxProps as RACComboBoxProps,\n type ListBoxItemProps as RACListBoxItemProps,\n type ValidationResult,\n} from 'react-aria-components';\nimport type { FieldOrientation } from '../Field/Field';\nimport { useOverlayPortal, type OverlayPortalProps } from '../../overlays/portalHost';\nimport f from '../field.module.css';\nimport listbox from '../listbox.module.css';\nimport s from './ComboBox.module.css';\n\n/**\n * One option in a {@link ComboBox}'s `options` list. `textValue` is the\n * plain-text form used for filtering and screen readers, needed only when\n * `label` isn't a bare string.\n */\nexport type ComboBoxOption = {\n value: string;\n label: ReactNode;\n textValue?: string;\n isDisabled?: boolean;\n};\n\ntype Key = string | number;\n\n/**\n * How the typed text narrows the options.\n *\n * `'contains'` is a locale-aware substring match over each option's text.\n * `'none'` shows every option given — what a list a server already filtered\n * and ranked needs, since a second pass would drop rows that do not contain\n * the query and reorder whatever survived.\n */\nexport type ComboBoxFilter = 'contains' | 'none' | ((textValue: string, inputValue: string) => boolean);\n\n/** What the user committed: an option they picked, or text they typed. */\nexport type ComboBoxCommit<T extends Key = string> =\n | { source: 'option'; key: T }\n | { source: 'text'; text: string };\n\nconst admitEverything = () => true;\n\n/**\n * Props for {@link ComboBox}, on top of React Aria's `ComboBox` props, with\n * the selection key narrowed to the option value type.\n */\nexport type ComboBoxProps<T extends Key = string> = Omit<RACComboBoxProps<object>, 'children' | 'className' | 'selectedKey' | 'defaultSelectedKey' | 'onSelectionChange'> & {\n label?: ReactNode;\n /**\n * `'stacked'` (the default) puts the label above the field; `'row'` sets it\n * beside the field at its own width, with any description or error on a\n * line below. Same vocabulary as {@link Field}'s `orientation`.\n */\n orientation?: FieldOrientation;\n description?: ReactNode;\n errorMessage?: ReactNode | ((v: ValidationResult) => ReactNode);\n placeholder?: string;\n options?: ReadonlyArray<ComboBoxOption & { value: T }>;\n children?: ReactNode;\n selectedKey?: T | null;\n defaultSelectedKey?: T;\n onSelectionChange?: (key: T | null) => void;\n emptyLabel?: ReactNode;\n /** Shown in place of `emptyLabel` while `loadError` is set. */\n errorLabel?: ReactNode;\n /**\n * How typed text narrows `options`. Defaults to `'contains'`. `'none'` also\n * keeps the popover open on an empty collection: a list the kit does not\n * filter can arrive empty from a server mid-query, and without that the\n * popover closes and `emptyLabel` is never seen.\n */\n filter?: ComboBoxFilter;\n /** Marks the options as out of date while the next set is being fetched. */\n isLoading?: boolean;\n /** A failed load, shown as `errorLabel` rather than as an empty corpus. */\n loadError?: unknown | null;\n /**\n * Fires when the user commits — Enter on the active option, a click on one,\n * or Enter on text matching none of them when `allowsCustomValue` is set.\n * Empty text commits nothing.\n */\n onCommit?: (commit: ComboBoxCommit<T>) => void;\n /**\n * `'fill'` (the default) takes the width of whatever row the combo box sits\n * in. `'fit'` sizes the input to its widest option, so it neither swallows a\n * toolbar's slack nor cuts a selection off once one is made.\n */\n width?: 'fill' | 'fit';\n className?: string;\n} & OverlayPortalProps;\n\n/**\n * Filterable single-select wrapping React Aria's ComboBox. The user can\n * type to filter; selection commits to `onSelectionChange`. When no value\n * is in the input, selection clears (key becomes null).\n */\nexport function ComboBox<T extends Key = string>(props: ComboBoxProps<T>) {\n const {\n label,\n orientation = 'stacked',\n description,\n errorMessage,\n placeholder,\n options,\n children,\n selectedKey,\n defaultSelectedKey,\n onSelectionChange,\n emptyLabel = 'No matches',\n errorLabel = \"Couldn't load options\",\n filter = 'contains',\n isLoading,\n loadError = null,\n onCommit,\n width = 'fill',\n className,\n portalContainer,\n ...rest\n } = props;\n\n const { anchor, portalProps } = useOverlayPortal(portalContainer);\n\n return (\n <RACComboBox\n {...rest}\n // 'contains' leaves this unset so React Aria uses its own locale-aware\n // match rather than a reimplementation of it.\n defaultFilter={filter === 'none' ? admitEverything : filter === 'contains' ? undefined : filter}\n allowsEmptyCollection={rest.allowsEmptyCollection ?? filter === 'none'}\n selectedKey={selectedKey}\n defaultSelectedKey={defaultSelectedKey}\n onSelectionChange={(k) => {\n onSelectionChange?.(k as T | null);\n if (k !== null) onCommit?.({ source: 'option', key: k as T });\n }}\n className={[\n f.field,\n f.control,\n width === 'fit' && `${f.fit} ${s.fit}`,\n orientation === 'row' && f.row,\n className,\n ]\n .filter(Boolean)\n .join(' ')}\n >\n {anchor}\n {label !== undefined && <Label className={f.label}>{label}</Label>}\n <ComboBoxFrame\n placeholder={placeholder}\n isLoading={isLoading}\n onCommitText={onCommit && ((text) => onCommit({ source: 'text', text }))}\n >\n {width === 'fit' && (\n <span className={s.sizer} aria-hidden=\"true\">\n {placeholder !== undefined && <span>{placeholder}</span>}\n {optionLabels(options, children).map((l, i) => (\n <span key={i}>{l}</span>\n ))}\n </span>\n )}\n </ComboBoxFrame>\n {description !== undefined && (\n <Text slot=\"description\" className={`${f.hint} ${f.below}`}>\n {description}\n </Text>\n )}\n <FieldError className={`${f.error} ${f.below}`}>{errorMessage}</FieldError>\n <RACPopover className={s.popover} data-weasel-overlay=\"\" {...portalProps}>\n <RACListBox\n className={`${listbox.surface} ${listbox.list}`}\n renderEmptyState={() =>\n loadError !== null ? (\n <div className={s.error} role=\"alert\">{errorLabel}</div>\n ) : (\n <div className={s.empty}>{emptyLabel}</div>\n )\n }\n >\n {options !== undefined\n ? options.map((o) => (\n <ComboBoxItem key={String(o.value)} id={o.value} textValue={o.textValue} isDisabled={o.isDisabled}>\n {o.label}\n </ComboBoxItem>\n ))\n : children}\n </RACListBox>\n </RACPopover>\n </RACComboBox>\n );\n}\n\n/**\n * The input and its trigger. Split out so it can read `ComboBoxStateContext`,\n * which only exists below `RACComboBox`.\n */\nfunction ComboBoxFrame({\n placeholder,\n isLoading,\n onCommitText,\n children,\n}: {\n placeholder?: string;\n isLoading?: boolean;\n onCommitText?: (text: string) => void;\n children?: ReactNode;\n}) {\n const state = useContext(ComboBoxStateContext);\n\n // Capture, not bubble: React Aria's own Enter handler runs on the way up and\n // clears the focused key, so by then there is no way to tell an option\n // commit from a text one.\n const onKeyDownCapture = (e: KeyboardEvent<HTMLInputElement>) => {\n if (e.key !== 'Enter' || e.nativeEvent.isComposing) return;\n if (state?.isOpen && state.selectionManager.focusedKey != null) return;\n const text = e.currentTarget.value;\n if (text !== '') onCommitText?.(text);\n };\n\n return (\n <div className={`${f.frame} ${s.frame}`}>\n <RACInput\n placeholder={placeholder}\n aria-busy={isLoading || undefined}\n onKeyDownCapture={onCommitText ? onKeyDownCapture : undefined}\n />\n {children}\n <RACButton className={s.openButton} aria-label=\"Show options\">\n <svg viewBox=\"0 0 10 10\" aria-hidden=\"true\">\n <path d=\"M2 4 L5 7 L8 4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n </RACButton>\n </div>\n );\n}\n\n/**\n * The labels a `width='fit'` input measures itself against. In the children\n * form the label sits one element deep, inside a `ComboBoxItem`.\n */\nfunction optionLabels(\n options: ReadonlyArray<ComboBoxOption> | undefined,\n children: ReactNode,\n): ReactNode[] {\n if (options !== undefined) return options.map((o) => o.label);\n return Children.toArray(children).map((c) =>\n isValidElement<{ children?: ReactNode }>(c) ? c.props.children : c,\n );\n}\n\n/** Props for {@link ComboBoxItem}, on top of React Aria's `ListBoxItem` props. */\nexport type ComboBoxItemProps = Omit<RACListBoxItemProps, 'className'> & {\n className?: string;\n};\n\n/** One row in a {@link ComboBox}'s filtered list. */\nexport function ComboBoxItem({ className, ...rest }: ComboBoxItemProps) {\n return <RACListBoxItem {...rest} className={[s.option, className].filter(Boolean).join(' ')} />;\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"DataGrid-BklgBqS0.js","names":["s","_Fragment","Fragment","useReorderDragList","Fragment$1","DataGrid_module_default","INTERACTIVE","fromControl","hit","DataGrid","props","sort","setSort","useState","defaultSort","ownExpanded","setOwnExpanded","defaultExpandedIds","expanded","expandedIds","detailIdBase","useId","toggleExpanded","next","id","onExpandedChange","sortedRows","useMemo","rows","col","columns","c","get","row","dir","av","a","bv","b","cycleSort","cur","columnId","drag","r","onReorder","dragEnabled","detailEnabled","renderDetail","colCount","cls","className","sortable","active","indicator","Fragment","empty","isDragging","target","expandable","rowExpandable","isExpanded","detail","detailId","rowCls","onRowClick","i","rowClassName","e","Disclosure","content"],"sources":["../../src/components/DataGrid/DataGrid.module.css","../../src/components/DataGrid/DataGrid.tsx"],"sourcesContent":[".grid {\n position: relative;\n width: 100%;\n}\n\n.table {\n width: 100%;\n border-collapse: collapse;\n font-size: var(--wzl-font-size-sm);\n}\n\n.table th,\n.table td {\n text-align: left;\n padding: 3px var(--wzl-space-3);\n vertical-align: middle;\n}\n.table tbody tr {\n border-bottom: 1px solid var(--wzl-line-subtle);\n}\n\n.table th {\n font-weight: var(--wzl-font-weight-bold);\n color: var(--wzl-fg-muted);\n position: sticky;\n top: 0;\n background: var(--wzl-surface);\n z-index: 1;\n border-bottom: 1px solid var(--wzl-border);\n}\n\n.sortButton {\n all: unset;\n cursor: pointer;\n user-select: none;\n display: block;\n width: 100%;\n}\n.sortButton:hover { color: var(--wzl-fg); }\n.sortButton:focus-visible {\n outline: 2px solid var(--wzl-focus-ring);\n outline-offset: -2px;\n}\n.sortActive { color: var(--wzl-fg); }\n\n.empty {\n color: var(--wzl-fg-muted);\n font-style: italic;\n padding: var(--wzl-space-4) 0;\n text-align: center;\n}\n\n/* Drag-handle column. */\n.handleCol { width: 14px; }\n.handleCell {\n width: 14px;\n cursor: grab;\n color: var(--wzl-fg-muted);\n user-select: none;\n text-align: center;\n}\n.handleCell:active { cursor: grabbing; }\n\n.dragging { opacity: 0.4; }\n\n.dropBefore > td { box-shadow: inset 0 2px 0 0 var(--wzl-accent); }\n.dropAfter > td { box-shadow: inset 0 -2px 0 0 var(--wzl-accent); }\n\n/* Row activation (`onRowClick`). */\n.clickable { cursor: pointer; }\n.clickable:hover > td { background: var(--wzl-surface-hover); }\n.clickable:focus-visible {\n outline: 2px solid var(--wzl-focus-ring);\n outline-offset: -2px;\n}\n\n/* Disclosure column and the detail row it opens. */\n.detailCol,\n.detailCell {\n width: var(--wzl-control-h-sm);\n padding-inline: 0;\n text-align: center;\n}\n.table .detailCell { padding-block: 0; }\n.detailRow > td {\n padding: var(--wzl-space-2) var(--wzl-space-3) var(--wzl-space-3);\n background: var(--wzl-surface-sunken);\n}\n","/**\n * Lightweight sortable data grid. Use for inspector tables — action\n * registries, route maps, telemetry rows. Intentionally narrow:\n *\n * - Sortable columns (click header to cycle asc → desc → none)\n * - Custom cell render via per-column `render`\n * - Optional drag handles for row reordering (when `onReorder` is set)\n * - Per-row class, row activation, and expandable full-width detail rows\n *\n * Not: virtual scrolling, column resizing, multi-column sort, filtering,\n * inline editing. Reach for a real grid library when you need those.\n */\nimport { Fragment, useId, useMemo, useState } from 'react';\nimport type { KeyboardEvent, MouseEvent, ReactNode } from 'react';\nimport { useReorderDragList } from '../../useReorderDragList';\nimport { Disclosure } from '../Disclosure';\nimport s from './DataGrid.module.css';\n\n/**\n * One column of a {@link DataGrid}. `id` doubles as the default property name\n * read off each row when no `accessor` is given.\n */\nexport interface DataGridColumn<Row> {\n id: string;\n header: ReactNode;\n /** Pull a sortable value from the row. Defaults to `(row as any)[column.id]`. */\n accessor?: (row: Row) => string | number | null | undefined;\n /** Render the cell. Defaults to `String(accessor(row) ?? '')`. */\n render?: (row: Row) => ReactNode;\n /** Default true. Set false to suppress the sort affordance on this column. */\n sortable?: boolean;\n /** Optional CSS class on every <td> in this column. */\n className?: string;\n}\n\n/** Props for {@link DataGrid}. */\nexport interface DataGridProps<Row extends { id: string }> {\n rows: readonly Row[];\n columns: readonly DataGridColumn<Row>[];\n /** Initial sort. Omit for unsorted (rows render in input order). */\n defaultSort?: { columnId: string; direction: 'asc' | 'desc' };\n /** Enable drag handles in a leading column. Receives the reorder spec\n * (the existing `useReorderDragList` semantics — see weasel-ui). */\n onReorder?: (ids: string[], targetIndex: number) => void;\n /** Empty-state row text. Default `'—'`. */\n empty?: ReactNode;\n className?: string;\n /** Extra class on a row's `<tr>`, for row-level state such as a verdict. */\n rowClassName?: (row: Row) => string | undefined;\n /**\n * Activate a row. Makes each row focusable: a click anywhere in it, or\n * Enter/Space while it has focus, calls this. Clicks and keys landing on a\n * control inside a cell (button, link, input, …) are left to that control.\n */\n onRowClick?: (row: Row) => void;\n /**\n * Content for a full-width detail row under `row`, shown while the row is\n * expanded. Setting it adds a leading disclosure column. Return `null` for\n * no detail row.\n */\n renderDetail?: (row: Row) => ReactNode;\n /** Which rows offer the disclosure. Default: every row. */\n rowExpandable?: (row: Row) => boolean;\n /** Expanded row ids, controlled. Pair with `onExpandedChange`. */\n expandedIds?: ReadonlySet<string>;\n /** Initially expanded row ids when uncontrolled. */\n defaultExpandedIds?: Iterable<string>;\n /** Called with the next expanded set when a disclosure toggles. */\n onExpandedChange?: (ids: ReadonlySet<string>) => void;\n}\n\ntype SortState = { columnId: string; direction: 'asc' | 'desc' } | null;\n\nconst INTERACTIVE = 'a[href], button, input, select, textarea, label, summary, [role=\"button\"], [contenteditable=\"true\"]';\n\n/** Did this event start on a control inside the row rather than on the row itself? */\nfunction fromControl(e: MouseEvent | KeyboardEvent): boolean {\n const target = e.target as Element | null;\n const hit = target?.closest?.(INTERACTIVE);\n return !!hit && e.currentTarget.contains(hit);\n}\n\n/**\n * Sortable table for inspector-style data. Rows are keyed by `id`. Clicking a\n * sortable header cycles ascending, descending, unsorted; nullish values sort\n * last regardless of direction. Passing `onReorder` adds a leading drag-handle\n * column — note that the drag indices it reports are into the *sorted* row\n * order, not the input order.\n */\nexport function DataGrid<Row extends { id: string }>(props: DataGridProps<Row>) {\n const {\n rows, columns, defaultSort, onReorder, empty = '—', className,\n rowClassName, onRowClick, renderDetail, rowExpandable, expandedIds, defaultExpandedIds, onExpandedChange,\n } = props;\n const [sort, setSort] = useState<SortState>(defaultSort ?? null);\n const [ownExpanded, setOwnExpanded] = useState<ReadonlySet<string>>(() => new Set(defaultExpandedIds));\n const expanded = expandedIds ?? ownExpanded;\n const detailIdBase = useId();\n\n const toggleExpanded = (id: string) => {\n const next = new Set(expanded);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n if (!expandedIds) setOwnExpanded(next);\n onExpandedChange?.(next);\n };\n\n const sortedRows = useMemo(() => {\n if (!sort) return [...rows];\n const col = columns.find((c) => c.id === sort.columnId);\n if (!col) return [...rows];\n const get = col.accessor ?? ((row: Row) => (row as unknown as Record<string, unknown>)[col.id] as string | number | null | undefined);\n const dir = sort.direction === 'asc' ? 1 : -1;\n return [...rows].sort((a, b) => {\n const av = get(a);\n const bv = get(b);\n if (av == null && bv == null) return 0;\n if (av == null) return 1;\n if (bv == null) return -1;\n if (av < bv) return -1 * dir;\n if (av > bv) return 1 * dir;\n return 0;\n });\n }, [rows, columns, sort]);\n\n const cycleSort = (columnId: string) => {\n setSort((cur) => {\n if (!cur || cur.columnId !== columnId) return { columnId, direction: 'asc' };\n if (cur.direction === 'asc') return { columnId, direction: 'desc' };\n return null;\n });\n };\n\n // Drag handles via useReorderDragList. Adapter: the hook expects\n // `{ id, label }`-shaped items; we map our rows to that shape (label\n // unused — the hook only uses id and locked).\n const drag = useReorderDragList({\n items: sortedRows.map((r) => ({ id: r.id, label: '' })),\n selectedIds: [],\n onReorder: onReorder ?? (() => {}),\n rowSelector: ':not([data-detail-of])',\n });\n const dragEnabled = !!onReorder;\n const detailEnabled = !!renderDetail;\n const colCount = columns.length + (dragEnabled ? 1 : 0) + (detailEnabled ? 1 : 0);\n\n const cls = [s.grid, className].filter(Boolean).join(' ');\n\n return (\n <div\n className={cls}\n >\n <table className={s.table}>\n <thead>\n <tr>\n {dragEnabled && <th className={s.handleCol} aria-label=\"drag\" />}\n {detailEnabled && <th className={s.detailCol} aria-label=\"details\" />}\n {columns.map((col) => {\n const sortable = col.sortable !== false;\n const active = sort?.columnId === col.id;\n const indicator = active ? (sort.direction === 'asc' ? ' ↑' : ' ↓') : '';\n return (\n <th\n key={col.id}\n className={[col.className, active ? s.sortActive : ''].filter(Boolean).join(' ')}\n aria-sort={sortable ? (active ? (sort.direction === 'asc' ? 'ascending' : 'descending') : 'none') : undefined}\n >\n {sortable ? (\n <button type=\"button\" className={s.sortButton} onClick={() => cycleSort(col.id)}>\n {col.header}{indicator}\n </button>\n ) : (\n <>{col.header}{indicator}</>\n )}\n </th>\n );\n })}\n </tr>\n </thead>\n <tbody ref={dragEnabled ? (drag.containerProps.ref as React.RefCallback<HTMLTableSectionElement>) : undefined}>\n {sortedRows.length === 0 && (\n <tr>\n <td colSpan={colCount} className={s.empty}>\n {empty}\n </td>\n </tr>\n )}\n {sortedRows.map((row, i) => {\n const isDragging = drag.state.draggedIds?.includes(row.id) ?? false;\n const target = drag.state.targetIndex;\n const expandable = detailEnabled && (rowExpandable?.(row) ?? true);\n const isExpanded = expandable && expanded.has(row.id);\n const detail = isExpanded ? renderDetail!(row) : null;\n const detailId = `${detailIdBase}-detail-${row.id}`;\n const rowCls = [\n onRowClick && s.clickable,\n isDragging && s.dragging,\n target === i && s.dropBefore,\n target === sortedRows.length && i === sortedRows.length - 1 && s.dropAfter,\n rowClassName?.(row),\n ].filter(Boolean).join(' ');\n return (\n <Fragment key={row.id}>\n <tr\n className={rowCls || undefined}\n data-expanded={isExpanded || undefined}\n tabIndex={onRowClick ? 0 : undefined}\n onClick={onRowClick ? (e) => { if (!fromControl(e)) onRowClick(row); } : undefined}\n onKeyDown={onRowClick ? (e) => {\n if (e.target !== e.currentTarget || (e.key !== 'Enter' && e.key !== ' ')) return;\n e.preventDefault();\n onRowClick(row);\n } : undefined}\n >\n {dragEnabled && (\n <td\n className={s.handleCell}\n onPointerDown={(e) => drag.rowProps(row.id, i).onPointerDown(e)}\n >\n <span aria-hidden=\"true\">⋮⋮</span>\n </td>\n )}\n {detailEnabled && (\n <td className={s.detailCell}>\n {expandable && (\n <Disclosure\n open={isExpanded}\n onToggle={() => toggleExpanded(row.id)}\n label={isExpanded ? 'Hide details' : 'Show details'}\n controls={detail != null ? detailId : undefined}\n />\n )}\n </td>\n )}\n {columns.map((col) => {\n const get = col.accessor ?? ((r: Row) => (r as unknown as Record<string, unknown>)[col.id] as string | number | null | undefined);\n const content = col.render ? col.render(row) : String(get(row) ?? '');\n return (\n <td key={col.id} className={col.className}>\n {content}\n </td>\n );\n })}\n </tr>\n {detail != null && (\n <tr id={detailId} className={s.detailRow} data-detail-of={row.id}>\n <td colSpan={colCount}>{detail}</td>\n </tr>\n )}\n </Fragment>\n );\n })}\n </tbody>\n </table>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { jsx as e, jsxs as t } from "react/jsx-runtime";
|
|
2
|
-
import { useId as n } from "react";
|
|
3
|
-
var r = {
|
|
4
|
-
list: "_list_10kiu_1",
|
|
5
|
-
row: "_row_10kiu_14",
|
|
6
|
-
label: "_label_10kiu_22",
|
|
7
|
-
value: "_value_10kiu_36",
|
|
8
|
-
dot: "_dot_10kiu_69",
|
|
9
|
-
title: "_title_10kiu_81"
|
|
10
|
-
};
|
|
11
|
-
//#endregion
|
|
12
|
-
//#region src/components/DetailList/DetailList.tsx
|
|
13
|
-
function i({ children: i, title: a, layout: o = "inline", values: s = "text", className: c }) {
|
|
14
|
-
let l = n(), u = /* @__PURE__ */ e("dl", {
|
|
15
|
-
className: c && a == null ? `${r.list} ${c}` : r.list,
|
|
16
|
-
"data-layout": o,
|
|
17
|
-
"data-values": s,
|
|
18
|
-
"aria-labelledby": a == null ? void 0 : l,
|
|
19
|
-
children: i
|
|
20
|
-
});
|
|
21
|
-
return a == null ? u : /* @__PURE__ */ t("section", {
|
|
22
|
-
className: c,
|
|
23
|
-
children: [/* @__PURE__ */ e("h3", {
|
|
24
|
-
id: l,
|
|
25
|
-
className: r.title,
|
|
26
|
-
children: a
|
|
27
|
-
}), u]
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
function a({ label: n, children: i, placeholder: a = "–", status: o, values: s, className: c }) {
|
|
31
|
-
let l = i == null;
|
|
32
|
-
return /* @__PURE__ */ t("div", {
|
|
33
|
-
className: c ? `${r.row} ${c}` : r.row,
|
|
34
|
-
"data-status": o,
|
|
35
|
-
"data-values": s,
|
|
36
|
-
children: [/* @__PURE__ */ e("dt", {
|
|
37
|
-
className: r.label,
|
|
38
|
-
children: n
|
|
39
|
-
}), /* @__PURE__ */ t("dd", {
|
|
40
|
-
className: r.value,
|
|
41
|
-
"data-empty": l || void 0,
|
|
42
|
-
children: [o && /* @__PURE__ */ e("span", {
|
|
43
|
-
className: r.dot,
|
|
44
|
-
"aria-hidden": "true"
|
|
45
|
-
}), l ? a : i]
|
|
46
|
-
})]
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
//#endregion
|
|
50
|
-
export { a as n, i as t };
|
|
51
|
-
|
|
52
|
-
//# sourceMappingURL=DetailList-DFtqdOJj.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"DetailList-DFtqdOJj.js","names":["s","DetailList_module_default","DetailList","titleId","useId","list","className","title","layout","values","DetailRow","empty","children","status","label","placeholder"],"sources":["../../src/components/DetailList/DetailList.module.css","../../src/components/DetailList/DetailList.tsx"],"sourcesContent":[".list {\n display: grid;\n grid-template-columns: var(--wzl-params-label-width, auto) minmax(0, 1fr);\n column-gap: var(--wzl-space-6);\n row-gap: var(--wzl-space-1);\n margin: 0;\n font-size: var(--wzl-font-size-md);\n}\n\n.list[data-layout='block'] {\n grid-template-columns: minmax(0, 1fr);\n}\n\n.row {\n display: grid;\n grid-column: 1 / -1;\n grid-template-columns: subgrid;\n /* A label meets the first line of its value, however many lines that runs to. */\n align-items: baseline;\n}\n\n.label {\n display: flex;\n align-items: baseline;\n justify-content: var(--wzl-params-label-align, start);\n gap: var(--wzl-space-2);\n font-family: var(--wzl-font-display);\n font-weight: var(--wzl-font-weight-light);\n font-size: var(--wzl-font-size-sm);\n line-height: var(--wzl-leading-snug);\n color: var(--wzl-fg-muted);\n text-transform: var(--wzl-params-label-case, uppercase);\n letter-spacing: var(--wzl-params-label-tracking, var(--wzl-tracking-wide));\n}\n\n.value {\n margin: 0;\n min-width: 0;\n /* The floor a property row stands on, so the two share a rhythm side by side. */\n min-height: var(--wzl-prop-row-h, var(--wzl-field-h, var(--wzl-control-h)));\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--wzl-space-2);\n overflow-wrap: anywhere;\n color: var(--wzl-fg);\n}\n\n.value[data-empty] {\n color: var(--wzl-fg-muted);\n}\n\n.list[data-values='figures'] {\n grid-template-columns: var(--wzl-params-label-width, auto) minmax(min-content, 1fr);\n}\n\n.list[data-values='figures'] .row:not([data-values='text']) .value,\n.row[data-values='figures'] .value {\n justify-content: flex-end;\n text-align: end;\n font-family: var(--wzl-font-numeric);\n font-variant-numeric: tabular-nums;\n /* On the cell, not the track, so ch measures the figures' own face. */\n min-width: var(--wzl-detail-figure-min-width, 8ch);\n white-space: pre;\n overflow-wrap: normal;\n}\n\n.dot {\n flex: none;\n width: 0.5em;\n height: 0.5em;\n border-radius: 50%;\n background: currentColor;\n}\n\n.row[data-status='success'] .dot { background: var(--wzl-success); }\n.row[data-status='warn'] .dot { background: var(--wzl-warning); }\n.row[data-status='danger'] .dot { background: var(--wzl-danger); }\n\n.title {\n margin: 0 0 var(--wzl-space-3);\n font-family: var(--wzl-font-display);\n font-weight: var(--wzl-font-weight-light);\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg-muted);\n text-transform: var(--wzl-params-label-case, uppercase);\n letter-spacing: var(--wzl-params-label-tracking, var(--wzl-tracking-wide));\n}\n","import { type ReactNode, useId } from 'react';\nimport s from './DetailList.module.css';\n\n/** Whether a row's label sits beside its value or above it. */\nexport type DetailListLayout = 'inline' | 'block';\n\n/**\n * How a list sets its values. `text` starts each value at the column's edge\n * and wraps it; `figures` right-aligns it in a column at least\n * `--wzl-detail-figure-min-width` (default `8ch`) wide, in the mono face,\n * with spaces kept, so figure-space padding lines a column up on its decimal\n * point.\n */\nexport type DetailListValues = 'text' | 'figures';\n\n/** The state a row's status dot reports, painted from `--wzl-success`,\n * `--wzl-warning` or `--wzl-danger`. */\nexport type DetailRowStatus = 'success' | 'warn' | 'danger';\n\n/** Props for {@link DetailList}. */\nexport interface DetailListProps {\n /** {@link DetailRow}s. */\n children: ReactNode;\n /** Heading drawn above the list, which also names it. */\n title?: ReactNode;\n /**\n * `inline` puts every label in one rail beside the values; `block` stacks\n * each label over its value, for a column too narrow to hold both.\n */\n layout?: DetailListLayout;\n /** `text` by default. See {@link DetailListValues}. */\n values?: DetailListValues;\n /** Class on the outermost element: the `<dl>`, or the `<section>` holding\n * the heading and the list when there is a `title`. */\n className?: string;\n}\n\n/** Props for {@link DetailRow}. */\nexport interface DetailRowProps {\n label: ReactNode;\n /** The value: text, or any run of elements — code chips, badges, keycaps,\n * links. Several children wrap as a line of items. `null` or `undefined`\n * keeps the row in place showing `placeholder`, so a list whose values\n * come and go holds its height. */\n children?: ReactNode;\n /** Shown while there is no value. An en dash by default. */\n placeholder?: ReactNode;\n /**\n * Draws a dot in the status color before the value. The dot is hidden from\n * assistive technology and color is never the only signal, so the value\n * itself has to say the same thing — \"locked\", not just a green dot.\n */\n status?: DetailRowStatus;\n /** Sets this row's value as `text` or `figures`, whatever the list's\n * `values` says — a sentence among figures, or one figure among text. */\n values?: DetailListValues;\n className?: string;\n}\n\n/**\n * A read-only list of labeled values — the `<dl>` a property panel would be\n * if its rows held facts instead of controls. Labels take the params label\n * recipe and rail: `--wzl-params-label-width` sets the rail, so a detail list\n * and a property panel beside it line their labels up from one declaration.\n */\nexport function DetailList({ children, title, layout = 'inline', values = 'text', className }: DetailListProps) {\n const titleId = useId();\n const list = (\n <dl\n className={className && title == null ? `${s.list} ${className}` : s.list}\n data-layout={layout}\n data-values={values}\n aria-labelledby={title != null ? titleId : undefined}\n >\n {children}\n </dl>\n );\n if (title == null) return list;\n return (\n <section className={className}>\n <h3 id={titleId} className={s.title}>{title}</h3>\n {list}\n </section>\n );\n}\n\n/** One label and its value in a {@link DetailList}. */\nexport function DetailRow({ label, children, placeholder = '–', status, values, className }: DetailRowProps) {\n const empty = children == null;\n return (\n <div className={className ? `${s.row} ${className}` : s.row} data-status={status} data-values={values}>\n <dt className={s.label}>{label}</dt>\n <dd className={s.value} data-empty={empty || undefined}>\n {status && <span className={s.dot} aria-hidden=\"true\" />}\n {empty ? placeholder : children}\n </dd>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;ACiEA,SAAgBE,EAAW,EAAE,aAAU,UAAO,YAAS,UAAU,YAAS,QAAQ,gBAA8B;CAC9G,IAAMC,IAAUC,EAAM,GAChBC,IACJ,kBAAC,MAAD;EACE,WAAWC,KAAaC,KAAS,OAAO,GAAGP,EAAE,KAAK,GAAGM,MAAcN,EAAE;EACrE,eAAaQ;EACb,eAAaC;EACb,mBAAiBF,KAAS,OAAiB,KAAA,IAAVJ;EAEhC;CACC,CAAA;CAGN,OADII,KAAS,OAAaF,IAExB,kBAAC,WAAD;EAAoB;EAApB,UAAA,CACE,kBAAC,MAAD;GAAI,IAAIF;GAAS,WAAWH,EAAE;GAAQ,UAAAO;EAAU,CAAA,GAC/CF,CACM;;AAEb;AAGA,SAAgBK,EAAU,EAAE,UAAO,aAAU,iBAAc,KAAK,WAAQ,WAAQ,gBAA6B;CAC3G,IAAMC,IAAQC,KAAY;CAC1B,OACE,kBAAC,OAAD;EAAK,WAAWN,IAAY,GAAGN,EAAE,IAAI,GAAGM,MAAcN,EAAE;EAAK,eAAaa;EAAQ,eAAaJ;EAA/F,UAAA,CACE,kBAAC,MAAD;GAAI,WAAWT,EAAE;GAAQ,UAAAc;EAAU,CAAA,GACnC,kBAAC,MAAD;GAAI,WAAWd,EAAE;GAAO,cAAYW,KAAS,KAAA;GAA7C,UAAA,CACGE,KAAU,kBAAC,QAAD;IAAM,WAAWb,EAAE;IAAK,eAAY;GAAQ,CAAA,GACtDW,IAAQI,IAAcH,CACrB;EACD,CAAA,CAAA;;AAET"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"FallthroughDiagram-C7BUe8eU.js","names":["_Fragment","s","Badge","keySpecFromKey","KeyCap","keySpecsFromMods","GestureRoute","FallthroughDiagram_module_default","placedByLabel","p","walkLabel","w","Fragment","isWinner","MOD_ORDER","InputLine","mods","input","m","KeySequence","predicted","Routes","candidate","r","Stage","id","useId","filter","title","note","children","MatchedStage","matched","Code","c","i","dropReason","d","FilterBand","mine","dropped","RankedStage","ranked","winner","FallthroughDiagram","cls","className","record"],"sources":["../../src/components/FallthroughDiagram/FallthroughDiagram.module.css","../../src/components/FallthroughDiagram/FallthroughDiagram.tsx"],"sourcesContent":[".diagram {\n display: grid;\n gap: var(--wzl-space-3);\n color: var(--wzl-fg);\n font-family: var(--wzl-font-ui);\n font-size: var(--wzl-font-size-sm);\n}\n\n.input {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--wzl-space-2) var(--wzl-space-4);\n margin: 0;\n color: var(--wzl-fg-muted);\n}\n.field { display: flex; align-items: baseline; gap: var(--wzl-space-1); }\n.field dt { margin: 0; }\n.field dd {\n margin: 0;\n color: var(--wzl-fg);\n font-weight: var(--wzl-font-weight-medium);\n}\n\n.stage {\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-md);\n background: var(--wzl-surface);\n padding: var(--wzl-space-2) var(--wzl-space-3);\n /* Ids and rules do not wrap mid-word; a narrow host scrolls the stage instead. */\n overflow-x: auto;\n}\n.stage[data-filter] { background: var(--wzl-surface-sunken); }\n\n.stageTitle {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--wzl-space-2);\n margin: 0;\n font-size: var(--wzl-font-size-sm);\n font-weight: var(--wzl-font-weight-bold);\n}\n.stageTitle + .table { margin-top: var(--wzl-space-2); }\n.note { color: var(--wzl-fg-muted); font-weight: var(--wzl-font-weight-medium); }\n\n.table { width: 100%; border-collapse: collapse; }\n.table th {\n text-align: start;\n color: var(--wzl-fg-muted);\n font-size: var(--wzl-font-size-xs);\n font-weight: var(--wzl-font-weight-bold);\n padding: var(--wzl-space-1) var(--wzl-space-2);\n}\n.table td {\n padding: var(--wzl-space-1) var(--wzl-space-2);\n border-top: 1px solid var(--wzl-border);\n vertical-align: top;\n white-space: nowrap;\n}\n\n.table code { white-space: nowrap; overflow-wrap: normal; }\n\n.routes { display: inline-flex; flex-direction: column; align-items: flex-start; gap: var(--wzl-space-1); }\n\n.num {\n font-family: var(--wzl-font-mono);\n font-variant-numeric: tabular-nums;\n text-align: end;\n white-space: nowrap;\n}\n\n.muted { color: var(--wzl-fg-muted); }\n\n.walk { color: var(--wzl-fg-muted); }\n.walk[data-walk='fired'],\n.walk[data-walk='would-fire'] { color: var(--wzl-success); font-weight: var(--wzl-font-weight-bold); }\n.walk[data-walk='declined'],\n.walk[data-walk='empty-handle'],\n.walk[data-walk='misbound'],\n.walk[data-walk='no-such-action'] { color: var(--wzl-fg); }\n\n.winner td { background: color-mix(in srgb, var(--wzl-success) 12%, transparent); }\n.winner td:first-child { box-shadow: inset 3px 0 var(--wzl-success); }\n\n.nothing { margin: 0; color: var(--wzl-fg-muted); }\n","import { useId, type ReactNode } from 'react';\nimport type {\n DispatchRecord,\n DispatchRecordInput,\n DroppedCandidate,\n PlacedBy,\n RankedCandidate,\n RecordCandidate,\n WalkStep,\n} from '@weasel-js/core/routing';\nimport { Badge } from '../Badge';\nimport { Code } from '../Code';\nimport { GestureRoute } from '../GestureRoute';\nimport { KeyCap, KeySequence, keySpecFromKey, keySpecsFromMods, type LogicalModSpec } from '../Keycaps';\nimport s from './FallthroughDiagram.module.css';\n\nfunction placedByLabel(p: PlacedBy): string {\n return p.step === 'specificity' ? `specificity · ${p.part}` : p.step;\n}\n\nfunction walkLabel(w: WalkStep): ReactNode {\n switch (w.kind) {\n case 'fired': return 'fired';\n case 'would-fire': return 'would fire';\n case 'declined': return <>declined: {w.reason}</>;\n case 'empty-handle': return 'empty handle';\n case 'misbound': return 'misbound';\n case 'duplicate': return 'duplicate';\n case 'no-such-action': return 'no such action';\n case 'not-asked': return 'not asked';\n case 'outranked': return 'outranked';\n }\n}\n\nconst isWinner = (w: WalkStep) => w.kind === 'fired' || w.kind === 'would-fire';\n\nconst MOD_ORDER = ['shift', 'alt', 'ctrl', 'meta'] as const;\n\nfunction InputLine({ input, predicted }: { input: DispatchRecordInput; predicted: boolean }) {\n const mods: LogicalModSpec[] = MOD_ORDER.filter((m) => input.modifiers[m]).map((name) => ({ name }));\n return (\n <dl className={s.input} aria-label=\"Input\">\n <div className={s.field}>\n <dt>Event</dt>\n <dd>{input.eventKind}</dd>\n </div>\n {input.key !== undefined && (\n <div className={s.field}>\n <dt>Key</dt>\n <dd data-key={input.key}>\n <KeyCap label={keySpecFromKey(input.key, { legend: 'text' }).label} variant=\"minimal\" />\n </dd>\n </div>\n )}\n <div className={s.field}>\n <dt>Modifiers</dt>\n <dd>{mods.length > 0 ? <KeySequence keys={keySpecsFromMods(mods)} variant=\"minimal\" /> : 'none'}</dd>\n </div>\n {input.viewId !== null && (\n <div className={s.field}>\n <dt>View</dt>\n <dd>{input.viewId}</dd>\n </div>\n )}\n {input.mode !== undefined && (\n <div className={s.field}>\n <dt>Mode</dt>\n <dd>{input.mode}</dd>\n </div>\n )}\n {predicted && (\n <div className={s.field}>\n <dd><Badge size=\"xs\" status=\"accent\" variant=\"subtle\">predicted</Badge></dd>\n </div>\n )}\n </dl>\n );\n}\n\nfunction Routes({ candidate }: { candidate: RecordCandidate }) {\n return (\n <span className={s.routes}>\n {candidate.routes.map((r) => <GestureRoute key={r} route={r} size=\"sm\" />)}\n </span>\n );\n}\n\nfunction Stage({ title, note, filter, children }: {\n title: string;\n note?: ReactNode;\n filter?: DroppedCandidate['filter'];\n children?: ReactNode;\n}) {\n const id = useId();\n return (\n <section className={s.stage} aria-labelledby={id} data-filter={filter}>\n <h3 className={s.stageTitle}>\n <span id={id}>{title}</span>\n {note && <span className={s.note}>{note}</span>}\n </h3>\n {children}\n </section>\n );\n}\n\nfunction MatchedStage({ matched }: { matched: readonly RecordCandidate[] }) {\n return (\n <Stage title=\"Matched\" note=\"every binding that fits the input\">\n <table className={s.table}>\n <thead>\n <tr><th>Action</th><th>Route</th><th>Owner</th><th>Tier</th></tr>\n </thead>\n <tbody>\n {matched.map((c, i) => (\n <tr key={i}>\n <td><Code size=\"sm\">{c.actionId}</Code></td>\n <td><Routes candidate={c} /></td>\n <td className={c.ownerId === null ? s.muted : undefined}>{c.ownerId ?? 'action'}</td>\n <td>{c.scope}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </Stage>\n );\n}\n\nfunction dropReason(d: DroppedCandidate): ReactNode {\n if (d.filter === 'ineligible') return <><Code size=\"sm\" variant=\"plain\">{d.rule}</Code> does not hold</>;\n return d.owner !== undefined\n ? <>barred by the exclusive claim of <Code size=\"sm\" variant=\"plain\">{d.owner}</Code></>\n : 'barred by an exclusive claim';\n}\n\nfunction FilterBand({ title, filter, dropped }: {\n title: string;\n filter: DroppedCandidate['filter'];\n dropped: readonly DroppedCandidate[];\n}) {\n const mine = dropped.filter((d) => d.filter === filter);\n if (mine.length === 0) return <Stage title={title} filter={filter} note=\"dropped nothing\" />;\n return (\n <Stage title={title} filter={filter} note=\"dropped\">\n <table className={s.table}>\n <thead>\n <tr><th>Action</th><th>Route</th><th>Why</th></tr>\n </thead>\n <tbody>\n {mine.map((d, i) => (\n <tr key={i}>\n <td><Code size=\"sm\">{d.candidate.actionId}</Code></td>\n <td><Routes candidate={d.candidate} /></td>\n <td>{dropReason(d)}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </Stage>\n );\n}\n\nfunction RankedStage({ ranked }: { ranked: readonly RankedCandidate[] }) {\n if (ranked.length === 0) return <Stage title=\"Ranked\" note=\"nothing survived the filters\" />;\n return (\n <Stage title=\"Ranked\" note=\"highest first, with what the walk did at each\">\n <table className={s.table}>\n <thead>\n <tr>\n <th className={s.num}>#</th><th>Action</th><th>Placed by</th><th>Walk</th>\n <th>Tier</th><th>Specificity</th><th>Rule</th><th>Route</th>\n </tr>\n </thead>\n <tbody>\n {ranked.map((r, i) => {\n const c = r.candidate;\n const winner = isWinner(r.walk);\n return (\n <tr\n key={i}\n className={winner ? s.winner : undefined}\n data-walk={r.walk.kind}\n data-winner={winner || undefined}\n aria-current={winner || undefined}\n >\n <td className={s.num}>{i + 1}</td>\n <td><Code size=\"sm\">{c.actionId}</Code></td>\n <td>\n <Badge size=\"xs\" variant=\"outline\" status={r.placedBy.step === 'first' ? 'muted' : 'warn'}>\n {placedByLabel(r.placedBy)}\n </Badge>\n </td>\n <td className={s.walk} data-walk={r.walk.kind}>{walkLabel(r.walk)}</td>\n <td>{c.scope}</td>\n <td className={s.num} aria-label=\"target, modifiers, phase, exact\">{c.specificity.join(' ')}</td>\n <td>{c.eligible ? <Code size=\"sm\" variant=\"plain\">{c.eligible}</Code> : <span className={s.muted}>none</span>}</td>\n <td><Routes candidate={c} /></td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </Stage>\n );\n}\n\n/** Props for {@link FallthroughDiagram}. */\nexport interface FallthroughDiagramProps {\n record: DispatchRecord;\n className?: string;\n}\n\n/**\n * One dispatch drawn top to bottom: the input, every binding it matched, what\n * the claim and eligibility filters dropped, and the ranked survivors with the\n * step that placed each one and what the walk did with it. The winner's row\n * carries `data-winner`.\n */\nexport function FallthroughDiagram({ record, className }: FallthroughDiagramProps) {\n const cls = className ? `${s.diagram} ${className}` : s.diagram;\n return (\n <div className={cls} data-outcome={record.outcome} data-predicted={record.predicted || undefined}>\n <InputLine input={record.input} predicted={record.predicted} />\n {record.matched.length === 0 ? (\n <p className={s.nothing}>Nothing matched this input.</p>\n ) : (\n <>\n <MatchedStage matched={record.matched} />\n <FilterBand title=\"Claim\" filter=\"claim\" dropped={record.dropped} />\n <FilterBand title=\"Eligibility\" filter=\"ineligible\" dropped={record.dropped} />\n <RankedStage ranked={record.ranked} />\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"FillStrokeSwatch-CgsYQgew.js","names":["s","solidColorOf","useRecordRecentColor","paintPreviewCss","FillStrokeSwatch_module_default","SLOT_LABEL","PICKER_FALLBACK","Chip","props","draft","setDraft","useState","record","none","paint","preview","pickerValue","slot","focused","color","FillStrokeSwatch","focusedPaint","fill","stroke","chip","anyButton","onToggleNone","onSwap","onReset","className","Button","shortcuts"],"sources":["../../src/components/FillStrokeSwatch/FillStrokeSwatch.module.css","../../src/components/FillStrokeSwatch/FillStrokeSwatch.tsx"],"sourcesContent":[".root {\n display: inline-flex;\n flex-direction: column;\n align-items: center;\n gap: var(--wzl-space-2);\n}\n\n/* The two chips overlap on a diagonal: fill up-left, behind; stroke\n down-right, in front, so the fill shows through the stroke's hole. */\n.pair {\n --chip: 32px;\n --offset: 12px;\n --ring: 8px;\n position: relative;\n inline-size: calc(var(--chip) + var(--offset));\n block-size: calc(var(--chip) + var(--offset));\n}\n\n.chip {\n position: absolute;\n inline-size: var(--chip);\n block-size: var(--chip);\n box-sizing: border-box;\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n}\n\n.chip[data-slot='fill'] {\n inset-block-start: 0;\n inset-inline-start: 0;\n}\n\n.chip[data-slot='stroke'] {\n inset-block-end: 0;\n inset-inline-end: 0;\n}\n\n.chip[data-focused] {\n border-color: var(--wzl-fg);\n box-shadow: 0 0 0 1px var(--wzl-fg);\n}\n\n.chip:has(.input:focus-visible) {\n outline: 2px solid var(--wzl-focus-ring);\n outline-offset: 1px;\n}\n\n/* The paint sits over the checker, so a translucent one reads as such. */\n.well {\n position: absolute;\n inset: 0;\n overflow: hidden;\n border-radius: calc(var(--wzl-radius-sm) - var(--wzl-border-w));\n background: repeating-conic-gradient(\n var(--wzl-checker-a) 0% 25%,\n var(--wzl-checker-b) 0% 50%\n )\n 50% / var(--wzl-checker-size) var(--wzl-checker-size);\n}\n\n/* The stroke well is a ring: the mask subtracts the content box from the\n padding box, so whatever is behind the chip shows through its hole. */\n.chip[data-slot='stroke'] .well {\n box-sizing: border-box;\n padding: var(--ring);\n mask:\n linear-gradient(#000, #000) padding-box,\n linear-gradient(#000, #000) content-box;\n mask-composite: exclude;\n}\n\n/* The hole's edge, so a focused stroke reads as focused on both sides. */\n.chip[data-slot='stroke'][data-focused]::before {\n content: '';\n position: absolute;\n inset: calc(var(--ring) - var(--wzl-border-w));\n border: 1px solid var(--wzl-fg);\n border-radius: 0;\n pointer-events: none;\n}\n\n.paint {\n position: absolute;\n inset: 0;\n}\n\n/* No paint: the surface crossed by the danger diagonal. */\n.chip[data-none] .well {\n background:\n linear-gradient(\n 45deg,\n transparent calc(50% - 1px),\n var(--wzl-danger) calc(50% - 1px),\n var(--wzl-danger) calc(50% + 1px),\n transparent calc(50% + 1px)\n ),\n var(--wzl-surface);\n}\n\n/* The native picker covers its chip, invisible: it takes the click and the\n keyboard focus, and the well draws the paint. */\n.input {\n position: absolute;\n inset: 0;\n inline-size: 100%;\n block-size: 100%;\n margin: 0;\n padding: 0;\n border: 0;\n opacity: 0;\n cursor: pointer;\n}\n\n.buttons {\n display: flex;\n flex-wrap: wrap;\n justify-content: center;\n gap: var(--wzl-space-1);\n}\n","import { useState, type ReactElement } from 'react';\nimport type { FillStyle } from '@weasel-js/core';\nimport { paintPreviewCss } from '../../paintPreview';\nimport { solidColorOf } from '../paintValue';\nimport { Button } from '../Button';\nimport { useRecordRecentColor } from '../RecentColors/RecentColorsProvider';\nimport type { PaintSlot } from '../GradientEditor';\nimport s from './FillStrokeSwatch.module.css';\n\n/** Props for {@link FillStrokeSwatch}. */\nexport interface FillStrokeSwatchProps {\n /** `null` is an explicit \"no paint\". */\n fill: FillStyle | null | undefined;\n stroke: FillStyle | null | undefined;\n /** The slot that edits aimed at \"the current paint\" land on — the None\n * button, a palette click, a keyboard shortcut the app binds. */\n focused: PaintSlot;\n onFocusChange: (slot: PaintSlot) => void;\n /** Every tick of the color picker, as `#rrggbb`. */\n onInput?: (slot: PaintSlot, color: string) => void;\n /** Once when the picker closes, with its last color, if it moved. */\n onChange: (slot: PaintSlot, color: string) => void;\n /** Toggle a slot between no paint and a paint. Given, it adds the None\n * button (acting on `focused`) and shift-click on a chip. */\n onToggleNone?: (slot: PaintSlot) => void;\n /** Given, adds a Swap button that exchanges the two paints. */\n onSwap?: () => void;\n /** Given, adds a Default button that restores the app's default pair. */\n onReset?: () => void;\n /** Key hints for the buttons' tooltips. The app binds the keys itself. */\n shortcuts?: { none?: string; swap?: string; reset?: string };\n 'aria-label'?: string;\n className?: string;\n}\n\nconst SLOT_LABEL: Record<PaintSlot, string> = { fill: 'Fill', stroke: 'Stroke' };\nconst PICKER_FALLBACK: Record<PaintSlot, string> = { fill: '#ffffff', stroke: '#000000' };\n\nfunction Chip(props: {\n slot: PaintSlot;\n paint: FillStyle | null | undefined;\n focused: boolean;\n onFocusChange: (slot: PaintSlot) => void;\n onInput?: (slot: PaintSlot, color: string) => void;\n onChange: (slot: PaintSlot, color: string) => void;\n onToggleNone?: (slot: PaintSlot) => void;\n}): ReactElement {\n const { slot, paint, focused } = props;\n // Tracks the picker through a pick; the prop only catches up on commit.\n const [draft, setDraft] = useState<string | null>(null);\n const record = useRecordRecentColor();\n const none = paint === null;\n const preview = paintPreviewCss(paint);\n const pickerValue = (solidColorOf(paint) ?? PICKER_FALLBACK[slot]).slice(0, 7);\n\n return (\n <span\n className={s.chip}\n data-slot={slot}\n {...(focused ? { 'data-focused': '' } : {})}\n {...(none ? { 'data-none': '' } : {})}\n >\n <span className={s.well} aria-hidden=\"true\">\n {preview && <span className={s.paint} style={{ background: preview }} />}\n </span>\n <input\n type=\"color\"\n className={s.input}\n value={draft ?? pickerValue}\n aria-label={SLOT_LABEL[slot]}\n aria-current={focused ? 'true' : undefined}\n onFocus={() => props.onFocusChange(slot)}\n onClick={(e) => {\n props.onFocusChange(slot);\n // Shift-click is the none toggle; preventing it keeps the OS picker shut.\n if (e.shiftKey && props.onToggleNone) {\n e.preventDefault();\n props.onToggleNone(slot);\n }\n }}\n onInput={(e) => {\n const color = (e.target as HTMLInputElement).value;\n setDraft(color);\n props.onInput?.(slot, color);\n }}\n onBlur={() => {\n if (draft === null) return;\n setDraft(null);\n props.onChange(slot, draft);\n record(draft);\n }}\n />\n </span>\n );\n}\n\n/**\n * The active fill and stroke as one overlapping pair, the way drawing apps\n * show the paint the next shape gets: fill a solid square behind, stroke a\n * frame in front of it. Each chip is a native color picker; the focused one\n * is what the None button — and whatever the app binds — acts on.\n *\n * Holds no state of its own: the paints and the focused slot are props, and\n * the chips report picks through `onInput`/`onChange` for the app to route —\n * into its own active-paint state, into the selection through\n * `useOngoingAction`, or both.\n */\nexport function FillStrokeSwatch(props: FillStrokeSwatchProps): ReactElement {\n const {\n fill, stroke, focused, onFocusChange, onInput, onChange,\n onToggleNone, onSwap, onReset, shortcuts, className,\n } = props;\n const focusedPaint = focused === 'fill' ? fill : stroke;\n const chip = { onFocusChange, onInput, onChange, onToggleNone };\n const anyButton = onToggleNone || onSwap || onReset;\n\n return (\n <div\n className={[s.root, className].filter(Boolean).join(' ')}\n role=\"group\"\n aria-label={props['aria-label'] ?? 'Fill and stroke'}\n >\n <div className={s.pair}>\n <Chip slot=\"fill\" paint={fill} focused={focused === 'fill'} {...chip} />\n <Chip slot=\"stroke\" paint={stroke} focused={focused === 'stroke'} {...chip} />\n </div>\n {anyButton && (\n <div className={s.buttons}>\n {onToggleNone && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n pressed={focusedPaint === null}\n onClick={() => onToggleNone(focused)}\n shortcut={shortcuts?.none}\n >\n None\n </Button>\n )}\n {onSwap && (\n <Button variant=\"ghost\" size=\"sm\" onClick={onSwap} shortcut={shortcuts?.swap}>\n Swap\n </Button>\n )}\n {onReset && (\n <Button variant=\"ghost\" size=\"sm\" onClick={onReset} shortcut={shortcuts?.reset}>\n Default\n </Button>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;GCmCMK,IAAwC;CAAE,MAAM;CAAQ,QAAQ;AAAS,GACzEC,IAA6C;CAAE,MAAM;CAAW,QAAQ;AAAU;AAExF,SAASC,EAAK,GAQG;CACf,IAAM,EAAE,SAAM,UAAO,eAAYC,GAE3B,CAACC,GAAOC,KAAYC,EAAwB,IAAI,GAChDC,IAASV,EAAqB,GAC9BW,IAAOC,MAAU,MACjBC,IAAUZ,EAAgBW,CAAK,GAC/BE,KAAef,EAAaa,CAAK,KAAKR,EAAgBW,GAAA,CAAO,MAAM,GAAG,CAAC;CAE7E,OACE,kBAAC,QAAD;EACE,WAAWjB,EAAE;EACb,aAAWiB;EACX,GAAKC,IAAU,EAAE,gBAAgB,GAAG,IAAI,CAAC;EACzC,GAAKL,IAAO,EAAE,aAAa,GAAG,IAAI,CAAC;EAJrC,UAAA,CAME,kBAAC,QAAD;GAAM,WAAWb,EAAE;GAAM,eAAY;GAClC,UAAAe,KAAW,kBAAC,QAAD;IAAM,WAAWf,EAAE;IAAO,OAAO,EAAE,YAAYe,EAAQ;GAAI,CAAA;EACnE,CAAA,GACN,kBAAC,SAAD;GACE,MAAK;GACL,WAAWf,EAAE;GACb,OAAOS,KAASO;GAChB,cAAYX,EAAWY;GACvB,gBAAcC,IAAU,SAAS,KAAA;GACjC,eAAeV,EAAM,cAAcS,CAAI;GACvC,UAAU,MAAM;IAGd,AAFAT,EAAM,cAAcS,CAAI,GAEpB,EAAE,YAAYT,EAAM,iBACtB,EAAE,eAAe,GACjBA,EAAM,aAAaS,CAAI;GAE3B;GACA,UAAU,MAAM;IACd,IAAME,IAAS,EAAE,OAA4B;IAE7C,AADAT,EAASS,CAAK,GACdX,EAAM,UAAUS,GAAME,CAAK;GAC7B;GACA,cAAc;IACZ,AAAIV,MAAU,SACdC,EAAS,IAAI,GACbF,EAAM,SAASS,GAAMR,CAAK,GAC1BG,EAAOH,CAAK;GACd;EACD,CAAA,CACG;;AAEV;AAaA,SAAgBW,EAAiB,GAA4C;CAC3E,IAAM,EACJ,SAAM,WAAQ,YAAS,kBAAe,YAAS,aAC/C,iBAAc,WAAQ,YAAS,cAAW,iBACxCZ,GACEa,IAAeH,MAAY,SAASI,IAAOC,GAC3CC,IAAO;EAAE;EAAe;EAAS;EAAU;CAAa,GACxDC,IAAYC,KAAgBC,KAAUC;CAE5C,OACE,kBAAC,OAAD;EACE,WAAW,CAAC5B,EAAE,MAAM6B,CAAS,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;EACvD,MAAK;EACL,cAAYrB,EAAM,iBAAiB;EAHrC,UAAA,CAKE,kBAAC,OAAD;GAAK,WAAWR,EAAE;GAAlB,UAAA,CACE,kBAACO,GAAD;IAAM,MAAK;IAAO,OAAOe;IAAM,SAASJ,MAAY;IAAQ,GAAIM;GAAO,CAAA,GACvE,kBAACjB,GAAD;IAAM,MAAK;IAAS,OAAOgB;IAAQ,SAASL,MAAY;IAAU,GAAIM;GAAO,CAAA,CAC1E;EACJ,CAAA,GAAAC,KACC,kBAAC,OAAD;GAAK,WAAWzB,EAAE;GAAlB,UAAA;IACG0B,KACC,kBAACI,GAAD;KACE,SAAQ;KACR,MAAK;KACL,SAAST,MAAiB;KAC1B,eAAeK,EAAaR,CAAO;KACnC,UAAUa,GAAW;KACtB,UAAA;IAEO,CAAA;IAETJ,KACC,kBAACG,GAAD;KAAQ,SAAQ;KAAQ,MAAK;KAAK,SAASH;KAAQ,UAAUI,GAAW;KAAM,UAAA;IAEtE,CAAA;IAETH,KACC,kBAACE,GAAD;KAAQ,SAAQ;KAAQ,MAAK;KAAK,SAASF;KAAS,UAAUG,GAAW;KAAO,UAAA;IAExE,CAAA;GAEP;EAEJ,CAAA,CAAA;;AAET"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"InlineRange-P01ABSzo.js","names":["range","shared","s","range_module_default","InlineRange_module_default","InlineRange","forwardRef","rest","props","span","max","min","filled","value","className","style"],"sources":["../../src/components/range.module.css","../../src/components/InlineRange/InlineRange.module.css","../../src/components/InlineRange/InlineRange.tsx"],"sourcesContent":["/* The one skin for a native <input type=\"range\">. Imported by InlineRange and\n by the property rows, which render the same DOM and used to carry three\n independently authored copies of these rules. labkit's theme/base.less\n extends it onto a bare range under `.lk-root`, so it is the only copy.\n\n The track is painted here rather than left to `accent-color`: a UA fills the\n left of the track with the accent but draws the remainder near-white on a\n dark surface whatever `color-scheme` says, and overriding the track's\n background removes the accent fill instead of recoloring the remainder. */\n.range {\n appearance: none;\n -webkit-appearance: none;\n width: 100%;\n min-inline-size: 48px;\n /* The box is taller than the track so the thumb has room to spill; a surface\n where the element itself IS the track overrides this. */\n height: var(--wzl-range-box-h, 12px);\n margin: 0;\n padding: 0;\n /* Transparent so the track pseudo-element shows; a surface that paints the element itself overrides it. */\n background: var(--wzl-range-bg, transparent);\n cursor: pointer;\n}\n\n.range::-webkit-slider-runnable-track {\n height: var(--wzl-slider-track-h);\n border-radius: var(--wzl-radius-sm);\n background: var(\n --wzl-range-track-bg,\n color-mix(in srgb, var(--wzl-accent) var(--wzl-slider-track-mix), transparent)\n );\n}\n\n.range::-moz-range-track {\n height: var(--wzl-slider-track-h);\n border-radius: var(--wzl-radius-sm);\n background: var(\n --wzl-range-track-bg,\n color-mix(in srgb, var(--wzl-accent) var(--wzl-slider-track-mix), transparent)\n );\n}\n\n.range::-webkit-slider-thumb {\n appearance: none;\n -webkit-appearance: none;\n width: var(--wzl-slider-thumb-size);\n height: var(--wzl-slider-thumb-size);\n border: none;\n border-radius: 50%;\n background: color-mix(in srgb, var(--wzl-accent) var(--wzl-slider-thumb-mix), transparent);\n /* WebKit and Blink hang the thumb from the track's top edge. A transform, not\n margin-top: Blink rounds a thumb margin to a whole CSS pixel, which leaves an\n odd track/thumb gap half a pixel off center at every DPR. */\n transform: translateY(calc((var(--wzl-slider-track-h) - var(--wzl-slider-thumb-size)) / 2));\n}\n\n.range::-moz-range-thumb {\n width: var(--wzl-slider-thumb-size);\n height: var(--wzl-slider-thumb-size);\n border: none;\n border-radius: 50%;\n background: color-mix(in srgb, var(--wzl-accent) var(--wzl-slider-thumb-mix), transparent);\n}\n\n/* :where() keeps these at the specificity of `.range` alone, which is what a bare\n labkit range extending this skin needs to stay at zero. */\n.range:where(:focus-visible) {\n outline: 2px solid var(--wzl-focus-ring);\n outline-offset: 2px;\n}\n\n.range:where(:disabled) {\n cursor: not-allowed;\n opacity: 0.55;\n}\n\n/* Alpha variant: a fainter track, used where the row already carries a color\n swatch and the slider must not compete with it. */\n.alpha::-webkit-slider-runnable-track {\n background: var(--wzl-range-track-bg, color-mix(in srgb, var(--wzl-accent) 10%, transparent));\n}\n\n.alpha::-moz-range-track {\n background: var(--wzl-range-track-bg, color-mix(in srgb, var(--wzl-accent) 10%, transparent));\n}\n\n/* A dead alpha track keeps its groove but loses its thumb, so the affordance\n reads inert rather than merely dimmed. Keyed off `:disabled`, which a color field\n already sets on the input — not off a class on the row. One rule per engine:\n Blink and WebKit drop a whole selector list that names a -moz- pseudo-element. */\n.alpha:disabled::-webkit-slider-runnable-track {\n background: color-mix(in srgb, var(--wzl-fg) 5%, transparent);\n}\n\n.alpha:disabled::-moz-range-track {\n background: color-mix(in srgb, var(--wzl-fg) 5%, transparent);\n}\n\n.alpha:disabled::-webkit-slider-thumb {\n background: transparent;\n}\n\n.alpha:disabled::-moz-range-thumb {\n background: transparent;\n}\n","/* Track and thumb come from the shared range skin. What is local to\n InlineRange is the filled-to-N% paint, which no other surface has:\n `--slider-fill` carries the value, since no static rule expresses it. */\n.range {\n flex: 1;\n --wzl-range-bg: linear-gradient(\n to right,\n var(--wzl-accent) 0 var(--slider-fill, 0%),\n var(--wzl-border-strong) var(--slider-fill, 0%) 100%\n );\n --wzl-range-box-h: var(--wzl-slider-track-h);\n /* The gradient above IS the track, so the shared track paint must not land on top of it. */\n --wzl-range-track-bg: transparent;\n border-radius: var(--wzl-radius-pill);\n}\n","import { type CSSProperties, forwardRef, type InputHTMLAttributes, type Ref } from 'react';\nimport shared from '../range.module.css';\nimport s from './InlineRange.module.css';\n\n/** Props for {@link InlineRange} — a range input's, with `value` required. */\nexport type InlineRangeProps = Omit<\n InputHTMLAttributes<HTMLInputElement>,\n 'type' | 'value' | 'min' | 'max'\n> & {\n value: number;\n min?: number;\n max?: number;\n};\n\n/**\n * Compact range input for a property row: one track, no label, no output.\n *\n * Distinct from the kit's other two — `Slider` is shaped for canvas scrubbing,\n * and `RangeSlider` is a full-width labelled form control on React Aria. This\n * one exists to sit inline beside a swatch or a readout, and to own the track\n * painting both of those surfaces need.\n */\nexport const InlineRange = forwardRef(function InlineRange(\n props: InlineRangeProps,\n ref: Ref<HTMLInputElement>,\n) {\n const { value, min = 0, max = 100, className, style, ...rest } = props;\n const span = max - min;\n const filled = span > 0 ? ((Math.min(Math.max(value, min), max) - min) / span) * 100 : 0;\n return (\n <input\n {...rest}\n ref={ref}\n type=\"range\"\n value={value}\n min={min}\n max={max}\n className={[shared.range, s.range, className].filter(Boolean).join(' ')}\n style={{ ['--slider-fill' as string]: `${filled}%`, ...style } as CSSProperties}\n />\n );\n});\n"],"mappings":";;;;;oCEsBaK,IAAcC,EAAW,SACpC,GACA,GACA;CACA,IAAM,EAAE,UAAO,SAAM,GAAG,SAAM,KAAK,cAAW,UAAO,GAAGC,MAASC,GAC3DC,IAAOC,IAAMC,GACbC,IAASH,IAAO,KAAM,KAAK,IAAI,KAAK,IAAII,GAAOF,CAAG,GAAGD,CAAG,IAAIC,KAAOF,IAAQ,MAAM;CACvF,OACE,kBAAC,SAAD;EACE,GAAIF;EACC;EACL,MAAK;EACE;EACF;EACA;EACL,WAAW;GAACN,EAAO;GAAOC,EAAE;GAAOY;EAAS,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;EACtE,OAAO;GAAG,iBAA4B,GAAGF,EAAO;GAAI,GAAGG;EAAM;CAC9D,CAAA;AAEL,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Jog-DfNUYejm.js","names":["s","StepBackIcon","PauseIcon","PlayIcon","StepForwardIcon","Button","Slider","Jog_module_default","Jog","props","last","count","at","index","spoken","unit","className","onStep","playing","onPlay","jsx","onScrub","next","pad","caption","value"],"sources":["../../src/components/Jog/Jog.module.css","../../src/components/Jog/Jog.tsx"],"sourcesContent":["/* A row of transport controls on the panel surface, sized to sit in a bar\n * beside a title or a caption rather than to own a strip of its own. */\n\n.jog {\n display: flex;\n align-items: center;\n gap: var(--wzl-space-2);\n color: var(--wzl-fg);\n}\n\n.scrub {\n flex: 1 1 auto;\n min-width: 80px;\n margin-inline: var(--wzl-space-2);\n}\n\n.readout {\n font-variant-numeric: tabular-nums;\n font-family: var(--wzl-font-numeric);\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg-muted);\n white-space: pre;\n}\n\n.caption {\n overflow: hidden;\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg-muted);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n","import type { ReactNode } from 'react';\nimport { PauseIcon, PlayIcon, StepBackIcon, StepForwardIcon } from '../../icons';\nimport { Button } from '../Button';\nimport { Slider } from '../Slider/Slider';\nimport s from './Jog.module.css';\n\n/** Props for {@link Jog}. */\nexport interface JogProps {\n /** Which step is showing, 0-based. */\n index: number;\n /** How many steps there are. */\n count: number;\n playing: boolean;\n onPlay: (playing: boolean) => void;\n /** Move one step, back or forward. */\n onStep: (by: -1 | 1) => void;\n /** Jump to a step. Left out, no scrubber is drawn. */\n onScrub?: (index: number) => void;\n /** What one step is called, for the spoken labels. Defaults to `Step`. */\n unit?: string;\n /** Drawn after the readout — a beat's caption, a snapshot's date. */\n caption?: ReactNode;\n className?: string;\n}\n\n/**\n * Transport for something counted rather than timed: beat 3 of 24, not 4.20s of 12.00s.\n * `<Transport>` is the continuous-time one, and wants a playhead in milliseconds, a rate and\n * a loop, none of which a list of steps has.\n *\n * Named for the jog of jog/shuttle — moving one frame at a time, as against running at speed.\n */\nexport function Jog(props: JogProps) {\n const { index, count, playing, onPlay, onStep, onScrub, unit = 'Step', caption, className } =\n props;\n\n const last = Math.max(0, count - 1);\n const at = Math.min(Math.max(index, 0), last);\n const spoken = `${unit} ${at + 1} of ${count}`;\n\n return (\n <div className={[s.jog, className].filter(Boolean).join(' ')}>\n <Button\n iconOnly\n size=\"sm\"\n variant=\"ghost\"\n ariaLabel={`Previous ${unit.toLowerCase()}`}\n disabled={at <= 0}\n onClick={() => onStep(-1)}\n >\n <StepBackIcon size={14} />\n </Button>\n <Button\n iconOnly\n size=\"sm\"\n variant=\"ghost\"\n ariaLabel={playing ? 'Pause' : 'Play'}\n disabled={count < 2}\n onClick={() => onPlay(!playing)}\n >\n {playing ? <PauseIcon size={14} /> : <PlayIcon size={14} />}\n </Button>\n <Button\n iconOnly\n size=\"sm\"\n variant=\"ghost\"\n ariaLabel={`Next ${unit.toLowerCase()}`}\n disabled={at >= last}\n onClick={() => onStep(1)}\n >\n <StepForwardIcon size={14} />\n </Button>\n\n {onScrub && (\n <Slider\n className={s.scrub}\n min={0}\n max={last}\n step={1}\n snap=\"strict\"\n density=\"slim\"\n ariaLabel={unit}\n thumbs={[{ value: at, valueText: spoken }]}\n onInput={(next) => onScrub(Math.round(next[0]?.value ?? 0))}\n />\n )}\n\n <span className={s.readout} data-testid=\"jog-readout\">\n {pad(at + 1, count)}/{count}\n </span>\n {caption ? <span className={s.caption}>{caption}</span> : null}\n </div>\n );\n}\n\n/**\n * Figure spaces, not regular ones: the readout updates every beat, and a run from 9 to 10 that\n * widens by a digit shoves whatever follows it along the row. `tabular-nums` fixes the width of\n * a digit and not how many there are, so the padding is what holds the row still.\n */\nfunction pad(value: number, count: number): string {\n return String(value).padStart(String(count).length, ' ');\n}\n"],"mappings":";;;;;;;;;;;;ACgCA,SAAgBQ,EAAI,GAAiB;CACnC,IAAM,EAAE,UAAO,UAAO,YAAS,WAAQ,WAAQ,YAAS,UAAO,QAAQ,YAAS,iBAC9EC,GAEIC,IAAO,KAAK,IAAI,GAAGC,IAAQ,CAAC,GAC5BC,IAAK,KAAK,IAAI,KAAK,IAAIC,GAAO,CAAC,GAAGH,CAAI,GACtCI,IAAS,GAAGC,EAAK,GAAGH,IAAK,EAAE,MAAMD;CAEvC,OACE,kBAAC,OAAD;EAAK,WAAW,CAACX,EAAE,KAAKgB,CAAS,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;EAA3D,UAAA;GACE,kBAACX,GAAD;IACE,UAAA;IACA,MAAK;IACL,SAAQ;IACR,WAAW,YAAYU,EAAK,YAAY;IACxC,UAAUH,KAAM;IAChB,eAAeK,EAAO,EAAE;IAExB,UAAA,kBAAChB,GAAD,EAAc,MAAM,GAAK,CAAA;GACnB,CAAA;GACR,kBAACI,GAAD;IACE,UAAA;IACA,MAAK;IACL,SAAQ;IACR,WAAWa,IAAU,UAAU;IAC/B,UAAUP,IAAQ;IAClB,eAAeQ,EAAO,CAACD,CAAO;IAE7B,UAAUE,EAAVF,IAAWhB,IAA0BC,GAA3B,EAAW,MAAM,GAAK,CAAyB;GACpD,CAAA;GACR,kBAACE,GAAD;IACE,UAAA;IACA,MAAK;IACL,SAAQ;IACR,WAAW,QAAQU,EAAK,YAAY;IACpC,UAAUH,KAAMF;IAChB,eAAeO,EAAO,CAAC;IAEvB,UAAA,kBAACb,GAAD,EAAiB,MAAM,GAAK,CAAA;GACtB,CAAA;GAEPiB,KACC,kBAACf,GAAD;IACE,WAAWN,EAAE;IACb,KAAK;IACL,KAAKU;IACL,MAAM;IACN,MAAK;IACL,SAAQ;IACR,WAAWK;IACX,QAAQ,CAAC;KAAE,OAAOH;KAAI,WAAWE;IAAO,CAAC;IACzC,UAAU,MAASO,EAAQ,KAAK,MAAMC,EAAK,EAAE,EAAE,SAAS,CAAC,CAAC;GAC3D,CAAA;GAGH,kBAAC,QAAD;IAAM,WAAWtB,EAAE;IAAS,eAAY;IAAxC,UAAA;KACGuB,EAAIX,IAAK,GAAGD,CAAK;KAAE;KAAEA;IAClB;;GACLa,IAAU,kBAAC,QAAD;IAAM,WAAWxB,EAAE;IAAU,UAAAwB;GAAc,CAAA,IAAI;EACvD;;AAET;AAOA,SAASD,EAAI,GAAe,GAAuB;CACjD,OAAO,OAAOE,CAAK,CAAC,CAAC,SAAS,OAAOd,CAAK,CAAC,CAAC,QAAQ,GAAG;AACzD"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"NumberField-DLXXgh48.js","names":["NumberField","RACNumberField","f","s","RACInput","RACButton","s","f","field_module_default","NumberField_module_default","NumberField$1","forwardRef","rest","props","NumberField","width","orientation","className","label","Label","Group","ghost","Input","hideSteppers","Button","description","Text","FieldError","errorMessage","clampToBounds","max","min","n","placesOf","text","e","dot","spinKey","by","places","from","bounds","key","FULL_PRECISION","decimal","REPEAT_DELAY_MS","REPEAT_EVERY_MS","UnitField","ariaLabel","draft","setDraft","useState","canceled","useRef","input","known","value","qty","display","read","accepts","parseNumber","typed","parseAs","minValue","maxValue","stepTo","onInput","next","onChange","spin","latestSpin","useEffect","el","onWheel","repeat","stopRepeat","startRepeat","tick","delay","stepper","atBound","glyph","steppers"],"sources":["../../src/components/NumberField/NumberField.module.css","../../src/components/NumberField/NumberField.tsx","../../src/components/spin.ts","../../src/components/NumberField/UnitField.tsx"],"sourcesContent":[".frame {\n display: flex;\n align-items: stretch;\n overflow: hidden;\n}\n\n.frame input {\n padding: 0 var(--wzl-field-pad-x);\n text-align: right;\n -moz-appearance: textfield;\n}\n\n.frame input::-webkit-outer-spin-button,\n.frame input::-webkit-inner-spin-button {\n -webkit-appearance: none;\n margin: 0;\n}\n\n.steppers {\n display: flex;\n flex-direction: column;\n border-left: 1px solid var(--wzl-border);\n}\n\n.stepper {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--wzl-control-h-xs);\n flex: 1;\n /* The two share the frame's height; left to their glyphs' line boxes they\n overflow it, and the frame clips the lower one. */\n min-height: 0;\n height: auto;\n overflow: hidden;\n background: transparent;\n border: none;\n color: var(--wzl-fg-muted);\n cursor: pointer;\n padding: 0;\n font: inherit;\n font-size: min(var(--wzl-font-size-2xs), calc(var(--wzl-field-h, var(--wzl-control-h)) * 0.35));\n line-height: 1;\n}\n\n.stepper:not(:last-child) {\n border-bottom: 1px solid var(--wzl-border);\n}\n\n.stepper[data-hovered]:not([data-disabled]),\n.stepper:hover:not(:disabled):not([data-rac]) {\n background: var(--wzl-surface-hover);\n color: var(--wzl-fg);\n}\n\n.stepper[data-pressed]:not([data-disabled]),\n.stepper:active:not(:disabled):not([data-rac]) {\n background: var(--wzl-surface-pressed);\n}\n\n.stepper[data-disabled],\n.stepper:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n/* Mirrors the property rows' editable readout (`.readoutInput`), so a number\n sitting inside other chrome does not draw a second box around itself. */\n.ghost {\n --_frame-h: auto;\n --_frame-bg: transparent;\n --_frame-border: 1px solid transparent;\n --_frame-fg: var(--wzl-accent-fg);\n --_frame-focus-shadow: none;\n font-weight: var(--wzl-font-weight-medium);\n}\n\n.ghost input {\n padding: 1px 0;\n}\n\n.ghost:hover {\n background: color-mix(in srgb, currentColor 8%, transparent);\n}\n\n.ghost:focus-within {\n background: var(--wzl-surface-sunken);\n}\n\n/* `width='fit'` states the field's width in characters: left alone an `<input>`\n asks for its 20-character intrinsic width, wider than most rows it sits in. */\n.fit .frame {\n width: fit-content;\n max-width: 100%;\n}\n\n.fit .frame input {\n flex: 0 0 auto;\n width: var(--wzl-number-field-width, 9ch);\n}\n","import { forwardRef, type ReactNode, type Ref } from 'react';\nimport {\n NumberField as RACNumberField,\n Label,\n Input as RACInput,\n Group,\n Button as RACButton,\n Text,\n FieldError,\n type NumberFieldProps as RACNumberFieldProps,\n type ValidationResult,\n} from 'react-aria-components';\nimport type { FieldOrientation } from '../Field/Field';\nimport f from '../field.module.css';\nimport s from './NumberField.module.css';\n\n/** Props for {@link NumberField}, on top of React Aria's `NumberField` props. */\nexport type NumberFieldProps = Omit<RACNumberFieldProps, 'children' | 'className'> & {\n label?: ReactNode;\n /**\n * `'stacked'` (the default) puts the label above the field; `'row'` sets it\n * beside the field at its own width, with any description or error on a\n * line below. Same vocabulary as {@link Field}'s `orientation`.\n */\n orientation?: FieldOrientation;\n description?: ReactNode;\n errorMessage?: ReactNode | ((v: ValidationResult) => ReactNode);\n /** Hide the up/down stepper buttons. Defaults to false. */\n hideSteppers?: boolean;\n /** Render with no box until focused — the readout treatment the property\n * rows use, for a value that sits inside other chrome rather than in a form. */\n ghost?: boolean;\n /** Native input placeholder — e.g. `'Mixed'` for a multi-selection\n * editor with no shared value. */\n placeholder?: string;\n /**\n * `'fill'` (the default) takes the width of whatever row the field sits in.\n * `'fit'` sizes it to `--wzl-number-field-width` (`9ch` by default) plus its\n * own chrome, rather than to the input's 20-character intrinsic width.\n */\n width?: 'fill' | 'fit';\n className?: string;\n};\n\n/**\n * Numeric input with stepper buttons, wrapping React Aria's NumberField and\n * supplying the same label / description / errorMessage slots as `Input`.\n * Arrow keys and scroll step the value; parsing and formatting are React\n * Aria's.\n *\n * `ref` forwards to the underlying `<input>`.\n */\nexport const NumberField = forwardRef(function NumberField(\n props: NumberFieldProps,\n ref: Ref<HTMLInputElement>,\n) {\n const {\n label,\n orientation = 'stacked',\n description,\n errorMessage,\n hideSteppers,\n ghost,\n placeholder,\n width = 'fill',\n className,\n ...rest\n } = props;\n return (\n <RACNumberField\n {...rest}\n className={[\n f.field,\n f.control,\n width === 'fit' && `${f.fit} ${s.fit}`,\n orientation === 'row' && f.row,\n className,\n ]\n .filter(Boolean)\n .join(' ')}\n >\n {label !== undefined && <Label className={f.label}>{label}</Label>}\n <Group className={[f.frame, s.frame, ghost && s.ghost].filter(Boolean).join(' ')}>\n <RACInput ref={ref} placeholder={placeholder} />\n {!hideSteppers && (\n <div className={s.steppers}>\n <RACButton slot=\"increment\" className={s.stepper} aria-label=\"Increment\">▲</RACButton>\n <RACButton slot=\"decrement\" className={s.stepper} aria-label=\"Decrement\">▼</RACButton>\n </div>\n )}\n </Group>\n {description !== undefined && (\n <Text slot=\"description\" className={`${f.hint} ${f.below}`}>\n {description}\n </Text>\n )}\n <FieldError className={`${f.error} ${f.below}`}>{errorMessage}</FieldError>\n </RACNumberField>\n );\n});\n","/** The bounds a spin key moves a value within. */\nexport interface SpinBounds {\n step: number;\n min?: number;\n max?: number;\n}\n\n/** `n` held within the bounds. */\nexport function clampToBounds(n: number, { min, max }: SpinBounds): number {\n return Math.min(max ?? Infinity, Math.max(min ?? -Infinity, n));\n}\n\n/** Decimal places `n` is written with, up to ten. */\nfunction placesOf(n: number): number {\n if (!Number.isFinite(n)) return 0;\n const text = String(n);\n const e = text.indexOf('e-');\n if (e >= 0) return Math.min(10, Number(text.slice(e + 2)));\n const dot = text.indexOf('.');\n return dot < 0 ? 0 : Math.min(10, text.length - dot - 1);\n}\n\n/**\n * The value a spin-button key moves `from` to, or `null` for a key that does\n * not spin: the arrows by a step, Page Up and Page Down by ten, and Home and\n * End to a bound, where there is one. A sum of steps is rounded to the places\n * its operands carry, so `0.1 + 0.2` lands on `0.3`.\n */\nexport function spinKey(key: string, from: number, bounds: SpinBounds): number | null {\n const by = (n: number) => {\n const places = Math.max(placesOf(from), placesOf(bounds.step));\n return clampToBounds(Number((from + n * bounds.step).toFixed(places)), bounds);\n };\n switch (key) {\n case 'ArrowUp':\n return by(1);\n case 'ArrowDown':\n return by(-1);\n case 'PageUp':\n return by(10);\n case 'PageDown':\n return by(-10);\n case 'Home':\n return bounds.min ?? null;\n case 'End':\n return bounds.max ?? null;\n default:\n return null;\n }\n}\n","import { useEffect, useRef, useState } from 'react';\nimport { decimal, parseAs, parseNumber, qty, type Display, type UnitTable } from '@weasel-js/quantity';\nimport { clampToBounds, spinKey } from '../spin';\nimport f from '../field.module.css';\nimport s from './NumberField.module.css';\n\n/** Props for {@link UnitField}. */\nexport interface UnitFieldProps {\n /** The value in the unit the field shows. NaN shows an empty field. */\n value: number;\n /** The committed value, read and clamped, in the unit the field shows: on\n * blur or Enter, and on every step. */\n onChange: (value: number) => void;\n /** The live value: every keystroke that reads as a number, and every step. */\n onInput?: (value: number) => void;\n minValue?: number;\n maxValue?: number;\n /** What an arrow key, a stepper or a wheel notch adds or takes away; Page Up\n * and Page Down move ten. Defaults to 1. */\n step?: number;\n /** Units a person may type, each mapped to the factor that turns a number\n * in it into the unit the field shows: `{ mm: 0.1, cm: 1 }`. */\n accepts?: Readonly<UnitTable>;\n /** How the value shows, is spoken, and reads back when typed: `fraction()`\n * shows `1/12` and reads `1/12` typed. `accepts`, when given, does the\n * reading instead. Default: the number with every decimal it has. */\n display?: Display;\n placeholder?: string;\n /** Up and down buttons beside the value, which repeat while held. */\n steppers?: boolean;\n /** Render with no box until focused, as {@link NumberField}'s `ghost` does. */\n ghost?: boolean;\n /** As {@link NumberField}'s `width`. */\n width?: 'fill' | 'fit';\n className?: string;\n /** Id for the input, for a `<label htmlFor>` outside it. */\n id?: string;\n 'aria-label'?: string;\n}\n\nconst FULL_PRECISION = decimal({ maxPlaces: 20, grouping: false });\n\nconst REPEAT_DELAY_MS = 400;\nconst REPEAT_EVERY_MS = 60;\n\n/**\n * A number typed as text, so it can carry a unit: `12mm` in a field showing\n * centimeters commits `1.2`. Commits on blur or Enter, reverts on Escape or on\n * text it cannot read, and clamps to its bounds. Steps like a spin button —\n * arrows, Page Up and Down, Home and End to a bound, the wheel while focused,\n * and optional steppers. React Aria's `NumberField` refuses letters as they\n * are typed, which is why this is not that.\n */\nexport function UnitField({\n value,\n onChange,\n onInput,\n minValue,\n maxValue,\n step = 1,\n accepts,\n display = FULL_PRECISION,\n placeholder,\n steppers,\n ghost,\n width = 'fill',\n className,\n id,\n 'aria-label': ariaLabel,\n}: UnitFieldProps) {\n const [draft, setDraft] = useState<string | null>(null);\n const canceled = useRef(false);\n const input = useRef<HTMLInputElement>(null);\n const known = Number.isFinite(value);\n const text = known ? qty(value, display).text : '';\n const read = (typed: string) => (accepts ? parseNumber(typed, accepts) : parseAs(typed, display));\n const bounds = { step, min: minValue, max: maxValue };\n\n // What a step starts from: the text as typed where it reads, else the value.\n const from = (): number => {\n const typed = draft === null ? value : read(draft);\n return Number.isFinite(typed) ? typed : Number.isFinite(value) ? value : 0;\n };\n const stepTo = (next: number) => {\n setDraft(null);\n onInput?.(next);\n if (next !== value) onChange(next);\n };\n const spin = (key: string): boolean => {\n const next = spinKey(key, from(), bounds);\n if (next === null) return false;\n stepTo(next);\n return true;\n };\n\n // The latest `spin`, for the listeners below, which outlive a render.\n const latestSpin = useRef(spin);\n useEffect(() => {\n latestSpin.current = spin;\n });\n\n // The wheel steps only a focused field, so scrolling past one changes nothing.\n // Not passive, so it can keep the page from scrolling under it.\n useEffect(() => {\n const el = input.current;\n if (!el) return;\n const onWheel = (e: WheelEvent) => {\n if (document.activeElement !== el || e.deltaY === 0) return;\n e.preventDefault();\n latestSpin.current(e.deltaY < 0 ? 'ArrowUp' : 'ArrowDown');\n };\n el.addEventListener('wheel', onWheel, { passive: false });\n return () => el.removeEventListener('wheel', onWheel);\n }, []);\n\n const repeat = useRef<ReturnType<typeof setTimeout> | null>(null);\n const stopRepeat = () => {\n if (repeat.current !== null) clearTimeout(repeat.current);\n repeat.current = null;\n };\n useEffect(() => stopRepeat, []);\n const startRepeat = (key: 'ArrowUp' | 'ArrowDown') => {\n stopRepeat();\n latestSpin.current(key);\n const tick = (delay: number) => {\n repeat.current = setTimeout(() => {\n latestSpin.current(key);\n tick(REPEAT_EVERY_MS);\n }, delay);\n };\n tick(REPEAT_DELAY_MS);\n };\n const stepper = (key: 'ArrowUp' | 'ArrowDown', label: string, glyph: string, atBound: boolean) => (\n <button\n type=\"button\"\n className={s.stepper}\n aria-label={label}\n tabIndex={-1}\n disabled={atBound}\n // Keeps focus in the input, where a press on the button would take it.\n onPointerDown={(e) => {\n e.preventDefault();\n startRepeat(key);\n }}\n onPointerUp={stopRepeat}\n onPointerLeave={stopRepeat}\n onPointerCancel={stopRepeat}\n // A click with no pointer down before it — assistive tech, or a test.\n onClick={(e) => {\n if (e.detail === 0) latestSpin.current(key);\n }}\n >\n {glyph}\n </button>\n );\n\n return (\n <div\n className={[f.field, f.control, width === 'fit' && `${f.fit} ${s.fit}`, className]\n .filter(Boolean)\n .join(' ')}\n >\n <div className={[f.frame, s.frame, ghost && s.ghost].filter(Boolean).join(' ')}>\n <input\n ref={input}\n id={id}\n type=\"text\"\n role=\"spinbutton\"\n inputMode=\"decimal\"\n autoComplete=\"off\"\n aria-label={ariaLabel}\n aria-valuenow={known ? value : undefined}\n aria-valuetext={known ? qty(value, display).spoken : undefined}\n aria-valuemin={minValue}\n aria-valuemax={maxValue}\n placeholder={placeholder}\n value={draft ?? text}\n onChange={(e) => {\n const typed = e.target.value;\n setDraft(typed);\n const n = read(typed);\n if (onInput && typed.trim() !== '' && Number.isFinite(n)) onInput(clampToBounds(n, bounds));\n }}\n onFocus={() => {\n canceled.current = false;\n }}\n onBlur={() => {\n if (draft !== null && !canceled.current) {\n const n = read(draft);\n if (Number.isFinite(n)) {\n const next = clampToBounds(n, bounds);\n if (next !== value) onChange(next);\n }\n }\n setDraft(null);\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n e.currentTarget.blur();\n } else if (e.key === 'Escape') {\n canceled.current = true;\n e.currentTarget.blur();\n } else if (spin(e.key)) {\n e.preventDefault();\n }\n }}\n />\n {steppers && (\n <div className={s.steppers}>\n {stepper('ArrowUp', 'Increment', '▲', known && maxValue !== undefined && value >= maxValue)}\n {stepper('ArrowDown', 'Decrement', '▼', known && minValue !== undefined && value <= minValue)}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;GCoDaA,IAAcW,EAAW,SACpC,GACA,GACA;CACA,IAAM,EACJ,UACA,iBAAc,WACd,gBACA,iBACA,iBACA,UACA,gBACA,WAAQ,QACR,cACA,GAAGC,MACDC;CACJ,OACE,kBAACZ,GAAD;EACE,GAAIW;EACJ,WAAW;GACTV,EAAE;GACFA,EAAE;GACFa,MAAU,SAAS,GAAGb,EAAE,IAAI,GAAGC,EAAE;GACjCa,MAAgB,SAASd,EAAE;GAC3Be;EACF,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;EAVb,UAAA;GAYGC,MAAU,KAAA,KAAa,kBAACC,GAAD;IAAO,WAAWjB,EAAE;IAAQ,UAAAgB;GAAa,CAAA;GACjE,kBAACE,GAAD;IAAO,WAAW;KAAClB,EAAE;KAAOC,EAAE;KAAOkB,KAASlB,EAAE;IAAK,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;IAA/E,UAAA,CACE,kBAACC,GAAD;KAAe;KAAkB;IAAc,CAAA,GAC9C,CAACmB,KACA,kBAAC,OAAD;KAAK,WAAWpB,EAAE;KAAlB,UAAA,CACE,kBAACE,GAAD;MAAW,MAAK;MAAY,WAAWF,EAAE;MAAS,cAAW;MAAY,UAAA;KAAY,CAAA,GACrF,kBAACE,GAAD;MAAW,MAAK;MAAY,WAAWF,EAAE;MAAS,cAAW;MAAY,UAAA;KAAY,CAAA,CAClF;IAEF,CAAA,CAAA;;GACNsB,MAAgB,KAAA,KACf,kBAACC,GAAD;IAAM,MAAK;IAAc,WAAW,GAAGxB,EAAE,KAAK,GAAGA,EAAE;IAChD,UAAAuB;GACG,CAAA;GAER,kBAACE,GAAD;IAAY,WAAW,GAAGzB,EAAE,MAAM,GAAGA,EAAE;IAAU,UAAA0B;GAAyB,CAAA;EAC5D;;AAEpB,CAAC;;;AC3FD,SAAgBC,EAAc,GAAW,EAAE,QAAK,UAA2B;CACzE,OAAO,KAAK,IAAIC,KAAO,UAAU,KAAK,IAAIC,KAAO,WAAWC,CAAC,CAAC;AAChE;AAGA,SAASC,EAAS,GAAmB;CACnC,IAAI,CAAC,OAAO,SAASD,CAAC,GAAG,OAAO;CAChC,IAAME,IAAO,OAAOF,CAAC,GACfG,IAAID,EAAK,QAAQ,IAAI;CAC3B,IAAIC,KAAK,GAAG,OAAO,KAAK,IAAI,IAAI,OAAOD,EAAK,MAAMC,IAAI,CAAC,CAAC,CAAC;CACzD,IAAMC,IAAMF,EAAK,QAAQ,GAAG;CAC5B,OAAOE,IAAM,IAAI,IAAI,KAAK,IAAI,IAAIF,EAAK,SAASE,IAAM,CAAC;AACzD;AAQA,SAAgBC,EAAQ,GAAa,GAAc,GAAmC;CACpF,IAAMC,KAAM,MAAc;EACxB,IAAMC,IAAS,KAAK,IAAIN,EAASO,CAAI,GAAGP,EAASQ,EAAO,IAAI,CAAC;EAC7D,OAAOZ,EAAc,QAAQW,IAAOR,IAAIS,EAAO,KAAA,CAAM,QAAQF,CAAM,CAAC,GAAGE,CAAM;CAC/E;CACA,QAAQC,GAAR;EACE,KAAK,WACH,OAAOJ,EAAG,CAAC;EACb,KAAK,aACH,OAAOA,EAAG,EAAE;EACd,KAAK,UACH,OAAOA,EAAG,EAAE;EACd,KAAK,YACH,OAAOA,EAAG,GAAG;EACf,KAAK,QACH,OAAOG,EAAO,OAAO;EACvB,KAAK,OACH,OAAOA,EAAO,OAAO;EACvB,SACE,OAAO;CACX;AACF;;;ACTA,IAAME,IAAiBC,EAAQ;CAAE,WAAW;CAAI,UAAU;AAAM,CAAC,GAE3DC,IAAkB,KAClBC,IAAkB;AAUxB,SAAgBC,EAAU,EACxB,UACA,aACA,YACA,aACA,aACA,UAAO,GACP,YACA,aAAUJ,GACV,gBACA,aACA,UACA,WAAQ,QACR,cACA,OACA,cAAcK,KACG;CACjB,IAAM,CAACC,GAAOC,KAAYC,EAAwB,IAAI,GAChDC,IAAWC,EAAO,EAAK,GACvBC,IAAQD,EAAyB,IAAI,GACrCE,IAAQ,OAAO,SAASC,CAAK,GAC7BtB,IAAOqB,IAAQE,EAAID,GAAOE,CAAO,CAAC,CAAC,OAAO,IAC1CC,KAAQ,MAAmBC,IAAUC,EAAYC,GAAOF,CAAO,IAAIG,EAAQD,GAAOJ,CAAO,GACzFjB,IAAS;EAAE;EAAM,KAAKuB;EAAU,KAAKC;CAAS,GAG9CzB,UAAqB;EACzB,IAAMsB,IAAQb,MAAU,OAAOO,IAAQG,EAAKV,CAAK;EACjD,OAAO,OAAO,SAASa,CAAK,IAAIA,IAAQ,OAAO,SAASN,CAAK,IAAIA,IAAQ;CAC3E,GACMU,KAAU,MAAiB;EAG/B,AAFAhB,EAAS,IAAI,GACbiB,IAAUC,CAAI,GACVA,MAASZ,KAAOa,EAASD,CAAI;CACnC,GACME,KAAQ,MAAyB;EACrC,IAAMF,IAAO/B,EAAQK,GAAKF,EAAK,GAAGC,CAAM;EAGxC,OAFI2B,MAAS,SACbF,EAAOE,CAAI,GACJ;CACT,GAGMG,IAAalB,EAAOiB,CAAI;CAO9B,AANAE,QAAgB;EACd,EAAW,UAAUF;CACvB,CAAC,GAIDE,QAAgB;EACd,IAAMC,IAAKnB,EAAM;EACjB,IAAI,CAACmB,GAAI;EACT,IAAMC,KAAW,MAAkB;GACjC,AAAI,SAAS,kBAAkBD,KAAMtC,EAAE,WAAW,MAClDA,EAAE,eAAe,GACjBoC,EAAW,QAAQpC,EAAE,SAAS,IAAI,YAAY,WAAW;EAC3D;EAEA,OADAsC,EAAG,iBAAiB,SAASC,GAAS,EAAE,SAAS,GAAM,CAAC,SAC3CD,EAAG,oBAAoB,SAASC,CAAO;CACtD,GAAG,CAAC,CAAC;CAEL,IAAMC,IAAStB,EAA6C,IAAI,GAC1DuB,UAAmB;EAEvB,AADID,EAAO,YAAY,QAAM,aAAaA,EAAO,OAAO,GACxD,EAAO,UAAU;CACnB;CACA,QAAgBC,GAAY,CAAC,CAAC;CAC9B,IAAMC,KAAe,MAAiC;EAEpD,AADAD,EAAW,GACXL,EAAW,QAAQ7B,CAAG;EACtB,IAAMoC,KAAQ,MAAkB;GAC9B,EAAO,UAAU,iBAAiB;IAEhC,AADAP,EAAW,QAAQ7B,CAAG,GACtBoC,EAAKhC,CAAe;GACtB,GAAGiC,CAAK;EACV;EACA,EAAKlC,CAAe;CACtB,GACMmC,KAAW,GAA8B,GAAe,GAAe,MAC3E,kBAAC,UAAD;EACE,MAAK;EACL,WAAW1E,EAAE;EACb,cAAYY;EACZ,UAAU;EACV,UAAU+D;EAEV,gBAAgB,MAAM;GAEpB,AADA9C,EAAE,eAAe,GACjB0C,EAAYnC,CAAG;EACjB;EACA,aAAakC;EACb,gBAAgBA;EAChB,iBAAiBA;EAEjB,UAAU,MAAM;GACd,AAAIzC,EAAE,WAAW,KAAGoC,EAAW,QAAQ7B,CAAG;EAC5C;EAEC,UAAAwC;CACK,CAAA;CAGV,OACE,kBAAC,OAAD;EACE,WAAW;GAAC3E,EAAE;GAAOA,EAAE;GAASQ,MAAU,SAAS,GAAGR,EAAE,IAAI,GAAGD,EAAE;GAAOW;EAAS,CAAC,CAC/E,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;EAEX,UAAA,kBAAC,OAAD;GAAK,WAAW;IAACV,EAAE;IAAOD,EAAE;IAAOe,KAASf,EAAE;GAAK,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;GAA7E,UAAA,CACE,kBAAC,SAAD;IACE,KAAKgD;IACD;IACJ,MAAK;IACL,MAAK;IACL,WAAU;IACV,cAAa;IACb,cAAYN;IACZ,iBAAeO,IAAQC,IAAQ,KAAA;IAC/B,kBAAgBD,IAAQE,EAAID,GAAOE,CAAO,CAAC,CAAC,SAAS,KAAA;IACrD,iBAAeM;IACf,iBAAeC;IACF;IACb,OAAOhB,KAASf;IAChB,WAAW,MAAM;KACf,IAAM4B,IAAQ,EAAE,OAAO;KACvB,EAASA,CAAK;KACd,IAAM,IAAIH,EAAKG,CAAK;KACpB,AAAIK,KAAWL,EAAM,KAAK,MAAM,MAAM,OAAO,SAAS,CAAC,KAAGK,EAAQtC,EAAc,GAAGY,CAAM,CAAC;IAC5F;IACA,eAAe;KACb,EAAS,UAAU;IACrB;IACA,cAAc;KACZ,IAAIQ,MAAU,QAAQ,CAACG,EAAS,SAAS;MACvC,IAAMpB,IAAI2B,EAAKV,CAAK;MACpB,IAAI,OAAO,SAASjB,CAAC,GAAG;OACtB,IAAMoC,IAAOvC,EAAcG,GAAGS,CAAM;OACpC,AAAI2B,MAASZ,KAAOa,EAASD,CAAI;MACnC;KACF;KACA,EAAS,IAAI;IACf;IACA,YAAY,MAAM;KAChB,AAAI,EAAE,QAAQ,UACZ,EAAE,cAAc,KAAK,IACZ,EAAE,QAAQ,YACnB,EAAS,UAAU,IACnB,EAAE,cAAc,KAAK,KACZE,EAAK,EAAE,GAAG,KACnB,EAAE,eAAe;IAErB;GACD,CAAA,GACAa,KACC,kBAAC,OAAD;IAAK,WAAW7E,EAAE;IAAlB,UAAA,CACG0E,EAAQ,WAAW,aAAa,KAAKzB,KAASU,MAAa,KAAA,KAAaT,KAASS,CAAQ,GACzFe,EAAQ,aAAa,aAAa,KAAKzB,KAASS,MAAa,KAAA,KAAaR,KAASQ,CAAQ,CACzF;GAEJ,CAAA,CAAA;;CACF,CAAA;AAET"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Plot2D-BHNbgCAl.js","names":["s","dlog","modelToPlot","xFrac","p","m","yFrac","plot","plotToModel","MAX_DECIMALS","niceStep","mag","rough","norm","stepDecimals","step","tickDecimals","most","v","values","d","niceTicks","none","lo","range","hi","span","options","pixels","minSpacing","unitsPerPx","decimals","from","inset","to","first","last","i","formatTick","text","value","ctx","Plot2D_module_default","TICK_SPACING_PX","TICK_INSET_PX","LABEL_GAP_PX","resolveTicks","settings","eps","gaps","even","g","set","axis","format","toPlot","Plot2D","forwardRef","props","svgRef","useRef","modelRange","useMemo","xRange","yRange","plotSize","width","height","clientToPlot","useCallback","rect","left","top","useEffect","useImperativeHandle","ref","pt","handlePointerDown","onPointerDown","model","xTickList","xTicks","yTickList","yTicks","yInside","yLabelsRef","yLabelWidth","setYLabelWidth","useState","yLabelKey","t","measure","widest","el","prev","live","cls","className","renderTickLines","list","renderTickLabels","placement","outside","gap","place","grid","divisions","stroke","fractions","f","axes","children"],"sources":["../../src/components/Plot2D/geometry.ts","../../src/components/Plot2D/ticks.ts","../../src/components/Plot2D/Plot2D.module.css","../../src/components/Plot2D/Plot2D.tsx"],"sourcesContent":["/**\n * Coordinate transforms (model ↔ plot) for Plot2D. Pure / deterministic —\n * no DOM, no React, no state.\n *\n * Model space: caller-defined xRange × yRange. Y axis goes UP.\n * Plot space: 0..width × 0..height in CSS pixels. Y axis goes DOWN (SVG).\n */\n\nexport interface Point {\n x: number;\n y: number;\n}\n\n/**\n * The caller-defined extent of model space, the coordinate system a plot's\n * data lives in.\n */\nexport interface ModelRange {\n xMin: number;\n xMax: number;\n yMin: number;\n yMax: number;\n}\n\n/** The plot's drawing area in CSS pixels. */\nexport interface PlotSize {\n width: number;\n height: number;\n}\n\n/**\n * Maps a model-space point into plot space, flipping the y axis so model y\n * grows upward while SVG y grows downward.\n */\nexport function modelToPlot(p: Point, m: ModelRange, plot: PlotSize): Point {\n const xFrac = (p.x - m.xMin) / (m.xMax - m.xMin);\n const yFrac = (p.y - m.yMin) / (m.yMax - m.yMin);\n return {\n x: xFrac * plot.width,\n // Invert y so model y=0 sits at plot bottom (SVG y grows downward).\n y: (1 - yFrac) * plot.height,\n };\n}\n\n/** The inverse of {@link modelToPlot}. */\nexport function plotToModel(p: Point, m: ModelRange, plot: PlotSize): Point {\n const xFrac = p.x / plot.width;\n const yFrac = 1 - p.y / plot.height;\n return {\n x: m.xMin + xFrac * (m.xMax - m.xMin),\n y: m.yMin + yFrac * (m.yMax - m.yMin),\n };\n}\n","/**\n * Tick placement for a numeric axis. Pure — no DOM, no React.\n */\n\n/** What a tick label formatter knows about the column it is part of. */\nexport interface TickFormatCtx {\n /** Places after the point that every label in the column shares. */\n decimals: number;\n /** Distance between neighboring ticks, or 0 when the ticks were given\n * explicitly and are not evenly spaced. */\n step: number;\n}\n\n/** Turns a tick value into its label. */\nexport type TickFormatter = (value: number, ctx: TickFormatCtx) => string;\n\n/** A column of ticks and the facts its labels share. */\nexport interface TickSet {\n values: number[];\n step: number;\n decimals: number;\n}\n\n/** Options for {@link niceTicks}. */\nexport interface NiceTickOptions {\n /** Smallest gap between neighboring ticks, in px. */\n minSpacing: number;\n /** Ticks closer than this to either end of the axis are dropped, in px.\n * Default 0. */\n inset?: number;\n}\n\nconst MAX_DECIMALS = 20;\n\n/** The smallest of 1, 2 or 5 times a power of ten that is at least `rough`. */\nexport function niceStep(rough: number): number {\n const mag = Math.pow(10, Math.floor(Math.log10(rough)));\n const norm = rough / mag;\n const step = norm <= 1 ? 1 : norm <= 2 ? 2 : norm <= 5 ? 5 : 10;\n return step * mag;\n}\n\n/** Places after the point a multiple of `step` needs, for a 1/2/5 step. */\nexport function stepDecimals(step: number): number {\n if (!(step > 0) || !Number.isFinite(step)) return 0;\n return Math.min(MAX_DECIMALS, Math.max(0, -Math.floor(Math.log10(step) + 1e-9)));\n}\n\n/** The most places after the point any of `values` needs. */\nexport function tickDecimals(values: readonly number[]): number {\n let most = 0;\n for (const v of values) {\n if (!Number.isFinite(v)) continue;\n let d = 0;\n while (d < MAX_DECIMALS && Math.abs(Number(v.toFixed(d)) - v) > 1e-9 * Math.max(1, Math.abs(v))) d++;\n most = Math.max(most, d);\n }\n return most;\n}\n\n/**\n * Ticks at round values across `range`, drawn `pixels` long: the finest\n * 1/2/5 step that keeps neighbors `minSpacing` px apart, so a taller plot\n * gets more of them.\n */\nexport function niceTicks(\n range: readonly [number, number],\n pixels: number,\n options: NiceTickOptions,\n): TickSet {\n const none: TickSet = { values: [], step: 0, decimals: 0 };\n const lo = Math.min(range[0], range[1]);\n const hi = Math.max(range[0], range[1]);\n const span = hi - lo;\n const { minSpacing, inset = 0 } = options;\n if (!(span > 0) || !Number.isFinite(span) || !(pixels > 0) || !(minSpacing > 0)) return none;\n\n const unitsPerPx = span / pixels;\n const step = niceStep(minSpacing * unitsPerPx);\n const decimals = stepDecimals(step);\n const from = lo + inset * unitsPerPx;\n const to = hi - inset * unitsPerPx;\n // A tolerance in step units, so a tick sitting exactly on a bound survives\n // the float error in `from / step`.\n const first = Math.ceil(from / step - 1e-9);\n const last = Math.floor(to / step + 1e-9);\n const values: number[] = [];\n for (let i = first; i <= last; i++) values.push(Number((i * step).toFixed(decimals)) + 0);\n return { values, step, decimals };\n}\n\n/** The default label: `value` fixed to the column's shared decimals, with\n * no negative zero. */\nexport function formatTick(value: number, ctx: TickFormatCtx): string {\n const text = value.toFixed(ctx.decimals);\n return /^-0(\\.0*)?$/.test(text) ? text.slice(1) : text;\n}\n",".root {\n /* Component-local vars layered over theme tokens. Consumers override\n * either the plot-* var (component scope) or the wzl-* token (app\n * scope). */\n --plot-bg: var(--wzl-surface-sunken);\n --plot-grid: var(--wzl-line);\n /* Axes get full foreground contrast — they're the plot frame, the\n * most prominent structural element after caller-rendered content. */\n --plot-axis: var(--wzl-fg);\n --plot-tick: var(--wzl-line-subtle);\n --plot-tick-label: var(--wzl-fg-muted);\n /* Knocks the tick line out from behind an inside label. */\n --plot-tick-label-halo: var(--plot-bg);\n\n display: block;\n /* Caller-rendered marks at the very edge (e.g. CurveEditor anchors)\n * extend past the SVG box by their radius — let them paint over the\n * host page instead of being half-clipped. */\n overflow: visible;\n background: var(--plot-bg);\n user-select: none;\n touch-action: none;\n outline: none;\n}\n\n/* Keyboard-only focus ring. Caller opts in via `tabIndex`. */\n.root:focus-visible {\n outline: 2px solid var(--wzl-focus-ring);\n outline-offset: 2px;\n}\n\n.grid {\n stroke: var(--plot-grid);\n stroke-width: 1;\n fill: none;\n}\n\n.axis {\n stroke: var(--plot-axis);\n stroke-width: var(--wzl-line-width);\n fill: none;\n}\n\n.tick {\n stroke: var(--plot-tick);\n stroke-width: 1;\n fill: none;\n}\n\n.tickLabels { pointer-events: none; }\n\n.tickLabel {\n fill: var(--plot-tick-label);\n font-size: var(--wzl-font-size-xs);\n font-variant-numeric: tabular-nums;\n font-family: var(--wzl-font-numeric);\n}\n\n.tickLabel[data-placement='inside'] {\n stroke: var(--plot-tick-label-halo);\n stroke-width: 3px;\n stroke-linejoin: round;\n paint-order: stroke;\n}\n","import {\n forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState,\n type CSSProperties,\n type KeyboardEvent as ReactKeyboardEvent,\n type PointerEvent as ReactPointerEvent,\n type ReactNode,\n} from 'react';\nimport {\n modelToPlot, plotToModel,\n type ModelRange, type Point,\n} from './geometry';\nimport {\n formatTick, niceTicks, tickDecimals,\n type TickFormatCtx, type TickFormatter,\n} from './ticks';\nimport s from './Plot2D.module.css';\nimport { dlog } from '../../dlog';\n\n/** Background grid configuration for a {@link Plot2D}. */\nexport interface GridSettings {\n /** Number of evenly-spaced internal grid lines per axis (excluding\n * the edges). Applied to both x and y. Default 3. */\n divisions?: number;\n /** Stroke color override. When omitted, uses `var(--plot-grid)`. */\n color?: string;\n}\n\n/** Axis-line configuration for a {@link Plot2D}. */\nexport interface AxesSettings {\n /** Stroke color override. When omitted, uses `var(--plot-axis)`. */\n color?: string;\n}\n\n/**\n * Ticks along one axis of a {@link Plot2D}: a line across the plot at each\n * tick value, and a label naming it. Every label in a column is formatted\n * to the same number of decimal places.\n */\nexport interface TickSettings {\n /** Tick values in model space; any outside the range are skipped.\n * Omitted = round 1/2/5 steps, as many as `minSpacing` allows at the\n * plot's current size. */\n values?: readonly number[];\n /** Smallest gap between generated ticks, in px. Default 24 on y, 64 on x. */\n minSpacing?: number;\n /** A generated tick nearer either end of the axis than this is dropped,\n * so its label stays on the plot, in px. Default 4 on y, 16 on x. */\n inset?: number;\n /** Draw a line across the plot at each tick. Default true. */\n lines?: boolean;\n /** Where labels sit: `'inside'` the plot along its left (y) or bottom (x)\n * edge, `'outside'` it past that edge in space the caller leaves free,\n * or `false` for none. Default `'inside'`. */\n labels?: 'inside' | 'outside' | false;\n /** Space between a label and the plot edge it sits against, in px.\n * Default 4. */\n gap?: number;\n /** Label text for a tick. Default {@link formatTick}. */\n format?: TickFormatter;\n}\n\n/** A pointer position given in both of the plot's coordinate systems. */\nexport interface Plot2DCoords {\n plot: Point;\n model: Point;\n}\n\n/** Props for {@link Plot2D}. */\nexport interface Plot2DProps {\n width: number;\n height: number;\n /** Model-space x range. Default [0, 1]. */\n xRange?: readonly [number, number];\n /** Model-space y range. Default [0, 1]. */\n yRange?: readonly [number, number];\n /** Background grid. `false` / `null` / omitted = no grid. Pass `{}`\n * for default (3 divisions per axis) or populated GridSettings. */\n grid?: GridSettings | false | null;\n /** Axis lines. `false` / `null` = no axes. Omitted = default-styled\n * axes (on). Pass AxesSettings to customize. */\n axes?: AxesSettings | false | null;\n /** Ticks at x values. `false` / `null` / omitted = none; `{}` = defaults. */\n xTicks?: TickSettings | false | null;\n /** Ticks at y values. `false` / `null` / omitted = none; `{}` = defaults. */\n yTicks?: TickSettings | false | null;\n /** Forwarded to the underlying svg. Consumer opts into focus this way. */\n tabIndex?: number;\n /** Default `'img'`. A plot holding focusable marks must not be an image,\n * whose children are presentational to assistive technology. */\n role?: string;\n 'aria-label'?: string;\n className?: string;\n style?: CSSProperties;\n /** Pointer down on the SVG. Receives both plot- and model-space coords\n * pre-computed so consumers don't repeat the rect/transform dance. */\n onPointerDown?: (e: ReactPointerEvent<SVGSVGElement>, coords: Plot2DCoords) => void;\n onKeyDown?: (e: ReactKeyboardEvent<SVGSVGElement>) => void;\n children?: ReactNode;\n}\n\n/**\n * Imperative handle on a {@link Plot2D}: the SVG element, its size, and the\n * coordinate conversions, including ones that start from a raw DOM event so a\n * drag tracked on `window` can still map back into the plot.\n */\nexport interface Plot2DHandle {\n readonly svg: SVGSVGElement | null;\n plotToModel(pt: Point): Point;\n modelToPlot(pt: Point): Point;\n /** Convert a DOM event's clientX/clientY (e.g. from a window-attached\n * pointermove during drag) to plot-space. */\n clientToPlot(e: { clientX: number; clientY: number }): Point;\n /** Convenience: clientToPlot → plotToModel. */\n clientToModel(e: { clientX: number; clientY: number }): Point;\n readonly width: number;\n readonly height: number;\n}\n\ntype Axis = 'x' | 'y';\n\nconst TICK_SPACING_PX: Record<Axis, number> = { x: 64, y: 24 };\nconst TICK_INSET_PX: Record<Axis, number> = { x: 16, y: 4 };\nconst LABEL_GAP_PX = 4;\n\ninterface ResolvedTick {\n value: number;\n /** Plot-space position along the axis. */\n at: number;\n label: string;\n}\n\nfunction resolveTicks(\n settings: TickSettings,\n axis: Axis,\n range: readonly [number, number],\n pixels: number,\n toPlot: (v: number) => number,\n): ResolvedTick[] {\n let values: number[];\n let ctx: TickFormatCtx;\n if (settings.values) {\n const lo = Math.min(range[0], range[1]);\n const hi = Math.max(range[0], range[1]);\n const eps = (hi - lo) * 1e-9;\n values = settings.values.filter((v) => v >= lo - eps && v <= hi + eps);\n const gaps = values.slice(1).map((v, i) => v - values[i]);\n const even = gaps.length > 0 && gaps.every((g) => Math.abs(g - gaps[0]) <= Math.abs(gaps[0]) * 1e-9);\n ctx = { decimals: tickDecimals(values), step: even ? gaps[0] : 0 };\n } else {\n const set = niceTicks(range, pixels, {\n minSpacing: settings.minSpacing ?? TICK_SPACING_PX[axis],\n inset: settings.inset ?? TICK_INSET_PX[axis],\n });\n values = set.values;\n ctx = { decimals: set.decimals, step: set.step };\n }\n const format = settings.format ?? formatTick;\n return values.map((value) => ({ value, at: toPlot(value), label: format(value, ctx) }));\n}\n\n/**\n * An SVG plotting surface with an optional grid, axes and ticks. It draws the\n * frame and owns the model-space to plot-space mapping; the plotted content is\n * whatever children are passed, positioned in plot space.\n */\nexport const Plot2D = forwardRef<Plot2DHandle, Plot2DProps>(function Plot2D(props, ref) {\n const {\n width, height,\n xRange = [0, 1],\n yRange = [0, 1],\n grid,\n axes,\n xTicks,\n yTicks,\n tabIndex,\n role = 'img',\n className,\n style,\n onPointerDown,\n onKeyDown,\n children,\n } = props;\n\n const svgRef = useRef<SVGSVGElement | null>(null);\n\n const modelRange: ModelRange = useMemo(\n () => ({ xMin: xRange[0], xMax: xRange[1], yMin: yRange[0], yMax: yRange[1] }),\n [xRange, yRange],\n );\n\n const plotSize = useMemo(() => ({ width, height }), [width, height]);\n\n const clientToPlot = useCallback((e: { clientX: number; clientY: number }): Point => {\n const rect = svgRef.current?.getBoundingClientRect();\n const left = rect?.left ?? 0;\n const top = rect?.top ?? 0;\n return { x: e.clientX - left, y: e.clientY - top };\n }, []);\n\n useEffect(() => {\n dlog('plot2d', 'mount', { width, height });\n return () => dlog('plot2d', 'unmount');\n // Mount/unmount trace: re-running on a resize would log a spurious mount.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n useImperativeHandle(ref, () => ({\n get svg() { return svgRef.current; },\n plotToModel: (pt: Point) => plotToModel(pt, modelRange, plotSize),\n modelToPlot: (pt: Point) => modelToPlot(pt, modelRange, plotSize),\n clientToPlot,\n clientToModel: (e) => plotToModel(clientToPlot(e), modelRange, plotSize),\n get width() { return plotSize.width; },\n get height() { return plotSize.height; },\n }), [modelRange, plotSize, clientToPlot]);\n\n const handlePointerDown = useCallback((e: ReactPointerEvent<SVGSVGElement>) => {\n if (!onPointerDown) return;\n const plot = clientToPlot(e);\n const model = plotToModel(plot, modelRange, plotSize);\n onPointerDown(e, { plot, model });\n }, [onPointerDown, clientToPlot, modelRange, plotSize]);\n\n const xTickList = xTicks\n ? resolveTicks(xTicks, 'x', xRange, width, (v) => modelToPlot({ x: v, y: yRange[0] }, modelRange, plotSize).x)\n : [];\n const yTickList = yTicks\n ? resolveTicks(yTicks, 'y', yRange, height, (v) => modelToPlot({ x: xRange[0], y: v }, modelRange, plotSize).y)\n : [];\n\n // An inside y column is right-aligned against its widest label, which only\n // the rendered text can say.\n const yInside = !!yTicks && (yTicks.labels ?? 'inside') === 'inside';\n const yLabelsRef = useRef<SVGGElement | null>(null);\n const [yLabelWidth, setYLabelWidth] = useState(0);\n const yLabelKey = yInside ? yTickList.map((t) => t.label).join('\\n') : '';\n useLayoutEffect(() => {\n if (!yInside) return;\n const measure = (): void => {\n let widest = 0;\n for (const el of yLabelsRef.current?.querySelectorAll('text') ?? []) {\n if (typeof el.getComputedTextLength === 'function') widest = Math.max(widest, el.getComputedTextLength());\n }\n setYLabelWidth((prev) => (prev === widest ? prev : widest));\n };\n measure();\n let live = true;\n // A web font landing after the first measure changes every width.\n document.fonts?.ready.then(() => { if (live) measure(); });\n return () => { live = false; };\n }, [yInside, yLabelKey]);\n\n const cls = [s.root, className].filter(Boolean).join(' ');\n\n const renderTickLines = (axis: Axis, list: ResolvedTick[], settings: TickSettings | false | null | undefined) => {\n if (!settings || settings.lines === false || list.length === 0) return null;\n return (\n <g>\n {list.map((t) => (\n <line\n key={`t${axis}-${t.value}`}\n data-plot-element=\"tick\"\n data-axis={axis}\n className={s.tick}\n {...(axis === 'x'\n ? { x1: t.at, x2: t.at, y1: 0, y2: height }\n : { x1: 0, x2: width, y1: t.at, y2: t.at })}\n />\n ))}\n </g>\n );\n };\n\n const renderTickLabels = (axis: Axis, list: ResolvedTick[], settings: TickSettings | false | null | undefined) => {\n if (!settings || list.length === 0) return null;\n const placement = settings.labels ?? 'inside';\n if (placement === false) return null;\n const outside = placement === 'outside';\n const gap = settings.gap ?? LABEL_GAP_PX;\n let place: { x?: number; y?: number; textAnchor: 'start' | 'middle' | 'end'; dominantBaseline?: 'central' | 'hanging' };\n if (axis === 'y') {\n place = outside\n ? { x: -gap, textAnchor: 'end', dominantBaseline: 'central' }\n : yLabelWidth > 0\n ? { x: gap + yLabelWidth, textAnchor: 'end', dominantBaseline: 'central' }\n : { x: gap, textAnchor: 'start', dominantBaseline: 'central' };\n } else {\n place = outside\n ? { y: height + gap, textAnchor: 'middle', dominantBaseline: 'hanging' }\n : { y: height - gap, textAnchor: 'middle' };\n }\n return (\n <g ref={axis === 'y' ? yLabelsRef : undefined} className={s.tickLabels} aria-hidden=\"true\">\n {list.map((t) => (\n <text\n key={`l${axis}-${t.value}`}\n data-plot-element=\"tick-label\"\n data-axis={axis}\n data-placement={placement}\n className={s.tickLabel}\n x={axis === 'x' ? t.at : place.x}\n y={axis === 'y' ? t.at : place.y}\n textAnchor={place.textAnchor}\n dominantBaseline={place.dominantBaseline}\n >\n {t.label}\n </text>\n ))}\n </g>\n );\n };\n\n return (\n <svg\n ref={svgRef}\n className={cls}\n style={style}\n width={width}\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n role={role}\n aria-label={props['aria-label']}\n tabIndex={tabIndex}\n onPointerDown={onPointerDown ? handlePointerDown : undefined}\n onKeyDown={onKeyDown}\n >\n {grid && (() => {\n const divisions = grid.divisions ?? 3;\n const stroke = grid.color;\n // Evenly-spaced internal divisions, excluding the edges (which\n // belong to the axes). For divisions=3, fractions are 1/4, 2/4, 3/4.\n const fractions: number[] = [];\n for (let i = 1; i <= divisions; i++) fractions.push(i / (divisions + 1));\n return (\n <g>\n {fractions.map((f) => (\n <line\n key={`gx-${f}`}\n data-plot-element=\"grid\"\n className={s.grid}\n stroke={stroke}\n x1={f * width} x2={f * width}\n y1={0} y2={height}\n />\n ))}\n {fractions.map((f) => (\n <line\n key={`gy-${f}`}\n data-plot-element=\"grid\"\n className={s.grid}\n stroke={stroke}\n x1={0} x2={width}\n y1={f * height} y2={f * height}\n />\n ))}\n </g>\n );\n })()}\n {renderTickLines('x', xTickList, xTicks)}\n {renderTickLines('y', yTickList, yTicks)}\n {axes !== false && axes !== null && (() => {\n const stroke = axes?.color;\n return (\n <g>\n <line\n data-plot-element=\"axis\"\n className={s.axis}\n stroke={stroke}\n x1={0} x2={width}\n y1={height} y2={height}\n />\n <line\n data-plot-element=\"axis\"\n className={s.axis}\n stroke={stroke}\n x1={0} x2={0}\n y1={0} y2={height}\n />\n </g>\n );\n })()}\n {renderTickLabels('x', xTickList, xTicks)}\n {renderTickLabels('y', yTickList, yTicks)}\n {children}\n </svg>\n );\n});\n"],"mappings":";;;;AAkCA,SAAgBE,EAAY,GAAU,GAAe,GAAuB;CAC1E,IAAMC,KAASC,EAAE,IAAIC,EAAE,SAASA,EAAE,OAAOA,EAAE,OACrCC,KAASF,EAAE,IAAIC,EAAE,SAASA,EAAE,OAAOA,EAAE;CAC3C,OAAO;EACL,GAAGF,IAAQI,EAAK;EAEhB,IAAI,IAAID,KAASC,EAAK;CACxB;AACF;AAGA,SAAgBC,EAAY,GAAU,GAAe,GAAuB;CAC1E,IAAML,IAAQC,EAAE,IAAIG,EAAK,OACnBD,IAAQ,IAAIF,EAAE,IAAIG,EAAK;CAC7B,OAAO;EACL,GAAGF,EAAE,OAAOF,KAASE,EAAE,OAAOA,EAAE;EAChC,GAAGA,EAAE,OAAOC,KAASD,EAAE,OAAOA,EAAE;CAClC;AACF;;;ACpBA,IAAMI,IAAe;AAGrB,SAAgBC,EAAS,GAAuB;CAC9C,IAAMC,IAAe,MAAI,KAAK,MAAM,KAAK,MAAMC,CAAK,CAAC,GAC/CC,IAAOD,IAAQD;CAErB,QADaE,KAAQ,IAAI,IAAIA,KAAQ,IAAI,IAAIA,KAAQ,IAAI,IAAI,MAC/CF;AAChB;AAGA,SAAgBG,EAAa,GAAsB;CAEjD,OADI,EAAEC,IAAO,MAAM,CAAC,OAAO,SAASA,CAAI,IAAU,IAC3C,KAAK,IAAIN,GAAc,KAAK,IAAI,GAAG,CAAC,KAAK,MAAM,KAAK,MAAMM,CAAI,IAAI,IAAI,CAAC,CAAC;AACjF;AAGA,SAAgBC,EAAa,GAAmC;CAC9D,IAAIC,IAAO;CACX,KAAK,IAAMC,KAAKC,GAAQ;EACtB,IAAI,CAAC,OAAO,SAASD,CAAC,GAAG;EACzB,IAAIE,IAAI;EACR,OAAOA,IAAIX,KAAgB,KAAK,IAAI,OAAOS,EAAE,QAAQE,CAAC,CAAC,IAAIF,CAAC,IAAI,OAAO,KAAK,IAAI,GAAG,KAAK,IAAIA,CAAC,CAAC,IAAG;EACjG,IAAO,KAAK,IAAID,GAAMG,CAAC;CACzB;CACA,OAAOH;AACT;AAOA,SAAgBI,EACd,GACA,GACA,GACS;CACT,IAAMC,IAAgB;EAAE,QAAQ,CAAC;EAAG,MAAM;EAAG,UAAU;CAAE,GACnDC,IAAK,KAAK,IAAIC,EAAM,IAAIA,EAAM,EAAE,GAChCC,IAAK,KAAK,IAAID,EAAM,IAAIA,EAAM,EAAE,GAChCE,IAAOD,IAAKF,GACZ,EAAE,eAAY,WAAQ,MAAMI;CAClC,IAAI,EAAED,IAAO,MAAM,CAAC,OAAO,SAASA,CAAI,KAAK,EAAEE,IAAS,MAAM,EAAEC,IAAa,IAAI,OAAOP;CAExF,IAAMQ,IAAaJ,IAAOE,GACpBb,IAAOL,EAASmB,IAAaC,CAAU,GACvCC,IAAWjB,EAAaC,CAAI,GAC5BiB,IAAOT,IAAKU,IAAQH,GACpBI,IAAKT,IAAKQ,IAAQH,GAGlBK,IAAQ,KAAK,KAAKH,IAAOjB,IAAO,IAAI,GACpCqB,IAAO,KAAK,MAAMF,IAAKnB,IAAO,IAAI,GAClCI,IAAmB,CAAC;CAC1B,KAAK,IAAIkB,IAAIF,GAAOE,KAAKD,GAAM,KAAK,EAAO,KAAK,QAAQC,IAAItB,EAAA,CAAM,QAAQgB,CAAQ,CAAC,IAAI,CAAC;CACxF,OAAO;EAAE;EAAQ;EAAM;CAAS;AAClC;AAIA,SAAgBO,EAAW,GAAe,GAA4B;CACpE,IAAMC,IAAOC,EAAM,QAAQC,EAAI,QAAQ;CACvC,OAAO,cAAc,KAAKF,CAAI,IAAIA,EAAK,MAAM,CAAC,IAAIA;AACpD;;;;;;;;GEwBMI,IAAwC;CAAE,GAAG;CAAI,GAAG;AAAG,GACvDC,IAAsC;CAAE,GAAG;CAAI,GAAG;AAAE,GACpDC,IAAe;AASrB,SAASC,EACP,GACA,GACA,GACA,GACA,GACgB;CAChB,IAAI3B,GACAsB;CACJ,IAAIM,EAAS,QAAQ;EACnB,IAAMxB,IAAK,KAAK,IAAIC,EAAM,IAAIA,EAAM,EAAE,GAChCC,IAAK,KAAK,IAAID,EAAM,IAAIA,EAAM,EAAE,GAChCwB,KAAOvB,IAAKF,KAAM;EACxB,IAASwB,EAAS,OAAO,QAAQ,MAAM7B,KAAKK,IAAKyB,KAAO9B,KAAKO,IAAKuB,CAAG;EACrE,IAAMC,IAAO9B,EAAO,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,MAAMD,IAAIC,EAAOkB,EAAE,GAClDa,IAAOD,EAAK,SAAS,KAAKA,EAAK,OAAO,MAAM,KAAK,IAAIE,IAAIF,EAAK,EAAE,KAAK,KAAK,IAAIA,EAAK,EAAE,IAAI,IAAI;EACnG,IAAM;GAAE,UAAUjC,EAAaG,CAAM;GAAG,MAAM+B,IAAOD,EAAK,KAAK;EAAE;CACnE,OAAO;EACL,IAAMG,IAAM/B,EAAUG,GAAOI,GAAQ;GACnC,YAAYmB,EAAS,cAAcJ,EAAgBU;GACnD,OAAON,EAAS,SAASH,EAAcS;EACzC,CAAC;EAED,AADA,IAASD,EAAI,QACb,IAAM;GAAE,UAAUA,EAAI;GAAU,MAAMA,EAAI;EAAK;CACjD;CACA,IAAME,IAASP,EAAS,UAAUT;CAClC,OAAOnB,EAAO,KAAK,OAAW;EAAE;EAAO,IAAIoC,EAAOf,CAAK;EAAG,OAAOc,EAAOd,GAAOC,CAAG;CAAE,EAAE;AACxF;AAOA,IAAae,IAASC,EAAsC,SAAgB,GAAO,GAAK;CACtF,IAAM,EACJ,UAAO,WACP,YAAS,CAAC,GAAG,CAAC,GACd,YAAS,CAAC,GAAG,CAAC,GACd,SACA,SACA,WACA,WACA,aACA,UAAO,OACP,cACA,UACA,kBACA,cACA,gBACEC,GAEEC,IAASC,EAA6B,IAAI,GAE1CC,IAAyBC,SACtB;EAAE,MAAMC,EAAO;EAAI,MAAMA,EAAO;EAAI,MAAMC,EAAO;EAAI,MAAMA,EAAO;CAAG,IAC5E,CAACD,GAAQC,CAAM,CACjB,GAEMC,IAAWH,SAAe;EAAE;EAAO;CAAO,IAAI,CAACI,GAAOC,CAAM,CAAC,GAE7DC,IAAeC,GAAa,MAAmD;EACnF,IAAMC,IAAOX,EAAO,SAAS,sBAAsB,GAC7CY,IAAOD,GAAM,QAAQ,GACrBE,IAAMF,GAAM,OAAO;EACzB,OAAO;GAAE,GAAG,EAAE,UAAUC;GAAM,GAAG,EAAE,UAAUC;EAAI;CACnD,GAAG,CAAC,CAAC;CAQL,AANAC,SACExE,EAAK,UAAU,SAAS;EAAE;EAAO;CAAO,CAAC,SAC5BA,EAAK,UAAU,SAAS,IAGpC,CAAC,CAAC,GACLyE,EAAoBC,UAAY;EAC9B,IAAI,MAAM;GAAE,OAAOhB,EAAO;EAAS;EACnC,cAAc,MAAcnD,EAAYoE,GAAIf,GAAYI,CAAQ;EAChE,cAAc,MAAc/D,EAAY0E,GAAIf,GAAYI,CAAQ;EAChE;EACA,gBAAgB,MAAMzD,EAAY4D,EAAa,CAAC,GAAGP,GAAYI,CAAQ;EACvE,IAAI,QAAQ;GAAE,OAAOA,EAAS;EAAO;EACrC,IAAI,SAAS;GAAE,OAAOA,EAAS;EAAQ;CACzC,IAAI;EAACJ;EAAYI;EAAUG;CAAY,CAAC;CAExC,IAAMS,IAAoBR,GAAa,MAAwC;EAC7E,IAAI,CAACS,GAAe;EACpB,IAAMvE,IAAO6D,EAAa,CAAC,GACrBW,IAAQvE,EAAYD,GAAMsD,GAAYI,CAAQ;EACpD,EAAc,GAAG;GAAE;GAAM;EAAM,CAAC;CAClC,GAAG;EAACa;EAAeV;EAAcP;EAAYI;CAAQ,CAAC,GAEhDe,IAAYC,IACdnC,EAAamC,GAAQ,KAAKlB,GAAQG,IAAQ,MAAMhE,EAAY;EAAE,GAAGgB;EAAG,GAAG8C,EAAO;CAAG,GAAGH,GAAYI,CAAQ,CAAC,CAAC,CAAC,IAC3G,CAAC,GACCiB,IAAYC,IACdrC,EAAaqC,GAAQ,KAAKnB,GAAQG,IAAS,MAAMjE,EAAY;EAAE,GAAG6D,EAAO;EAAI,GAAG7C;CAAE,GAAG2C,GAAYI,CAAQ,CAAC,CAAC,CAAC,IAC5G,CAAC,GAICmB,IAAU,CAAC,CAACD,MAAWA,EAAO,UAAU,cAAc,UACtDE,IAAazB,EAA2B,IAAI,GAC5C,CAAC0B,GAAaC,KAAkBC,EAAS,CAAC,GAC1CC,IAAYL,IAAUF,EAAU,KAAK,MAAMQ,EAAE,KAAK,CAAC,CAAC,KAAK,IAAI,IAAI;CACvE,QAAsB;EACpB,IAAI,CAACN,GAAS;EACd,IAAMO,UAAsB;GAC1B,IAAIC,IAAS;GACb,KAAK,IAAMC,KAAMR,EAAW,SAAS,iBAAiB,MAAM,KAAK,CAAC,GAChE,AAAI,OAAOQ,EAAG,yBAA0B,eAAY,IAAS,KAAK,IAAID,GAAQC,EAAG,sBAAsB,CAAC;GAE1G,GAAgB,MAAUC,MAASF,IAASE,IAAOF,CAAO;EAC5D;EACA,EAAQ;EACR,IAAIG,IAAO;EAGX,OADA,SAAS,OAAO,MAAM,WAAW;GAAE,AAAIA,KAAMJ,EAAQ;EAAG,CAAC,SAC5C;GAAE,IAAO;EAAO;CAC/B,GAAG,CAACP,GAASK,CAAS,CAAC;CAEvB,IAAMO,IAAM,CAAChG,EAAE,MAAMiG,CAAS,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG,GAElDC,KAAmB,GAAY,GAAsB,MACrD,CAACnD,KAAYA,EAAS,UAAU,MAASoD,EAAK,WAAW,IAAU,OAErE,kBAAC,KAAD,EAAA,UACGA,EAAK,KAAK,MACT,kBAAC,QAAD;EAEE,qBAAkB;EAClB,aAAW9C;EACX,WAAWrD,EAAE;EACb,GAAKqD,MAAS,MACV;GAAE,IAAIqC,EAAE;GAAI,IAAIA,EAAE;GAAI,IAAI;GAAG,IAAIvB;EAAO,IACxC;GAAE,IAAI;GAAG,IAAID;GAAO,IAAIwB,EAAE;GAAI,IAAIA,EAAE;EAAG;CAC5C,GAPM,IAAIrC,EAAK,GAAGqC,EAAE,OAOpB,CACF,EACA,CAAA,GAIDU,KAAoB,GAAY,GAAsB,MAAsD;EAChH,IAAI,CAACrD,KAAYoD,EAAK,WAAW,GAAG,OAAO;EAC3C,IAAME,IAAYtD,EAAS,UAAU;EACrC,IAAIsD,MAAc,IAAO,OAAO;EAChC,IAAMC,IAAUD,MAAc,WACxBE,IAAMxD,EAAS,OAAOF,GACxB2D;EAYJ,OAXA,AAOEA,IAPEnD,MAAS,MACHiD,IACJ;GAAE,GAAG,CAACC;GAAK,YAAY;GAAO,kBAAkB;EAAU,IAC1DjB,IAAc,IACZ;GAAE,GAAGiB,IAAMjB;GAAa,YAAY;GAAO,kBAAkB;EAAU,IACvE;GAAE,GAAGiB;GAAK,YAAY;GAAS,kBAAkB;EAAU,IAEzDD,IACJ;GAAE,GAAGnC,IAASoC;GAAK,YAAY;GAAU,kBAAkB;EAAU,IACrE;GAAE,GAAGpC,IAASoC;GAAK,YAAY;EAAS,GAG5C,kBAAC,KAAD;GAAG,KAAKlD,MAAS,MAAMgC,IAAa,KAAA;GAAW,WAAWrF,EAAE;GAAY,eAAY;GACjF,UAAAmG,EAAK,KAAK,MACT,kBAAC,QAAD;IAEE,qBAAkB;IAClB,aAAW9C;IACX,kBAAgBgD;IAChB,WAAWrG,EAAE;IACb,GAAGqD,MAAS,MAAMqC,EAAE,KAAKc,EAAM;IAC/B,GAAGnD,MAAS,MAAMqC,EAAE,KAAKc,EAAM;IAC/B,YAAYA,EAAM;IAClB,kBAAkBA,EAAM;IAEvB,UAAAd,EAAE;GACC,GAXC,IAAIrC,EAAK,GAAGqC,EAAE,OAWf,CACP;EACA,CAAA;CAEP;CAEA,OACE,kBAAC,OAAD;EACE,KAAK/B;EACL,WAAWqC;EACJ;EACA;EACC;EACR,SAAS,OAAO9B,EAAM,GAAGC;EACnB;EACN,cAAYT,EAAM;EACR;EACV,eAAeoB,IAAgBD,IAAoB,KAAA;EACxC;EAXb,UAAA;GAaG4B,YAAe;IACd,IAAMC,IAAYD,EAAK,aAAa,GAC9BE,IAASF,EAAK,OAGdG,IAAsB,CAAC;IAC7B,KAAK,IAAIvE,IAAI,GAAGA,KAAKqE,GAAW,KAAK,EAAU,KAAKrE,KAAKqE,IAAY,EAAE;IACvE,OACE,kBAAC,KAAD,EAAA,UAAA,CACGE,EAAU,KAAK,MACd,kBAAC,QAAD;KAEE,qBAAkB;KAClB,WAAW5G,EAAE;KACL;KACR,IAAI6G,IAAI3C;KAAO,IAAI2C,IAAI3C;KACvB,IAAI;KAAG,IAAIC;IACZ,GANM,MAAM0C,GAMZ,CACF,GACAD,EAAU,KAAK,MACd,kBAAC,QAAD;KAEE,qBAAkB;KAClB,WAAW5G,EAAE;KACL;KACR,IAAI;KAAG,IAAIkE;KACX,IAAI2C,IAAI1C;KAAQ,IAAI0C,IAAI1C;IACzB,GANM,MAAM0C,GAMZ,CACF,CACA,EAAA,CAAA;GAEP,EAAA,CAAG;GACFX,EAAgB,KAAKlB,GAAWC,CAAM;GACtCiB,EAAgB,KAAKhB,GAAWC,CAAM;GACtC2B,MAAS,MAASA,MAAS,eAAe;IACzC,IAAMH,IAASG,GAAM;IACrB,OACE,kBAAC,KAAD,EAAA,UAAA,CACE,kBAAC,QAAD;KACE,qBAAkB;KAClB,WAAW9G,EAAE;KACL;KACR,IAAI;KAAG,IAAIkE;KACX,IAAIC;KAAQ,IAAIA;IACjB,CAAA,GACD,kBAAC,QAAD;KACE,qBAAkB;KAClB,WAAWnE,EAAE;KACL;KACR,IAAI;KAAG,IAAI;KACX,IAAI;KAAG,IAAImE;IACZ,CAAA,CACA,EAAA,CAAA;GAEP,EAAA,CAAG;GACFiC,EAAiB,KAAKpB,GAAWC,CAAM;GACvCmB,EAAiB,KAAKlB,GAAWC,CAAM;GACvC4B;EACE;;AAET,CAAC"}
|