@weasel-js/ui 1.7.2 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/dist/chunks/{Callout-BUn0dP_S.js → Callout-DpTO3fkb.js} +10 -10
  2. package/dist/chunks/Callout-DpTO3fkb.js.map +1 -0
  3. package/dist/chunks/{Checkbox-DFn-x0JO.js → Checkbox-d4IfhiXh.js} +2 -2
  4. package/dist/chunks/{Checkbox-DFn-x0JO.js.map → Checkbox-d4IfhiXh.js.map} +1 -1
  5. package/dist/chunks/{CloseButton-BmNmRVtb.js → CloseButton-wb7Or5Eg.js} +2 -2
  6. package/dist/chunks/{CloseButton-BmNmRVtb.js.map → CloseButton-wb7Or5Eg.js.map} +1 -1
  7. package/dist/chunks/{ColorModeControl-DaVJ-SyX.js → ColorModeControl-DET6Itwq.js} +3 -3
  8. package/dist/chunks/{ColorModeControl-DaVJ-SyX.js.map → ColorModeControl-DET6Itwq.js.map} +1 -1
  9. package/dist/chunks/{DataGrid-CmHJe57v.js → DataGrid-B6IS9vUk.js} +2 -2
  10. package/dist/chunks/{DataGrid-CmHJe57v.js.map → DataGrid-B6IS9vUk.js.map} +1 -1
  11. package/dist/chunks/{DetentSlider-DNTAk8xP.js → DetentSlider-D40V8Oma.js} +2 -2
  12. package/dist/chunks/{DetentSlider-DNTAk8xP.js.map → DetentSlider-D40V8Oma.js.map} +1 -1
  13. package/dist/chunks/{Dialog-DKfsk5VN.js → Dialog-h-5MeYpu.js} +9 -9
  14. package/dist/chunks/{Dialog-DKfsk5VN.js.map → Dialog-h-5MeYpu.js.map} +1 -1
  15. package/dist/chunks/{Disclosure-Ch2KxYuB.js → Disclosure-CpWt87nO.js} +7 -7
  16. package/dist/chunks/{Disclosure-Ch2KxYuB.js.map → Disclosure-CpWt87nO.js.map} +1 -1
  17. package/dist/chunks/FitLabel-BzZGkWfL.js +76 -0
  18. package/dist/chunks/FitLabel-BzZGkWfL.js.map +1 -0
  19. package/dist/chunks/{GradientEditor-DD2OjQtn.js → GradientEditor-vz771M-N.js} +4 -4
  20. package/dist/chunks/{GradientEditor-DD2OjQtn.js.map → GradientEditor-vz771M-N.js.map} +1 -1
  21. package/dist/chunks/{Icon--B1ME-6c.js → Icon-DPRUIo3G.js} +4 -1
  22. package/dist/chunks/Icon-DPRUIo3G.js.map +1 -0
  23. package/dist/chunks/{Jog-BrkzdGV2.js → Jog-TE6QI9i1.js} +3 -3
  24. package/dist/chunks/{Jog-BrkzdGV2.js.map → Jog-TE6QI9i1.js.map} +1 -1
  25. package/dist/chunks/{LayerList-Bxj1N6bj.js → LayerList-BXTAjK-4.js} +6 -6
  26. package/dist/chunks/{LayerList-Bxj1N6bj.js.map → LayerList-BXTAjK-4.js.map} +1 -1
  27. package/dist/chunks/{ListEditor-OQDzG4A5.js → ListEditor-DGtwj99m.js} +2 -2
  28. package/dist/chunks/{ListEditor-OQDzG4A5.js.map → ListEditor-DGtwj99m.js.map} +1 -1
  29. package/dist/chunks/{MeshEditor-DWpvg8H6.js → MeshEditor-8CRsu-Td.js} +2 -2
  30. package/dist/chunks/{MeshEditor-DWpvg8H6.js.map → MeshEditor-8CRsu-Td.js.map} +1 -1
  31. package/dist/chunks/{NumberField-DS0WSiin.js → NumberField-BXFrOi5M.js} +8 -5
  32. package/dist/chunks/{NumberField-DS0WSiin.js.map → NumberField-BXFrOi5M.js.map} +1 -1
  33. package/dist/chunks/{PaintField-BdmQM53v.js → PaintField-DX4uYlF9.js} +2 -2
  34. package/dist/chunks/{PaintField-BdmQM53v.js.map → PaintField-DX4uYlF9.js.map} +1 -1
  35. package/dist/chunks/{PaintInput-DaQMeBsI.js → PaintInput-CWyOJXUM.js} +5 -5
  36. package/dist/chunks/{PaintInput-DaQMeBsI.js.map → PaintInput-CWyOJXUM.js.map} +1 -1
  37. package/dist/chunks/{PatternPicker-B3HcMZWg.js → PatternPicker-0j51aDJ3.js} +9 -9
  38. package/dist/chunks/PatternPicker-0j51aDJ3.js.map +1 -0
  39. package/dist/chunks/{Prefs-C4MZ28SX.js → Prefs-gbjYqvSD.js} +41 -41
  40. package/dist/chunks/Prefs-gbjYqvSD.js.map +1 -0
  41. package/dist/chunks/{Properties-DseN56WI.js → Properties-BbNuMiic.js} +4 -4
  42. package/dist/chunks/{Properties-DseN56WI.js.map → Properties-BbNuMiic.js.map} +1 -1
  43. package/dist/chunks/PropertyField-CWUtNK1w.js +751 -0
  44. package/dist/chunks/PropertyField-CWUtNK1w.js.map +1 -0
  45. package/dist/chunks/{SelectionPanel-C3WzyaZV.js → SelectionPanel-BpUqP75b.js} +2 -2
  46. package/dist/chunks/{SelectionPanel-C3WzyaZV.js.map → SelectionPanel-BpUqP75b.js.map} +1 -1
  47. package/dist/chunks/{SidebarPanel-AXQ0jRDx.js → SidebarPanel--dJ-k9-V.js} +3 -3
  48. package/dist/chunks/{SidebarPanel-AXQ0jRDx.js.map → SidebarPanel--dJ-k9-V.js.map} +1 -1
  49. package/dist/chunks/Slider-CHBQ8uUC.js +439 -0
  50. package/dist/chunks/Slider-CHBQ8uUC.js.map +1 -0
  51. package/dist/chunks/{SwatchGrid-9ZPiDmxd.js → SwatchGrid-DxK9e_IV.js} +3 -3
  52. package/dist/chunks/SwatchGrid-DxK9e_IV.js.map +1 -0
  53. package/dist/chunks/{SwatchStrip-Bi9iPLoh.js → SwatchStrip-BY6A511W.js} +2 -2
  54. package/dist/chunks/{SwatchStrip-Bi9iPLoh.js.map → SwatchStrip-BY6A511W.js.map} +1 -1
  55. package/dist/chunks/{ThemeSwitcher-DiBmMh_B.js → ThemeSwitcher-tnATkIo1.js} +2 -2
  56. package/dist/chunks/{ThemeSwitcher-DiBmMh_B.js.map → ThemeSwitcher-tnATkIo1.js.map} +1 -1
  57. package/dist/chunks/{Timeline-sMCH2fah.js → Timeline-FlLUm8nd.js} +4 -4
  58. package/dist/chunks/{Timeline-sMCH2fah.js.map → Timeline-FlLUm8nd.js.map} +1 -1
  59. package/dist/chunks/{Toast-C3ozq11V.js → Toast-CuHkT0x0.js} +2 -2
  60. package/dist/chunks/{Toast-C3ozq11V.js.map → Toast-CuHkT0x0.js.map} +1 -1
  61. package/dist/chunks/ToggleBar-BZ4Gk1IU.js +84 -0
  62. package/dist/chunks/ToggleBar-BZ4Gk1IU.js.map +1 -0
  63. package/dist/chunks/{TokenPanel-WtKGY2Db.js → TokenPanel-DongdiZM.js} +23 -23
  64. package/dist/chunks/{TokenPanel-WtKGY2Db.js.map → TokenPanel-DongdiZM.js.map} +1 -1
  65. package/dist/chunks/ToolOptionsBar-wTPJFfOn.js +94 -0
  66. package/dist/chunks/ToolOptionsBar-wTPJFfOn.js.map +1 -0
  67. package/dist/chunks/{Tree-Cc8lIzgM.js → Tree-hzIbj2q1.js} +2 -2
  68. package/dist/chunks/{Tree-Cc8lIzgM.js.map → Tree-hzIbj2q1.js.map} +1 -1
  69. package/dist/chunks/checkbox.module-Dcx8ILgR.js +5 -0
  70. package/dist/chunks/checkbox.module-Dcx8ILgR.js.map +1 -0
  71. package/dist/chunks/{icons-Dr2-r30x.js → icons-CofIOTiI.js} +2 -2
  72. package/dist/chunks/{icons-Dr2-r30x.js.map → icons-CofIOTiI.js.map} +1 -1
  73. package/dist/chunks/{model-qsAFK7_0.js → model-qQj8Ny9w.js} +134 -133
  74. package/dist/chunks/model-qQj8Ny9w.js.map +1 -0
  75. package/dist/chunks/{prefField-BE_Ytr2H.js → prefField-CCm_nzlM.js} +81 -75
  76. package/dist/chunks/prefField-CCm_nzlM.js.map +1 -0
  77. package/dist/components/Callout/index.js +1 -1
  78. package/dist/components/Checkbox/index.js +1 -1
  79. package/dist/components/CloseButton/index.js +1 -1
  80. package/dist/components/ColorModeControl/index.js +1 -1
  81. package/dist/components/DataGrid/index.js +1 -1
  82. package/dist/components/DetentSlider/index.js +1 -1
  83. package/dist/components/Dialog/index.js +1 -1
  84. package/dist/components/Disclosure/index.js +1 -1
  85. package/dist/components/FitLabel/FitLabel.d.ts +41 -0
  86. package/dist/components/FitLabel/FitLabel.d.ts.map +1 -0
  87. package/dist/components/FitLabel/index.d.ts +3 -0
  88. package/dist/components/FitLabel/index.d.ts.map +1 -0
  89. package/dist/components/FitLabel/index.js +2 -0
  90. package/dist/components/GradientEditor/index.js +1 -1
  91. package/dist/components/InlineRange/InlineRange.d.ts +1 -1
  92. package/dist/components/Jog/index.js +1 -1
  93. package/dist/components/LayerList/index.js +1 -1
  94. package/dist/components/ListEditor/index.js +1 -1
  95. package/dist/components/MeshEditor/index.js +1 -1
  96. package/dist/components/NumberField/UnitField.d.ts.map +1 -1
  97. package/dist/components/NumberField/index.js +1 -1
  98. package/dist/components/PaintField/index.js +1 -1
  99. package/dist/components/PaintInput/index.js +1 -1
  100. package/dist/components/PatternPicker/index.js +1 -1
  101. package/dist/components/Prefs/index.js +2 -2
  102. package/dist/components/Prefs/prefField.d.ts.map +1 -1
  103. package/dist/components/Properties/NumberControls.d.ts +16 -0
  104. package/dist/components/Properties/NumberControls.d.ts.map +1 -0
  105. package/dist/components/Properties/PropertyField.d.ts +18 -8
  106. package/dist/components/Properties/PropertyField.d.ts.map +1 -1
  107. package/dist/components/Properties/index.js +2 -2
  108. package/dist/components/SelectionPanel/index.js +2 -2
  109. package/dist/components/SelectionPanel/renderLeaf.d.ts.map +1 -1
  110. package/dist/components/SidebarPanel/index.js +1 -1
  111. package/dist/components/Slider/Slider.d.ts +6 -0
  112. package/dist/components/Slider/Slider.d.ts.map +1 -1
  113. package/dist/components/Slider/index.js +1 -1
  114. package/dist/components/SwatchGrid/index.js +1 -1
  115. package/dist/components/SwatchStrip/index.js +1 -1
  116. package/dist/components/ThemeSwitcher/index.js +1 -1
  117. package/dist/components/Timeline/index.js +1 -1
  118. package/dist/components/Toast/index.js +1 -1
  119. package/dist/components/ToggleBar/ToggleBar.d.ts.map +1 -1
  120. package/dist/components/ToggleBar/index.js +1 -1
  121. package/dist/components/TokenPanel/index.js +1 -1
  122. package/dist/components/ToolOptionsBar/ToolOptionsBar.d.ts +1 -1
  123. package/dist/components/ToolOptionsBar/ToolOptionsBar.d.ts.map +1 -1
  124. package/dist/components/ToolOptionsBar/index.js +1 -1
  125. package/dist/components/Tree/index.js +1 -1
  126. package/dist/components/stanceCss.d.ts +6 -0
  127. package/dist/components/stanceCss.d.ts.map +1 -1
  128. package/dist/components/stanceSurfaces.d.ts.map +1 -1
  129. package/dist/endless.d.ts +7 -0
  130. package/dist/endless.d.ts.map +1 -0
  131. package/dist/icons/paths.d.ts +2 -0
  132. package/dist/icons/paths.d.ts.map +1 -1
  133. package/dist/index.d.ts +1 -0
  134. package/dist/index.d.ts.map +1 -1
  135. package/dist/index.js +72 -70
  136. package/dist/index.js.map +1 -1
  137. package/dist/style.css +1 -1
  138. package/package.json +9 -9
  139. package/dist/chunks/Callout-BUn0dP_S.js.map +0 -1
  140. package/dist/chunks/Icon--B1ME-6c.js.map +0 -1
  141. package/dist/chunks/PatternPicker-B3HcMZWg.js.map +0 -1
  142. package/dist/chunks/Prefs-C4MZ28SX.js.map +0 -1
  143. package/dist/chunks/PropertyField-BbNqK8xr.js +0 -743
  144. package/dist/chunks/PropertyField-BbNqK8xr.js.map +0 -1
  145. package/dist/chunks/Slider-DSrlMEq8.js +0 -389
  146. package/dist/chunks/Slider-DSrlMEq8.js.map +0 -1
  147. package/dist/chunks/SwatchGrid-9ZPiDmxd.js.map +0 -1
  148. package/dist/chunks/ToggleBar-Z2jZiguF.js +0 -80
  149. package/dist/chunks/ToggleBar-Z2jZiguF.js.map +0 -1
  150. package/dist/chunks/ToolOptionsBar-CZTYPoC2.js +0 -88
  151. package/dist/chunks/ToolOptionsBar-CZTYPoC2.js.map +0 -1
  152. package/dist/chunks/checkbox.module-DLQV9tMX.js +0 -5
  153. package/dist/chunks/checkbox.module-DLQV9tMX.js.map +0 -1
  154. package/dist/chunks/model-qsAFK7_0.js.map +0 -1
  155. package/dist/chunks/prefField-BE_Ytr2H.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"Jog-BrkzdGV2.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 composes: numeric from '@weasel-js/theme/numeric.module.css';\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
+ {"version":3,"file":"Jog-TE6QI9i1.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 composes: numeric from '@weasel-js/theme/numeric.module.css';\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,13 +1,13 @@
1
1
  import { t as e } from "./dlog-F0I8KsIW.js";
2
- import { J as t } from "./icons-Dr2-r30x.js";
3
- import { t as n } from "./CloseButton-BmNmRVtb.js";
2
+ import { J as t } from "./icons-CofIOTiI.js";
3
+ import { t as n } from "./CloseButton-wb7Or5Eg.js";
4
4
  import { t as r } from "./useReorderDragList-CStYKjHl.js";
5
- import { t as i } from "./Disclosure-Ch2KxYuB.js";
5
+ import { t as i } from "./Disclosure-CpWt87nO.js";
6
6
  import { t as a } from "./DragGhost-BeYDJyLZ.js";
7
7
  import { t as o } from "./DragGrip-AoIZXfZb.js";
8
8
  import { t as s } from "./ItemList.module-BSpCSxan.js";
9
- import { n as c } from "./Properties-DseN56WI.js";
10
- import { a as l } from "./PropertyField-BbNqK8xr.js";
9
+ import { n as c } from "./Properties-BbNuMiic.js";
10
+ import { a as l } from "./PropertyField-CWUtNK1w.js";
11
11
  import { asNodeId as u, createMoveToIndexOp as d, dispatchApplyBatch as f, useLatest as p } from "@weasel-js/core";
12
12
  import { Fragment as m, jsx as h, jsxs as g } from "react/jsx-runtime";
13
13
  import { useId as _, useLayoutEffect as v, useRef as y, useState as b } from "react";
@@ -418,4 +418,4 @@ function F(e) {
418
418
  //#endregion
419
419
  export { D as n, S as r, F as t };
420
420
 
421
- //# sourceMappingURL=LayerList-Bxj1N6bj.js.map
421
+ //# sourceMappingURL=LayerList-BXTAjK-4.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"LayerList-Bxj1N6bj.js","names":["stop","s","rows","_Fragment","dlog","LockIcon","CloseButton","useReorderDragList","Disclosure","DragGhost","DragGrip","ItemList_module_default","PropertyGroup","PropertyList","LayerList_module_default","moveLayers","move","reorder","items","item","moving","ids","kept","siblings","it","moved","at","index","collectCollapsed","isCollapsed","into","anyNested","hasBody","body","LayerList","props","toggled","setToggled","useState","collapsed","collapsedIds","toggle","next","id","prev","onCollapsedChange","live","useLatest","press","onSelect","mods","locked","lockedIds","selectedIds","bodies","collectBodies","list","visible","walk","level","rowEls","useRef","focusId","setFocusId","pendingFocus","anchor","nudgers","el","tabStop","v","focusRow","selectRange","from","to","a","b","range","keys","e","go","branch","open","delta","parentId","shared","useId","palette","onAdd","addKinds","cls","className","title","kind","empty","Level","out","selected","drag","target","setEl","bind","node","cards","entry","i","kids","childId","path","onPointerDown","ghosted","isSelected","draggedIds","targetIndex","controls","Controls","head","Handle","stop","Grip","Remove","Fragment","ghost","onNudge","onVisibilityChange","onRemove","useSceneLayerList","args","build","scene","itemFor","selection","asNodeId","back","adapter","createMoveToIndexOp","label"],"sources":["../../src/components/LayerList/LayerList.module.css","../../src/components/LayerList/LayerList.tsx","../../src/components/LayerList/useSceneLayerList.ts"],"sourcesContent":["/* Row box, hover and selection are `ItemList`'s, so a layer list and the\n history list stacked under it agree. What is here is only what a layer has. */\n.root,\n.root * {\n box-sizing: border-box;\n}\n\n.root {\n display: grid;\n gap: var(--wzl-space-4);\n}\n\n.head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--wzl-space-4);\n}\n\n.title {\n margin: 0;\n font-family: var(--wzl-font-display);\n font-weight: var(--wzl-font-weight-light);\n font-size: var(--wzl-font-size-lg);\n color: var(--wzl-fg);\n}\n\n.palette {\n display: flex;\n gap: var(--wzl-space-2);\n}\n\n.add {\n font: inherit;\n height: var(--wzl-control-h);\n background: transparent;\n border: 1px dashed var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n padding: var(--wzl-space-1) var(--wzl-space-4);\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg-subtle);\n cursor: pointer;\n}\n\n.add:hover {\n background: var(--wzl-surface-raised);\n color: var(--wzl-fg);\n}\n\n.level {\n display: flex;\n flex-direction: column;\n gap: 1px;\n position: relative;\n width: 100%;\n}\n\n/* A level of cards is a `PropertyList`, so the controls in each body take the\n panel's density; its rows are spaced like cards rather than lines. */\n.level.cards {\n display: grid;\n row-gap: var(--wzl-space-3);\n}\n\n.entry {\n min-width: 0;\n}\n\n.row {\n user-select: none;\n}\n\n.row:focus-visible {\n outline: 2px solid var(--wzl-focus-ring);\n outline-offset: -2px;\n}\n\n/* A child's twisty sits under its parent's grip. */\n.children {\n padding-left: calc(var(--wzl-disclosure-target, var(--wzl-control-h-sm)) + var(--wzl-space-4));\n}\n\n/* The layer the drag picked up holds its place, faded, while its ghost follows the pointer. */\n.dragging {\n opacity: 0.5;\n}\n\n/* Where the drop would land: a line on the edge of the layer it would land against. */\n.dropBefore {\n box-shadow: 0 -2px 0 var(--wzl-accent);\n}\n\n.dropAfter {\n box-shadow: 0 2px 0 var(--wzl-accent);\n}\n\n/* One width for the grip and the lock, so a locked row's label lines up. */\n.grip,\n.lock {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: 0 0 auto;\n min-width: 13px;\n color: var(--wzl-fg-muted);\n}\n\n.row:not([data-locked]) {\n cursor: grab;\n}\n\n.row:not([data-locked]):active {\n cursor: grabbing;\n}\n\n.stop {\n display: inline-flex;\n align-items: center;\n flex: 0 0 auto;\n}\n\n/* Holds the twisty column open on rows with nothing to expand, so labels line up down a level. */\n.twistyGap {\n flex: 0 0 auto;\n width: var(--wzl-disclosure-target, var(--wzl-control-h-sm));\n}\n\n/* Sized, so the empty column a locked row keeps is the checkbox's width. */\n.check {\n flex: 0 0 auto;\n width: 13px;\n height: 13px;\n margin: 0;\n accent-color: var(--wzl-accent);\n}\n\n.handle {\n font: inherit;\n background: transparent;\n border: 0;\n color: var(--wzl-fg-subtle);\n padding: var(--wzl-space-1) var(--wzl-space-2);\n cursor: grab;\n touch-action: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: var(--wzl-icon-button-size);\n height: var(--wzl-icon-button-size);\n border-radius: var(--wzl-radius-sm);\n font-weight: var(--wzl-font-weight-bold);\n font-size: var(--wzl-font-size);\n}\n\n.card[data-tone] .handle {\n background: var(--wzl-tone);\n color: var(--wzl-fg-on-accent);\n}\n\n.handle:active {\n cursor: grabbing;\n}\n\n.entry[data-selected] > .card {\n outline: 2px solid var(--wzl-accent);\n outline-offset: -1px;\n}\n","import {\n type KeyboardEvent,\n type ReactNode,\n type RefCallback,\n useId,\n useLayoutEffect,\n useRef,\n useState,\n} from 'react';\nimport { useLatest } from '@weasel-js/core';\nimport { dlog } from '../../dlog';\nimport { LockIcon } from '../../icons';\nimport { type PressModifiers, useReorderDragList } from '../../useReorderDragList';\nimport { CloseButton } from '../CloseButton';\nimport { Disclosure } from '../Disclosure';\nimport { DragGhost } from '../DragGhost';\nimport { DragGrip } from '../DragGrip';\nimport rows from '../ItemList/ItemList.module.css';\nimport { PropertyGroup, PropertyList } from '../Properties';\nimport type { StanceProps } from '../stance';\nimport s from './LayerList.module.css';\n\n/** One layer. With a body (see `renderBody`) it draws as a card that folds its\n * body away; without one, as a one-line row. Either can hold child layers. */\nexport interface LayerListItem {\n id: string;\n /** The layer's name. Also what its controls are labeled by. */\n label: string;\n /** Shown in the header in place of `label` — a select that switches the\n * layer's kind, say. */\n title?: ReactNode;\n /** Fixed-size content before the label: a color swatch, a kind icon. */\n leading?: ReactNode;\n /** Drawn in the drag handle in place of the grip — an ordinal. */\n badge?: ReactNode;\n /** Which of its list this layer is: an index into the theme's tone list, or\n * a color. A card draws its edge and handle in it and recolors its controls. */\n tone?: StanceProps['tone'];\n /** Cannot be dragged, hidden or removed, walls off drops on either side, and\n * selects alone rather than joining a multi-selection. */\n locked?: boolean;\n /** Shown or not, when the list has `onVisibilityChange`. Default true. */\n visible?: boolean;\n children?: LayerListItem[];\n /** Folds the body and children away until the user opens them.\n * `collapsedIds` overrides it. */\n defaultCollapsed?: boolean;\n}\n\n/**\n * A reorder: `ids`, siblings under `parentId` (`null` at the top level), now\n * sit as one block in list order starting at `index` of the parent's children.\n * A drag never moves a layer to another parent.\n */\nexport interface LayerMove {\n ids: string[];\n parentId: string | null;\n index: number;\n}\n\n/** Props for `<LayerList>`. */\nexport interface LayerListProps {\n items: LayerListItem[];\n /** Omit for a list the user cannot reorder. */\n onReorder?: (move: LayerMove) => void;\n /** Giving both makes rows selectable: a click selects one, shift-click adds\n * or removes one, and dragging a selected row drags its selected siblings. */\n selectedIds?: readonly string[];\n onSelect?: (ids: string[]) => void;\n /** Adds a visibility checkbox to every unlocked layer. */\n onVisibilityChange?: (id: string, visible: boolean) => void;\n /** Adds a remove button to every unlocked layer. */\n onRemove?: (id: string) => void;\n /** A layer's controls. Returning `null` leaves that layer a one-line row. */\n renderBody?: (item: LayerListItem) => ReactNode;\n /** Ids whose body and children are folded away. Giving it makes folding\n * controlled; `onCollapsedChange` fires either way. */\n collapsedIds?: readonly string[];\n onCollapsedChange?: (ids: string[]) => void;\n /** Heading above the list. */\n title?: string;\n /** Names the list for assistive tech when it has no `title`. */\n label?: string;\n /** Kinds the user can add, as buttons beside the title. Shown only with `onAdd`. */\n addKinds?: string[];\n onAdd?: (kind: string) => void;\n /** Shown in place of the list when `items` is empty. */\n empty?: ReactNode;\n /** Appended to the root's class list — the module's own names are hashed. */\n className?: string;\n}\n\n/** Returns `items` with `move` applied, for a consumer that holds the tree as state. */\nexport function moveLayers(items: LayerListItem[], move: LayerMove): LayerListItem[] {\n if (move.parentId === null) return reorder(items, move);\n return items.map((item) => {\n if (item.id === move.parentId) return { ...item, children: reorder(item.children ?? [], move) };\n return item.children ? { ...item, children: moveLayers(item.children, move) } : item;\n });\n}\n\nfunction reorder(siblings: LayerListItem[], { ids, index }: LayerMove): LayerListItem[] {\n const moving = new Set(ids);\n const kept = siblings.filter((it) => !moving.has(it.id));\n const moved = siblings.filter((it) => moving.has(it.id));\n const at = Math.max(0, Math.min(kept.length, index));\n return [...kept.slice(0, at), ...moved, ...kept.slice(at)];\n}\n\nfunction collectCollapsed(\n items: LayerListItem[],\n isCollapsed: (item: LayerListItem) => boolean,\n into = new Set<string>(),\n): Set<string> {\n for (const item of items) {\n if (isCollapsed(item)) into.add(item.id);\n if (item.children) collectCollapsed(item.children, isCollapsed, into);\n }\n return into;\n}\n\nfunction anyNested(items: LayerListItem[]): boolean {\n return items.some((it) => (it.children?.length ?? 0) > 0);\n}\n\n/** What every level of the tree reads from the list. */\ninterface Shared {\n props: LayerListProps;\n collapsed: ReadonlySet<string>;\n toggle: (id: string) => void;\n press: (item: LayerListItem, mods: PressModifiers) => void;\n uid: string;\n /** Some layer has children, so every row keeps a twisty column. */\n nested: boolean;\n /** Each layer's body, rendered once; a row where it is empty. */\n bodies: ReadonlyMap<string, ReactNode>;\n /** Per sibling group, keyed by parent: move a row one place, as its drag would. */\n nudgers: Map<string | null, (id: string, delta: -1 | 1) => boolean>;\n keys: RowKeys;\n}\n\n/** The roving focus over the rows: one row is in the tab order at a time. */\ninterface RowKeys {\n tabStop: string | undefined;\n register: (id: string) => (el: HTMLElement | null) => void;\n onKeyDown: (item: LayerListItem, parentId: string | null) => (e: KeyboardEvent<HTMLElement>) => void;\n onFocus: (id: string) => void;\n}\n\ninterface VisibleRow {\n item: LayerListItem;\n parentId: string | null;\n level: number;\n}\n\nfunction hasBody(body: ReactNode): boolean {\n return body !== null && body !== undefined && body !== false;\n}\n\n/**\n * An ordered, drag-reorderable list of layers — a document's objects, a lab's\n * canvases, a fill's effects. Each layer is a one-line row or, with a body, a\n * card whose controls fold away; either can nest child layers. Selection,\n * visibility, removal and an add palette are each on when their handler is given.\n */\nexport function LayerList(props: LayerListProps) {\n const { items, collapsedIds, onCollapsedChange, title, addKinds, onAdd, empty, className } = props;\n // Only the user's own toggles are state; everything else reads the layer's\n // `defaultCollapsed`, so a layer added later starts the way it asks to.\n const [toggled, setToggled] = useState<ReadonlyMap<string, boolean>>(new Map());\n const collapsed = collapsedIds\n ? new Set(collapsedIds)\n : collectCollapsed(items, (item) => toggled.get(item.id) ?? item.defaultCollapsed === true);\n const toggle = (id: string) => {\n const next = new Set(collapsed);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n if (!collapsedIds) setToggled((prev) => new Map(prev).set(id, next.has(id)));\n onCollapsedChange?.([...next]);\n };\n\n // The press arrives after the drag session ends, which can be after a render\n // the closure did not see, so it reads the selection from here.\n const live = useLatest(props);\n const press = (item: LayerListItem, mods: PressModifiers) => {\n const { selectedIds = [], onSelect } = live.current;\n if (!onSelect) return;\n anchor.current = item.id;\n if (item.locked || !mods.shiftKey) {\n onSelect([item.id]);\n return;\n }\n // A leftover locked selection does not carry into a multi-selection.\n const locked = new Set(lockedIds(live.current.items));\n const kept = selectedIds.filter((id) => !locked.has(id));\n onSelect(kept.includes(item.id) ? kept.filter((id) => id !== item.id) : [...kept, item.id]);\n };\n\n const bodies = new Map<string, ReactNode>();\n const collectBodies = (list: LayerListItem[]) => {\n for (const it of list) {\n bodies.set(it.id, props.renderBody ? props.renderBody(it) : null);\n if (it.children) collectBodies(it.children);\n }\n };\n collectBodies(items);\n\n // The rows the arrow keys walk: open branches only, and not cards, whose\n // heads are their own controls.\n const visible: VisibleRow[] = [];\n const walk = (list: LayerListItem[], parentId: string | null, level: number) => {\n for (const item of list) {\n if (!hasBody(bodies.get(item.id))) visible.push({ item, parentId, level });\n if (item.children?.length && !collapsed.has(item.id)) walk(item.children, item.id, level + 1);\n }\n };\n walk(items, null, 1);\n\n const rowEls = useRef(new Map<string, HTMLElement>());\n const [focusId, setFocusId] = useState<string | null>(null);\n const pendingFocus = useRef<string | null>(null);\n const anchor = useRef<string | null>(null);\n const nudgers = useRef(new Map<string | null, (id: string, delta: -1 | 1) => boolean>()).current;\n useLayoutEffect(() => {\n const id = pendingFocus.current;\n if (id === null) return;\n pendingFocus.current = null;\n const el = rowEls.current.get(id);\n if (el && document.activeElement !== el) el.focus();\n });\n\n const selectedIds = props.selectedIds ?? [];\n const tabStop = visible.some((v) => v.item.id === focusId)\n ? (focusId ?? undefined)\n : (visible.find((v) => selectedIds.includes(v.item.id)) ?? visible[0])?.item.id;\n\n const focusRow = (id: string) => {\n pendingFocus.current = id;\n setFocusId(id);\n };\n // Locked rows never join a multi-selection, so a range passes over them.\n const selectRange = (to: string) => {\n const from = anchor.current ?? to;\n const a = visible.findIndex((v) => v.item.id === from);\n const b = visible.findIndex((v) => v.item.id === to);\n if (a < 0 || b < 0) return;\n const range = visible\n .slice(Math.min(a, b), Math.max(a, b) + 1)\n .filter((v) => !v.item.locked)\n .map((v) => v.item.id);\n if (range.length > 0) live.current.onSelect?.(range);\n };\n\n const keys: RowKeys = {\n tabStop,\n register: (id) => (el) => {\n if (el) rowEls.current.set(id, el);\n else rowEls.current.delete(id);\n },\n onFocus: (id) => {\n setFocusId(id);\n anchor.current ??= id;\n },\n onKeyDown: (item, parentId) => (e) => {\n // Keys on a control inside the row are the control's.\n if (e.target !== e.currentTarget) return;\n const at = visible.findIndex((v) => v.item.id === item.id);\n const go = (index: number) => {\n const next = visible[Math.max(0, Math.min(visible.length - 1, index))];\n if (!next) return;\n focusRow(next.item.id);\n if (e.shiftKey && props.onSelect) selectRange(next.item.id);\n else anchor.current = next.item.id;\n };\n const branch = (item.children?.length ?? 0) > 0;\n const open = branch && !collapsed.has(item.id);\n switch (e.key) {\n case 'ArrowDown':\n case 'ArrowUp': {\n const delta = e.key === 'ArrowDown' ? 1 : -1;\n if (e.altKey) {\n if (nudgers.get(parentId)?.(item.id, delta)) pendingFocus.current = item.id;\n } else {\n go(at + delta);\n }\n break;\n }\n case 'Home':\n go(0);\n break;\n case 'End':\n go(visible.length - 1);\n break;\n case 'ArrowRight':\n if (branch && !open) toggle(item.id);\n else if (open) go(at + 1);\n else return;\n break;\n case 'ArrowLeft':\n if (open) toggle(item.id);\n else if (parentId !== null) go(visible.findIndex((v) => v.item.id === parentId));\n else return;\n break;\n case 'Enter':\n case ' ':\n press(item, { shiftKey: e.shiftKey, ctrlKey: e.ctrlKey, metaKey: e.metaKey, altKey: e.altKey });\n break;\n default:\n return;\n }\n e.preventDefault();\n },\n };\n\n const uid = useId();\n const shared: Shared = { props, collapsed, toggle, press, uid, nested: anyNested(items), bodies, nudgers, keys };\n const palette = onAdd ? (addKinds ?? []) : [];\n const cls = className ? `${s.root} ${className}` : s.root;\n\n return (\n <div className={cls}>\n {(title !== undefined || palette.length > 0) && (\n <div className={s.head}>\n {title !== undefined && <h2 className={s.title}>{title}</h2>}\n <div className={s.palette}>\n {palette.map((kind) => (\n <button\n key={kind}\n type=\"button\"\n className={s.add}\n aria-label={`Add ${kind}`}\n onClick={() => {\n dlog('layer-list', 'onAdd', { kind });\n onAdd?.(kind);\n }}\n >\n {kind}\n </button>\n ))}\n </div>\n </div>\n )}\n {items.length === 0 ? (\n <div className={rows.empty}>\n {empty ?? (palette.length > 0 ? 'No layers — add one above.' : 'No layers.')}\n </div>\n ) : (\n <Level items={items} parentId={null} shared={shared} path=\"\" level={1} />\n )}\n </div>\n );\n}\n\nfunction lockedIds(items: LayerListItem[]): string[] {\n const out: string[] = [];\n for (const it of items) {\n if (it.locked) out.push(it.id);\n if (it.children) out.push(...lockedIds(it.children));\n }\n return out;\n}\n\ninterface LevelProps {\n items: LayerListItem[];\n parentId: string | null;\n shared: Shared;\n path: string;\n level: number;\n}\n\n/** One sibling group. Each has its own drag, so a drag stays under one parent. */\nfunction Level({ items, parentId, shared, path, level }: LevelProps) {\n const { props } = shared;\n const selected = props.selectedIds ?? [];\n const drag = useReorderDragList({\n // With no `onReorder` every row is a locked one to the drag, so a press\n // still reaches `onPress` and nothing ever engages.\n items: items.map((it) => ({ id: it.id, label: it.label, locked: it.locked || !props.onReorder })),\n selectedIds: items.filter((it) => selected.includes(it.id)).map((it) => it.id),\n onReorder: (ids, target) => {\n const moving = new Set(ids);\n const inOrder = items.filter((it) => moving.has(it.id)).map((it) => it.id);\n // The hook's index counts the dragged rows; a move's index does not.\n const before = items.slice(0, target).filter((it) => moving.has(it.id)).length;\n const move = { ids: inOrder, parentId, index: target - before };\n dlog('layer-list', 'onReorder', move);\n props.onReorder?.(move);\n },\n onPress: (id, mods) => {\n const item = items.find((it) => it.id === id);\n if (item) shared.press(item, mods);\n },\n });\n shared.nudgers.set(parentId, (id, delta) =>\n drag.nudge(id, items.findIndex((it) => it.id === id), delta),\n );\n const [el, setEl] = useState<HTMLDivElement | null>(null);\n const bind = (node: HTMLDivElement | null) => {\n setEl(node);\n (drag.containerProps.ref as RefCallback<HTMLDivElement>)(node);\n };\n\n const bodies = items.map((it) => shared.bodies.get(it.id));\n const cards = bodies.some(hasBody);\n const { draggedIds, targetIndex, ghost } = drag.state;\n\n const entry = (item: LayerListItem, i: number, ghosted: boolean) => {\n const body = bodies[i];\n const kids = item.children ?? [];\n const open = !shared.collapsed.has(item.id);\n const childId = `${shared.uid}${path}-${i}`;\n const onPointerDown = ghosted ? undefined : drag.rowProps(item.id, i).onPointerDown;\n const isSelected = selected.includes(item.id);\n const cls = [s.entry];\n if (!ghosted) {\n if (draggedIds?.includes(item.id)) cls.push(s.dragging);\n if (targetIndex === i && !draggedIds?.includes(item.id)) cls.push(s.dropBefore);\n if (targetIndex === items.length && i === items.length - 1) cls.push(s.dropAfter);\n }\n const controls = <Controls item={item} shared={shared} />;\n const head =\n hasBody(body) ? (\n <PropertyGroup\n className={s.card}\n title={item.title ?? item.label}\n {...(item.tone === undefined ? {} : { tone: item.tone })}\n collapsed={ghosted || !open}\n onCollapsedChange={() => shared.toggle(item.id)}\n leading={\n <Handle\n item={item}\n reorder={props.onReorder !== undefined}\n onPointerDown={onPointerDown}\n onNudge={(delta) => shared.nudgers.get(parentId)?.(item.id, delta)}\n />\n }\n actions={ghosted ? undefined : controls}\n >\n {ghosted ? null : body}\n </PropertyGroup>\n ) : (\n <div\n className={[rows.row, s.row, isSelected ? rows.selected : ''].filter(Boolean).join(' ')}\n data-selected={isSelected ? 'true' : undefined}\n data-locked={item.locked ? 'true' : undefined}\n onPointerDown={onPointerDown}\n {...(ghosted\n ? {}\n : {\n role: 'row',\n 'aria-level': level,\n ...(kids.length > 0 ? { 'aria-expanded': open } : {}),\n ...(props.onSelect ? { 'aria-selected': isSelected } : {}),\n tabIndex: shared.keys.tabStop === item.id ? 0 : -1,\n ref: shared.keys.register(item.id),\n onKeyDown: shared.keys.onKeyDown(item, parentId),\n onFocus: () => shared.keys.onFocus(item.id),\n })}\n >\n {shared.nested &&\n (kids.length > 0 ? (\n <span className={s.stop} onPointerDown={stop}>\n <Disclosure\n open={open}\n onToggle={() => shared.toggle(item.id)}\n label={`${item.label} sublayers`}\n controls={childId}\n />\n </span>\n ) : (\n <span className={s.twistyGap} />\n ))}\n <Grip item={item} reorder={props.onReorder !== undefined} />\n {props.onVisibilityChange &&\n (item.locked ? (\n <span className={s.check} />\n ) : (\n <input\n className={s.check}\n type=\"checkbox\"\n checked={item.visible !== false}\n onPointerDown={stop}\n onChange={(e) => props.onVisibilityChange?.(item.id, e.target.checked)}\n aria-label={`Show ${item.label}`}\n />\n ))}\n {item.leading}\n <span className={rows.label}>{item.title ?? item.label}</span>\n {ghosted ? null : <Remove item={item} shared={shared} />}\n </div>\n );\n return (\n <div key={item.id} role=\"none\" className={cls.join(' ')} data-selected={isSelected ? 'true' : undefined}>\n {head}\n {!ghosted && kids.length > 0 && open && (\n <div id={childId} role=\"rowgroup\" className={s.children}>\n <Level items={kids} parentId={item.id} shared={shared} path={`${path}-${i}`} level={level + 1} />\n </div>\n )}\n </div>\n );\n };\n\n return (\n <>\n {cards ? (\n <PropertyList ref={bind} className={`${s.level} ${s.cards}`}>\n {items.map((it, i) => entry(it, i, false))}\n </PropertyList>\n ) : (\n <div\n ref={bind}\n className={s.level}\n {...(parentId === null\n ? {\n role: 'treegrid',\n 'aria-label': props.label ?? props.title ?? 'Layers',\n ...(props.onSelect ? { 'aria-multiselectable': true } : {}),\n }\n : { role: 'none' })}\n >\n {items.map((it, i) => entry(it, i, false))}\n </div>\n )}\n {ghost && el ? (\n <DragGhost at={ghost} from={el}>\n {items.map((it, i) => (ghost.ids.includes(it.id) ? entry(it, i, true) : null))}\n </DragGhost>\n ) : null}\n </>\n );\n}\n\n// Keeps a press on a control inside a row from also starting the row's press.\nconst stop = (e: { stopPropagation(): void }) => e.stopPropagation();\n\n/** A row's grip: decoration, since the whole row takes the drag. */\nfunction Grip({ item, reorder }: { item: LayerListItem; reorder: boolean }) {\n if (item.locked) {\n return (\n <span className={s.lock} role=\"img\" aria-label=\"Locked\">\n <LockIcon size={12} />\n </span>\n );\n }\n if (!reorder && item.badge === undefined) return null;\n return (\n <span className={s.grip} aria-hidden=\"true\">\n {item.badge ?? <DragGrip size={13} />}\n </span>\n );\n}\n\n/** A card's handle: the drag target, since the rest of its head folds it, and\n * Alt+Up/Down on it moves the card. */\nfunction Handle({\n item,\n reorder,\n onPointerDown,\n onNudge,\n}: {\n item: LayerListItem;\n reorder: boolean;\n onPointerDown: ((e: React.PointerEvent) => void) | undefined;\n onNudge: (delta: -1 | 1) => void;\n}) {\n if (item.locked) return <Grip item={item} reorder={reorder} />;\n if (!reorder && item.badge === undefined) return undefined;\n return (\n <button\n type=\"button\"\n className={s.handle}\n aria-label={`Drag to reorder ${item.label}`}\n aria-keyshortcuts=\"Alt+ArrowUp Alt+ArrowDown\"\n onPointerDown={onPointerDown}\n onKeyDown={(e) => {\n if (!e.altKey || (e.key !== 'ArrowUp' && e.key !== 'ArrowDown')) return;\n e.preventDefault();\n onNudge(e.key === 'ArrowUp' ? -1 : 1);\n }}\n >\n {item.badge ?? <DragGrip />}\n </button>\n );\n}\n\nfunction Controls({ item, shared }: { item: LayerListItem; shared: Shared }) {\n const { onVisibilityChange } = shared.props;\n return (\n <>\n {onVisibilityChange && !item.locked && (\n <input\n className={s.check}\n type=\"checkbox\"\n checked={item.visible !== false}\n onClick={stop}\n onChange={(e) => onVisibilityChange(item.id, e.target.checked)}\n aria-label={`Show ${item.label}`}\n />\n )}\n <Remove item={item} shared={shared} />\n </>\n );\n}\n\nfunction Remove({ item, shared }: { item: LayerListItem; shared: Shared }) {\n const { onRemove } = shared.props;\n if (!onRemove || item.locked) return null;\n return (\n <span className={s.stop} onPointerDown={stop}>\n <CloseButton ariaLabel={`Remove ${item.label}`} onClick={() => onRemove(item.id)} />\n </span>\n );\n}\n","import { asNodeId, createMoveToIndexOp, dispatchApplyBatch } from '@weasel-js/core';\nimport type { NodeId, Scene, SceneNode, SelectionApi } from '@weasel-js/core';\nimport type { LayerListItem, LayerListProps } from './LayerList';\n\n/** Arguments for {@link useSceneLayerList}. */\nexport interface UseSceneLayerListArgs<TData, TLayer extends string, TPose> {\n scene: Scene<TData, TLayer, TPose>;\n selection: SelectionApi;\n /** What the reorder op is dispatched against — the adapter `<SceneCanvas>` uses. */\n adapter: object;\n /** A row's content for a node: its label, a leading swatch or icon, whether it is locked. */\n itemFor: (node: SceneNode<TData, TLayer, TPose>) => Omit<LayerListItem, 'id' | 'children'>;\n /** Label of the reorder's history entry. Default `'Reorder'`. */\n label?: string;\n}\n\n/**\n * The props that put a scene in a `<LayerList>`: a row per node, a container's\n * children nested under it, the front-most at the top, selection read from and\n * written to `selection`, and a drag dispatched as a `MoveToIndexOp`.\n */\nexport function useSceneLayerList<TData, TLayer extends string, TPose>(\n args: UseSceneLayerListArgs<TData, TLayer, TPose>,\n): Required<Pick<LayerListProps, 'items' | 'selectedIds' | 'onSelect' | 'onReorder'>> {\n const { scene, selection, adapter, itemFor, label = 'Reorder' } = args;\n\n // A scene orders children back to front and the list front to back. Rebuilt\n // every render: the scene keeps its identity across a reorder, so a memo keyed\n // on it would serve the order from before.\n const build = (ids: readonly NodeId[]): LayerListItem[] =>\n [...ids].reverse().flatMap((id) => {\n const node = scene.get(id);\n if (!node) return [];\n const kids = scene.childrenOf(id);\n return [{ ...itemFor(node), id, ...(kids.length > 0 ? { children: build(kids) } : {}) }];\n });\n\n return {\n items: build(scene.roots),\n selectedIds: selection.current.map(String),\n onSelect: (ids) => selection.set(ids.map(asNodeId)),\n onReorder: ({ ids, parentId, index }) => {\n const siblings = parentId === null ? scene.roots : scene.childrenOf(asNodeId(parentId));\n // `index` counts from the front among the siblings the drag left behind.\n const back = siblings.length - ids.length - index;\n dispatchApplyBatch(adapter, [createMoveToIndexOp({ ids, parentId, index: Math.max(0, back) })], label);\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"LayerList-BXTAjK-4.js","names":["stop","s","rows","_Fragment","dlog","LockIcon","CloseButton","useReorderDragList","Disclosure","DragGhost","DragGrip","ItemList_module_default","PropertyGroup","PropertyList","LayerList_module_default","moveLayers","move","reorder","items","item","moving","ids","kept","siblings","it","moved","at","index","collectCollapsed","isCollapsed","into","anyNested","hasBody","body","LayerList","props","toggled","setToggled","useState","collapsed","collapsedIds","toggle","next","id","prev","onCollapsedChange","live","useLatest","press","onSelect","mods","locked","lockedIds","selectedIds","bodies","collectBodies","list","visible","walk","level","rowEls","useRef","focusId","setFocusId","pendingFocus","anchor","nudgers","el","tabStop","v","focusRow","selectRange","from","to","a","b","range","keys","e","go","branch","open","delta","parentId","shared","useId","palette","onAdd","addKinds","cls","className","title","kind","empty","Level","out","selected","drag","target","setEl","bind","node","cards","entry","i","kids","childId","path","onPointerDown","ghosted","isSelected","draggedIds","targetIndex","controls","Controls","head","Handle","stop","Grip","Remove","Fragment","ghost","onNudge","onVisibilityChange","onRemove","useSceneLayerList","args","build","scene","itemFor","selection","asNodeId","back","adapter","createMoveToIndexOp","label"],"sources":["../../src/components/LayerList/LayerList.module.css","../../src/components/LayerList/LayerList.tsx","../../src/components/LayerList/useSceneLayerList.ts"],"sourcesContent":["/* Row box, hover and selection are `ItemList`'s, so a layer list and the\n history list stacked under it agree. What is here is only what a layer has. */\n.root,\n.root * {\n box-sizing: border-box;\n}\n\n.root {\n display: grid;\n gap: var(--wzl-space-4);\n}\n\n.head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--wzl-space-4);\n}\n\n.title {\n margin: 0;\n font-family: var(--wzl-font-display);\n font-weight: var(--wzl-font-weight-light);\n font-size: var(--wzl-font-size-lg);\n color: var(--wzl-fg);\n}\n\n.palette {\n display: flex;\n gap: var(--wzl-space-2);\n}\n\n.add {\n font: inherit;\n height: var(--wzl-control-h);\n background: transparent;\n border: 1px dashed var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n padding: var(--wzl-space-1) var(--wzl-space-4);\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg-subtle);\n cursor: pointer;\n}\n\n.add:hover {\n background: var(--wzl-surface-raised);\n color: var(--wzl-fg);\n}\n\n.level {\n display: flex;\n flex-direction: column;\n gap: 1px;\n position: relative;\n width: 100%;\n}\n\n/* A level of cards is a `PropertyList`, so the controls in each body take the\n panel's density; its rows are spaced like cards rather than lines. */\n.level.cards {\n display: grid;\n row-gap: var(--wzl-space-3);\n}\n\n.entry {\n min-width: 0;\n}\n\n.row {\n user-select: none;\n}\n\n.row:focus-visible {\n outline: 2px solid var(--wzl-focus-ring);\n outline-offset: -2px;\n}\n\n/* A child's twisty sits under its parent's grip. */\n.children {\n padding-left: calc(var(--wzl-disclosure-target, var(--wzl-control-h-sm)) + var(--wzl-space-4));\n}\n\n/* The layer the drag picked up holds its place, faded, while its ghost follows the pointer. */\n.dragging {\n opacity: 0.5;\n}\n\n/* Where the drop would land: a line on the edge of the layer it would land against. */\n.dropBefore {\n box-shadow: 0 -2px 0 var(--wzl-accent);\n}\n\n.dropAfter {\n box-shadow: 0 2px 0 var(--wzl-accent);\n}\n\n/* One width for the grip and the lock, so a locked row's label lines up. */\n.grip,\n.lock {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: 0 0 auto;\n min-width: 13px;\n color: var(--wzl-fg-muted);\n}\n\n.row:not([data-locked]) {\n cursor: grab;\n}\n\n.row:not([data-locked]):active {\n cursor: grabbing;\n}\n\n.stop {\n display: inline-flex;\n align-items: center;\n flex: 0 0 auto;\n}\n\n/* Holds the twisty column open on rows with nothing to expand, so labels line up down a level. */\n.twistyGap {\n flex: 0 0 auto;\n width: var(--wzl-disclosure-target, var(--wzl-control-h-sm));\n}\n\n/* Sized, so the empty column a locked row keeps is the checkbox's width. */\n.check {\n flex: 0 0 auto;\n width: 13px;\n height: 13px;\n margin: 0;\n accent-color: var(--wzl-accent);\n}\n\n.handle {\n font: inherit;\n background: transparent;\n border: 0;\n color: var(--wzl-fg-subtle);\n padding: var(--wzl-space-1) var(--wzl-space-2);\n cursor: grab;\n touch-action: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: var(--wzl-icon-button-size);\n height: var(--wzl-icon-button-size);\n border-radius: var(--wzl-radius-sm);\n font-weight: var(--wzl-font-weight-bold);\n font-size: var(--wzl-font-size);\n}\n\n.card[data-tone] .handle {\n background: var(--wzl-tone);\n color: var(--wzl-fg-on-accent);\n}\n\n.handle:active {\n cursor: grabbing;\n}\n\n.entry[data-selected] > .card {\n outline: 2px solid var(--wzl-accent);\n outline-offset: -1px;\n}\n","import {\n type KeyboardEvent,\n type ReactNode,\n type RefCallback,\n useId,\n useLayoutEffect,\n useRef,\n useState,\n} from 'react';\nimport { useLatest } from '@weasel-js/core';\nimport { dlog } from '../../dlog';\nimport { LockIcon } from '../../icons';\nimport { type PressModifiers, useReorderDragList } from '../../useReorderDragList';\nimport { CloseButton } from '../CloseButton';\nimport { Disclosure } from '../Disclosure';\nimport { DragGhost } from '../DragGhost';\nimport { DragGrip } from '../DragGrip';\nimport rows from '../ItemList/ItemList.module.css';\nimport { PropertyGroup, PropertyList } from '../Properties';\nimport type { StanceProps } from '../stance';\nimport s from './LayerList.module.css';\n\n/** One layer. With a body (see `renderBody`) it draws as a card that folds its\n * body away; without one, as a one-line row. Either can hold child layers. */\nexport interface LayerListItem {\n id: string;\n /** The layer's name. Also what its controls are labeled by. */\n label: string;\n /** Shown in the header in place of `label` — a select that switches the\n * layer's kind, say. */\n title?: ReactNode;\n /** Fixed-size content before the label: a color swatch, a kind icon. */\n leading?: ReactNode;\n /** Drawn in the drag handle in place of the grip — an ordinal. */\n badge?: ReactNode;\n /** Which of its list this layer is: an index into the theme's tone list, or\n * a color. A card draws its edge and handle in it and recolors its controls. */\n tone?: StanceProps['tone'];\n /** Cannot be dragged, hidden or removed, walls off drops on either side, and\n * selects alone rather than joining a multi-selection. */\n locked?: boolean;\n /** Shown or not, when the list has `onVisibilityChange`. Default true. */\n visible?: boolean;\n children?: LayerListItem[];\n /** Folds the body and children away until the user opens them.\n * `collapsedIds` overrides it. */\n defaultCollapsed?: boolean;\n}\n\n/**\n * A reorder: `ids`, siblings under `parentId` (`null` at the top level), now\n * sit as one block in list order starting at `index` of the parent's children.\n * A drag never moves a layer to another parent.\n */\nexport interface LayerMove {\n ids: string[];\n parentId: string | null;\n index: number;\n}\n\n/** Props for `<LayerList>`. */\nexport interface LayerListProps {\n items: LayerListItem[];\n /** Omit for a list the user cannot reorder. */\n onReorder?: (move: LayerMove) => void;\n /** Giving both makes rows selectable: a click selects one, shift-click adds\n * or removes one, and dragging a selected row drags its selected siblings. */\n selectedIds?: readonly string[];\n onSelect?: (ids: string[]) => void;\n /** Adds a visibility checkbox to every unlocked layer. */\n onVisibilityChange?: (id: string, visible: boolean) => void;\n /** Adds a remove button to every unlocked layer. */\n onRemove?: (id: string) => void;\n /** A layer's controls. Returning `null` leaves that layer a one-line row. */\n renderBody?: (item: LayerListItem) => ReactNode;\n /** Ids whose body and children are folded away. Giving it makes folding\n * controlled; `onCollapsedChange` fires either way. */\n collapsedIds?: readonly string[];\n onCollapsedChange?: (ids: string[]) => void;\n /** Heading above the list. */\n title?: string;\n /** Names the list for assistive tech when it has no `title`. */\n label?: string;\n /** Kinds the user can add, as buttons beside the title. Shown only with `onAdd`. */\n addKinds?: string[];\n onAdd?: (kind: string) => void;\n /** Shown in place of the list when `items` is empty. */\n empty?: ReactNode;\n /** Appended to the root's class list — the module's own names are hashed. */\n className?: string;\n}\n\n/** Returns `items` with `move` applied, for a consumer that holds the tree as state. */\nexport function moveLayers(items: LayerListItem[], move: LayerMove): LayerListItem[] {\n if (move.parentId === null) return reorder(items, move);\n return items.map((item) => {\n if (item.id === move.parentId) return { ...item, children: reorder(item.children ?? [], move) };\n return item.children ? { ...item, children: moveLayers(item.children, move) } : item;\n });\n}\n\nfunction reorder(siblings: LayerListItem[], { ids, index }: LayerMove): LayerListItem[] {\n const moving = new Set(ids);\n const kept = siblings.filter((it) => !moving.has(it.id));\n const moved = siblings.filter((it) => moving.has(it.id));\n const at = Math.max(0, Math.min(kept.length, index));\n return [...kept.slice(0, at), ...moved, ...kept.slice(at)];\n}\n\nfunction collectCollapsed(\n items: LayerListItem[],\n isCollapsed: (item: LayerListItem) => boolean,\n into = new Set<string>(),\n): Set<string> {\n for (const item of items) {\n if (isCollapsed(item)) into.add(item.id);\n if (item.children) collectCollapsed(item.children, isCollapsed, into);\n }\n return into;\n}\n\nfunction anyNested(items: LayerListItem[]): boolean {\n return items.some((it) => (it.children?.length ?? 0) > 0);\n}\n\n/** What every level of the tree reads from the list. */\ninterface Shared {\n props: LayerListProps;\n collapsed: ReadonlySet<string>;\n toggle: (id: string) => void;\n press: (item: LayerListItem, mods: PressModifiers) => void;\n uid: string;\n /** Some layer has children, so every row keeps a twisty column. */\n nested: boolean;\n /** Each layer's body, rendered once; a row where it is empty. */\n bodies: ReadonlyMap<string, ReactNode>;\n /** Per sibling group, keyed by parent: move a row one place, as its drag would. */\n nudgers: Map<string | null, (id: string, delta: -1 | 1) => boolean>;\n keys: RowKeys;\n}\n\n/** The roving focus over the rows: one row is in the tab order at a time. */\ninterface RowKeys {\n tabStop: string | undefined;\n register: (id: string) => (el: HTMLElement | null) => void;\n onKeyDown: (item: LayerListItem, parentId: string | null) => (e: KeyboardEvent<HTMLElement>) => void;\n onFocus: (id: string) => void;\n}\n\ninterface VisibleRow {\n item: LayerListItem;\n parentId: string | null;\n level: number;\n}\n\nfunction hasBody(body: ReactNode): boolean {\n return body !== null && body !== undefined && body !== false;\n}\n\n/**\n * An ordered, drag-reorderable list of layers — a document's objects, a lab's\n * canvases, a fill's effects. Each layer is a one-line row or, with a body, a\n * card whose controls fold away; either can nest child layers. Selection,\n * visibility, removal and an add palette are each on when their handler is given.\n */\nexport function LayerList(props: LayerListProps) {\n const { items, collapsedIds, onCollapsedChange, title, addKinds, onAdd, empty, className } = props;\n // Only the user's own toggles are state; everything else reads the layer's\n // `defaultCollapsed`, so a layer added later starts the way it asks to.\n const [toggled, setToggled] = useState<ReadonlyMap<string, boolean>>(new Map());\n const collapsed = collapsedIds\n ? new Set(collapsedIds)\n : collectCollapsed(items, (item) => toggled.get(item.id) ?? item.defaultCollapsed === true);\n const toggle = (id: string) => {\n const next = new Set(collapsed);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n if (!collapsedIds) setToggled((prev) => new Map(prev).set(id, next.has(id)));\n onCollapsedChange?.([...next]);\n };\n\n // The press arrives after the drag session ends, which can be after a render\n // the closure did not see, so it reads the selection from here.\n const live = useLatest(props);\n const press = (item: LayerListItem, mods: PressModifiers) => {\n const { selectedIds = [], onSelect } = live.current;\n if (!onSelect) return;\n anchor.current = item.id;\n if (item.locked || !mods.shiftKey) {\n onSelect([item.id]);\n return;\n }\n // A leftover locked selection does not carry into a multi-selection.\n const locked = new Set(lockedIds(live.current.items));\n const kept = selectedIds.filter((id) => !locked.has(id));\n onSelect(kept.includes(item.id) ? kept.filter((id) => id !== item.id) : [...kept, item.id]);\n };\n\n const bodies = new Map<string, ReactNode>();\n const collectBodies = (list: LayerListItem[]) => {\n for (const it of list) {\n bodies.set(it.id, props.renderBody ? props.renderBody(it) : null);\n if (it.children) collectBodies(it.children);\n }\n };\n collectBodies(items);\n\n // The rows the arrow keys walk: open branches only, and not cards, whose\n // heads are their own controls.\n const visible: VisibleRow[] = [];\n const walk = (list: LayerListItem[], parentId: string | null, level: number) => {\n for (const item of list) {\n if (!hasBody(bodies.get(item.id))) visible.push({ item, parentId, level });\n if (item.children?.length && !collapsed.has(item.id)) walk(item.children, item.id, level + 1);\n }\n };\n walk(items, null, 1);\n\n const rowEls = useRef(new Map<string, HTMLElement>());\n const [focusId, setFocusId] = useState<string | null>(null);\n const pendingFocus = useRef<string | null>(null);\n const anchor = useRef<string | null>(null);\n const nudgers = useRef(new Map<string | null, (id: string, delta: -1 | 1) => boolean>()).current;\n useLayoutEffect(() => {\n const id = pendingFocus.current;\n if (id === null) return;\n pendingFocus.current = null;\n const el = rowEls.current.get(id);\n if (el && document.activeElement !== el) el.focus();\n });\n\n const selectedIds = props.selectedIds ?? [];\n const tabStop = visible.some((v) => v.item.id === focusId)\n ? (focusId ?? undefined)\n : (visible.find((v) => selectedIds.includes(v.item.id)) ?? visible[0])?.item.id;\n\n const focusRow = (id: string) => {\n pendingFocus.current = id;\n setFocusId(id);\n };\n // Locked rows never join a multi-selection, so a range passes over them.\n const selectRange = (to: string) => {\n const from = anchor.current ?? to;\n const a = visible.findIndex((v) => v.item.id === from);\n const b = visible.findIndex((v) => v.item.id === to);\n if (a < 0 || b < 0) return;\n const range = visible\n .slice(Math.min(a, b), Math.max(a, b) + 1)\n .filter((v) => !v.item.locked)\n .map((v) => v.item.id);\n if (range.length > 0) live.current.onSelect?.(range);\n };\n\n const keys: RowKeys = {\n tabStop,\n register: (id) => (el) => {\n if (el) rowEls.current.set(id, el);\n else rowEls.current.delete(id);\n },\n onFocus: (id) => {\n setFocusId(id);\n anchor.current ??= id;\n },\n onKeyDown: (item, parentId) => (e) => {\n // Keys on a control inside the row are the control's.\n if (e.target !== e.currentTarget) return;\n const at = visible.findIndex((v) => v.item.id === item.id);\n const go = (index: number) => {\n const next = visible[Math.max(0, Math.min(visible.length - 1, index))];\n if (!next) return;\n focusRow(next.item.id);\n if (e.shiftKey && props.onSelect) selectRange(next.item.id);\n else anchor.current = next.item.id;\n };\n const branch = (item.children?.length ?? 0) > 0;\n const open = branch && !collapsed.has(item.id);\n switch (e.key) {\n case 'ArrowDown':\n case 'ArrowUp': {\n const delta = e.key === 'ArrowDown' ? 1 : -1;\n if (e.altKey) {\n if (nudgers.get(parentId)?.(item.id, delta)) pendingFocus.current = item.id;\n } else {\n go(at + delta);\n }\n break;\n }\n case 'Home':\n go(0);\n break;\n case 'End':\n go(visible.length - 1);\n break;\n case 'ArrowRight':\n if (branch && !open) toggle(item.id);\n else if (open) go(at + 1);\n else return;\n break;\n case 'ArrowLeft':\n if (open) toggle(item.id);\n else if (parentId !== null) go(visible.findIndex((v) => v.item.id === parentId));\n else return;\n break;\n case 'Enter':\n case ' ':\n press(item, { shiftKey: e.shiftKey, ctrlKey: e.ctrlKey, metaKey: e.metaKey, altKey: e.altKey });\n break;\n default:\n return;\n }\n e.preventDefault();\n },\n };\n\n const uid = useId();\n const shared: Shared = { props, collapsed, toggle, press, uid, nested: anyNested(items), bodies, nudgers, keys };\n const palette = onAdd ? (addKinds ?? []) : [];\n const cls = className ? `${s.root} ${className}` : s.root;\n\n return (\n <div className={cls}>\n {(title !== undefined || palette.length > 0) && (\n <div className={s.head}>\n {title !== undefined && <h2 className={s.title}>{title}</h2>}\n <div className={s.palette}>\n {palette.map((kind) => (\n <button\n key={kind}\n type=\"button\"\n className={s.add}\n aria-label={`Add ${kind}`}\n onClick={() => {\n dlog('layer-list', 'onAdd', { kind });\n onAdd?.(kind);\n }}\n >\n {kind}\n </button>\n ))}\n </div>\n </div>\n )}\n {items.length === 0 ? (\n <div className={rows.empty}>\n {empty ?? (palette.length > 0 ? 'No layers — add one above.' : 'No layers.')}\n </div>\n ) : (\n <Level items={items} parentId={null} shared={shared} path=\"\" level={1} />\n )}\n </div>\n );\n}\n\nfunction lockedIds(items: LayerListItem[]): string[] {\n const out: string[] = [];\n for (const it of items) {\n if (it.locked) out.push(it.id);\n if (it.children) out.push(...lockedIds(it.children));\n }\n return out;\n}\n\ninterface LevelProps {\n items: LayerListItem[];\n parentId: string | null;\n shared: Shared;\n path: string;\n level: number;\n}\n\n/** One sibling group. Each has its own drag, so a drag stays under one parent. */\nfunction Level({ items, parentId, shared, path, level }: LevelProps) {\n const { props } = shared;\n const selected = props.selectedIds ?? [];\n const drag = useReorderDragList({\n // With no `onReorder` every row is a locked one to the drag, so a press\n // still reaches `onPress` and nothing ever engages.\n items: items.map((it) => ({ id: it.id, label: it.label, locked: it.locked || !props.onReorder })),\n selectedIds: items.filter((it) => selected.includes(it.id)).map((it) => it.id),\n onReorder: (ids, target) => {\n const moving = new Set(ids);\n const inOrder = items.filter((it) => moving.has(it.id)).map((it) => it.id);\n // The hook's index counts the dragged rows; a move's index does not.\n const before = items.slice(0, target).filter((it) => moving.has(it.id)).length;\n const move = { ids: inOrder, parentId, index: target - before };\n dlog('layer-list', 'onReorder', move);\n props.onReorder?.(move);\n },\n onPress: (id, mods) => {\n const item = items.find((it) => it.id === id);\n if (item) shared.press(item, mods);\n },\n });\n shared.nudgers.set(parentId, (id, delta) =>\n drag.nudge(id, items.findIndex((it) => it.id === id), delta),\n );\n const [el, setEl] = useState<HTMLDivElement | null>(null);\n const bind = (node: HTMLDivElement | null) => {\n setEl(node);\n (drag.containerProps.ref as RefCallback<HTMLDivElement>)(node);\n };\n\n const bodies = items.map((it) => shared.bodies.get(it.id));\n const cards = bodies.some(hasBody);\n const { draggedIds, targetIndex, ghost } = drag.state;\n\n const entry = (item: LayerListItem, i: number, ghosted: boolean) => {\n const body = bodies[i];\n const kids = item.children ?? [];\n const open = !shared.collapsed.has(item.id);\n const childId = `${shared.uid}${path}-${i}`;\n const onPointerDown = ghosted ? undefined : drag.rowProps(item.id, i).onPointerDown;\n const isSelected = selected.includes(item.id);\n const cls = [s.entry];\n if (!ghosted) {\n if (draggedIds?.includes(item.id)) cls.push(s.dragging);\n if (targetIndex === i && !draggedIds?.includes(item.id)) cls.push(s.dropBefore);\n if (targetIndex === items.length && i === items.length - 1) cls.push(s.dropAfter);\n }\n const controls = <Controls item={item} shared={shared} />;\n const head =\n hasBody(body) ? (\n <PropertyGroup\n className={s.card}\n title={item.title ?? item.label}\n {...(item.tone === undefined ? {} : { tone: item.tone })}\n collapsed={ghosted || !open}\n onCollapsedChange={() => shared.toggle(item.id)}\n leading={\n <Handle\n item={item}\n reorder={props.onReorder !== undefined}\n onPointerDown={onPointerDown}\n onNudge={(delta) => shared.nudgers.get(parentId)?.(item.id, delta)}\n />\n }\n actions={ghosted ? undefined : controls}\n >\n {ghosted ? null : body}\n </PropertyGroup>\n ) : (\n <div\n className={[rows.row, s.row, isSelected ? rows.selected : ''].filter(Boolean).join(' ')}\n data-selected={isSelected ? 'true' : undefined}\n data-locked={item.locked ? 'true' : undefined}\n onPointerDown={onPointerDown}\n {...(ghosted\n ? {}\n : {\n role: 'row',\n 'aria-level': level,\n ...(kids.length > 0 ? { 'aria-expanded': open } : {}),\n ...(props.onSelect ? { 'aria-selected': isSelected } : {}),\n tabIndex: shared.keys.tabStop === item.id ? 0 : -1,\n ref: shared.keys.register(item.id),\n onKeyDown: shared.keys.onKeyDown(item, parentId),\n onFocus: () => shared.keys.onFocus(item.id),\n })}\n >\n {shared.nested &&\n (kids.length > 0 ? (\n <span className={s.stop} onPointerDown={stop}>\n <Disclosure\n open={open}\n onToggle={() => shared.toggle(item.id)}\n label={`${item.label} sublayers`}\n controls={childId}\n />\n </span>\n ) : (\n <span className={s.twistyGap} />\n ))}\n <Grip item={item} reorder={props.onReorder !== undefined} />\n {props.onVisibilityChange &&\n (item.locked ? (\n <span className={s.check} />\n ) : (\n <input\n className={s.check}\n type=\"checkbox\"\n checked={item.visible !== false}\n onPointerDown={stop}\n onChange={(e) => props.onVisibilityChange?.(item.id, e.target.checked)}\n aria-label={`Show ${item.label}`}\n />\n ))}\n {item.leading}\n <span className={rows.label}>{item.title ?? item.label}</span>\n {ghosted ? null : <Remove item={item} shared={shared} />}\n </div>\n );\n return (\n <div key={item.id} role=\"none\" className={cls.join(' ')} data-selected={isSelected ? 'true' : undefined}>\n {head}\n {!ghosted && kids.length > 0 && open && (\n <div id={childId} role=\"rowgroup\" className={s.children}>\n <Level items={kids} parentId={item.id} shared={shared} path={`${path}-${i}`} level={level + 1} />\n </div>\n )}\n </div>\n );\n };\n\n return (\n <>\n {cards ? (\n <PropertyList ref={bind} className={`${s.level} ${s.cards}`}>\n {items.map((it, i) => entry(it, i, false))}\n </PropertyList>\n ) : (\n <div\n ref={bind}\n className={s.level}\n {...(parentId === null\n ? {\n role: 'treegrid',\n 'aria-label': props.label ?? props.title ?? 'Layers',\n ...(props.onSelect ? { 'aria-multiselectable': true } : {}),\n }\n : { role: 'none' })}\n >\n {items.map((it, i) => entry(it, i, false))}\n </div>\n )}\n {ghost && el ? (\n <DragGhost at={ghost} from={el}>\n {items.map((it, i) => (ghost.ids.includes(it.id) ? entry(it, i, true) : null))}\n </DragGhost>\n ) : null}\n </>\n );\n}\n\n// Keeps a press on a control inside a row from also starting the row's press.\nconst stop = (e: { stopPropagation(): void }) => e.stopPropagation();\n\n/** A row's grip: decoration, since the whole row takes the drag. */\nfunction Grip({ item, reorder }: { item: LayerListItem; reorder: boolean }) {\n if (item.locked) {\n return (\n <span className={s.lock} role=\"img\" aria-label=\"Locked\">\n <LockIcon size={12} />\n </span>\n );\n }\n if (!reorder && item.badge === undefined) return null;\n return (\n <span className={s.grip} aria-hidden=\"true\">\n {item.badge ?? <DragGrip size={13} />}\n </span>\n );\n}\n\n/** A card's handle: the drag target, since the rest of its head folds it, and\n * Alt+Up/Down on it moves the card. */\nfunction Handle({\n item,\n reorder,\n onPointerDown,\n onNudge,\n}: {\n item: LayerListItem;\n reorder: boolean;\n onPointerDown: ((e: React.PointerEvent) => void) | undefined;\n onNudge: (delta: -1 | 1) => void;\n}) {\n if (item.locked) return <Grip item={item} reorder={reorder} />;\n if (!reorder && item.badge === undefined) return undefined;\n return (\n <button\n type=\"button\"\n className={s.handle}\n aria-label={`Drag to reorder ${item.label}`}\n aria-keyshortcuts=\"Alt+ArrowUp Alt+ArrowDown\"\n onPointerDown={onPointerDown}\n onKeyDown={(e) => {\n if (!e.altKey || (e.key !== 'ArrowUp' && e.key !== 'ArrowDown')) return;\n e.preventDefault();\n onNudge(e.key === 'ArrowUp' ? -1 : 1);\n }}\n >\n {item.badge ?? <DragGrip />}\n </button>\n );\n}\n\nfunction Controls({ item, shared }: { item: LayerListItem; shared: Shared }) {\n const { onVisibilityChange } = shared.props;\n return (\n <>\n {onVisibilityChange && !item.locked && (\n <input\n className={s.check}\n type=\"checkbox\"\n checked={item.visible !== false}\n onClick={stop}\n onChange={(e) => onVisibilityChange(item.id, e.target.checked)}\n aria-label={`Show ${item.label}`}\n />\n )}\n <Remove item={item} shared={shared} />\n </>\n );\n}\n\nfunction Remove({ item, shared }: { item: LayerListItem; shared: Shared }) {\n const { onRemove } = shared.props;\n if (!onRemove || item.locked) return null;\n return (\n <span className={s.stop} onPointerDown={stop}>\n <CloseButton ariaLabel={`Remove ${item.label}`} onClick={() => onRemove(item.id)} />\n </span>\n );\n}\n","import { asNodeId, createMoveToIndexOp, dispatchApplyBatch } from '@weasel-js/core';\nimport type { NodeId, Scene, SceneNode, SelectionApi } from '@weasel-js/core';\nimport type { LayerListItem, LayerListProps } from './LayerList';\n\n/** Arguments for {@link useSceneLayerList}. */\nexport interface UseSceneLayerListArgs<TData, TLayer extends string, TPose> {\n scene: Scene<TData, TLayer, TPose>;\n selection: SelectionApi;\n /** What the reorder op is dispatched against — the adapter `<SceneCanvas>` uses. */\n adapter: object;\n /** A row's content for a node: its label, a leading swatch or icon, whether it is locked. */\n itemFor: (node: SceneNode<TData, TLayer, TPose>) => Omit<LayerListItem, 'id' | 'children'>;\n /** Label of the reorder's history entry. Default `'Reorder'`. */\n label?: string;\n}\n\n/**\n * The props that put a scene in a `<LayerList>`: a row per node, a container's\n * children nested under it, the front-most at the top, selection read from and\n * written to `selection`, and a drag dispatched as a `MoveToIndexOp`.\n */\nexport function useSceneLayerList<TData, TLayer extends string, TPose>(\n args: UseSceneLayerListArgs<TData, TLayer, TPose>,\n): Required<Pick<LayerListProps, 'items' | 'selectedIds' | 'onSelect' | 'onReorder'>> {\n const { scene, selection, adapter, itemFor, label = 'Reorder' } = args;\n\n // A scene orders children back to front and the list front to back. Rebuilt\n // every render: the scene keeps its identity across a reorder, so a memo keyed\n // on it would serve the order from before.\n const build = (ids: readonly NodeId[]): LayerListItem[] =>\n [...ids].reverse().flatMap((id) => {\n const node = scene.get(id);\n if (!node) return [];\n const kids = scene.childrenOf(id);\n return [{ ...itemFor(node), id, ...(kids.length > 0 ? { children: build(kids) } : {}) }];\n });\n\n return {\n items: build(scene.roots),\n selectedIds: selection.current.map(String),\n onSelect: (ids) => selection.set(ids.map(asNodeId)),\n onReorder: ({ ids, parentId, index }) => {\n const siblings = parentId === null ? scene.roots : scene.childrenOf(asNodeId(parentId));\n // `index` counts from the front among the siblings the drag left behind.\n const back = siblings.length - ids.length - index;\n dispatchApplyBatch(adapter, [createMoveToIndexOp({ ids, parentId, index: Math.max(0, back) })], label);\n },\n };\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { t as e } from "./Button-XB-T75YL.js";
2
- import { t } from "./CloseButton-BmNmRVtb.js";
2
+ import { t } from "./CloseButton-wb7Or5Eg.js";
3
3
  import { t as n } from "./field.module-B27pWv0i.js";
4
4
  import { jsx as r, jsxs as i } from "react/jsx-runtime";
5
5
  import { useEffect as a, useRef as o, useState as s } from "react";
@@ -69,4 +69,4 @@ function l({ value: l, onChange: u, placeholder: d, empty: f = "None", addLabel:
69
69
  //#endregion
70
70
  export { l as t };
71
71
 
72
- //# sourceMappingURL=ListEditor-OQDzG4A5.js.map
72
+ //# sourceMappingURL=ListEditor-DGtwj99m.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ListEditor-OQDzG4A5.js","names":["s","f","Button","field_module_default","ListEditor_module_default","ListEditor","ariaLabel","inputs","useRef","focus","setFocus","useState","insert","onChange","value","at","remove","i","onKeyDown","name","className","empty","el","entry","j","v","CloseButton","addLabel"],"sources":["../../src/components/ListEditor/ListEditor.module.css","../../src/components/ListEditor/ListEditor.tsx"],"sourcesContent":[".editor {\n display: flex;\n flex-direction: column;\n gap: var(--wzl-space-4);\n min-width: min(28rem, calc(100vw - 64px));\n}\n\n.list {\n display: flex;\n flex-direction: column;\n gap: var(--wzl-space-2);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.entry {\n display: flex;\n align-items: center;\n gap: var(--wzl-space-2);\n}\n\n.input {\n flex: 1 1 auto;\n min-width: 0;\n padding: 0 var(--wzl-field-pad-x);\n}\n\n.empty {\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg-subtle);\n font-style: italic;\n}\n\n.actions {\n display: flex;\n}\n","import { useEffect, useRef, useState, type KeyboardEvent } from 'react';\nimport { Button } from '../Button';\nimport { CloseButton } from '../CloseButton';\nimport f from '../field.module.css';\nimport s from './ListEditor.module.css';\n\n/** Props for {@link ListEditor}. */\nexport interface ListEditorProps {\n value: readonly string[];\n /** Every edit, add and removal, with the whole list. Entries are written as\n * typed — an empty one stays until it is removed. */\n onChange: (next: string[]) => void;\n /** Shown in an empty entry. */\n placeholder?: string;\n /** Shown in place of the entries when there are none. */\n empty?: string;\n /** Text of the add button. */\n addLabel?: string;\n /** Names the list for assistive tech; each entry is named from it. */\n 'aria-label'?: string;\n className?: string;\n}\n\n/**\n * An editable list of strings: one field per entry, a remove button beside\n * each, and an add button under them. Enter in an entry adds one after it;\n * Backspace in an empty one removes it.\n */\nexport function ListEditor({\n value,\n onChange,\n placeholder,\n empty = 'None',\n addLabel = 'Add',\n 'aria-label': ariaLabel,\n className,\n}: ListEditorProps) {\n const inputs = useRef<(HTMLInputElement | null)[]>([]);\n const [focus, setFocus] = useState<number | null>(null);\n\n useEffect(() => {\n if (focus === null) return;\n inputs.current[focus]?.focus();\n setFocus(null);\n }, [focus]);\n\n const insert = (at: number): void => {\n onChange([...value.slice(0, at), '', ...value.slice(at)]);\n setFocus(at);\n };\n const remove = (at: number): void => {\n onChange(value.filter((_, i) => i !== at));\n if (value.length > 1) setFocus(Math.max(0, at - 1));\n };\n const onKeyDown = (e: KeyboardEvent<HTMLInputElement>, at: number): void => {\n if (e.key === 'Enter') {\n e.preventDefault();\n insert(at + 1);\n } else if (e.key === 'Backspace' && value[at] === '') {\n e.preventDefault();\n remove(at);\n }\n };\n const name = (i: number): string => `${ariaLabel ?? 'Entry'} ${i + 1}`;\n\n return (\n <div className={[s.editor, className].filter(Boolean).join(' ')} role=\"group\" aria-label={ariaLabel}>\n {value.length === 0 ? (\n <div className={s.empty}>{empty}</div>\n ) : (\n <ul className={s.list}>\n {value.map((entry, i) => (\n // Entries have no identity beyond their position, and two may be equal.\n <li key={i} className={s.entry}>\n <input\n ref={(el) => {\n inputs.current[i] = el;\n }}\n type=\"text\"\n className={`${f.frame} ${s.input}`}\n aria-label={name(i)}\n value={entry}\n placeholder={placeholder}\n onChange={(e) => onChange(value.map((v, j) => (j === i ? e.target.value : v)))}\n onKeyDown={(e) => onKeyDown(e, i)}\n />\n <CloseButton ariaLabel={`Remove ${name(i)}`} onClick={() => remove(i)} />\n </li>\n ))}\n </ul>\n )}\n <div className={s.actions}>\n <Button size=\"sm\" onClick={() => insert(value.length)}>\n {addLabel}\n </Button>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;AC4BA,SAAgBK,EAAW,EACzB,UACA,aACA,gBACA,WAAQ,QACR,cAAW,OACX,cAAcC,GACd,gBACkB;CAClB,IAAMC,IAASC,EAAoC,CAAC,CAAC,GAC/C,CAACC,GAAOC,KAAYC,EAAwB,IAAI;CAEtD,QAAgB;EACd,AAAIF,MAAU,SACdF,EAAO,QAAQE,EAAM,EAAE,MAAM,GAC7BC,EAAS,IAAI;CACf,GAAG,CAACD,CAAK,CAAC;CAEV,IAAMG,KAAU,MAAqB;EAEnC,AADAC,EAAS;GAAC,GAAGC,EAAM,MAAM,GAAGC,CAAE;GAAG;GAAI,GAAGD,EAAM,MAAMC,CAAE;EAAC,CAAC,GACxDL,EAASK,CAAE;CACb,GACMC,KAAU,MAAqB;EAEnC,AADAH,EAASC,EAAM,QAAQ,GAAG,MAAMG,MAAMF,CAAE,CAAC,GACrCD,EAAM,SAAS,KAAGJ,EAAS,KAAK,IAAI,GAAGK,IAAK,CAAC,CAAC;CACpD,GACMG,KAAa,GAAoC,MAAqB;EAC1E,AAAI,EAAE,QAAQ,WACZ,EAAE,eAAe,GACjBN,EAAOG,IAAK,CAAC,KACJ,EAAE,QAAQ,eAAeD,EAAMC,OAAQ,OAChD,EAAE,eAAe,GACjBC,EAAOD,CAAE;CAEb,GACMI,KAAQ,MAAsB,GAAGb,KAAa,QAAQ,GAAGW,IAAI;CAEnE,OACE,kBAAC,OAAD;EAAK,WAAW,CAACjB,EAAE,QAAQoB,CAAS,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;EAAG,MAAK;EAAQ,cAAYd;EAA1F,UAAA,CACGQ,EAAM,WAAW,IAChB,kBAAC,OAAD;GAAK,WAAWd,EAAE;GAAQ,UAAAqB;EAAW,CAAA,IAErC,kBAAC,MAAD;GAAI,WAAWrB,EAAE;GACd,UAAAc,EAAM,KAAK,GAAO,MAEjB,kBAAC,MAAD;IAAY,WAAWd,EAAE;IAAzB,UAAA,CACE,kBAAC,SAAD;KACE,MAAM,MAAO;MACX,EAAO,QAAQiB,KAAKK;KACtB;KACA,MAAK;KACL,WAAW,GAAGrB,EAAE,MAAM,GAAGD,EAAE;KAC3B,cAAYmB,EAAKF,CAAC;KAClB,OAAOM;KACM;KACb,WAAW,MAAMV,EAASC,EAAM,KAAK,GAAG,MAAOU,MAAMP,IAAI,EAAE,OAAO,QAAQQ,CAAE,CAAC;KAC7E,YAAY,MAAMP,EAAU,GAAGD,CAAC;IACjC,CAAA,GACD,kBAACS,GAAD;KAAa,WAAW,UAAUP,EAAKF,CAAC;KAAK,eAAeD,EAAOC,CAAC;IAAI,CAAA,CACtE;GAdK,GAAAA,CAcL,CACL;EACC,CAAA,GAEN,kBAAC,OAAD;GAAK,WAAWjB,EAAE;GAChB,UAAA,kBAACE,GAAD;IAAQ,MAAK;IAAK,eAAeU,EAAOE,EAAM,MAAM;IACjD,UAAAa;GACK,CAAA;EACL,CAAA,CACF;;AAET"}
1
+ {"version":3,"file":"ListEditor-DGtwj99m.js","names":["s","f","Button","field_module_default","ListEditor_module_default","ListEditor","ariaLabel","inputs","useRef","focus","setFocus","useState","insert","onChange","value","at","remove","i","onKeyDown","name","className","empty","el","entry","j","v","CloseButton","addLabel"],"sources":["../../src/components/ListEditor/ListEditor.module.css","../../src/components/ListEditor/ListEditor.tsx"],"sourcesContent":[".editor {\n display: flex;\n flex-direction: column;\n gap: var(--wzl-space-4);\n min-width: min(28rem, calc(100vw - 64px));\n}\n\n.list {\n display: flex;\n flex-direction: column;\n gap: var(--wzl-space-2);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.entry {\n display: flex;\n align-items: center;\n gap: var(--wzl-space-2);\n}\n\n.input {\n flex: 1 1 auto;\n min-width: 0;\n padding: 0 var(--wzl-field-pad-x);\n}\n\n.empty {\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg-subtle);\n font-style: italic;\n}\n\n.actions {\n display: flex;\n}\n","import { useEffect, useRef, useState, type KeyboardEvent } from 'react';\nimport { Button } from '../Button';\nimport { CloseButton } from '../CloseButton';\nimport f from '../field.module.css';\nimport s from './ListEditor.module.css';\n\n/** Props for {@link ListEditor}. */\nexport interface ListEditorProps {\n value: readonly string[];\n /** Every edit, add and removal, with the whole list. Entries are written as\n * typed — an empty one stays until it is removed. */\n onChange: (next: string[]) => void;\n /** Shown in an empty entry. */\n placeholder?: string;\n /** Shown in place of the entries when there are none. */\n empty?: string;\n /** Text of the add button. */\n addLabel?: string;\n /** Names the list for assistive tech; each entry is named from it. */\n 'aria-label'?: string;\n className?: string;\n}\n\n/**\n * An editable list of strings: one field per entry, a remove button beside\n * each, and an add button under them. Enter in an entry adds one after it;\n * Backspace in an empty one removes it.\n */\nexport function ListEditor({\n value,\n onChange,\n placeholder,\n empty = 'None',\n addLabel = 'Add',\n 'aria-label': ariaLabel,\n className,\n}: ListEditorProps) {\n const inputs = useRef<(HTMLInputElement | null)[]>([]);\n const [focus, setFocus] = useState<number | null>(null);\n\n useEffect(() => {\n if (focus === null) return;\n inputs.current[focus]?.focus();\n setFocus(null);\n }, [focus]);\n\n const insert = (at: number): void => {\n onChange([...value.slice(0, at), '', ...value.slice(at)]);\n setFocus(at);\n };\n const remove = (at: number): void => {\n onChange(value.filter((_, i) => i !== at));\n if (value.length > 1) setFocus(Math.max(0, at - 1));\n };\n const onKeyDown = (e: KeyboardEvent<HTMLInputElement>, at: number): void => {\n if (e.key === 'Enter') {\n e.preventDefault();\n insert(at + 1);\n } else if (e.key === 'Backspace' && value[at] === '') {\n e.preventDefault();\n remove(at);\n }\n };\n const name = (i: number): string => `${ariaLabel ?? 'Entry'} ${i + 1}`;\n\n return (\n <div className={[s.editor, className].filter(Boolean).join(' ')} role=\"group\" aria-label={ariaLabel}>\n {value.length === 0 ? (\n <div className={s.empty}>{empty}</div>\n ) : (\n <ul className={s.list}>\n {value.map((entry, i) => (\n // Entries have no identity beyond their position, and two may be equal.\n <li key={i} className={s.entry}>\n <input\n ref={(el) => {\n inputs.current[i] = el;\n }}\n type=\"text\"\n className={`${f.frame} ${s.input}`}\n aria-label={name(i)}\n value={entry}\n placeholder={placeholder}\n onChange={(e) => onChange(value.map((v, j) => (j === i ? e.target.value : v)))}\n onKeyDown={(e) => onKeyDown(e, i)}\n />\n <CloseButton ariaLabel={`Remove ${name(i)}`} onClick={() => remove(i)} />\n </li>\n ))}\n </ul>\n )}\n <div className={s.actions}>\n <Button size=\"sm\" onClick={() => insert(value.length)}>\n {addLabel}\n </Button>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;AC4BA,SAAgBK,EAAW,EACzB,UACA,aACA,gBACA,WAAQ,QACR,cAAW,OACX,cAAcC,GACd,gBACkB;CAClB,IAAMC,IAASC,EAAoC,CAAC,CAAC,GAC/C,CAACC,GAAOC,KAAYC,EAAwB,IAAI;CAEtD,QAAgB;EACd,AAAIF,MAAU,SACdF,EAAO,QAAQE,EAAM,EAAE,MAAM,GAC7BC,EAAS,IAAI;CACf,GAAG,CAACD,CAAK,CAAC;CAEV,IAAMG,KAAU,MAAqB;EAEnC,AADAC,EAAS;GAAC,GAAGC,EAAM,MAAM,GAAGC,CAAE;GAAG;GAAI,GAAGD,EAAM,MAAMC,CAAE;EAAC,CAAC,GACxDL,EAASK,CAAE;CACb,GACMC,KAAU,MAAqB;EAEnC,AADAH,EAASC,EAAM,QAAQ,GAAG,MAAMG,MAAMF,CAAE,CAAC,GACrCD,EAAM,SAAS,KAAGJ,EAAS,KAAK,IAAI,GAAGK,IAAK,CAAC,CAAC;CACpD,GACMG,KAAa,GAAoC,MAAqB;EAC1E,AAAI,EAAE,QAAQ,WACZ,EAAE,eAAe,GACjBN,EAAOG,IAAK,CAAC,KACJ,EAAE,QAAQ,eAAeD,EAAMC,OAAQ,OAChD,EAAE,eAAe,GACjBC,EAAOD,CAAE;CAEb,GACMI,KAAQ,MAAsB,GAAGb,KAAa,QAAQ,GAAGW,IAAI;CAEnE,OACE,kBAAC,OAAD;EAAK,WAAW,CAACjB,EAAE,QAAQoB,CAAS,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;EAAG,MAAK;EAAQ,cAAYd;EAA1F,UAAA,CACGQ,EAAM,WAAW,IAChB,kBAAC,OAAD;GAAK,WAAWd,EAAE;GAAQ,UAAAqB;EAAW,CAAA,IAErC,kBAAC,MAAD;GAAI,WAAWrB,EAAE;GACd,UAAAc,EAAM,KAAK,GAAO,MAEjB,kBAAC,MAAD;IAAY,WAAWd,EAAE;IAAzB,UAAA,CACE,kBAAC,SAAD;KACE,MAAM,MAAO;MACX,EAAO,QAAQiB,KAAKK;KACtB;KACA,MAAK;KACL,WAAW,GAAGrB,EAAE,MAAM,GAAGD,EAAE;KAC3B,cAAYmB,EAAKF,CAAC;KAClB,OAAOM;KACM;KACb,WAAW,MAAMV,EAASC,EAAM,KAAK,GAAG,MAAOU,MAAMP,IAAI,EAAE,OAAO,QAAQQ,CAAE,CAAC;KAC7E,YAAY,MAAMP,EAAU,GAAGD,CAAC;IACjC,CAAA,GACD,kBAACS,GAAD;KAAa,WAAW,UAAUP,EAAKF,CAAC;KAAK,eAAeD,EAAOC,CAAC;IAAI,CAAA,CACtE;GAdK,GAAAA,CAcL,CACL;EACC,CAAA,GAEN,kBAAC,OAAD;GAAK,WAAWjB,EAAE;GAChB,UAAA,kBAACE,GAAD;IAAQ,MAAK;IAAK,eAAeU,EAAOE,EAAM,MAAM;IACjD,UAAAa;GACK,CAAA;EACL,CAAA,CACF;;AAET"}
@@ -1,7 +1,7 @@
1
1
  import { n as e } from "./handles-CqVANLtu.js";
2
2
  import { t } from "./ColorField-BwlelY26.js";
3
3
  import { t as n } from "./paintPreview-CuSZiHpR.js";
4
- import { t as r } from "./ToggleBar-Z2jZiguF.js";
4
+ import { t as r } from "./ToggleBar-BZ4Gk1IU.js";
5
5
  import { asPaint as i, useHandleDrag as a } from "@weasel-js/core";
6
6
  import { jsx as o, jsxs as s } from "react/jsx-runtime";
7
7
  import { useRef as c } from "react";
@@ -196,4 +196,4 @@ function D(e) {
196
196
  //#endregion
197
197
  export { v as a, y as i, S as n, h as o, b as r, _ as s, E as t };
198
198
 
199
- //# sourceMappingURL=MeshEditor-DWpvg8H6.js.map
199
+ //# sourceMappingURL=MeshEditor-8CRsu-Td.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MeshEditor-DWpvg8H6.js","names":["s","s","handleHalf","paintPreviewCss","ToggleBar","MeshEditor_module_default","SPACES","MeshEditor","props","space","value","withCorner","asPaint","patchIndex","patch","b","c","d","next","corner","color","className","previewOf","ColorField","onInput","onChange","spaceSwitch","isMeshPaint","isMeshGradientFill","fill","handleOverlay_module_default","HandleOverlay","Guide","Edge","a","points","DragPoint","start","useRef","at","drag","useHandleDrag","p","moved","onDrag","point","variant","radius","label","amount","e","KEY_STEP","dx","dy","CORNER_RADIUS","CONTROL_RADIUS","MeshHandles","meshGuides","handles","meshHandles","emit","moveMeshHandle","handle","toLocal","phase","ordered","h","edges","toScreen","edge","i","arms","from","to","labelOf","prefix","index"],"sources":["../../src/components/MeshEditor/MeshEditor.module.css","../../src/components/MeshEditor/MeshEditor.tsx","../../src/components/GradientEditor/handleOverlay.module.css","../../src/components/GradientEditor/handleOverlay.tsx","../../src/components/MeshEditor/MeshHandles.tsx"],"sourcesContent":[".root {\n display: flex;\n flex-direction: column;\n gap: var(--wzl-space-4);\n}\n\n.patch {\n display: flex;\n align-items: center;\n gap: var(--wzl-space-4);\n}\n\n.preview {\n inline-size: 34px;\n block-size: 34px;\n flex: 0 0 auto;\n border-radius: var(--wzl-radius-sm);\n box-shadow: inset 0 0 0 1px var(--wzl-border);\n}\n\n.corners {\n display: grid;\n grid-template-columns: repeat(2, auto);\n gap: var(--wzl-space-2) var(--wzl-space-3);\n min-inline-size: 0;\n}\n\n.corner {\n min-inline-size: 0;\n}\n","import { type ReactElement } from 'react';\nimport { asPaint, type ColorSpace, type FillStyle } from '@weasel-js/core';\nimport { isMeshGradientFill, type MeshGradientFill } from '@weasel-js/core/mesh';\nimport { ColorField } from '../ColorField';\nimport { ToggleBar, type ToggleBarItem } from '../ToggleBar';\nimport { paintPreviewCss } from '../../paintPreview';\nimport s from './MeshEditor.module.css';\n\nconst SPACES: readonly ToggleBarItem<ColorSpace>[] = [\n { value: 'rgb', label: 'sRGB' },\n { value: 'oklab', label: 'OKLab' },\n { value: 'oklch', label: 'OKLCh' },\n];\n\n/** Props for {@link MeshEditor}. `onInput` fires throughout a gesture and\n * `onChange` once at its end. */\nexport interface MeshEditorProps {\n value: MeshGradientFill;\n /** Emitted as a `FillStyle`, which is what every paint control passes\n * around — `FillStyle`'s discriminant stays closed over the built-in\n * members, so a registered kind's own shape only travels through it. */\n onInput?: (next: FillStyle) => void;\n onChange: (next: FillStyle) => void;\n /** Show the sRGB / OKLab / OKLCh switch. Default true. */\n spaceSwitch?: boolean;\n className?: string;\n}\n\n/**\n * Editor for a mesh gradient's colors.\n *\n * Geometry is absent for the same reason it is absent from `GradientEditor`:\n * a patch's twelve control points are a thing to drag on the artwork\n * (`MeshHandles`), not a column of numbers in a panel. What is left is what a panel is good at — the\n * color at each corner, and the space the four of them blend through.\n *\n * Corners are numbered in the patch's own walk order, which is the order the\n * paint stores them in, so a corner's number is stable while it is dragged.\n */\nexport function MeshEditor(props: MeshEditorProps): ReactElement {\n const { value, onInput, onChange, spaceSwitch = true, className } = props;\n const space = value.interpolate ?? 'rgb';\n\n const withCorner = (patchIndex: number, corner: number, color: string): FillStyle => asPaint({\n ...value,\n patches: value.patches.map((patch, i) => {\n if (i !== patchIndex) return patch;\n const [a, b, c, d] = patch.colors;\n const next: [string, string, string, string] = [a, b, c, d];\n next[corner] = color;\n return { ...patch, colors: next };\n }),\n });\n\n return (\n <div className={[s.root, className].filter(Boolean).join(' ')}>\n {value.patches.map((patch, patchIndex) => (\n // A patch has no id; its position in the list is its identity, the way\n // a gradient stop's is.\n <div key={patchIndex} className={s.patch}>\n <span\n className={s.preview}\n aria-hidden=\"true\"\n style={{ background: previewOf(value, patchIndex) }}\n />\n <div className={s.corners}>\n {patch.colors.map((color, corner) => (\n <ColorField\n key={corner}\n value={color}\n alpha\n aria-label={`Patch ${patchIndex + 1} corner ${corner + 1}`}\n className={s.corner}\n onInput={(next) => onInput?.(withCorner(patchIndex, corner, next))}\n onChange={(next) => onChange(withCorner(patchIndex, corner, next))}\n />\n ))}\n </div>\n </div>\n ))}\n\n {spaceSwitch && (\n <ToggleBar<ColorSpace>\n items={SPACES}\n value={space}\n size=\"sm\"\n ariaLabel=\"Blend space\"\n onChange={(next) => next && onChange(asPaint({ ...value, interpolate: next }))}\n />\n )}\n </div>\n );\n}\n\n/** One patch as a swatch: the paint with the other patches taken out, so a\n * multi-patch mesh shows which row belongs to which part of it. */\nfunction previewOf(value: MeshGradientFill, patchIndex: number): string | undefined {\n return paintPreviewCss(\n { ...value, patches: [value.patches[patchIndex]] } as unknown as FillStyle,\n );\n}\n\n/** Narrowing for a consumer rendering this behind a kind switch. */\nexport function isMeshPaint(fill: FillStyle | null | undefined): fill is FillStyle & MeshGradientFill {\n return isMeshGradientFill(fill);\n}\n","/* The overlay covers the canvas but must not intercept tool input; only the\n * handles opt back into hit-testing. */\n.overlay {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n\n/* These sit over artwork of unknown color, not over the app's own surface,\n * so they are deliberately not themed: a themed handle disappears against\n * whatever the user painted underneath. White-on-dark reads on anything,\n * which is why every editor draws handles this way. */\n.guide {\n fill: none;\n stroke: #ffffff;\n stroke-opacity: 0.7;\n stroke-dasharray: 4 4;\n paint-order: stroke;\n}\n\n.handle {\n fill: #ffffff;\n stroke: #222222;\n stroke-width: 2;\n cursor: grab;\n pointer-events: auto;\n}\n\n.handle:active {\n cursor: grabbing;\n}\n\n.handle:focus-visible {\n outline: 2px solid var(--wzl-accent);\n outline-offset: 2px;\n}\n\n/* A point that shapes an edge rather than ending one — drawn dark-on-light,\n * the inverse of the handle it hangs off, so the two read apart at a glance. */\n.control {\n fill: #222222;\n stroke: #ffffff;\n stroke-width: 1.5;\n}\n\n/* A patch edge: the outline the handles reshape, solid so it reads as the\n * artwork's structure rather than as a construction line. */\n.edge {\n fill: none;\n stroke: #ffffff;\n stroke-opacity: 0.85;\n stroke-width: 1;\n}\n","import { useRef, type KeyboardEvent as ReactKeyboardEvent, type ReactElement, type ReactNode } from 'react';\nimport { useHandleDrag } from '@weasel-js/core';\nimport s from './handleOverlay.module.css';\n\n/** The pieces every paint-geometry overlay draws with — `GradientHandles`\n * and `MeshHandles` — kept here so the two drag, step and look alike. */\n\nexport interface OverlayPoint {\n x: number;\n y: number;\n}\n\n/** Which half of a gesture an edit belongs to: a live preview, or the one\n * write that ends it. */\nexport type EditPhase = 'input' | 'commit';\n\n/** The SVG overlay itself: covers its container, passes pointer input\n * through everywhere but the handles. */\nexport function HandleOverlay(props: {\n width: number;\n height: number;\n className?: string;\n children: ReactNode;\n}): ReactElement {\n return (\n <svg\n className={[s.overlay, props.className].filter(Boolean).join(' ')}\n width={props.width}\n height={props.height}\n >\n {props.children}\n </svg>\n );\n}\n\nexport function Guide(props: { x1: number; y1: number; x2: number; y2: number }): ReactElement {\n return <line className={s.guide} {...props} />;\n}\n\n/** A cubic Bézier edge through four overlay-space points. */\nexport function Edge({ points }: { points: readonly [OverlayPoint, OverlayPoint, OverlayPoint, OverlayPoint] }): ReactElement {\n const [a, b, c, d] = points;\n return <path className={s.edge} d={`M${a.x} ${a.y}C${b.x} ${b.y} ${c.x} ${c.y} ${d.x} ${d.y}`} />;\n}\n\n/**\n * One draggable point. `onDrag` receives overlay pixels; a press that never\n * moved writes nothing, and a canceled gesture puts the preview back.\n */\nexport function DragPoint({\n at,\n label,\n radius,\n variant = 'handle',\n onDrag,\n}: {\n at: OverlayPoint;\n label: string;\n radius: number;\n /** `control` draws the inverted style a secondary point wears. */\n variant?: 'handle' | 'control';\n onDrag: (p: OverlayPoint, phase: EditPhase) => void;\n}): ReactElement {\n const start = useRef<OverlayPoint>(at);\n const drag = useHandleDrag<SVGCircleElement>({\n onStart: () => { start.current = at; },\n onMove: (p) => { onDrag(p, 'input'); },\n onEnd: ({ point, moved }) => {\n if (moved) onDrag(point, 'commit');\n },\n onCancel: () => { onDrag(start.current, 'input'); },\n });\n const onKeyDown = (e: ReactKeyboardEvent<SVGCircleElement>): void => {\n const amount = e.shiftKey ? KEY_STEP * 10 : KEY_STEP;\n let dx = 0;\n let dy = 0;\n if (e.key === 'ArrowLeft') dx = -amount;\n else if (e.key === 'ArrowRight') dx = amount;\n else if (e.key === 'ArrowUp') dy = -amount;\n else if (e.key === 'ArrowDown') dy = amount;\n else return;\n e.preventDefault();\n const next = { x: at.x + dx, y: at.y + dy };\n onDrag(next, 'input');\n onDrag(next, 'commit');\n };\n return (\n <circle\n className={variant === 'control' ? `${s.handle} ${s.control}` : s.handle}\n cx={at.x}\n cy={at.y}\n r={radius}\n // Not `slider`: the handle carries a 2-D position, not one value, so\n // it has no `aria-valuenow` to honor the role's contract with.\n role=\"button\"\n aria-label={label}\n tabIndex={0}\n onKeyDown={onKeyDown}\n {...drag}\n />\n );\n}\n\n/** One arrow-key step, in overlay pixels. */\nconst KEY_STEP = 1;\n","import { type ReactElement } from 'react';\nimport { asPaint, type FillStyle } from '@weasel-js/core';\nimport {\n meshGuides,\n meshHandles,\n moveMeshHandle,\n type MeshGradientFill,\n type MeshHandle,\n} from '@weasel-js/core/mesh';\nimport { handleHalf } from '../../handles';\nimport {\n DragPoint,\n Edge,\n Guide,\n HandleOverlay,\n type EditPhase,\n type OverlayPoint as Point,\n} from '../GradientEditor/handleOverlay';\n\n/** Props for {@link MeshHandles}. `onInput` fires throughout a drag and\n * `onChange` once at its end. */\nexport interface MeshHandlesProps {\n /** The mesh whose points these handles move, in the space `toScreen`\n * maps from. A `units: 'bounds'` mesh is resolved first with\n * `fillInPoseFrame(fill, box)`, and edits converted back with\n * `fillToBoundsFrame(next, box)`. */\n value: MeshGradientFill;\n /** Mesh space → overlay pixels. */\n toScreen: (p: Point) => Point;\n /** Overlay pixels → mesh space. Must invert `toScreen`. */\n toLocal: (p: Point) => Point;\n /** Live during a drag — wire for preview, do not write to history. Emitted\n * as a `FillStyle`, which is what every paint control passes around. */\n onInput?: (next: FillStyle) => void;\n /** Committed at drag end: one call per gesture. */\n onChange: (next: FillStyle) => void;\n /** Overlay size in CSS pixels. */\n width: number;\n height: number;\n className?: string;\n}\n\nconst CORNER_RADIUS = handleHalf('--wzl-handle-size-lg');\nconst CONTROL_RADIUS = handleHalf('--wzl-handle-size');\n\n/**\n * Direct-manipulation handles for a mesh gradient's geometry, drawn as an\n * SVG overlay: every patch corner, the two controls of each edge, and a\n * tensor patch's interior points, over the patch outlines they shape.\n *\n * Dragging a corner carries its controls with it. Points two patches share\n * are one handle, so a drag moves the seam instead of tearing it.\n *\n * The overlay ignores pointer events except on the handles themselves.\n */\nexport function MeshHandles(props: MeshHandlesProps): ReactElement {\n const { value, toScreen, toLocal, onInput, onChange, width, height, className } = props;\n const { edges, arms } = meshGuides(value);\n const handles = meshHandles(value);\n\n const emit = (handle: MeshHandle, p: Point, phase: EditPhase): void => {\n const next = asPaint(moveMeshHandle(value, handle, toLocal(p)));\n if (phase === 'input') onInput?.(next);\n else onChange(next);\n };\n\n // Corners last, so where a control collapses onto its corner the corner\n // is the one on top.\n const ordered = [\n ...handles.filter((h) => h.kind !== 'corner'),\n ...handles.filter((h) => h.kind === 'corner'),\n ];\n\n return (\n <HandleOverlay width={width} height={height} className={className}>\n {edges.map((edge, i) => (\n // Guides have no identity beyond their place in the walk.\n <Edge key={`e${i}`} points={[toScreen(edge[0]), toScreen(edge[1]), toScreen(edge[2]), toScreen(edge[3])]} />\n ))}\n {arms.map(([from, to], i) => {\n const a = toScreen(from);\n const b = toScreen(to);\n return <Guide key={`a${i}`} x1={a.x} y1={a.y} x2={b.x} y2={b.y} />;\n })}\n {ordered.map((handle) => (\n <DragPoint\n key={handle.id}\n at={toScreen(handle.at)}\n label={labelOf(handle)}\n radius={handle.kind === 'corner' ? CORNER_RADIUS : CONTROL_RADIUS}\n variant={handle.kind === 'corner' ? 'handle' : 'control'}\n onDrag={(p, phase) => emit(handle, p, phase)}\n />\n ))}\n </HandleOverlay>\n );\n}\n\n/** Numbered in the patch's own walk, matching `MeshEditor`'s corner fields. */\nfunction labelOf(handle: MeshHandle): string {\n const { patch, index } = handle.refs[0];\n const prefix = `Patch ${patch + 1}`;\n if (handle.kind === 'corner') return `${prefix} corner ${index / 3 + 1}`;\n if (handle.kind === 'interior') return `${prefix} interior ${index - 11}`;\n return `${prefix} edge ${Math.floor(index / 3) + 1} control ${index % 3}`;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"MeshEditor-8CRsu-Td.js","names":["s","s","handleHalf","paintPreviewCss","ToggleBar","MeshEditor_module_default","SPACES","MeshEditor","props","space","value","withCorner","asPaint","patchIndex","patch","b","c","d","next","corner","color","className","previewOf","ColorField","onInput","onChange","spaceSwitch","isMeshPaint","isMeshGradientFill","fill","handleOverlay_module_default","HandleOverlay","Guide","Edge","a","points","DragPoint","start","useRef","at","drag","useHandleDrag","p","moved","onDrag","point","variant","radius","label","amount","e","KEY_STEP","dx","dy","CORNER_RADIUS","CONTROL_RADIUS","MeshHandles","meshGuides","handles","meshHandles","emit","moveMeshHandle","handle","toLocal","phase","ordered","h","edges","toScreen","edge","i","arms","from","to","labelOf","prefix","index"],"sources":["../../src/components/MeshEditor/MeshEditor.module.css","../../src/components/MeshEditor/MeshEditor.tsx","../../src/components/GradientEditor/handleOverlay.module.css","../../src/components/GradientEditor/handleOverlay.tsx","../../src/components/MeshEditor/MeshHandles.tsx"],"sourcesContent":[".root {\n display: flex;\n flex-direction: column;\n gap: var(--wzl-space-4);\n}\n\n.patch {\n display: flex;\n align-items: center;\n gap: var(--wzl-space-4);\n}\n\n.preview {\n inline-size: 34px;\n block-size: 34px;\n flex: 0 0 auto;\n border-radius: var(--wzl-radius-sm);\n box-shadow: inset 0 0 0 1px var(--wzl-border);\n}\n\n.corners {\n display: grid;\n grid-template-columns: repeat(2, auto);\n gap: var(--wzl-space-2) var(--wzl-space-3);\n min-inline-size: 0;\n}\n\n.corner {\n min-inline-size: 0;\n}\n","import { type ReactElement } from 'react';\nimport { asPaint, type ColorSpace, type FillStyle } from '@weasel-js/core';\nimport { isMeshGradientFill, type MeshGradientFill } from '@weasel-js/core/mesh';\nimport { ColorField } from '../ColorField';\nimport { ToggleBar, type ToggleBarItem } from '../ToggleBar';\nimport { paintPreviewCss } from '../../paintPreview';\nimport s from './MeshEditor.module.css';\n\nconst SPACES: readonly ToggleBarItem<ColorSpace>[] = [\n { value: 'rgb', label: 'sRGB' },\n { value: 'oklab', label: 'OKLab' },\n { value: 'oklch', label: 'OKLCh' },\n];\n\n/** Props for {@link MeshEditor}. `onInput` fires throughout a gesture and\n * `onChange` once at its end. */\nexport interface MeshEditorProps {\n value: MeshGradientFill;\n /** Emitted as a `FillStyle`, which is what every paint control passes\n * around — `FillStyle`'s discriminant stays closed over the built-in\n * members, so a registered kind's own shape only travels through it. */\n onInput?: (next: FillStyle) => void;\n onChange: (next: FillStyle) => void;\n /** Show the sRGB / OKLab / OKLCh switch. Default true. */\n spaceSwitch?: boolean;\n className?: string;\n}\n\n/**\n * Editor for a mesh gradient's colors.\n *\n * Geometry is absent for the same reason it is absent from `GradientEditor`:\n * a patch's twelve control points are a thing to drag on the artwork\n * (`MeshHandles`), not a column of numbers in a panel. What is left is what a panel is good at — the\n * color at each corner, and the space the four of them blend through.\n *\n * Corners are numbered in the patch's own walk order, which is the order the\n * paint stores them in, so a corner's number is stable while it is dragged.\n */\nexport function MeshEditor(props: MeshEditorProps): ReactElement {\n const { value, onInput, onChange, spaceSwitch = true, className } = props;\n const space = value.interpolate ?? 'rgb';\n\n const withCorner = (patchIndex: number, corner: number, color: string): FillStyle => asPaint({\n ...value,\n patches: value.patches.map((patch, i) => {\n if (i !== patchIndex) return patch;\n const [a, b, c, d] = patch.colors;\n const next: [string, string, string, string] = [a, b, c, d];\n next[corner] = color;\n return { ...patch, colors: next };\n }),\n });\n\n return (\n <div className={[s.root, className].filter(Boolean).join(' ')}>\n {value.patches.map((patch, patchIndex) => (\n // A patch has no id; its position in the list is its identity, the way\n // a gradient stop's is.\n <div key={patchIndex} className={s.patch}>\n <span\n className={s.preview}\n aria-hidden=\"true\"\n style={{ background: previewOf(value, patchIndex) }}\n />\n <div className={s.corners}>\n {patch.colors.map((color, corner) => (\n <ColorField\n key={corner}\n value={color}\n alpha\n aria-label={`Patch ${patchIndex + 1} corner ${corner + 1}`}\n className={s.corner}\n onInput={(next) => onInput?.(withCorner(patchIndex, corner, next))}\n onChange={(next) => onChange(withCorner(patchIndex, corner, next))}\n />\n ))}\n </div>\n </div>\n ))}\n\n {spaceSwitch && (\n <ToggleBar<ColorSpace>\n items={SPACES}\n value={space}\n size=\"sm\"\n ariaLabel=\"Blend space\"\n onChange={(next) => next && onChange(asPaint({ ...value, interpolate: next }))}\n />\n )}\n </div>\n );\n}\n\n/** One patch as a swatch: the paint with the other patches taken out, so a\n * multi-patch mesh shows which row belongs to which part of it. */\nfunction previewOf(value: MeshGradientFill, patchIndex: number): string | undefined {\n return paintPreviewCss(\n { ...value, patches: [value.patches[patchIndex]] } as unknown as FillStyle,\n );\n}\n\n/** Narrowing for a consumer rendering this behind a kind switch. */\nexport function isMeshPaint(fill: FillStyle | null | undefined): fill is FillStyle & MeshGradientFill {\n return isMeshGradientFill(fill);\n}\n","/* The overlay covers the canvas but must not intercept tool input; only the\n * handles opt back into hit-testing. */\n.overlay {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n\n/* These sit over artwork of unknown color, not over the app's own surface,\n * so they are deliberately not themed: a themed handle disappears against\n * whatever the user painted underneath. White-on-dark reads on anything,\n * which is why every editor draws handles this way. */\n.guide {\n fill: none;\n stroke: #ffffff;\n stroke-opacity: 0.7;\n stroke-dasharray: 4 4;\n paint-order: stroke;\n}\n\n.handle {\n fill: #ffffff;\n stroke: #222222;\n stroke-width: 2;\n cursor: grab;\n pointer-events: auto;\n}\n\n.handle:active {\n cursor: grabbing;\n}\n\n.handle:focus-visible {\n outline: 2px solid var(--wzl-accent);\n outline-offset: 2px;\n}\n\n/* A point that shapes an edge rather than ending one — drawn dark-on-light,\n * the inverse of the handle it hangs off, so the two read apart at a glance. */\n.control {\n fill: #222222;\n stroke: #ffffff;\n stroke-width: 1.5;\n}\n\n/* A patch edge: the outline the handles reshape, solid so it reads as the\n * artwork's structure rather than as a construction line. */\n.edge {\n fill: none;\n stroke: #ffffff;\n stroke-opacity: 0.85;\n stroke-width: 1;\n}\n","import { useRef, type KeyboardEvent as ReactKeyboardEvent, type ReactElement, type ReactNode } from 'react';\nimport { useHandleDrag } from '@weasel-js/core';\nimport s from './handleOverlay.module.css';\n\n/** The pieces every paint-geometry overlay draws with — `GradientHandles`\n * and `MeshHandles` — kept here so the two drag, step and look alike. */\n\nexport interface OverlayPoint {\n x: number;\n y: number;\n}\n\n/** Which half of a gesture an edit belongs to: a live preview, or the one\n * write that ends it. */\nexport type EditPhase = 'input' | 'commit';\n\n/** The SVG overlay itself: covers its container, passes pointer input\n * through everywhere but the handles. */\nexport function HandleOverlay(props: {\n width: number;\n height: number;\n className?: string;\n children: ReactNode;\n}): ReactElement {\n return (\n <svg\n className={[s.overlay, props.className].filter(Boolean).join(' ')}\n width={props.width}\n height={props.height}\n >\n {props.children}\n </svg>\n );\n}\n\nexport function Guide(props: { x1: number; y1: number; x2: number; y2: number }): ReactElement {\n return <line className={s.guide} {...props} />;\n}\n\n/** A cubic Bézier edge through four overlay-space points. */\nexport function Edge({ points }: { points: readonly [OverlayPoint, OverlayPoint, OverlayPoint, OverlayPoint] }): ReactElement {\n const [a, b, c, d] = points;\n return <path className={s.edge} d={`M${a.x} ${a.y}C${b.x} ${b.y} ${c.x} ${c.y} ${d.x} ${d.y}`} />;\n}\n\n/**\n * One draggable point. `onDrag` receives overlay pixels; a press that never\n * moved writes nothing, and a canceled gesture puts the preview back.\n */\nexport function DragPoint({\n at,\n label,\n radius,\n variant = 'handle',\n onDrag,\n}: {\n at: OverlayPoint;\n label: string;\n radius: number;\n /** `control` draws the inverted style a secondary point wears. */\n variant?: 'handle' | 'control';\n onDrag: (p: OverlayPoint, phase: EditPhase) => void;\n}): ReactElement {\n const start = useRef<OverlayPoint>(at);\n const drag = useHandleDrag<SVGCircleElement>({\n onStart: () => { start.current = at; },\n onMove: (p) => { onDrag(p, 'input'); },\n onEnd: ({ point, moved }) => {\n if (moved) onDrag(point, 'commit');\n },\n onCancel: () => { onDrag(start.current, 'input'); },\n });\n const onKeyDown = (e: ReactKeyboardEvent<SVGCircleElement>): void => {\n const amount = e.shiftKey ? KEY_STEP * 10 : KEY_STEP;\n let dx = 0;\n let dy = 0;\n if (e.key === 'ArrowLeft') dx = -amount;\n else if (e.key === 'ArrowRight') dx = amount;\n else if (e.key === 'ArrowUp') dy = -amount;\n else if (e.key === 'ArrowDown') dy = amount;\n else return;\n e.preventDefault();\n const next = { x: at.x + dx, y: at.y + dy };\n onDrag(next, 'input');\n onDrag(next, 'commit');\n };\n return (\n <circle\n className={variant === 'control' ? `${s.handle} ${s.control}` : s.handle}\n cx={at.x}\n cy={at.y}\n r={radius}\n // Not `slider`: the handle carries a 2-D position, not one value, so\n // it has no `aria-valuenow` to honor the role's contract with.\n role=\"button\"\n aria-label={label}\n tabIndex={0}\n onKeyDown={onKeyDown}\n {...drag}\n />\n );\n}\n\n/** One arrow-key step, in overlay pixels. */\nconst KEY_STEP = 1;\n","import { type ReactElement } from 'react';\nimport { asPaint, type FillStyle } from '@weasel-js/core';\nimport {\n meshGuides,\n meshHandles,\n moveMeshHandle,\n type MeshGradientFill,\n type MeshHandle,\n} from '@weasel-js/core/mesh';\nimport { handleHalf } from '../../handles';\nimport {\n DragPoint,\n Edge,\n Guide,\n HandleOverlay,\n type EditPhase,\n type OverlayPoint as Point,\n} from '../GradientEditor/handleOverlay';\n\n/** Props for {@link MeshHandles}. `onInput` fires throughout a drag and\n * `onChange` once at its end. */\nexport interface MeshHandlesProps {\n /** The mesh whose points these handles move, in the space `toScreen`\n * maps from. A `units: 'bounds'` mesh is resolved first with\n * `fillInPoseFrame(fill, box)`, and edits converted back with\n * `fillToBoundsFrame(next, box)`. */\n value: MeshGradientFill;\n /** Mesh space → overlay pixels. */\n toScreen: (p: Point) => Point;\n /** Overlay pixels → mesh space. Must invert `toScreen`. */\n toLocal: (p: Point) => Point;\n /** Live during a drag — wire for preview, do not write to history. Emitted\n * as a `FillStyle`, which is what every paint control passes around. */\n onInput?: (next: FillStyle) => void;\n /** Committed at drag end: one call per gesture. */\n onChange: (next: FillStyle) => void;\n /** Overlay size in CSS pixels. */\n width: number;\n height: number;\n className?: string;\n}\n\nconst CORNER_RADIUS = handleHalf('--wzl-handle-size-lg');\nconst CONTROL_RADIUS = handleHalf('--wzl-handle-size');\n\n/**\n * Direct-manipulation handles for a mesh gradient's geometry, drawn as an\n * SVG overlay: every patch corner, the two controls of each edge, and a\n * tensor patch's interior points, over the patch outlines they shape.\n *\n * Dragging a corner carries its controls with it. Points two patches share\n * are one handle, so a drag moves the seam instead of tearing it.\n *\n * The overlay ignores pointer events except on the handles themselves.\n */\nexport function MeshHandles(props: MeshHandlesProps): ReactElement {\n const { value, toScreen, toLocal, onInput, onChange, width, height, className } = props;\n const { edges, arms } = meshGuides(value);\n const handles = meshHandles(value);\n\n const emit = (handle: MeshHandle, p: Point, phase: EditPhase): void => {\n const next = asPaint(moveMeshHandle(value, handle, toLocal(p)));\n if (phase === 'input') onInput?.(next);\n else onChange(next);\n };\n\n // Corners last, so where a control collapses onto its corner the corner\n // is the one on top.\n const ordered = [\n ...handles.filter((h) => h.kind !== 'corner'),\n ...handles.filter((h) => h.kind === 'corner'),\n ];\n\n return (\n <HandleOverlay width={width} height={height} className={className}>\n {edges.map((edge, i) => (\n // Guides have no identity beyond their place in the walk.\n <Edge key={`e${i}`} points={[toScreen(edge[0]), toScreen(edge[1]), toScreen(edge[2]), toScreen(edge[3])]} />\n ))}\n {arms.map(([from, to], i) => {\n const a = toScreen(from);\n const b = toScreen(to);\n return <Guide key={`a${i}`} x1={a.x} y1={a.y} x2={b.x} y2={b.y} />;\n })}\n {ordered.map((handle) => (\n <DragPoint\n key={handle.id}\n at={toScreen(handle.at)}\n label={labelOf(handle)}\n radius={handle.kind === 'corner' ? CORNER_RADIUS : CONTROL_RADIUS}\n variant={handle.kind === 'corner' ? 'handle' : 'control'}\n onDrag={(p, phase) => emit(handle, p, phase)}\n />\n ))}\n </HandleOverlay>\n );\n}\n\n/** Numbered in the patch's own walk, matching `MeshEditor`'s corner fields. */\nfunction labelOf(handle: MeshHandle): string {\n const { patch, index } = handle.refs[0];\n const prefix = `Patch ${patch + 1}`;\n if (handle.kind === 'corner') return `${prefix} corner ${index / 3 + 1}`;\n if (handle.kind === 'interior') return `${prefix} interior ${index - 11}`;\n return `${prefix} edge ${Math.floor(index / 3) + 1} control ${index % 3}`;\n}\n"],"mappings":";;;;;;;;;;;;;;GCQMM,IAA+C;CACnD;EAAE,OAAO;EAAO,OAAO;CAAO;CAC9B;EAAE,OAAO;EAAS,OAAO;CAAQ;CACjC;EAAE,OAAO;EAAS,OAAO;CAAQ;AACnC;AA2BA,SAAgBC,EAAW,GAAsC;CAC/D,IAAM,EAAE,UAAO,YAAS,aAAU,iBAAc,IAAM,iBAAcC,GAC9DC,IAAQC,EAAM,eAAe,OAE7BC,KAAc,GAAoB,GAAgB,MAA6BC,EAAQ;EAC3F,GAAGF;EACH,SAASA,EAAM,QAAQ,KAAK,GAAO,MAAM;GACvC,IAAI,MAAMG,GAAY,OAAOC;GAC7B,IAAM,CAAC,GAAGC,GAAGC,GAAGC,KAAKH,EAAM,QACrBI,IAAyC;IAAC;IAAGH;IAAGC;IAAGC;GAAC;GAE1D,OADA,EAAKE,KAAUC,GACR;IAAE,GAAGN;IAAO,QAAQI;GAAK;EAClC,CAAC;CACH,CAAC;CAED,OACE,kBAAC,OAAD;EAAK,WAAW,CAAClB,EAAE,MAAMqB,CAAS,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;EAA5D,UAAA,CACGX,EAAM,QAAQ,KAAK,GAAO,MAGzB,kBAAC,OAAD;GAAsB,WAAWV,EAAE;GAAnC,UAAA,CACE,kBAAC,QAAD;IACE,WAAWA,EAAE;IACb,eAAY;IACZ,OAAO,EAAE,YAAYsB,EAAUZ,GAAOG,CAAU,EAAE;GACnD,CAAA,GACD,kBAAC,OAAD;IAAK,WAAWb,EAAE;IACf,UAAAc,EAAM,OAAO,KAAK,GAAO,MACxB,kBAACS,GAAD;KAEE,OAAOH;KACP,OAAA;KACA,cAAY,SAASP,IAAa,EAAE,UAAUM,IAAS;KACvD,WAAWnB,EAAE;KACb,UAAU,MAASwB,IAAUb,EAAWE,GAAYM,GAAQD,CAAI,CAAC;KACjE,WAAW,MAASO,EAASd,EAAWE,GAAYM,GAAQD,CAAI,CAAC;IAClE,GAPMC,CAON,CACF;GACE,CAAA,CACF;EAnBK,GAAAN,CAmBL,CACN,GAEAa,KACC,kBAACtB,GAAD;GACE,OAAOE;GACP,OAAOG;GACP,MAAK;GACL,WAAU;GACV,WAAW,MAASS,KAAQO,EAASb,EAAQ;IAAE,GAAGF;IAAO,aAAaQ;GAAK,CAAC,CAAC;EAC9E,CAAA,CAEA;;AAET;AAIA,SAASI,EAAU,GAAyB,GAAwC;CAClF,OAAOnB,EACL;EAAE,GAAGO;EAAO,SAAS,CAACA,EAAM,QAAQG,EAAW;CAAE,CACnD;AACF;AAGA,SAAgBc,EAAY,GAA0E;CACpG,OAAOC,EAAmBC,CAAI;AAChC;;;;;;;;;;AEvFA,SAAgBE,EAAc,GAKb;CACf,OACE,kBAAC,OAAD;EACE,WAAW,CAAC9B,EAAE,SAASO,EAAM,SAAS,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;EAChE,OAAOA,EAAM;EACb,QAAQA,EAAM;EAEb,UAAAA,EAAM;CACJ,CAAA;AAET;AAEA,SAAgBwB,EAAM,GAAyE;CAC7F,OAAO,kBAAC,QAAD;EAAM,WAAW/B,EAAE;EAAO,GAAIO;CAAQ,CAAA;AAC/C;AAGA,SAAgByB,EAAK,EAAE,aAAuG;CAC5H,IAAM,CAACC,GAAGnB,GAAGC,GAAGC,KAAKkB;CACrB,OAAO,kBAAC,QAAD;EAAM,WAAWlC,EAAE;EAAM,GAAG,IAAIiC,EAAE,EAAE,GAAGA,EAAE,EAAE,GAAGnB,EAAE,EAAE,GAAGA,EAAE,EAAE,GAAGC,EAAE,EAAE,GAAGA,EAAE,EAAE,GAAGC,EAAE,EAAE,GAAGA,EAAE;CAAM,CAAA;AAClG;AAMA,SAAgBmB,EAAU,EACxB,OACA,UACA,WACA,aAAU,UACV,aAQe;CACf,IAAMC,IAAQC,EAAqBC,CAAE,GAC/BC,IAAOC,EAAgC;EAC3C,eAAe;GAAE,EAAM,UAAUF;EAAI;EACrC,SAAS,MAAM;GAAE,EAAOG,GAAG,OAAO;EAAG;EACrC,QAAQ,EAAE,UAAO,eAAY;GAC3B,AAAIC,KAAOC,EAAOC,GAAO,QAAQ;EACnC;EACA,gBAAgB;GAAE,EAAOR,EAAM,SAAS,OAAO;EAAG;CACpD,CAAC;CAeD,OACE,kBAAC,UAAD;EACE,WAAWS,MAAY,YAAY,GAAG7C,EAAE,OAAO,GAAGA,EAAE,YAAYA,EAAE;EAClE,IAAIsC,EAAG;EACP,IAAIA,EAAG;EACP,GAAGQ;EAGH,MAAK;EACL,cAAYC;EACZ,UAAU;EACC,YAzBI,MAAkD;GACnE,IAAMC,IAASC,EAAE,WAAWC,IAAW,KAAKA,GACxCC,IAAK,GACLC,IAAK;GACT,IAAIH,EAAE,QAAQ,aAAa,IAAK,CAACD;QAC5B,IAAIC,EAAE,QAAQ,cAAc,IAAKD;QACjC,IAAIC,EAAE,QAAQ,WAAW,IAAK,CAACD;QAC/B,IAAIC,EAAE,QAAQ,aAAa,IAAKD;QAChC;GACL,EAAE,eAAe;GACjB,IAAM/B,IAAO;IAAE,GAAGqB,EAAG,IAAIa;IAAI,GAAGb,EAAG,IAAIc;GAAG;GAE1C,AADAT,EAAO1B,GAAM,OAAO,GACpB0B,EAAO1B,GAAM,QAAQ;EACvB;EAaI,GAAIsB;CACL,CAAA;AAEL;AAGA,IAAMW,IAAW,GC9DXG,IAAgBpD,EAAW,sBAAsB,GACjDqD,IAAiBrD,EAAW,mBAAmB;AAYrD,SAAgBsD,EAAY,GAAuC;CACjE,IAAM,EAAE,UAAO,aAAU,YAAS,YAAS,aAAU,UAAO,WAAQ,iBAAchD,GAC5E,EAAE,UAAO,YAASiD,EAAW/C,CAAK,GAClCgD,IAAUC,EAAYjD,CAAK,GAE3BkD,KAAQ,GAAoB,GAAU,MAA2B;EACrE,IAAM1C,IAAON,EAAQiD,EAAenD,GAAOoD,GAAQC,EAAQrB,CAAC,CAAC,CAAC;EAC9D,AAAIsB,MAAU,UAASxC,IAAUN,CAAI,IAChCO,EAASP,CAAI;CACpB,GAIM+C,IAAU,CACd,GAAGP,EAAQ,QAAQ,MAAMQ,EAAE,SAAS,QAAQ,GAC5C,GAAGR,EAAQ,QAAQ,MAAMQ,EAAE,SAAS,QAAQ,CAC9C;CAEA,OACE,kBAACnC,GAAD;EAAsB;EAAe;EAAmB;EAAxD,UAAA;GACGoC,EAAM,KAAK,GAAM,MAEhB,kBAAClC,GAAD,EAAoB,QAAQ;IAACmC,EAASC,EAAK,EAAE;IAAGD,EAASC,EAAK,EAAE;IAAGD,EAASC,EAAK,EAAE;IAAGD,EAASC,EAAK,EAAE;GAAC,EAAI,GAAhG,IAAIC,GAA4F,CAC5G;GACAC,EAAK,KAAK,CAACC,GAAMC,IAAK,MAAM;IAC3B,IAAMvC,IAAIkC,EAASI,CAAI,GACjBzD,IAAIqD,EAASK,CAAE;IACrB,OAAO,kBAACzC,GAAD;KAAqB,IAAIE,EAAE;KAAG,IAAIA,EAAE;KAAG,IAAInB,EAAE;KAAG,IAAIA,EAAE;IAAI,GAA9C,IAAIuD,GAA0C;GACnE,CAAC;GACAL,EAAQ,KAAK,MACZ,kBAAC7B,GAAD;IAEE,IAAIgC,EAASN,EAAO,EAAE;IACtB,OAAOY,EAAQZ,CAAM;IACrB,QAAQA,EAAO,SAAS,WAAWR,IAAgBC;IACnD,SAASO,EAAO,SAAS,WAAW,WAAW;IAC/C,SAAS,GAAG,MAAUF,EAAKE,GAAQpB,GAAGsB,CAAK;GAC5C,GANMF,EAAO,EAMb,CACF;EACY;;AAEnB;AAGA,SAASY,EAAQ,GAA4B;CAC3C,IAAM,EAAE,UAAO,aAAUZ,EAAO,KAAK,IAC/Ba,IAAS,SAAS7D,IAAQ;CAGhC,OAFIgD,EAAO,SAAS,WAAiB,GAAGa,EAAO,UAAUC,IAAQ,IAAI,MACjEd,EAAO,SAAS,aAAmB,GAAGa,EAAO,YAAYC,IAAQ,OAC9D,GAAGD,EAAO,QAAQ,KAAK,MAAMC,IAAQ,CAAC,IAAI,EAAE,WAAWA,IAAQ;AACxE"}
@@ -95,7 +95,10 @@ var C = m({
95
95
  grouping: !1
96
96
  }), w = 400, T = 60;
97
97
  function E({ value: r, onChange: s, onInput: c, minValue: l, maxValue: u, step: d = 1, accepts: f, display: p = C, placeholder: m, steppers: y, ghost: x, width: E = "fill", className: D, id: O, "aria-label": k }) {
98
- let [A, j] = o(null), M = a(!1), N = a(null), P = Number.isFinite(r), F = P ? _(r, p).text : "", I = (e) => f ? g(e, f) : h(e, p), L = {
98
+ let [A, j] = o(null), M = a(!1), N = a(null), P = !Number.isNaN(r), F = P ? _(r, p).text : "", I = (e) => {
99
+ let t = f ? g(e, f) : NaN;
100
+ return Number.isNaN(t) ? h(e, p) : t;
101
+ }, L = {
99
102
  step: d,
100
103
  min: l,
101
104
  max: u
@@ -168,7 +171,7 @@ function E({ value: r, onChange: s, onInput: c, minValue: l, maxValue: u, step:
168
171
  inputMode: "decimal",
169
172
  autoComplete: "off",
170
173
  "aria-label": k,
171
- "aria-valuenow": P ? r : void 0,
174
+ "aria-valuenow": Number.isFinite(r) ? r : void 0,
172
175
  "aria-valuetext": P ? _(r, p).spoken : void 0,
173
176
  "aria-valuemin": l,
174
177
  "aria-valuemax": u,
@@ -178,7 +181,7 @@ function E({ value: r, onChange: s, onInput: c, minValue: l, maxValue: u, step:
178
181
  let t = e.target.value;
179
182
  j(t);
180
183
  let n = I(t);
181
- c && t.trim() !== "" && Number.isFinite(n) && c(b(n, L));
184
+ c && t.trim() !== "" && !Number.isNaN(n) && c(b(n, L));
182
185
  },
183
186
  onFocus: () => {
184
187
  M.current = !1;
@@ -186,7 +189,7 @@ function E({ value: r, onChange: s, onInput: c, minValue: l, maxValue: u, step:
186
189
  onBlur: () => {
187
190
  if (A !== null && !M.current) {
188
191
  let e = I(A);
189
- if (Number.isFinite(e)) {
192
+ if (!Number.isNaN(e)) {
190
193
  let t = b(e, L);
191
194
  t !== r && s(t);
192
195
  }
@@ -206,4 +209,4 @@ function E({ value: r, onChange: s, onInput: c, minValue: l, maxValue: u, step:
206
209
  //#endregion
207
210
  export { S as n, y as r, E as t };
208
211
 
209
- //# sourceMappingURL=NumberField-DS0WSiin.js.map
212
+ //# sourceMappingURL=NumberField-BXFrOi5M.js.map