@weasel-js/ui 1.7.1 → 1.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/dist/chunks/{BandEditor-Dd0egAgE.js → BandEditor-B4jUAHQ8.js} +13 -13
  2. package/dist/chunks/BandEditor-B4jUAHQ8.js.map +1 -0
  3. package/dist/chunks/{ColorField-BLs9fbeq.js → ColorField-BwlelY26.js} +4 -4
  4. package/dist/chunks/ColorField-BwlelY26.js.map +1 -0
  5. package/dist/chunks/{ComboBox-CuTLRmUg.js → ComboBox-DKln_mOd.js} +11 -11
  6. package/dist/chunks/ComboBox-DKln_mOd.js.map +1 -0
  7. package/dist/chunks/{CurveEditor-jVBEqqdv.js → CurveEditor-0EE00D6s.js} +2 -2
  8. package/dist/chunks/{CurveEditor-jVBEqqdv.js.map → CurveEditor-0EE00D6s.js.map} +1 -1
  9. package/dist/chunks/{DataGrid-BklgBqS0.js → DataGrid-CmHJe57v.js} +22 -17
  10. package/dist/chunks/DataGrid-CmHJe57v.js.map +1 -0
  11. package/dist/chunks/DetailList-BpU50XVe.js +54 -0
  12. package/dist/chunks/DetailList-BpU50XVe.js.map +1 -0
  13. package/dist/chunks/{DetentSlider-Bh7EpMqT.js → DetentSlider-DNTAk8xP.js} +2 -2
  14. package/dist/chunks/{DetentSlider-Bh7EpMqT.js.map → DetentSlider-DNTAk8xP.js.map} +1 -1
  15. package/dist/chunks/{FallthroughDiagram-C7BUe8eU.js → FallthroughDiagram-CS1IhQaU.js} +14 -14
  16. package/dist/chunks/FallthroughDiagram-CS1IhQaU.js.map +1 -0
  17. package/dist/chunks/{Field-DsBlnFKf.js → Field-DiX_WByH.js} +2 -2
  18. package/dist/chunks/{Field-DsBlnFKf.js.map → Field-DiX_WByH.js.map} +1 -1
  19. package/dist/chunks/{FillStrokeSwatch-CgsYQgew.js → FillStrokeSwatch-C4h3rUFM.js} +2 -2
  20. package/dist/chunks/FillStrokeSwatch-C4h3rUFM.js.map +1 -0
  21. package/dist/chunks/{FontFamilySelect-CGJ5OIOy.js → FontFamilySelect-DEvMXyZd.js} +2 -2
  22. package/dist/chunks/{FontFamilySelect-CGJ5OIOy.js.map → FontFamilySelect-DEvMXyZd.js.map} +1 -1
  23. package/dist/chunks/{FontWeightSelect-RbcXYJPS.js → FontWeightSelect-BLQ_LMJF.js} +2 -2
  24. package/dist/chunks/{FontWeightSelect-RbcXYJPS.js.map → FontWeightSelect-BLQ_LMJF.js.map} +1 -1
  25. package/dist/chunks/{GradientEditor-DypXzMc-.js → GradientEditor-DD2OjQtn.js} +4 -4
  26. package/dist/chunks/{GradientEditor-DypXzMc-.js.map → GradientEditor-DD2OjQtn.js.map} +1 -1
  27. package/dist/chunks/{InlineRange-P01ABSzo.js → InlineRange-DPjIsJB3.js} +3 -3
  28. package/dist/chunks/InlineRange-DPjIsJB3.js.map +1 -0
  29. package/dist/chunks/{Input-CzwvCZs_.js → Input-D7SDKbST.js} +2 -2
  30. package/dist/chunks/{Input-CzwvCZs_.js.map → Input-D7SDKbST.js.map} +1 -1
  31. package/dist/chunks/{Jog-DfNUYejm.js → Jog-BrkzdGV2.js} +6 -6
  32. package/dist/chunks/Jog-BrkzdGV2.js.map +1 -0
  33. package/dist/chunks/{LayerList-CsDqwYh-.js → LayerList-BqeWFuGc.js} +3 -3
  34. package/dist/chunks/{LayerList-CsDqwYh-.js.map → LayerList-BqeWFuGc.js.map} +1 -1
  35. package/dist/chunks/{ListEditor-BnGzqERm.js → ListEditor-OQDzG4A5.js} +2 -2
  36. package/dist/chunks/{ListEditor-BnGzqERm.js.map → ListEditor-OQDzG4A5.js.map} +1 -1
  37. package/dist/chunks/{MenuButton-L9f5vido.js → MenuButton-TAFg88Lp.js} +2 -2
  38. package/dist/chunks/{MenuButton-L9f5vido.js.map → MenuButton-TAFg88Lp.js.map} +1 -1
  39. package/dist/chunks/{MeshEditor-GbqqiPk8.js → MeshEditor-DWpvg8H6.js} +2 -2
  40. package/dist/chunks/{MeshEditor-GbqqiPk8.js.map → MeshEditor-DWpvg8H6.js.map} +1 -1
  41. package/dist/chunks/{NumberField-DLXXgh48.js → NumberField-DS0WSiin.js} +7 -7
  42. package/dist/chunks/NumberField-DS0WSiin.js.map +1 -0
  43. package/dist/chunks/{PaintField-C0hoXc0Y.js → PaintField-BdmQM53v.js} +2 -2
  44. package/dist/chunks/{PaintField-C0hoXc0Y.js.map → PaintField-BdmQM53v.js.map} +1 -1
  45. package/dist/chunks/{PaintInput-jQRxM95Y.js → PaintInput-DaQMeBsI.js} +4 -4
  46. package/dist/chunks/{PaintInput-jQRxM95Y.js.map → PaintInput-DaQMeBsI.js.map} +1 -1
  47. package/dist/chunks/{PatternPicker-CaMYXzhF.js → PatternPicker-B3HcMZWg.js} +2 -2
  48. package/dist/chunks/{PatternPicker-CaMYXzhF.js.map → PatternPicker-B3HcMZWg.js.map} +1 -1
  49. package/dist/chunks/{Plot2D-BHNbgCAl.js → Plot2D-BrvQS0dh.js} +7 -7
  50. package/dist/chunks/Plot2D-BrvQS0dh.js.map +1 -0
  51. package/dist/chunks/{PointPlotter-BZ_vk6gV.js → PointPlotter-BBXpQ_QD.js} +2 -2
  52. package/dist/chunks/{PointPlotter-BZ_vk6gV.js.map → PointPlotter-BBXpQ_QD.js.map} +1 -1
  53. package/dist/chunks/{Prefs-CkyldX-e.js → Prefs-BnVEyfWO.js} +76 -66
  54. package/dist/chunks/Prefs-BnVEyfWO.js.map +1 -0
  55. package/dist/chunks/{Properties-D15c8NP-.js → Properties-BoG2MMHR.js} +24 -24
  56. package/dist/chunks/{Properties-D15c8NP-.js.map → Properties-BoG2MMHR.js.map} +1 -1
  57. package/dist/chunks/{PropertyField-CV_Gw57S.js → PropertyField-PMrqBA0n.js} +243 -186
  58. package/dist/chunks/PropertyField-PMrqBA0n.js.map +1 -0
  59. package/dist/chunks/{RadioGroup-4dbzqvGy.js → RadioGroup-B70_M-gT.js} +2 -2
  60. package/dist/chunks/{RadioGroup-4dbzqvGy.js.map → RadioGroup-B70_M-gT.js.map} +1 -1
  61. package/dist/chunks/{RangeSlider-BxokZViJ.js → RangeSlider-DO5TZVk7.js} +8 -8
  62. package/dist/chunks/RangeSlider-DO5TZVk7.js.map +1 -0
  63. package/dist/chunks/{Select-BppLsJTz.js → Select-ouV8O6cp.js} +19 -19
  64. package/dist/chunks/Select-ouV8O6cp.js.map +1 -0
  65. package/dist/chunks/{SelectionPanel-Cm1qu-ZX.js → SelectionPanel-COZ6z9_P.js} +2 -2
  66. package/dist/chunks/{SelectionPanel-Cm1qu-ZX.js.map → SelectionPanel-COZ6z9_P.js.map} +1 -1
  67. package/dist/chunks/{Slider-Cl5RCeZ7.js → Slider-DSrlMEq8.js} +19 -19
  68. package/dist/chunks/Slider-DSrlMEq8.js.map +1 -0
  69. package/dist/chunks/{StatusBar-Ca9Dvhhq.js → StatusBar-BEOfOX0D.js} +6 -6
  70. package/dist/chunks/StatusBar-BEOfOX0D.js.map +1 -0
  71. package/dist/chunks/{SwatchStrip-Cw0l8Om7.js → SwatchStrip-Bi9iPLoh.js} +2 -2
  72. package/dist/chunks/{SwatchStrip-Cw0l8Om7.js.map → SwatchStrip-Bi9iPLoh.js.map} +1 -1
  73. package/dist/chunks/{Timeline-pqnYk5cV.js → Timeline-sMCH2fah.js} +99 -84
  74. package/dist/chunks/Timeline-sMCH2fah.js.map +1 -0
  75. package/dist/chunks/{TokenPanel-DAkL6vwQ.js → TokenPanel-WtKGY2Db.js} +29 -29
  76. package/dist/chunks/TokenPanel-WtKGY2Db.js.map +1 -0
  77. package/dist/chunks/{ToolOptionsBar-COsTJ1cu.js → ToolOptionsBar-BJKwnemU.js} +2 -2
  78. package/dist/chunks/{ToolOptionsBar-COsTJ1cu.js.map → ToolOptionsBar-BJKwnemU.js.map} +1 -1
  79. package/dist/chunks/{createKeyframeLayer-BTa4LyJE.js → createKeyframeLayer-DSVf13Su.js} +2 -2
  80. package/dist/chunks/{createKeyframeLayer-BTa4LyJE.js.map → createKeyframeLayer-DSVf13Su.js.map} +1 -1
  81. package/dist/chunks/field.module-B27pWv0i.js +15 -0
  82. package/dist/chunks/{field.module-Bh7BTKMh.js.map → field.module-B27pWv0i.js.map} +1 -1
  83. package/dist/chunks/{model-B1-gMBIW.js → model-B6zuQg6b.js} +2 -2
  84. package/dist/chunks/{model-B1-gMBIW.js.map → model-B6zuQg6b.js.map} +1 -1
  85. package/dist/components/BandEditor/index.js +1 -1
  86. package/dist/components/ColorField/index.js +1 -1
  87. package/dist/components/ComboBox/index.js +1 -1
  88. package/dist/components/CurveEditor/index.js +2 -2
  89. package/dist/components/DataGrid/DataGrid.d.ts +3 -0
  90. package/dist/components/DataGrid/DataGrid.d.ts.map +1 -1
  91. package/dist/components/DataGrid/index.js +1 -1
  92. package/dist/components/DetailList/DetailList.d.ts +1 -1
  93. package/dist/components/DetailList/DetailList.d.ts.map +1 -1
  94. package/dist/components/DetailList/index.js +1 -1
  95. package/dist/components/DetentSlider/index.js +1 -1
  96. package/dist/components/FallthroughDiagram/index.js +1 -1
  97. package/dist/components/Field/index.js +1 -1
  98. package/dist/components/FillStrokeSwatch/FillStrokeSwatch.d.ts +5 -2
  99. package/dist/components/FillStrokeSwatch/FillStrokeSwatch.d.ts.map +1 -1
  100. package/dist/components/FillStrokeSwatch/index.js +1 -1
  101. package/dist/components/FontFamilySelect/index.js +1 -1
  102. package/dist/components/FontWeightSelect/index.js +1 -1
  103. package/dist/components/GradientEditor/index.js +1 -1
  104. package/dist/components/InlineRange/index.js +1 -1
  105. package/dist/components/Input/index.js +1 -1
  106. package/dist/components/Jog/index.js +1 -1
  107. package/dist/components/LayerList/index.js +1 -1
  108. package/dist/components/ListEditor/index.js +1 -1
  109. package/dist/components/MenuButton/index.js +1 -1
  110. package/dist/components/MeshEditor/index.js +1 -1
  111. package/dist/components/NumberField/index.js +1 -1
  112. package/dist/components/PaintField/index.js +1 -1
  113. package/dist/components/PaintInput/index.js +1 -1
  114. package/dist/components/PatternPicker/index.js +1 -1
  115. package/dist/components/Plot2D/index.js +1 -1
  116. package/dist/components/PointPlotter/index.js +1 -1
  117. package/dist/components/Prefs/PrefsForm.d.ts +14 -0
  118. package/dist/components/Prefs/PrefsForm.d.ts.map +1 -1
  119. package/dist/components/Prefs/PrefsRow.d.ts +4 -0
  120. package/dist/components/Prefs/PrefsRow.d.ts.map +1 -1
  121. package/dist/components/Prefs/index.js +1 -1
  122. package/dist/components/Properties/PropertyField.d.ts.map +1 -1
  123. package/dist/components/Properties/PropertyPanel.d.ts +18 -1
  124. package/dist/components/Properties/PropertyPanel.d.ts.map +1 -1
  125. package/dist/components/Properties/index.d.ts +2 -2
  126. package/dist/components/Properties/index.d.ts.map +1 -1
  127. package/dist/components/Properties/index.js +3 -3
  128. package/dist/components/RadioGroup/index.js +1 -1
  129. package/dist/components/RangeSlider/index.js +1 -1
  130. package/dist/components/Select/index.js +1 -1
  131. package/dist/components/SelectionPanel/index.js +2 -2
  132. package/dist/components/Slider/index.js +1 -1
  133. package/dist/components/StatusBar/StatusBar.d.ts +1 -1
  134. package/dist/components/StatusBar/index.js +1 -1
  135. package/dist/components/SwatchStrip/index.js +1 -1
  136. package/dist/components/Timeline/Transport.d.ts.map +1 -1
  137. package/dist/components/Timeline/index.js +1 -1
  138. package/dist/components/TokenPanel/index.js +1 -1
  139. package/dist/components/ToolOptionsBar/index.js +1 -1
  140. package/dist/index.js +75 -75
  141. package/dist/index.js.map +1 -1
  142. package/dist/style.css +1 -1
  143. package/package.json +9 -9
  144. package/dist/chunks/BandEditor-Dd0egAgE.js.map +0 -1
  145. package/dist/chunks/ColorField-BLs9fbeq.js.map +0 -1
  146. package/dist/chunks/ComboBox-CuTLRmUg.js.map +0 -1
  147. package/dist/chunks/DataGrid-BklgBqS0.js.map +0 -1
  148. package/dist/chunks/DetailList-DFtqdOJj.js +0 -52
  149. package/dist/chunks/DetailList-DFtqdOJj.js.map +0 -1
  150. package/dist/chunks/FallthroughDiagram-C7BUe8eU.js.map +0 -1
  151. package/dist/chunks/FillStrokeSwatch-CgsYQgew.js.map +0 -1
  152. package/dist/chunks/InlineRange-P01ABSzo.js.map +0 -1
  153. package/dist/chunks/Jog-DfNUYejm.js.map +0 -1
  154. package/dist/chunks/NumberField-DLXXgh48.js.map +0 -1
  155. package/dist/chunks/Plot2D-BHNbgCAl.js.map +0 -1
  156. package/dist/chunks/Prefs-CkyldX-e.js.map +0 -1
  157. package/dist/chunks/PropertyField-CV_Gw57S.js.map +0 -1
  158. package/dist/chunks/RangeSlider-BxokZViJ.js.map +0 -1
  159. package/dist/chunks/Select-BppLsJTz.js.map +0 -1
  160. package/dist/chunks/Slider-Cl5RCeZ7.js.map +0 -1
  161. package/dist/chunks/StatusBar-Ca9Dvhhq.js.map +0 -1
  162. package/dist/chunks/Timeline-pqnYk5cV.js.map +0 -1
  163. package/dist/chunks/TokenPanel-DAkL6vwQ.js.map +0 -1
  164. package/dist/chunks/field.module-Bh7BTKMh.js +0 -15
@@ -4,18 +4,18 @@ import { jsx as n, jsxs as r } from "react/jsx-runtime";
4
4
  import { Fragment as i, useEffect as a, useId as o, useRef as s, useState as c } from "react";
5
5
  import { amount as l, decimal as u, qty as d, retag as f } from "@weasel-js/quantity";
6
6
  var p = {
7
- root: "_root_1muzo_1",
8
- label: "_label_1muzo_11",
9
- ruler: "_ruler_1muzo_19",
10
- tick: "_tick_1muzo_25",
11
- tickLabel: "_tickLabel_1muzo_35",
12
- bands: "_bands_1muzo_45",
13
- band: "_band_1muzo_45",
14
- selected: "_selected_1muzo_92",
15
- bandContent: "_bandContent_1muzo_99",
16
- bandGhost: "_bandGhost_1muzo_107",
17
- seamGhost: "_seamGhost_1muzo_118",
18
- seam: "_seam_1muzo_118"
7
+ root: "_root_rdp07_1",
8
+ label: "_label_rdp07_11",
9
+ ruler: "_ruler_rdp07_19",
10
+ tick: "_tick_rdp07_25",
11
+ tickLabel: "_tickLabel_rdp07_35 _numeric_zns9n_4",
12
+ bands: "_bands_rdp07_48",
13
+ band: "_band_rdp07_48",
14
+ selected: "_selected_rdp07_95",
15
+ bandContent: "_bandContent_rdp07_102",
16
+ bandGhost: "_bandGhost_rdp07_110",
17
+ seamGhost: "_seamGhost_rdp07_121",
18
+ seam: "_seam_rdp07_121"
19
19
  };
20
20
  //#endregion
21
21
  //#region src/components/BandEditor/scale.ts
@@ -289,4 +289,4 @@ function j(l) {
289
289
  //#endregion
290
290
  export { h as n, g as r, j as t };
291
291
 
292
- //# sourceMappingURL=BandEditor-Dd0egAgE.js.map
292
+ //# sourceMappingURL=BandEditor-B4jUAHQ8.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BandEditor-B4jUAHQ8.js","names":["s","Fragment","snapToNearest","Fragment$1","BandEditor_module_default","clamp01","u","linearScale","max","min","value","unit","logScale","lo","hi","warnedNonPositiveMin","isDev","resolveScale","requested","scale","normalizeBands","bands","b","bandBounds","index","seamBounds","clampSeamTo","to","setSeam","next","unitEdges","edges","clampBandShift","isFirst","isLast","shift","moveBandEdges","from","splitBands","at","makeData","mergeBand","i","SEAM_DISPLAY","decimal","untag","band","amount","retagBands","firstTagged","input","source","retag","KEY_STEP","pct","keepData","isTextEntry","target","tag","BandEditor","tagged","props","snap","splitBand","trackRef","useRef","sessionRef","labelId","useId","draft","setDraft","useState","retagged","onInput","onChange","committed","shown","sc","toUnit","v","fromUnit","trackWidth","unitAt","rect","clientX","snapped","altKey","ticks","width","tick","drag","openPointerSession","down","onEnd","onSeamPointerDown","e","base","latest","ev","onSeamKeyDown","delta","onBandPointerDown","selectedIndex","onSelect","startUnit","onBandFocus","className","label","qty","isSelected","renderBand","display"],"sources":["../../src/components/BandEditor/BandEditor.module.css","../../src/components/BandEditor/scale.ts","../../src/components/BandEditor/bands.ts","../../src/components/BandEditor/BandEditor.tsx"],"sourcesContent":[".root {\n position: relative;\n user-select: none;\n -webkit-user-select: none;\n touch-action: none;\n display: flex;\n flex-direction: column;\n gap: var(--wzl-space-1);\n}\n\n.label {\n font: var(--wzl-font-weight-medium) var(--wzl-font-size-sm) / var(--wzl-leading-tight) var(--wzl-font-ui);\n color: var(--wzl-fg-muted);\n}\n\n/* The ruler carries the ticks and is the split affordance: a click anywhere\n * on it divides the band underneath. The band strip below owns selection and\n * body drags, so neither gesture has to guess which the pointer meant. */\n.ruler {\n position: relative;\n height: var(--be-ruler-height, 20px);\n cursor: crosshair;\n}\n\n.tick {\n position: absolute;\n left: var(--be-at);\n bottom: 0;\n width: 1px;\n height: 6px;\n background: var(--wzl-fg-subtle);\n pointer-events: none;\n}\n\n.tickLabel {\n composes: numeric from '@weasel-js/theme/numeric.module.css';\n position: absolute;\n bottom: 7px;\n left: 0;\n transform: translateX(-50%);\n font-weight: var(--wzl-font-weight-medium);\n font-size: var(--wzl-font-size-xs);\n line-height: var(--wzl-leading-tight);\n color: var(--wzl-fg-muted);\n white-space: nowrap;\n}\n\n.bands {\n position: relative;\n height: var(--be-band-height, 34px);\n}\n\n.band {\n position: absolute;\n top: 0;\n bottom: 0;\n left: var(--be-from);\n width: calc(var(--be-to) - var(--be-from));\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n padding: 0 var(--wzl-space-2);\n margin: 0;\n background: var(--wzl-surface-raised);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n color: var(--wzl-fg);\n font: var(--wzl-font-weight-medium) var(--wzl-font-size-sm) / var(--wzl-leading-tight) var(--wzl-font-ui);\n cursor: grab;\n}\n\n.band:hover {\n background: var(--wzl-surface-hover);\n}\n\n.band:active {\n cursor: grabbing;\n}\n\n.band:focus-visible {\n outline: 2px solid var(--wzl-accent);\n outline-offset: -2px;\n}\n\n/* First and last bands are pinned to `min` / `max`, so their bodies do not\n * translate — the cursor should not promise otherwise. */\n.band:first-of-type,\n.band:last-of-type {\n cursor: default;\n}\n\n/* Accent mixed toward transparent rather than a flat token, so the tint\n reads over whatever surface the consumer's theme puts behind it. */\n.selected {\n background: color-mix(in srgb, var(--wzl-accent) 22%, transparent);\n border-color: var(--wzl-accent);\n box-shadow: inset 0 0 0 1px var(--wzl-accent);\n color: var(--wzl-fg);\n}\n\n.bandContent {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n pointer-events: none;\n}\n\n/* Where a dragged band or seam started, held until the drag ends. */\n.bandGhost {\n position: absolute;\n top: 0;\n bottom: 0;\n left: var(--be-from);\n width: calc(var(--be-to) - var(--be-from));\n border: 1px dashed var(--wzl-border-strong);\n border-radius: var(--wzl-radius-sm);\n pointer-events: none;\n}\n\n.seamGhost {\n position: absolute;\n left: var(--be-at);\n top: -3px;\n bottom: -3px;\n width: 0;\n border-left: 1px dashed var(--wzl-border-strong);\n pointer-events: none;\n}\n\n/* Wider than the line it draws: a 1px seam is unhittable. */\n.seam {\n position: absolute;\n left: var(--be-at);\n top: -3px;\n bottom: -3px;\n width: 11px;\n margin-left: -5px;\n z-index: 1;\n cursor: ew-resize;\n background: none;\n border: none;\n}\n\n.seam::before {\n content: '';\n position: absolute;\n top: 0;\n bottom: 0;\n left: 5px;\n width: 1px;\n background: var(--wzl-border-strong);\n}\n\n.seam:hover::before,\n.seam:focus-visible::before {\n background: var(--wzl-accent);\n width: 2px;\n}\n\n.seam:focus-visible {\n outline: 2px solid var(--wzl-accent);\n outline-offset: 0;\n border-radius: var(--wzl-radius-sm);\n}\n","/** Maps a domain value onto its position along the track, and back. */\nexport interface BandScale {\n /** Domain → position in [0,1]. Must be monotonic increasing. */\n toUnit(value: number, min: number, max: number): number;\n /** Inverse of `toUnit`. */\n fromUnit(unit: number, min: number, max: number): number;\n}\n\nexport function clamp01(u: number): number {\n return u < 0 ? 0 : u > 1 ? 1 : u;\n}\n\n/** Even domain steps take even track steps. */\nexport const linearScale: BandScale = {\n toUnit: (value, min, max) => (max === min ? 0 : (value - min) / (max - min)),\n fromUnit: (unit, min, max) => min + unit * (max - min),\n};\n\n/** Even *ratios* take even track steps. Requires `min > 0`. */\nexport const logScale: BandScale = {\n toUnit: (value, min, max) => {\n const lo = Math.log(min);\n const hi = Math.log(max);\n return hi === lo ? 0 : (Math.log(value) - lo) / (hi - lo);\n },\n fromUnit: (unit, min, max) => {\n const lo = Math.log(min);\n return Math.exp(lo + unit * (Math.log(max) - lo));\n },\n};\n\nlet warnedNonPositiveMin = false;\n\nfunction isDev(): boolean {\n return typeof process !== 'undefined' ? process.env.NODE_ENV !== 'production' : true;\n}\n\n/**\n * Resolves the `scale` prop, substituting `linearScale` for a `logScale`\n * that `min` would send to `NaN`.\n */\nexport function resolveScale(\n scale: 'linear' | 'log' | BandScale | undefined,\n min: number,\n): BandScale {\n const requested =\n scale === undefined || scale === 'log'\n ? logScale\n : scale === 'linear'\n ? linearScale\n : scale;\n\n if (requested !== logScale || min > 0) return requested;\n\n if (isDev() && !warnedNonPositiveMin) {\n warnedNonPositiveMin = true;\n console.warn(\n `BandEditor: a log scale needs min > 0, but min is ${min}. Falling back to a linear scale. ` +\n `Pass scale=\"linear\" to silence this.`,\n );\n }\n return linearScale;\n}\n","import type { Quantity } from '@weasel-js/quantity';\nimport type { BandScale } from './scale';\nimport { clamp01 } from './scale';\n\n/** One band. `from` may be a tagged quantity, which keeps its display and\n * unit through every edit: see `BandEditor`. */\nexport interface Band<T, F extends Quantity = number> {\n /** Domain value where this band starts. The first band's is normalized to `min`. */\n from: F;\n data: T;\n}\n\n/**\n * A band list covers `[min, max]` exactly: N bands, N−1 interior seams, no\n * gaps and no overlaps. Seam *k* is `bands[k + 1].from`, and lives between\n * `bands[k].from` and the start of the band after next (or `max`).\n *\n * Every function here preserves that. Each returns the array it was given\n * when the edit would be a no-op, so callers can skip a callback with `===`.\n */\n\n/** Pins the first band's start to `min`; leaves the rest alone. */\nexport function normalizeBands<T>(value: readonly Band<T>[], min: number): Band<T>[] {\n const bands = value.map((b) => ({ ...b }));\n if (bands.length > 0) bands[0].from = min;\n return bands;\n}\n\n/** `[from, to]` of band `index` in domain space. */\nexport function bandBounds<T>(\n bands: readonly Band<T>[],\n index: number,\n min: number,\n max: number,\n): [number, number] {\n const from = index === 0 ? min : bands[index].from;\n const to = index === bands.length - 1 ? max : bands[index + 1].from;\n return [from, to];\n}\n\n/** How far seam `index` can travel before it would cross a neighbour. */\nexport function seamBounds<T>(\n bands: readonly Band<T>[],\n index: number,\n min: number,\n max: number,\n): [number, number] {\n const lo = index === 0 ? min : bands[index].from;\n const hi = index + 2 <= bands.length - 1 ? bands[index + 2].from : max;\n return [lo, hi];\n}\n\n/** Clamps a target position for seam `index` to its neighbours, inclusive. */\nexport function clampSeamTo<T>(\n bands: readonly Band<T>[],\n index: number,\n to: number,\n min: number,\n max: number,\n): number {\n const [lo, hi] = seamBounds(bands, index, min, max);\n return Math.max(lo, Math.min(hi, to));\n}\n\nexport function setSeam<T>(bands: readonly Band<T>[], index: number, to: number): Band<T>[] {\n if (bands[index + 1] === undefined || bands[index + 1].from === to) return bands as Band<T>[];\n const next = bands.map((b) => ({ ...b }));\n next[index + 1].from = to;\n return next;\n}\n\n/** Band edges as track positions: `length + 1` values from 0 to 1. */\nexport function unitEdges<T>(\n bands: readonly Band<T>[],\n scale: BandScale,\n min: number,\n max: number,\n): number[] {\n const edges = bands.map((b, i) => (i === 0 ? 0 : clamp01(scale.toUnit(b.from, min, max))));\n edges.push(1);\n return edges;\n}\n\n/**\n * Largest part of `shift` band `index` can take without pushing a neighbour's\n * far edge past it. Zero for the first and last band, whose outer edges are\n * `min` and `max` and do not move.\n */\nexport function clampBandShift(edges: readonly number[], index: number, shift: number): number {\n const isFirst = index === 0;\n const isLast = index === edges.length - 2;\n if (isFirst || isLast) return 0;\n const lo = edges[index - 1] - edges[index];\n const hi = edges[index + 2] - edges[index + 1];\n return Math.max(lo, Math.min(hi, shift));\n}\n\n/** Moves both edges of band `index`, which is the same as moving two seams. */\nexport function moveBandEdges<T>(\n bands: readonly Band<T>[],\n index: number,\n from: number,\n to: number,\n): Band<T>[] {\n const next = bands.map((b) => ({ ...b }));\n next[index].from = from;\n if (next[index + 1] !== undefined) next[index + 1].from = to;\n return next;\n}\n\n/**\n * Splits the band containing `at` in two, the new band starting there.\n * A position already on a seam or on `min` / `max` splits nothing.\n */\nexport function splitBands<T>(\n bands: readonly Band<T>[],\n at: number,\n min: number,\n max: number,\n makeData: (at: number, from: T) => T,\n): Band<T>[] {\n if (bands.length === 0 || at <= min || at >= max) return bands as Band<T>[];\n let index = -1;\n for (let i = 0; i < bands.length; i++) {\n const [from, to] = bandBounds(bands, i, min, max);\n if (at > from && at < to) {\n index = i;\n break;\n }\n }\n if (index < 0) return bands as Band<T>[];\n const next = bands.map((b) => ({ ...b }));\n next.splice(index + 1, 0, { from: at, data: makeData(at, bands[index].data) });\n return next;\n}\n\n/**\n * Merges band `index` into its left neighbour, which keeps its payload and\n * absorbs the span. The first band has no left neighbour and cannot be\n * merged away — a partition always has at least one part.\n */\nexport function mergeBand<T>(bands: readonly Band<T>[], index: number): Band<T>[] {\n if (index <= 0 || index >= bands.length) return bands as Band<T>[];\n return bands.filter((_, i) => i !== index).map((b) => ({ ...b }));\n}\n","import {\n Fragment,\n useEffect,\n useId,\n useRef,\n useState,\n type CSSProperties,\n type KeyboardEvent as ReactKeyboardEvent,\n type PointerEvent as ReactPointerEvent,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport { openPointerSession, type PointerSession } from '@weasel-js/core';\nimport { amount, decimal, qty, retag, type Display, type Quantity } from '@weasel-js/quantity';\nimport s from './BandEditor.module.css';\nimport { SNAP_RADIUS_PX, snapToNearest } from '../../snap';\nimport { clamp01, resolveScale, type BandScale } from './scale';\nimport {\n bandBounds,\n clampBandShift,\n clampSeamTo,\n mergeBand,\n moveBandEdges,\n normalizeBands,\n seamBounds,\n setSeam,\n splitBands,\n unitEdges,\n type Band,\n} from './bands';\n\nexport type { Band, BandScale };\n\n/** Props for {@link BandEditor}. */\nexport interface BandEditorProps<T, F extends Quantity = number> {\n /** Ascending by `from`. `value[0].from` is normalized to `min` on read. A\n * tagged `from` stays tagged through every edit, with its display and unit. */\n value: Band<T, F>[];\n /** Live during a drag — wire for preview, do not write to history. */\n onInput?: (next: Band<T, F>[]) => void;\n /** Committed at gesture end: one call per gesture. */\n onChange: (next: Band<T, F>[]) => void;\n min: number;\n max: number;\n /** Default `'log'`. */\n scale?: 'linear' | 'log' | BandScale;\n /** A tick with no `label` of its own is labeled through `display`, when given. */\n ticks?: { at: number; label?: ReactNode }[];\n /**\n * How a seam's value shows and is spoken — `fraction()` makes a seam at\n * 1/12 announce `1 over 12` rather than `0.08333333333333333`. A tagged\n * `from` uses its own display instead. Default: three decimals.\n */\n display?: Display;\n /** Snap a dragged seam to a tick within ~6px. Default true; `alt` defeats it per-drag. */\n snap?: boolean;\n renderBand?: (band: Band<T, F>, index: number) => ReactNode;\n selectedIndex?: number | null;\n onSelect?: (index: number) => void;\n /** Payload for a band split off an existing one. Default duplicates `from`. */\n splitBand?: (at: number, from: T) => T;\n label?: ReactNode;\n className?: string;\n}\n\n/** A drag under way: the bands as the pointer has them, and which edges it took from where. */\ntype Draft<T> = { bands: Band<T>[]; ghost: { seam: number } | { band: number } };\n\nconst SEAM_DISPLAY = decimal();\n\nfunction untag<T, F extends Quantity>(band: Band<T, F>): Band<T> {\n return typeof band.from === 'number' ? (band as Band<T>) : { from: amount(band.from), data: band.data };\n}\n\n/**\n * `next` with each `from` in the shape the consumer gave it. A band keeps the\n * tag of the band at its index when the count is unchanged; after a split or\n * a merge it takes the tag of the input band carrying the same payload, else\n * the first tagged one — a split band inherits its parent's presentation.\n */\nfunction retagBands<T, F extends Quantity>(input: readonly Band<T, F>[], next: readonly Band<T>[]): Band<T, F>[] {\n const firstTagged = input.find((b) => typeof b.from !== 'number');\n if (!firstTagged) return next as Band<T, F>[];\n return next.map((band, i) => {\n const source =\n (next.length === input.length ? input[i] : input.find((b) => b.data === band.data)) ?? firstTagged;\n return { from: retag(source.from, band.from), data: band.data };\n });\n}\n\n/** One arrow-key step, as a fraction of the track. */\nconst KEY_STEP = 0.01;\n\nfunction pct(unit: number): string {\n return `${clamp01(unit) * 100}%`;\n}\n\nfunction keepData<T>(_at: number, from: T): T {\n return from;\n}\n\nfunction isTextEntry(target: EventTarget | null): boolean {\n if (!(target instanceof HTMLElement)) return false;\n if (target.isContentEditable) return true;\n const tag = target.tagName;\n return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT';\n}\n\n/**\n * Divides a numeric axis into contiguous bands and lets you drag the seams\n * between them. Each band carries a payload the consumer supplies and\n * renders through `renderBand`; the control itself knows nothing about what\n * a band means.\n *\n * The axis is always fully covered — N bands, N−1 interior seams, no gaps and\n * no overlaps — so editing is editing a sorted list of seam positions. Seams\n * clamp at their neighbours rather than crossing, which means a drag can\n * never destroy a band: removal is only ever the explicit `x` / `Delete`\n * merge. The first band is doubly exceptional, its left edge pinned to `min`\n * and its body immovable, and it has no left neighbour to merge into.\n *\n * | Gesture | Effect |\n * |---|---|\n * | drag a seam | resize the two bands either side |\n * | drag a band body | move both its seams, preserving its span |\n * | click the ruler | split the band under the pointer |\n * | `x` / `Delete` | merge the selected band into its left neighbour |\n * | click a band | select it |\n * | `←` `→` on a focused seam | move it by one step; `shift` for ten |\n *\n * `scale` defaults to `'log'`, because the interesting part of a width axis\n * is usually its narrow end.\n */\nexport function BandEditor<T, F extends Quantity = number>(props: BandEditorProps<T, F>): ReactElement {\n const {\n value: tagged,\n display = SEAM_DISPLAY,\n min,\n max,\n scale,\n ticks,\n renderBand,\n selectedIndex,\n onSelect,\n label,\n className,\n } = props;\n const snap = props.snap ?? true;\n const splitBand = props.splitBand ?? keepData;\n\n const trackRef = useRef<HTMLDivElement | null>(null);\n const sessionRef = useRef<PointerSession | null>(null);\n useEffect(() => () => { sessionRef.current?.cancel(); }, []);\n const labelId = useId();\n const [draft, setDraft] = useState<Draft<T> | null>(null);\n const value = tagged.map(untag);\n const retagged = (next: Band<T>[]): Band<T, F>[] => retagBands(tagged, next);\n const onInput = props.onInput && ((next: Band<T>[]) => props.onInput?.(retagged(next)));\n const onChange = (next: Band<T>[]) => props.onChange(retagged(next));\n const committed = normalizeBands(value, min);\n // Drawn from the drag while one is under way, so the control moves with the pointer whether or not the consumer\n // wires `onInput`. Every edit reads `committed`.\n const bands = draft?.bands ?? committed;\n const shown = retagged(bands);\n const sc = resolveScale(scale, min);\n const toUnit = (v: number): number => clamp01(sc.toUnit(v, min, max));\n const fromUnit = (u: number): number => sc.fromUnit(clamp01(u), min, max);\n\n const trackWidth = (): number => trackRef.current?.getBoundingClientRect().width ?? 0;\n\n const unitAt = (clientX: number): number => {\n const rect = trackRef.current?.getBoundingClientRect();\n if (!rect || rect.width === 0) return 0;\n return clamp01((clientX - rect.left) / rect.width);\n };\n\n const snapped = (unit: number, altKey: boolean): number => {\n if (!snap || altKey || !ticks || ticks.length === 0) return unit;\n const width = trackWidth();\n if (width === 0) return unit;\n return snapToNearest(unit, ticks.map((tick) => toUnit(tick.at)), SNAP_RADIUS_PX / width);\n };\n\n // No pointer capture: a band body is a `<button>` whose content the consumer\n // renders, and capture would retarget pointerup and kill the click on it.\n // A drag that ends without a release commits nothing.\n const drag = (\n down: ReactPointerEvent<HTMLElement>,\n onMove: (ev: PointerEvent) => void,\n onEnd: () => void,\n ): void => {\n sessionRef.current?.cancel();\n sessionRef.current = openPointerSession(down.currentTarget, down, {\n onMove,\n onEnd: () => { sessionRef.current = null; setDraft(null); onEnd(); },\n onCancel: () => { sessionRef.current = null; setDraft(null); },\n }, { capture: false });\n };\n\n const onSeamPointerDown = (index: number) => (e: ReactPointerEvent<HTMLDivElement>): void => {\n if (typeof e.button === 'number' && e.button > 0) return;\n e.preventDefault();\n e.stopPropagation();\n const base = committed;\n let latest: Band<T>[] | null = null;\n drag(\n e,\n (ev) => {\n const to = clampSeamTo(base, index, fromUnit(snapped(unitAt(ev.clientX), ev.altKey)), min, max);\n latest = setSeam(base, index, to);\n setDraft({ bands: latest, ghost: { seam: index } });\n onInput?.(latest);\n },\n () => {\n if (latest) onChange(latest);\n },\n );\n };\n\n const onSeamKeyDown = (index: number) => (e: ReactKeyboardEvent<HTMLDivElement>): void => {\n const [lo, hi] = seamBounds(committed, index, min, max);\n let target: number;\n if (e.key === 'Home') target = lo;\n else if (e.key === 'End') target = hi;\n else {\n let delta = 0;\n if (e.key === 'ArrowRight' || e.key === 'ArrowUp') delta = e.shiftKey ? KEY_STEP * 10 : KEY_STEP;\n else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') delta = e.shiftKey ? -KEY_STEP * 10 : -KEY_STEP;\n else return;\n target = fromUnit(toUnit(committed[index + 1].from) + delta);\n }\n e.preventDefault();\n const next = setSeam(committed, index, clampSeamTo(committed, index, target, min, max));\n if (next !== committed) onChange(next);\n };\n\n const onBandPointerDown = (index: number) => (e: ReactPointerEvent<HTMLButtonElement>): void => {\n if (typeof e.button === 'number' && e.button > 0) return;\n if (index !== selectedIndex) onSelect?.(index);\n // The first and last bands' outer edges are `min` and `max`, which do not\n // move, so their bodies have nothing to translate.\n if (index === 0 || index === committed.length - 1) return;\n const base = committed;\n const edges = unitEdges(base, sc, min, max);\n const startUnit = unitAt(e.clientX);\n let latest: Band<T>[] | null = null;\n drag(\n e,\n (ev) => {\n const shift = clampBandShift(edges, index, unitAt(ev.clientX) - startUnit);\n latest = moveBandEdges(\n base,\n index,\n fromUnit(edges[index] + shift),\n fromUnit(edges[index + 1] + shift),\n );\n setDraft({ bands: latest, ghost: { band: index } });\n onInput?.(latest);\n },\n () => {\n if (latest) onChange(latest);\n },\n );\n };\n\n const onBandFocus = (index: number) => (): void => {\n if (index !== selectedIndex) onSelect?.(index);\n };\n\n const onTrackPointerDown = (e: ReactPointerEvent<HTMLDivElement>): void => {\n if (typeof e.button === 'number' && e.button > 0) return;\n e.preventDefault();\n const next = splitBands(committed, fromUnit(unitAt(e.clientX)), min, max, splitBand);\n if (next !== committed) onChange(next);\n };\n\n const onRootKeyDown = (e: ReactKeyboardEvent<HTMLDivElement>): void => {\n if (e.key !== 'x' && e.key !== 'Delete') return;\n // `x` is a bare letter and Cmd/Ctrl+X is cut: neither may be swallowed\n // when the keystroke belongs to something a consumer put in a band.\n if (e.metaKey || e.ctrlKey || e.altKey) return;\n if (isTextEntry(e.target)) return;\n if (selectedIndex === null || selectedIndex === undefined) return;\n const next = mergeBand(committed, selectedIndex);\n if (next === committed) return;\n e.preventDefault();\n onChange(next);\n onSelect?.(selectedIndex - 1);\n };\n\n return (\n <div\n className={[s.root, className].filter(Boolean).join(' ')}\n role=\"group\"\n aria-label={typeof label === 'string' ? label : undefined}\n aria-labelledby={label !== undefined && typeof label !== 'string' ? labelId : undefined}\n onKeyDown={onRootKeyDown}\n >\n {label !== undefined && <div id={labelId} className={s.label}>{label}</div>}\n <div className={s.ruler} ref={trackRef} data-band-ruler=\"\" onPointerDown={onTrackPointerDown}>\n {ticks?.map((tick, i) => (\n <span\n key={i}\n className={s.tick}\n data-tick-at={tick.at}\n style={{ '--be-at': pct(toUnit(tick.at)) } as CSSProperties}\n >\n {(tick.label !== undefined || props.display !== undefined) && (\n <span className={s.tickLabel}>{tick.label ?? qty(tick.at, props.display).text}</span>\n )}\n </span>\n ))}\n </div>\n <div className={s.bands}>\n {bands.map((_, i) => {\n const [from, to] = bandBounds(bands, i, min, max);\n const isSelected = selectedIndex === i;\n return (\n <Fragment key={i}>\n <button\n type=\"button\"\n className={[s.band, isSelected ? s.selected : null].filter(Boolean).join(' ')}\n style={{ '--be-from': pct(toUnit(from)), '--be-to': pct(toUnit(to)) } as CSSProperties}\n aria-label={`Band ${i + 1}`}\n aria-pressed={isSelected}\n data-band-index={i}\n onPointerDown={onBandPointerDown(i)}\n onFocus={onBandFocus(i)}\n >\n <span className={s.bandContent}>{renderBand?.(shown[i]!, i)}</span>\n </button>\n {i < bands.length - 1 && (\n <div\n role=\"slider\"\n tabIndex={0}\n className={s.seam}\n style={{ '--be-at': pct(toUnit(bands[i + 1].from)) } as CSSProperties}\n aria-label={`Seam ${i + 1}`}\n aria-orientation=\"horizontal\"\n aria-valuemin={seamBounds(bands, i, min, max)[0]}\n aria-valuemax={seamBounds(bands, i, min, max)[1]}\n aria-valuenow={bands[i + 1].from}\n aria-valuetext={qty(shown[i + 1]!.from, display).spoken}\n data-seam-index={i}\n onPointerDown={onSeamPointerDown(i)}\n onKeyDown={onSeamKeyDown(i)}\n />\n )}\n </Fragment>\n );\n })}\n {draft && 'band' in draft.ghost && (\n <div\n className={s.bandGhost}\n data-band-ghost=\"\"\n aria-hidden=\"true\"\n style={{\n '--be-from': pct(toUnit(bandBounds(committed, draft.ghost.band, min, max)[0])),\n '--be-to': pct(toUnit(bandBounds(committed, draft.ghost.band, min, max)[1])),\n } as CSSProperties}\n />\n )}\n {draft && 'seam' in draft.ghost && (\n <div\n className={s.seamGhost}\n data-band-ghost=\"\"\n aria-hidden=\"true\"\n style={{ '--be-at': pct(toUnit(committed[draft.ghost.seam + 1].from)) } as CSSProperties}\n />\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,11 +1,11 @@
1
1
  import { i as e } from "./RecentColorsProvider-DHcfk25u.js";
2
- import { n as t, t as n } from "./InlineRange-P01ABSzo.js";
2
+ import { n as t, t as n } from "./InlineRange-DPjIsJB3.js";
3
3
  import { getAlpha01 as r, toHex8 as i, withAlpha01 as a } from "@weasel-js/core";
4
4
  import { Fragment as o, jsx as s, jsxs as c } from "react/jsx-runtime";
5
5
  import { useEffect as l, useRef as u, useState as d } from "react";
6
6
  var f = {
7
- range: "_range_1rka6_2",
8
- readout: "_readout_1rka6_6"
7
+ range: "_range_7uif9_2",
8
+ readout: "_readout_7uif9_6 _numeric_zns9n_4"
9
9
  };
10
10
  //#endregion
11
11
  //#region src/components/ColorField/OpacityRange.tsx
@@ -87,4 +87,4 @@ function h(t) {
87
87
  //#endregion
88
88
  export { p as n, h as t };
89
89
 
90
- //# sourceMappingURL=ColorField-BLs9fbeq.js.map
90
+ //# sourceMappingURL=ColorField-BwlelY26.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ColorField-BwlelY26.js","names":["_Fragment","s","shared","s","useRecordRecentColor","range_module_default","InlineRange","OpacityRange_module_default","OpacityRange","props","label","draft","setDraft","useState","pct","value","commit","onChange","Fragment","next","onInput","ColorField_module_default","ColorField","hex8","toHex8","rgb6","alpha01","getAlpha01","alphaPct","colorDraft","setColorDraft","alphaDraft","setAlphaDraft","visibleRgb","visibleAlphaPct","compose","alpha","withAlpha01","rgb","a01","alphaLocked","mixed","record","swatch","useRef","latestCommit","useEffect","el","onClose","className","alphaDisabled"],"sources":["../../src/components/ColorField/OpacityRange.module.css","../../src/components/ColorField/OpacityRange.tsx","../../src/components/ColorField/ColorField.module.css","../../src/components/ColorField/ColorField.tsx"],"sourcesContent":["/* `InlineRange` owns the track; this only says how it shares the row. */\n.range {\n flex: 1;\n}\n\n.readout {\n composes: numeric from '@weasel-js/theme/numeric.module.css';\n min-inline-size: 3ch;\n text-align: end;\n font-size: var(--wzl-font-size-sm);\n opacity: 0.7;\n}\n","import { useState, type ReactElement } from 'react';\nimport { InlineRange } from '../InlineRange';\nimport shared from '../range.module.css';\nimport s from './OpacityRange.module.css';\n\n/** Props for {@link OpacityRange}. Alphas are 0..1 at 1% resolution. */\nexport interface OpacityRangeProps {\n value: number;\n disabled?: boolean;\n /** Names what the opacity belongs to; the slider is `<label> opacity`. */\n 'aria-label'?: string;\n onInput?: (alpha01: number) => void;\n /** One call per gesture, and none for a gesture that moved nothing. */\n onChange: (alpha01: number) => void;\n}\n\n/**\n * The opacity slider and its percent readout, as two inline items for the\n * caller's own row. `ColorField` puts it beside a swatch; `PaintInput` puts it\n * under a paint with no single color to carry an alpha.\n */\nexport function OpacityRange(props: OpacityRangeProps): ReactElement {\n const { value, disabled = false, onInput, onChange } = props;\n const label = props['aria-label'];\n // Tracks the thumb during a gesture — the committed prop only updates after\n // onChange — then resets to follow the prop again.\n const [draft, setDraft] = useState<number | null>(null);\n const pct = draft ?? Math.round(value * 100);\n\n const commit = (): void => {\n if (draft === null) return;\n onChange(draft / 100);\n setDraft(null);\n };\n\n return (\n <>\n <InlineRange\n className={`${s.range} ${shared.alpha}`}\n min={0}\n max={100}\n step={1}\n value={pct}\n disabled={disabled}\n aria-label={label ? `${label} opacity` : 'Opacity'}\n onInput={(e) => {\n const next = Number((e.target as HTMLInputElement).value);\n setDraft(next);\n onInput?.(next / 100);\n }}\n onPointerUp={commit}\n onPointerCancel={commit}\n onKeyUp={commit}\n onBlur={commit}\n />\n <span className={s.readout} aria-hidden=\"true\">{pct}%</span>\n </>\n );\n}\n",".root {\n display: inline-flex;\n align-items: center;\n gap: var(--wzl-space-3);\n}\n\n.chip {\n position: relative;\n display: inline-flex;\n border-radius: var(--wzl-radius-sm);\n overflow: hidden;\n}\n\n.color {\n inline-size: 28px;\n block-size: var(--wzl-control-h-sm);\n padding: 0;\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n background: none;\n cursor: pointer;\n}\n\n.color:focus-visible {\n outline: none;\n border-color: var(--wzl-focus-ring);\n box-shadow: 0 0 0 1px var(--wzl-focus-ring);\n}\n\n/* Indeterminate (mixed) presentation: checkerboard overlay atop the\n * swatch. pointer-events: none so the input stays clickable. */\n.root[data-mixed] .chip::after {\n content: '';\n position: absolute;\n inset: 1px;\n border-radius: var(--wzl-radius-sm);\n pointer-events: none;\n background: repeating-conic-gradient(\n var(--wzl-checker-a) 0% 25%,\n var(--wzl-checker-b) 0% 50%\n )\n 50% / var(--wzl-checker-size) var(--wzl-checker-size);\n}\n","import { useEffect, useRef, useState } from 'react';\nimport { toHex8, getAlpha01, withAlpha01 } from '@weasel-js/core';\nimport { useRecordRecentColor } from '../RecentColors/RecentColorsProvider';\nimport { OpacityRange } from './OpacityRange';\nimport s from './ColorField.module.css';\n\n/**\n * Props for {@link ColorField}. `onInput` fires throughout a gesture and\n * `onChange` once at its end.\n */\nexport interface ColorFieldProps {\n /** Current color, `#rrggbb` or `#rrggbbaa`. Omit when `mixed`. */\n value?: string;\n /** Indeterminate presentation (multi-selection with differing colors):\n * checkered chip, empty value. The first color edit produces a real\n * value; the opacity slider stays disabled until then (an alpha-only\n * edit would otherwise commit a value composed against the\n * placeholder black). */\n mixed?: boolean;\n /** Show the opacity slider; emitted values are `#rrggbbaa`. */\n alpha?: boolean;\n /** Draw the opacity slider inert, for a consumer that drops alpha. */\n alphaDisabled?: boolean;\n /** Live value during interaction (picker drag, slider drag). Optional —\n * wire it for live preview; omit it for commit-only consumers. */\n onInput?: (hex: string) => void;\n /** Committed value — picker close or blur, or slider release. One call\n * per user gesture; pair with an undoable write. */\n onChange: (hex: string) => void;\n /** Id for the color input, for a `<label htmlFor>` outside it. */\n id?: string;\n 'aria-label'?: string;\n className?: string;\n}\n\n/**\n * Compact color editor: native color input + optional opacity slider.\n * Commit semantics are gesture-based (see `onChange`) because native\n * color/range inputs fire `change` continuously during interaction —\n * commit-on-change would emit one undo entry per tick.\n */\nexport function ColorField(props: ColorFieldProps) {\n const { value, mixed = false, alpha = false, alphaDisabled = false, onInput, onChange, id, className } = props;\n const hex8 = toHex8(value ?? '#000000');\n const rgb6 = hex8.slice(0, 7);\n const alpha01 = getAlpha01(hex8);\n const alphaPct = Math.round(alpha01 * 100);\n\n // Drafts track the control during a gesture (the committed prop only\n // updates after onChange), then reset to follow the prop again.\n const [colorDraft, setColorDraft] = useState<string | null>(null);\n const [alphaDraft, setAlphaDraft] = useState<number | null>(null);\n const visibleRgb = colorDraft ?? rgb6;\n const visibleAlphaPct = alphaDraft ?? alphaPct;\n\n const compose = (rgb: string, a01: number): string =>\n alpha ? withAlpha01(rgb, a01) : rgb;\n\n // Mixed with no color edit yet: the alpha slider has nothing real to\n // compose against (rgb6 is the placeholder black), so keep it disabled\n // until the first color edit lands.\n const alphaLocked = mixed && colorDraft === null;\n\n // Commit only when a gesture actually changed something — blur with no\n // preceding input must not emit (it would create a no-op undo entry).\n const record = useRecordRecentColor();\n const commit = (rgb: string, a01: number): void => {\n if (colorDraft === null && alphaDraft === null) return;\n const next = compose(rgb, a01);\n onChange(next);\n record(next);\n setColorDraft(null);\n setAlphaDraft(null);\n };\n\n // The picker closing fires a native `change`, which React's `onChange` does\n // not deliver apart from `input`; the input can keep focus after it closes,\n // so waiting for blur alone would hold the commit until the next click.\n const swatch = useRef<HTMLInputElement>(null);\n const latestCommit = useRef(() => {});\n useEffect(() => {\n latestCommit.current = () => commit(visibleRgb, visibleAlphaPct / 100);\n });\n useEffect(() => {\n const el = swatch.current;\n if (!el) return;\n const onClose = () => latestCommit.current();\n el.addEventListener('change', onClose);\n return () => el.removeEventListener('change', onClose);\n }, []);\n\n return (\n <span\n className={[s.root, className].filter(Boolean).join(' ')}\n {...(mixed && colorDraft === null ? { 'data-mixed': '' } : {})}\n >\n <span className={s.chip}>\n <input\n ref={swatch}\n id={id}\n className={s.color}\n type=\"color\"\n value={visibleRgb}\n aria-label={props['aria-label'] ?? 'Color'}\n onInput={(e) => {\n const rgb = (e.target as HTMLInputElement).value;\n setColorDraft(rgb);\n onInput?.(compose(rgb, visibleAlphaPct / 100));\n }}\n onBlur={() => commit(visibleRgb, visibleAlphaPct / 100)}\n />\n </span>\n {alpha && (\n <OpacityRange\n value={visibleAlphaPct / 100}\n disabled={alphaLocked || alphaDisabled}\n aria-label={props['aria-label']}\n onInput={(a01) => {\n setAlphaDraft(Math.round(a01 * 100));\n onInput?.(compose(visibleRgb, a01));\n }}\n onChange={(a01) => commit(visibleRgb, a01)}\n />\n )}\n </span>\n );\n}\n"],"mappings":"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"}
@@ -1,19 +1,19 @@
1
1
  import { i as e } from "./portalHost-CE70wLzo.js";
2
- import { t } from "./field.module-Bh7BTKMh.js";
2
+ import { t } from "./field.module-B27pWv0i.js";
3
3
  import { t as n } from "./listbox.module-BcdVJMbm.js";
4
4
  import { jsx as r, jsxs as i } from "react/jsx-runtime";
5
5
  import { Children as a, isValidElement as o, useContext as s } from "react";
6
6
  import { Button as c, ComboBox as l, ComboBoxStateContext as u, FieldError as d, Input as f, Label as p, ListBox as m, ListBoxItem as h, Popover as g, Text as _ } from "react-aria-components";
7
7
  var v = {
8
- frame: "_frame_1v7bf_1",
9
- spin: "_spin_1v7bf_1",
10
- openButton: "_openButton_1v7bf_40",
11
- popover: "_popover_1v7bf_62",
12
- option: "_option_1v7bf_67",
13
- empty: "_empty_1v7bf_90",
14
- error: "_error_1v7bf_96",
15
- fit: "_fit_1v7bf_105",
16
- sizer: "_sizer_1v7bf_121"
8
+ frame: "_frame_gus0o_1",
9
+ spin: "_spin_gus0o_1",
10
+ openButton: "_openButton_gus0o_40",
11
+ popover: "_popover_gus0o_62",
12
+ option: "_option_gus0o_67",
13
+ empty: "_empty_gus0o_90",
14
+ error: "_error_gus0o_96",
15
+ fit: "_fit_gus0o_105",
16
+ sizer: "_sizer_gus0o_121 _sizer_18u60_4"
17
17
  }, y = () => !0;
18
18
  function b(a) {
19
19
  let { label: o, orientation: s = "stacked", description: c, errorMessage: u, placeholder: f, options: h, children: b, selectedKey: w, defaultSelectedKey: T, onSelectionChange: E, emptyLabel: D = "No matches", errorLabel: O = "Couldn't load options", filter: k = "contains", isLoading: A, loadError: j = null, onCommit: M, width: N = "fill", className: P, portalContainer: F, ...I } = a, { anchor: L, portalProps: R } = e(F);
@@ -135,4 +135,4 @@ function C({ className: e, ...t }) {
135
135
  //#endregion
136
136
  export { C as n, b as t };
137
137
 
138
- //# sourceMappingURL=ComboBox-CuTLRmUg.js.map
138
+ //# sourceMappingURL=ComboBox-DKln_mOd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ComboBox-DKln_mOd.js","names":["ComboBox","RACComboBox","f","s","RACPopover","RACListBox","listbox","RACInput","RACButton","RACListBoxItem","useOverlayPortal","listbox_module_default","ComboBox_module_default","admitEverything","ComboBox$1","rest","props","portalContainer","ComboBox","filter","onSelectionChange","k","onCommit","field_module_default","width","orientation","className","anchor","label","Label","ComboBoxFrame","placeholder","optionLabels","options","children","l","i","description","Text","FieldError","errorMessage","Popover","portalProps","ListBox","loadError","emptyLabel","errorLabel","ComboBoxItem","o","state","useContext","ComboBoxStateContext","Input","isLoading","onCommitText","text","Button","Children","isValidElement","c","ListBoxItem"],"sources":["../../src/components/ComboBox/ComboBox.module.css","../../src/components/ComboBox/ComboBox.tsx"],"sourcesContent":[".frame {\n display: flex;\n align-items: stretch;\n overflow: hidden;\n}\n\n.frame input {\n padding: 0 var(--wzl-field-pad-x);\n}\n\n/* A request is out: the rows on screen are the previous query's, so mark them\n stale rather than clearing the list out from under the arrow keys. */\n.frame:has(input[aria-busy='true'])::after {\n content: '';\n align-self: center;\n flex: none;\n width: 10px;\n height: 10px;\n margin-right: var(--wzl-space-2);\n border: 1.5px solid var(--wzl-fg-subtle);\n border-top-color: transparent;\n border-radius: 50%;\n animation: spin 700ms linear infinite;\n}\n\n@keyframes spin {\n to {\n transform: rotate(1turn);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .frame:has(input[aria-busy='true'])::after {\n animation: none;\n border-top-color: var(--wzl-fg-subtle);\n opacity: 0.5;\n }\n}\n\n.openButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--wzl-icon-button-size);\n background: transparent;\n border: none;\n border-left: 1px solid var(--wzl-border);\n color: var(--wzl-fg-muted);\n cursor: pointer;\n padding: 0;\n}\n\n.openButton[data-hovered]:not([data-disabled]) {\n color: var(--wzl-fg);\n}\n\n.openButton svg {\n width: 10px;\n height: 10px;\n}\n\n.popover {\n min-width: var(--trigger-width);\n outline: none;\n}\n\n.option {\n display: flex;\n align-items: center;\n padding: var(--wzl-space-2) var(--wzl-space-4);\n border-radius: var(--wzl-radius-sm);\n cursor: pointer;\n color: var(--wzl-fg);\n outline: none;\n}\n\n.option[data-focused] {\n background: var(--wzl-surface-hover);\n}\n\n.option[data-selected] {\n font-weight: var(--wzl-font-weight-medium);\n}\n\n.option[data-disabled] {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n.empty {\n padding: var(--wzl-space-3) var(--wzl-space-4);\n color: var(--wzl-fg-subtle);\n font-size: var(--wzl-font-size-sm);\n}\n\n.error {\n padding: var(--wzl-space-3) var(--wzl-space-4);\n color: var(--wzl-danger);\n font-size: var(--wzl-font-size-sm);\n}\n\n/* `width='fit'`: a hidden stack of every option label shares the input's grid\n cell, so the field is wide enough for whichever option is chosen and takes\n no more of the row than that. */\n.fit .frame {\n display: grid;\n grid-template-columns: auto auto;\n width: fit-content;\n max-width: 100%;\n}\n\n/* `width: 0` keeps the input's 20-character intrinsic width out of the track\n sizing — the sizer is what the column measures — and `min-width` then\n stretches it back across whatever that came to. */\n.fit .frame input {\n grid-area: 1 / 1;\n width: 0;\n min-width: 100%;\n}\n\n.fit .sizer {\n grid-area: 1 / 1;\n}\n\n.fit .openButton {\n grid-area: 1 / 2;\n}\n\n.sizer {\n composes: sizer from '../sizer.module.css';\n padding: 0 var(--wzl-field-pad-x);\n}\n","import { Children, isValidElement, useContext, type KeyboardEvent, type ReactNode } from 'react';\nimport {\n ComboBox as RACComboBox,\n ComboBoxStateContext,\n Label,\n Input as RACInput,\n Button as RACButton,\n Popover as RACPopover,\n ListBox as RACListBox,\n ListBoxItem as RACListBoxItem,\n Text,\n FieldError,\n type ComboBoxProps as RACComboBoxProps,\n type ListBoxItemProps as RACListBoxItemProps,\n type ValidationResult,\n} from 'react-aria-components';\nimport type { FieldOrientation } from '../Field/Field';\nimport { useOverlayPortal, type OverlayPortalProps } from '../../overlays/portalHost';\nimport f from '../field.module.css';\nimport listbox from '../listbox.module.css';\nimport s from './ComboBox.module.css';\n\n/**\n * One option in a {@link ComboBox}'s `options` list. `textValue` is the\n * plain-text form used for filtering and screen readers, needed only when\n * `label` isn't a bare string.\n */\nexport type ComboBoxOption = {\n value: string;\n label: ReactNode;\n textValue?: string;\n isDisabled?: boolean;\n};\n\ntype Key = string | number;\n\n/**\n * How the typed text narrows the options.\n *\n * `'contains'` is a locale-aware substring match over each option's text.\n * `'none'` shows every option given — what a list a server already filtered\n * and ranked needs, since a second pass would drop rows that do not contain\n * the query and reorder whatever survived.\n */\nexport type ComboBoxFilter = 'contains' | 'none' | ((textValue: string, inputValue: string) => boolean);\n\n/** What the user committed: an option they picked, or text they typed. */\nexport type ComboBoxCommit<T extends Key = string> =\n | { source: 'option'; key: T }\n | { source: 'text'; text: string };\n\nconst admitEverything = () => true;\n\n/**\n * Props for {@link ComboBox}, on top of React Aria's `ComboBox` props, with\n * the selection key narrowed to the option value type.\n */\nexport type ComboBoxProps<T extends Key = string> = Omit<RACComboBoxProps<object>, 'children' | 'className' | 'selectedKey' | 'defaultSelectedKey' | 'onSelectionChange'> & {\n label?: ReactNode;\n /**\n * `'stacked'` (the default) puts the label above the field; `'row'` sets it\n * beside the field at its own width, with any description or error on a\n * line below. Same vocabulary as {@link Field}'s `orientation`.\n */\n orientation?: FieldOrientation;\n description?: ReactNode;\n errorMessage?: ReactNode | ((v: ValidationResult) => ReactNode);\n placeholder?: string;\n options?: ReadonlyArray<ComboBoxOption & { value: T }>;\n children?: ReactNode;\n selectedKey?: T | null;\n defaultSelectedKey?: T;\n onSelectionChange?: (key: T | null) => void;\n emptyLabel?: ReactNode;\n /** Shown in place of `emptyLabel` while `loadError` is set. */\n errorLabel?: ReactNode;\n /**\n * How typed text narrows `options`. Defaults to `'contains'`. `'none'` also\n * keeps the popover open on an empty collection: a list the kit does not\n * filter can arrive empty from a server mid-query, and without that the\n * popover closes and `emptyLabel` is never seen.\n */\n filter?: ComboBoxFilter;\n /** Marks the options as out of date while the next set is being fetched. */\n isLoading?: boolean;\n /** A failed load, shown as `errorLabel` rather than as an empty corpus. */\n loadError?: unknown | null;\n /**\n * Fires when the user commits — Enter on the active option, a click on one,\n * or Enter on text matching none of them when `allowsCustomValue` is set.\n * Empty text commits nothing.\n */\n onCommit?: (commit: ComboBoxCommit<T>) => void;\n /**\n * `'fill'` (the default) takes the width of whatever row the combo box sits\n * in. `'fit'` sizes the input to its widest option, so it neither swallows a\n * toolbar's slack nor cuts a selection off once one is made.\n */\n width?: 'fill' | 'fit';\n className?: string;\n} & OverlayPortalProps;\n\n/**\n * Filterable single-select wrapping React Aria's ComboBox. The user can\n * type to filter; selection commits to `onSelectionChange`. When no value\n * is in the input, selection clears (key becomes null).\n */\nexport function ComboBox<T extends Key = string>(props: ComboBoxProps<T>) {\n const {\n label,\n orientation = 'stacked',\n description,\n errorMessage,\n placeholder,\n options,\n children,\n selectedKey,\n defaultSelectedKey,\n onSelectionChange,\n emptyLabel = 'No matches',\n errorLabel = \"Couldn't load options\",\n filter = 'contains',\n isLoading,\n loadError = null,\n onCommit,\n width = 'fill',\n className,\n portalContainer,\n ...rest\n } = props;\n\n const { anchor, portalProps } = useOverlayPortal(portalContainer);\n\n return (\n <RACComboBox\n {...rest}\n // 'contains' leaves this unset so React Aria uses its own locale-aware\n // match rather than a reimplementation of it.\n defaultFilter={filter === 'none' ? admitEverything : filter === 'contains' ? undefined : filter}\n allowsEmptyCollection={rest.allowsEmptyCollection ?? filter === 'none'}\n selectedKey={selectedKey}\n defaultSelectedKey={defaultSelectedKey}\n onSelectionChange={(k) => {\n onSelectionChange?.(k as T | null);\n if (k !== null) onCommit?.({ source: 'option', key: k as T });\n }}\n className={[\n f.field,\n f.control,\n width === 'fit' && `${f.fit} ${s.fit}`,\n orientation === 'row' && f.row,\n className,\n ]\n .filter(Boolean)\n .join(' ')}\n >\n {anchor}\n {label !== undefined && <Label className={f.label}>{label}</Label>}\n <ComboBoxFrame\n placeholder={placeholder}\n isLoading={isLoading}\n onCommitText={onCommit && ((text) => onCommit({ source: 'text', text }))}\n >\n {width === 'fit' && (\n <span className={s.sizer} aria-hidden=\"true\">\n {placeholder !== undefined && <span>{placeholder}</span>}\n {optionLabels(options, children).map((l, i) => (\n <span key={i}>{l}</span>\n ))}\n </span>\n )}\n </ComboBoxFrame>\n {description !== undefined && (\n <Text slot=\"description\" className={`${f.hint} ${f.below}`}>\n {description}\n </Text>\n )}\n <FieldError className={`${f.error} ${f.below}`}>{errorMessage}</FieldError>\n <RACPopover className={s.popover} data-weasel-overlay=\"\" {...portalProps}>\n <RACListBox\n className={`${listbox.surface} ${listbox.list}`}\n renderEmptyState={() =>\n loadError !== null ? (\n <div className={s.error} role=\"alert\">{errorLabel}</div>\n ) : (\n <div className={s.empty}>{emptyLabel}</div>\n )\n }\n >\n {options !== undefined\n ? options.map((o) => (\n <ComboBoxItem key={String(o.value)} id={o.value} textValue={o.textValue} isDisabled={o.isDisabled}>\n {o.label}\n </ComboBoxItem>\n ))\n : children}\n </RACListBox>\n </RACPopover>\n </RACComboBox>\n );\n}\n\n/**\n * The input and its trigger. Split out so it can read `ComboBoxStateContext`,\n * which only exists below `RACComboBox`.\n */\nfunction ComboBoxFrame({\n placeholder,\n isLoading,\n onCommitText,\n children,\n}: {\n placeholder?: string;\n isLoading?: boolean;\n onCommitText?: (text: string) => void;\n children?: ReactNode;\n}) {\n const state = useContext(ComboBoxStateContext);\n\n // Capture, not bubble: React Aria's own Enter handler runs on the way up and\n // clears the focused key, so by then there is no way to tell an option\n // commit from a text one.\n const onKeyDownCapture = (e: KeyboardEvent<HTMLInputElement>) => {\n if (e.key !== 'Enter' || e.nativeEvent.isComposing) return;\n if (state?.isOpen && state.selectionManager.focusedKey != null) return;\n const text = e.currentTarget.value;\n if (text !== '') onCommitText?.(text);\n };\n\n return (\n <div className={`${f.frame} ${s.frame}`}>\n <RACInput\n placeholder={placeholder}\n aria-busy={isLoading || undefined}\n onKeyDownCapture={onCommitText ? onKeyDownCapture : undefined}\n />\n {children}\n <RACButton className={s.openButton} aria-label=\"Show options\">\n <svg viewBox=\"0 0 10 10\" aria-hidden=\"true\">\n <path d=\"M2 4 L5 7 L8 4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n </RACButton>\n </div>\n );\n}\n\n/**\n * The labels a `width='fit'` input measures itself against. In the children\n * form the label sits one element deep, inside a `ComboBoxItem`.\n */\nfunction optionLabels(\n options: ReadonlyArray<ComboBoxOption> | undefined,\n children: ReactNode,\n): ReactNode[] {\n if (options !== undefined) return options.map((o) => o.label);\n return Children.toArray(children).map((c) =>\n isValidElement<{ children?: ReactNode }>(c) ? c.props.children : c,\n );\n}\n\n/** Props for {@link ComboBoxItem}, on top of React Aria's `ListBoxItem` props. */\nexport type ComboBoxItemProps = Omit<RACListBoxItemProps, 'className'> & {\n className?: string;\n};\n\n/** One row in a {@link ComboBox}'s filtered list. */\nexport function ComboBoxItem({ className, ...rest }: ComboBoxItemProps) {\n return <RACListBoxItem {...rest} className={[s.option, className].filter(Boolean).join(' ')} />;\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { t as e } from "./dlog-F0I8KsIW.js";
2
- import { a as t, i as n, o as r, s as i } from "./createKeyframeLayer-BTa4LyJE.js";
2
+ import { a as t, i as n, o as r, s as i } from "./createKeyframeLayer-DSVf13Su.js";
3
3
  import { n as a } from "./handles-CqVANLtu.js";
4
4
  import { useLatest as o } from "@weasel-js/core";
5
5
  import { Fragment as s, jsx as c, jsxs as l } from "react/jsx-runtime";
@@ -533,4 +533,4 @@ function z(e, t) {
533
533
  //#endregion
534
534
  export { x as a, I as i, L as n, F as r, R as t };
535
535
 
536
- //# sourceMappingURL=CurveEditor-jVBEqqdv.js.map
536
+ //# sourceMappingURL=CurveEditor-0EE00D6s.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CurveEditor-jVBEqqdv.js","names":["_Fragment","s","dlog","CurveEditor_module_default","LayeredCurveEditor","hitTestAnchor","hitTestCurve","handleHalf","knot","dx","b","a","dy","alpha","segmentSampleAt","t1","p0","p1","t2","p2","t3","p3","t","u","a1x","a1y","a2x","a2y","a3x","a3y","b1x","b1y","b2x","b2y","sampleCatmullRom","anchors","samplesPerSegment","n","out","reflectStart","reflectEnd","i","tStart","s","tangents","ys","m","avg","mag","hermite","m0","m1","sampleMonotone","xs","tx","ty","startStep","sampleLinear","sampleByInterpolation","mode","SAMPLES_PER_SEGMENT","ENDPOINT_HALF","LOCKED_HALF","ANCHOR_SNAP_PX","CURVE_HIT_PX","KEY_STEP_FRACTION","SHIFT_MULTIPLIER","formatCoord","clampToModelRange","ctx","xLo","xHi","yLo","yHi","p","isPinnedEndpointAt","endpoints","index","count","projectAnchors","points","segmentSamples","all","interpolation","start","end","slice","resolveInterpolation","cfg","createFunctionLayer","id","domain","constrain","addPointMode","interactive","curveVisible","render","state","plotAnchors","pathD","fillD","samples","parts","closingY","first","last","fillParts","Fragment","pinned","locked","active","activeIndex","isEndpoint","a11y","node","anchorCls","half","hitTest","plot","segs","c","moveAnchor","mr","xMin","xMax","nx","model","ny","epsilon","left","right","clamped","next","makeDragGesture","onPointerDown","hit","e","extra","insertIndex","segIdx","modelPt","nextPoints","onEmptyPointerDown","onKeyDown","target","el","mult","xSpan","moved","functionLayerState","CurveEditor","props","layer","useMemo","minPoints","maxPoints","curve","fill","hideNonInteractive","renderAnchor","layerState","setLayerState","useState","value","layerStateRef","useLatest","handleLayerChange","useCallback","nextUnknown","prevPoints","onInput","handleLayerCommit","onChange","prev","prevUnknown","onContextMenu","idxAttr","idx","layers","decorations","createSetCurveOp","args","controlPointsEqual","from","to"],"sources":["../../src/components/CurveEditor/catmullRom.ts","../../src/components/CurveEditor/monotone.ts","../../src/components/CurveEditor/interpolation.ts","../../src/components/CurveEditor/createFunctionLayer.tsx","../../src/components/CurveEditor/CurveEditor.tsx","../../src/components/CurveEditor/setCurveOp.ts"],"sourcesContent":["/**\n * Catmull-Rom spline sampling, parameterized by α.\n *\n * α = 0 → uniform parameterization (original Catmull-Rom). Fast, can\n * produce cusps and self-intersections at sharp 2D angles.\n * α = 0.5 → centripetal (Yuksel/Schneider 2011). No cusps, no\n * self-intersection. The \"safe default\" for 2D.\n * α = 1 → chordal. Smoother than uniform, different artifacts.\n *\n * `sampleCurve(...)` is the convenience alias for centripetal (α=0.5).\n */\n\nexport interface Point {\n x: number;\n y: number;\n}\n\nfunction knot(a: Point, b: Point, alpha: number): number {\n const dx = b.x - a.x;\n const dy = b.y - a.y;\n return Math.pow(Math.sqrt(dx * dx + dy * dy), alpha);\n}\n\n/**\n * Sample the Catmull-Rom segment between p1 and p2 at parameter\n * t ∈ [0, 1]. p0 and p3 are the surrounding control points (use phantom\n * reflection at endpoints — see sampleCatmullRom below).\n *\n * Returns p1 at t=0 and p2 at t=1. `alpha` controls parameterization.\n */\nexport function segmentSampleAt(\n p0: Point, p1: Point, p2: Point, p3: Point, t: number,\n alpha: number = 0.5,\n): Point {\n const t0 = 0;\n const t1 = t0 + knot(p0, p1, alpha);\n const t2 = t1 + knot(p1, p2, alpha);\n const t3 = t2 + knot(p2, p3, alpha);\n\n // Degenerate: coincident p1/p2 → linear interpolation fallback.\n if (t2 - t1 === 0) {\n return { x: p1.x + (p2.x - p1.x) * t, y: p1.y + (p2.y - p1.y) * t };\n }\n\n const u = t1 + t * (t2 - t1);\n\n const a1x = ((t1 - u) * p0.x + (u - t0) * p1.x) / (t1 - t0);\n const a1y = ((t1 - u) * p0.y + (u - t0) * p1.y) / (t1 - t0);\n const a2x = ((t2 - u) * p1.x + (u - t1) * p2.x) / (t2 - t1);\n const a2y = ((t2 - u) * p1.y + (u - t1) * p2.y) / (t2 - t1);\n const a3x = ((t3 - u) * p2.x + (u - t2) * p3.x) / (t3 - t2);\n const a3y = ((t3 - u) * p2.y + (u - t2) * p3.y) / (t3 - t2);\n\n const b1x = ((t2 - u) * a1x + (u - t0) * a2x) / (t2 - t0);\n const b1y = ((t2 - u) * a1y + (u - t0) * a2y) / (t2 - t0);\n const b2x = ((t3 - u) * a2x + (u - t1) * a3x) / (t3 - t1);\n const b2y = ((t3 - u) * a2y + (u - t1) * a3y) / (t3 - t1);\n\n const cx = ((t2 - u) * b1x + (u - t1) * b2x) / (t2 - t1);\n const cy = ((t2 - u) * b1y + (u - t1) * b2y) / (t2 - t1);\n return { x: cx, y: cy };\n}\n\n/**\n * Sample the whole curve through `anchors` using Catmull-Rom with the\n * given α. Returns `(n-1) * samplesPerSegment + 1` points. Phantom\n * endpoints by reflection so the first/last segments are tangent to\n * the chord into/out of the endpoints. Returns `[]` for fewer than 2\n * anchors.\n */\nexport function sampleCatmullRom(\n anchors: readonly Point[],\n samplesPerSegment: number,\n alpha: number = 0.5,\n): Point[] {\n if (anchors.length < 2) return [];\n if (samplesPerSegment < 1) samplesPerSegment = 1;\n\n const n = anchors.length;\n const out: Point[] = [];\n\n const reflectStart: Point = {\n x: 2 * anchors[0].x - anchors[1].x,\n y: 2 * anchors[0].y - anchors[1].y,\n };\n const reflectEnd: Point = {\n x: 2 * anchors[n - 1].x - anchors[n - 2].x,\n y: 2 * anchors[n - 1].y - anchors[n - 2].y,\n };\n\n for (let i = 0; i < n - 1; i++) {\n const p0 = i === 0 ? reflectStart : anchors[i - 1];\n const p1 = anchors[i];\n const p2 = anchors[i + 1];\n const p3 = i + 2 < n ? anchors[i + 2] : reflectEnd;\n const tStart = i === 0 ? 0 : 1;\n for (let s = tStart; s <= samplesPerSegment; s++) {\n if (s === 0) {\n out.push(p1);\n continue;\n }\n if (s === samplesPerSegment) {\n out.push(p2);\n continue;\n }\n const t = s / samplesPerSegment;\n out.push(segmentSampleAt(p0, p1, p2, p3, t, alpha));\n }\n }\n return out;\n}\n\n/** Convenience alias for centripetal Catmull-Rom (α=0.5). The most\n * common choice; safe default for 2D paths. */\nexport function sampleCurve(\n anchors: readonly Point[],\n samplesPerSegment: number,\n): Point[] {\n return sampleCatmullRom(anchors, samplesPerSegment, 0.5);\n}\n","/**\n * Monotone cubic interpolation (Fritsch-Carlson / Steffen variant).\n *\n * Passes through every anchor and guarantees the curve never\n * overshoots `min(neighbors)` / `max(neighbors)` along either axis.\n * The right choice for animation timing curves (no anticipation) or\n * audio envelopes (no out-of-range values).\n *\n * Parameterized by index — works for both 1D (function-shaped) and\n * 2D (parametric) anchor sequences. The x and y coordinates are\n * interpolated independently as functions of the anchor index.\n *\n * Reference: Fritsch & Carlson 1980, \"Monotone piecewise cubic\n * interpolation.\" Steffen's 1990 refinement is conceptually the same;\n * we use a min-clamp variant that's monotonicity-preserving by\n * construction.\n */\n\nimport type { Point } from './catmullRom';\n\n/** Per-axis tangent computation for a 1D series of y-values sampled at\n * unit intervals (index-parameterized). Returns one tangent per\n * anchor. */\nfunction tangents(ys: readonly number[]): number[] {\n const n = ys.length;\n if (n === 0) return [];\n if (n === 1) return [0];\n\n // Secant slopes (dx = 1 in index parameterization).\n const m: number[] = new Array(n - 1);\n for (let i = 0; i < n - 1; i++) m[i] = ys[i + 1] - ys[i];\n\n const t: number[] = new Array(n);\n t[0] = m[0];\n t[n - 1] = m[n - 2];\n\n for (let i = 1; i < n - 1; i++) {\n if (m[i - 1] * m[i] <= 0) {\n // Sign change or flat → local extremum; tangent is zero (forces\n // the curve to settle without overshoot).\n t[i] = 0;\n } else {\n // Weighted average of adjacent secants (with dx_i = dx_{i-1} = 1,\n // this is just the arithmetic mean). Clamped by Steffen's rule\n // so the cubic stays monotone within each segment.\n const avg = (m[i - 1] + m[i]) / 2;\n const mag = Math.min(Math.abs(m[i - 1]), Math.abs(m[i]), Math.abs(avg));\n t[i] = Math.sign(avg) * mag;\n }\n }\n return t;\n}\n\n/** Cubic Hermite basis at parameter t ∈ [0, 1] given endpoint values\n * p0, p1 and endpoint tangents m0, m1. */\nfunction hermite(\n p0: number, p1: number, m0: number, m1: number, t: number,\n): number {\n const t2 = t * t;\n const t3 = t2 * t;\n return (\n (2 * t3 - 3 * t2 + 1) * p0 +\n (t3 - 2 * t2 + t) * m0 +\n (-2 * t3 + 3 * t2) * p1 +\n (t3 - t2) * m1\n );\n}\n\n/**\n * Sample the monotone-cubic curve through `anchors`. Returns\n * `(n-1) * samplesPerSegment + 1` points (same shape as\n * `sampleCatmullRom`). `[]` for fewer than 2 anchors.\n */\nexport function sampleMonotone(\n anchors: readonly Point[],\n samplesPerSegment: number,\n): Point[] {\n if (anchors.length < 2) return [];\n if (samplesPerSegment < 1) samplesPerSegment = 1;\n\n const n = anchors.length;\n const xs = anchors.map((a) => a.x);\n const ys = anchors.map((a) => a.y);\n const tx = tangents(xs);\n const ty = tangents(ys);\n\n const out: Point[] = [];\n for (let i = 0; i < n - 1; i++) {\n const startStep = i === 0 ? 0 : 1; // skip duplicate boundary\n for (let s = startStep; s <= samplesPerSegment; s++) {\n if (s === 0) { out.push(anchors[i]); continue; }\n if (s === samplesPerSegment) { out.push(anchors[i + 1]); continue; }\n const t = s / samplesPerSegment;\n out.push({\n x: hermite(xs[i], xs[i + 1], tx[i], tx[i + 1], t),\n y: hermite(ys[i], ys[i + 1], ty[i], ty[i + 1], t),\n });\n }\n }\n return out;\n}\n","/**\n * Interpolation mode dispatcher for CurveEditor. The vocabulary of\n * supported algorithms and the unified `sample(...)` entry point that\n * picks the right one. All algorithms produce\n * `(n-1) * samplesPerSegment + 1` points and pass through every\n * anchor — CurveEditor is strictly interpolating, never approximating.\n */\n\nimport { sampleCatmullRom, type Point } from './catmullRom';\nimport { sampleMonotone } from './monotone';\n\n/** Supported interpolation modes. All are interpolating (pass through\n * every anchor); the differences are in tangent computation and\n * parameterization. */\nexport type InterpolationMode =\n | 'linear'\n | 'catmull-rom' // centripetal (α=0.5) — safe default\n | 'catmull-rom-uniform' // α=0; can cusp on sharp 2D angles\n | 'catmull-rom-chordal' // α=1; smoother than uniform\n | 'monotone'; // Fritsch-Carlson / Steffen; no overshoot\n\n/** Sample anchors as a polyline. Produces the same sample count shape\n * as the Catmull-Rom variants so downstream segment-slicing logic\n * (hit testing, fill path construction) works uniformly. */\nfunction sampleLinear(\n anchors: readonly Point[],\n samplesPerSegment: number,\n): Point[] {\n if (anchors.length < 2) return [];\n if (samplesPerSegment < 1) samplesPerSegment = 1;\n\n const out: Point[] = [];\n for (let i = 0; i < anchors.length - 1; i++) {\n const a = anchors[i];\n const b = anchors[i + 1];\n const startStep = i === 0 ? 0 : 1;\n for (let s = startStep; s <= samplesPerSegment; s++) {\n if (s === 0) { out.push(a); continue; }\n if (s === samplesPerSegment) { out.push(b); continue; }\n const t = s / samplesPerSegment;\n out.push({ x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t });\n }\n }\n return out;\n}\n\n/** Sample `anchors` using the chosen interpolation mode. */\nexport function sampleByInterpolation(\n anchors: readonly Point[],\n samplesPerSegment: number,\n mode: InterpolationMode,\n): Point[] {\n switch (mode) {\n case 'linear':\n return sampleLinear(anchors, samplesPerSegment);\n case 'catmull-rom-uniform':\n return sampleCatmullRom(anchors, samplesPerSegment, 0);\n case 'catmull-rom-chordal':\n return sampleCatmullRom(anchors, samplesPerSegment, 1);\n case 'monotone':\n return sampleMonotone(anchors, samplesPerSegment);\n case 'catmull-rom':\n default:\n return sampleCatmullRom(anchors, samplesPerSegment, 0.5);\n }\n}\n","/**\n * Built-in function/curve layer for LayeredCurveEditor. Bundles the\n * curve-render + anchor-render + drag/insert/delete gestures that were\n * the entirety of the original `CurveEditor`. Multiple instances coexist\n * by id; consumers can compose several to plot e.g. a primary function\n * and a derived (read-only) curve on the same chart.\n */\nimport type { ReactNode } from 'react';\nimport { sampleByInterpolation, type InterpolationMode } from './interpolation';\nimport { hitTestAnchor, hitTestCurve } from './hitTest';\nimport { handleHalf } from '../../handles';\nimport { dlog } from '../../dlog';\nimport s from './CurveEditor.module.css';\nimport type {\n CurveLayer, LayerCtx, LayerGesture, LayerRenderCtx, ModelPoint, PlotPoint,\n EmptyDownArgs, KeyDownArgs,\n} from './layerTypes';\n\n// ── Public types ─────────────────────────────────────────────────────\n\n/** One anchor of a curve, in model space. */\nexport interface ControlPoint {\n x: number;\n y: number;\n /** When true, this control point can't be moved or deleted by the\n * user. Render as a smaller diamond with locked styling. */\n locked?: boolean;\n}\n\n/**\n * Whether a curve's points keep their x order. In `'1d'` an anchor cannot be\n * dragged past its neighbors and a new point is inserted at its x position;\n * in `'2d'` points are free to move anywhere and keep their list order.\n */\nexport type CurveDomain = '1d' | '2d';\n/**\n * How much of a curve's first and last anchor the user may move: everything,\n * only their y, or nothing.\n */\nexport type EndpointMode = 'free' | 'pinned-x' | 'pinned-both';\n/**\n * Where a click adds a new anchor — on the curve itself, anywhere in the\n * plot, or nowhere.\n */\nexport type AddPointMode = 'click-curve' | 'click-empty' | 'never';\n\n/**\n * What a `renderAnchor` function is given for one anchor: the point, its\n * index, its position in plot space (`cx`/`cy`), and the states that change\n * how it should look.\n */\nexport interface AnchorRenderProps {\n point: ControlPoint;\n index: number;\n cx: number;\n cy: number;\n isActive: boolean;\n isLocked: boolean;\n isPinnedEndpoint: boolean;\n isEndpoint: boolean;\n}\n\n/**\n * Per-instance curve-rendering overrides. Currently empty — passing `{}`\n * means \"draw the curve\", and `false`/`null` means don't.\n */\nexport type CurveSettings = object;\n\n/** Shades the region between the curve and one edge of the plot. */\nexport interface FillSettings {\n side: 'below' | 'above';\n color?: string;\n}\n\n/** Configuration for {@link createFunctionLayer}. */\nexport interface FunctionLayerConfig {\n /** Layer id; default `'function'`. Two instances on the same editor\n * must have distinct ids. */\n id?: string;\n domain?: CurveDomain;\n endpoints?: EndpointMode;\n interpolation?: InterpolationMode;\n constrain?: 'none' | 'function';\n addPointMode?: AddPointMode;\n minPoints?: number;\n maxPoints?: number;\n curve?: CurveSettings | false | null;\n fill?: FillSettings | false | null;\n hideNonInteractive?: boolean;\n renderAnchor?: (info: AnchorRenderProps) => ReactNode;\n /** When false, the layer renders the curve+fill but doesn't render\n * anchors and doesn't accept pointer input. For derived read-only\n * curves (e.g. plotting f'(x) alongside f(x)). Default true. */\n interactive?: boolean;\n /** Override x clamp range for drags and pinned endpoints. Defaults\n * to the editor's modelRange x. Use this to constrain a layer to a\n * sub-range of the plot (e.g. a bevel curve occupying x ∈ [0, b]\n * while the editor's xRange is [0, halfWidth]). */\n xClamp?: readonly [number, number];\n /** Names the curve in each anchor's accessible name. Default `'Point'`. */\n label?: string;\n}\n\n/**\n * The state a function layer owns. Treat it as opaque and round-trip it\n * through `LayeredCurveEditor`'s `onLayerChange`; `points` is the part worth\n * reading.\n */\nexport interface FunctionLayerState {\n points: readonly ControlPoint[];\n /** Layer-internal: the index of the anchor currently being dragged.\n * Cleared between gestures. Consumers treat the whole state as\n * opaque and round-trip it via onLayerChange. */\n activeIndex: number | null;\n}\n\nconst SAMPLES_PER_SEGMENT = 16;\nconst ENDPOINT_HALF = handleHalf('--wzl-handle-size-lg');\nconst LOCKED_HALF = handleHalf('--wzl-handle-size-sm');\nconst ANCHOR_SNAP_PX = 12;\nconst CURVE_HIT_PX = 8;\n/** An arrow-key nudge, as a fraction of the range; shift takes ten. */\nconst KEY_STEP_FRACTION = 0.01;\nconst SHIFT_MULTIPLIER = 10;\n\nconst formatCoord = (n: number): string => String(Number(n.toFixed(3)));\n\n// ── Helpers ───────────────────────────────────────────────────────────\n\nfunction clampToModelRange(p: ModelPoint, ctx: LayerCtx): ModelPoint {\n const m = ctx.modelRange;\n const xLo = Math.min(m.xMin, m.xMax);\n const xHi = Math.max(m.xMin, m.xMax);\n const yLo = Math.min(m.yMin, m.yMax);\n const yHi = Math.max(m.yMin, m.yMax);\n return {\n x: Math.max(xLo, Math.min(xHi, p.x)),\n y: Math.max(yLo, Math.min(yHi, p.y)),\n };\n}\n\nfunction isPinnedEndpointAt(index: number, count: number, endpoints: EndpointMode): boolean {\n if (endpoints === 'free') return false;\n return index === 0 || index === count - 1;\n}\n\nfunction projectAnchors(points: readonly ControlPoint[], ctx: LayerCtx): PlotPoint[] {\n return points.map((p) => ctx.toPlot(p));\n}\n\nfunction segmentSamples(\n points: readonly ControlPoint[],\n interpolation: InterpolationMode,\n ctx: LayerCtx,\n): PlotPoint[][] {\n if (points.length < 2) return [];\n const all = sampleByInterpolation(points, SAMPLES_PER_SEGMENT, interpolation);\n const out: PlotPoint[][] = [];\n for (let i = 0; i < points.length - 1; i++) {\n const start = i * SAMPLES_PER_SEGMENT;\n const end = start + SAMPLES_PER_SEGMENT;\n const slice = all.slice(start, end + 1);\n out.push(slice.map((p) => ctx.toPlot(p)));\n }\n return out;\n}\n\nfunction resolveInterpolation(cfg: FunctionLayerConfig): InterpolationMode {\n return (cfg.constrain ?? 'none') === 'function'\n ? 'monotone'\n : (cfg.interpolation ?? 'catmull-rom');\n}\n\n// ── Factory ───────────────────────────────────────────────────────────\n\n/**\n * Builds the built-in curve layer for `LayeredCurveEditor`: it draws the\n * interpolated curve and its anchors, and handles dragging, inserting and\n * deleting them.\n *\n * Several can coexist on one editor as long as their ids differ — plotting a\n * derived read-only curve beside an editable one is the case it is shaped\n * for, via `interactive: false`.\n */\nexport function createFunctionLayer(cfg: FunctionLayerConfig = {}): CurveLayer<FunctionLayerState> {\n const id = cfg.id ?? 'function';\n const domain = cfg.domain ?? '2d';\n const endpoints = cfg.endpoints ?? 'free';\n const constrain = cfg.constrain ?? 'none';\n const addPointMode = cfg.addPointMode ?? 'click-curve';\n const interpolation = resolveInterpolation(cfg);\n const interactive = cfg.interactive !== false;\n const curveVisible = cfg.curve !== false && cfg.curve !== null;\n\n // ── render ──────────────────────────────────────────────────────────\n function render(state: FunctionLayerState, ctx: LayerRenderCtx): ReactNode {\n const { points, activeIndex } = state;\n const plotAnchors = projectAnchors(points, ctx);\n\n let pathD = '';\n let fillD: string | null = null;\n if (points.length >= 2) {\n const samples = sampleByInterpolation(points, SAMPLES_PER_SEGMENT, interpolation)\n .map((p) => ctx.toPlot(p));\n const parts: string[] = [`M${samples[0].x.toFixed(2)},${samples[0].y.toFixed(2)}`];\n for (let i = 1; i < samples.length; i++) {\n parts.push(`L${samples[i].x.toFixed(2)},${samples[i].y.toFixed(2)}`);\n }\n pathD = parts.join('');\n\n if (cfg.fill) {\n const closingY = cfg.fill.side === 'below' ? ctx.plotSize.height : 0;\n const first = samples[0];\n const last = samples[samples.length - 1];\n const fillParts = [...parts];\n fillParts.push(`L${last.x.toFixed(2)},${closingY.toFixed(2)}`);\n fillParts.push(`L${first.x.toFixed(2)},${closingY.toFixed(2)}`);\n fillParts.push('Z');\n fillD = fillParts.join('');\n }\n }\n\n return (\n <>\n {fillD && (\n <path\n data-curve-element=\"fill\"\n className={s.fill}\n d={fillD}\n fill={cfg.fill ? cfg.fill.color : undefined}\n />\n )}\n {pathD && curveVisible && (\n <path\n className={s.curve}\n d={pathD}\n fill=\"none\"\n />\n )}\n {interactive && plotAnchors.map((a, i) => {\n const pinned = isPinnedEndpointAt(i, points.length, endpoints);\n const locked = points[i]?.locked === true;\n if ((locked || pinned) && cfg.hideNonInteractive) return null;\n const active = activeIndex === i;\n const isEndpoint = curveVisible && (i === 0 || i === points.length - 1);\n const focus = !locked && !(endpoints === 'pinned-both' && pinned)\n ? { tabIndex: 0 }\n : {};\n const a11y = {\n ...focus,\n role: 'button',\n 'aria-label': `${cfg.label ?? 'Point'} ${i + 1} at ${formatCoord(points[i].x)}, ${formatCoord(points[i].y)}`,\n };\n if (cfg.renderAnchor) {\n const node = cfg.renderAnchor({\n point: points[i], index: i,\n cx: a.x, cy: a.y,\n isActive: active, isLocked: locked,\n isPinnedEndpoint: pinned, isEndpoint,\n });\n if (node !== null && node !== undefined) {\n return <g key={i} data-anchor-index={i} {...a11y}>{node}</g>;\n }\n }\n const anchorCls = [\n s.anchor,\n isEndpoint && s.endpoint,\n (pinned || locked) && s.pinned,\n locked && s.locked,\n active && s.active,\n ].filter(Boolean).join(' ');\n const renderAsDiamond = isEndpoint || locked;\n if (renderAsDiamond) {\n const half = locked ? LOCKED_HALF : ENDPOINT_HALF;\n return (\n <rect\n key={i}\n className={anchorCls}\n x={a.x - half}\n y={a.y - half}\n width={half * 2}\n height={half * 2}\n transform={`rotate(45 ${a.x} ${a.y})`}\n data-anchor-index={i}\n {...a11y}\n />\n );\n }\n return (\n <circle\n key={i}\n className={anchorCls}\n cx={a.x}\n cy={a.y}\n r={4}\n data-anchor-index={i}\n {...a11y}\n />\n );\n })}\n </>\n );\n }\n\n // ── hit test ────────────────────────────────────────────────────────\n function hitTest(state: FunctionLayerState, plot: PlotPoint, ctx: LayerCtx) {\n if (!interactive) return null;\n const plotAnchors = projectAnchors(state.points, ctx);\n const a = hitTestAnchor(plotAnchors, plot, ANCHOR_SNAP_PX);\n if (a) return { kind: 'anchor' as const, payload: { index: a.index } };\n if (addPointMode === 'click-curve' && curveVisible) {\n const segs = segmentSamples(state.points, interpolation, ctx);\n const c = hitTestCurve(segs, plot, CURVE_HIT_PX);\n if (c) return { kind: 'curve' as const, payload: { segIdx: c.segIdx, t: c.t } };\n }\n return null;\n }\n\n // ── move constraint, shared by drag and keyboard ────────────────────\n function moveAnchor(state: FunctionLayerState, index: number, model: ModelPoint, ctx: LayerCtx): FunctionLayerState {\n const points = state.points;\n if (index < 0 || index >= points.length) return state;\n const mr = ctx.modelRange;\n const xMin = cfg.xClamp ? cfg.xClamp[0] : mr.xMin;\n const xMax = cfg.xClamp ? cfg.xClamp[1] : mr.xMax;\n let nx = model.x;\n let ny = model.y;\n const isEndpoint = index === 0 || index === points.length - 1;\n\n if (endpoints === 'pinned-both' && isEndpoint) {\n nx = points[index].x;\n ny = points[index].y;\n } else if (endpoints === 'pinned-x' && isEndpoint) {\n nx = index === 0 ? xMin : xMax;\n } else if (constrain === 'function' || domain === '1d') {\n const epsilon = constrain === 'function'\n ? (xMax - xMin) / 1000\n : 0;\n const left = index > 0 ? points[index - 1].x + epsilon : xMin;\n const right = index < points.length - 1 ? points[index + 1].x - epsilon : xMax;\n nx = Math.max(left, Math.min(right, nx));\n }\n\n // Always clamp x to the layer's own range (so free-mode\n // interior anchors of a sub-range layer can't escape it).\n // Normalize bounds for inverted ranges.\n {\n const lo = Math.min(xMin, xMax);\n const hi = Math.max(xMin, xMax);\n nx = Math.max(lo, Math.min(hi, nx));\n }\n const clamped = clampToModelRange({ x: nx, y: ny }, ctx);\n const next = points.slice();\n next[index] = { ...points[index], x: clamped.x, y: clamped.y };\n return { ...state, points: next, activeIndex: index };\n }\n\n // ── drag gesture ────────────────────────────────────────────────────\n function makeDragGesture(index: number): LayerGesture<FunctionLayerState> {\n return {\n onMove(state, model, _e, ctx) { return moveAnchor(state, index, model, ctx); },\n onCommit(state) { return { ...state, activeIndex: null }; },\n onCancel(state) { return { ...state, activeIndex: null }; },\n };\n }\n\n // ── pointerdown on a positive hit ───────────────────────────────────\n function onPointerDown(\n state: FunctionLayerState,\n hit: { kind: string; payload?: unknown },\n e: PointerEvent,\n ctx: LayerCtx,\n extra: EmptyDownArgs<FunctionLayerState>,\n ): LayerGesture<FunctionLayerState> | void {\n if (hit.kind === 'anchor') {\n const { index } = hit.payload as { index: number };\n dlog('function-layer', 'anchor-down', { id, index, shift: ctx.modifiers.shift, button: e.button });\n if (state.points[index]?.locked) return;\n if (ctx.modifiers.shift) {\n if (isPinnedEndpointAt(index, state.points.length, endpoints)) return;\n if (cfg.minPoints !== undefined && state.points.length <= cfg.minPoints) return;\n extra.commit({\n ...state,\n points: state.points.filter((_, i) => i !== index),\n activeIndex: null,\n });\n return;\n }\n if (e.button === 2) return;\n extra.commit({ ...state, activeIndex: index });\n return makeDragGesture(index);\n }\n if (hit.kind === 'curve') {\n const { segIdx } = hit.payload as { segIdx: number; t: number };\n if (cfg.maxPoints !== undefined && state.points.length >= cfg.maxPoints) return;\n const insertIndex = segIdx + 1;\n const modelPt = clampToModelRange(extra.model, ctx);\n\n // function/1d: refuse insert that would be immediately clamped\n // against a neighbor (mouse jitter would otherwise drop a phantom\n // point directly above an existing anchor). Snap to dragging the\n // neighbor instead.\n if (constrain === 'function' || domain === '1d') {\n const epsilon = constrain === 'function'\n ? (ctx.modelRange.xMax - ctx.modelRange.xMin) / 1000\n : 0;\n const left = insertIndex > 0 ? state.points[insertIndex - 1] : null;\n const right = insertIndex < state.points.length ? state.points[insertIndex] : null;\n if (left && Math.abs(modelPt.x - left.x) < epsilon * 2) {\n extra.commit({ ...state, activeIndex: insertIndex - 1 });\n return makeDragGesture(insertIndex - 1);\n }\n if (right && Math.abs(modelPt.x - right.x) < epsilon * 2) {\n extra.commit({ ...state, activeIndex: insertIndex });\n return makeDragGesture(insertIndex);\n }\n }\n\n const nextPoints = [...state.points.slice(0, insertIndex), modelPt, ...state.points.slice(insertIndex)];\n extra.commit({ ...state, points: nextPoints, activeIndex: insertIndex });\n return makeDragGesture(insertIndex);\n }\n }\n\n // ── empty-space click ───────────────────────────────────────────────\n function onEmptyPointerDown(\n state: FunctionLayerState,\n model: ModelPoint,\n _e: PointerEvent,\n ctx: LayerCtx,\n extra: EmptyDownArgs<FunctionLayerState>,\n ): LayerGesture<FunctionLayerState> | void {\n if (!interactive) return;\n if (addPointMode !== 'click-empty') return;\n if (cfg.maxPoints !== undefined && state.points.length >= cfg.maxPoints) return;\n\n const clamped = clampToModelRange(model, ctx);\n let insertIndex = state.points.length;\n if (domain === '1d') {\n for (let i = 0; i < state.points.length; i++) {\n if (state.points[i].x > clamped.x) { insertIndex = i; break; }\n }\n }\n const nextPoints = [...state.points.slice(0, insertIndex), clamped, ...state.points.slice(insertIndex)];\n extra.commit({ ...state, points: nextPoints, activeIndex: null });\n return;\n }\n\n // ── keyboard ────────────────────────────────────────────────────────\n function onKeyDown(\n state: FunctionLayerState,\n e: KeyboardEvent,\n ctx: LayerCtx,\n extra: KeyDownArgs<FunctionLayerState>,\n ): void {\n if (!interactive) return;\n const target = e.target as Element | null;\n if (target?.closest?.('[data-layer-id]')?.getAttribute('data-layer-id') !== id) return;\n const el = target.closest('[data-anchor-index]');\n if (!el) return;\n const dx = e.key === 'ArrowRight' ? 1 : e.key === 'ArrowLeft' ? -1 : 0;\n const dy = e.key === 'ArrowUp' ? 1 : e.key === 'ArrowDown' ? -1 : 0;\n if (dx === 0 && dy === 0) return;\n e.preventDefault();\n const index = Number(el.getAttribute('data-anchor-index'));\n const p = state.points[index];\n if (!p || p.locked) return;\n const m = ctx.modelRange;\n const mult = e.shiftKey ? SHIFT_MULTIPLIER : 1;\n const xSpan = cfg.xClamp ? cfg.xClamp[1] - cfg.xClamp[0] : m.xMax - m.xMin;\n const next = moveAnchor(state, index, {\n x: p.x + dx * Math.abs(xSpan) * KEY_STEP_FRACTION * mult,\n y: p.y + dy * Math.abs(m.yMax - m.yMin) * KEY_STEP_FRACTION * mult,\n }, ctx);\n const moved = next.points[index];\n if (moved.x === p.x && moved.y === p.y) return;\n extra.commit({ ...next, activeIndex: null });\n }\n\n return {\n id,\n render,\n hitTest,\n onPointerDown,\n onEmptyPointerDown,\n onKeyDown,\n };\n}\n\n/** Convenience: build a function-layer state from a points array. */\nexport function functionLayerState(points: readonly ControlPoint[]): FunctionLayerState {\n return { points, activeIndex: null };\n}\n","/**\n * Back-compat wrapper around `LayeredCurveEditor` that preserves the\n * original single-curve API. New consumers wanting multi-layer\n * composition should reach for `LayeredCurveEditor` + `createFunctionLayer`\n * directly.\n */\nimport {\n useCallback, useEffect, useMemo, useState,\n type CSSProperties,\n type MouseEvent as ReactMouseEvent,\n type ReactNode,\n} from 'react';\nimport {\n type GridSettings,\n type AxesSettings,\n type TickSettings,\n} from '../Plot2D';\nimport type { InterpolationMode } from './interpolation';\nimport {\n createFunctionLayer,\n type ControlPoint,\n type CurveDomain,\n type EndpointMode,\n type AddPointMode,\n type CurveSettings,\n type FillSettings,\n type AnchorRenderProps,\n type FunctionLayerState,\n} from './createFunctionLayer';\nimport { LayeredCurveEditor } from './LayeredCurveEditor';\nimport { useLatest } from '@weasel-js/core';\n\nexport type { GridSettings, AxesSettings, TickSettings };\nexport type {\n ControlPoint, CurveDomain, EndpointMode, AddPointMode,\n CurveSettings, FillSettings, AnchorRenderProps,\n};\n\n/**\n * Props for {@link CurveEditor}. `onInput` fires throughout a gesture and\n * `onChange` once at its end, with the pre-gesture points as `prev`.\n */\nexport interface CurveEditorProps {\n value: readonly ControlPoint[];\n onInput: (next: ControlPoint[]) => void;\n onChange?: (next: ControlPoint[], prev: readonly ControlPoint[]) => void;\n domain?: CurveDomain;\n interpolation?: InterpolationMode;\n endpoints?: EndpointMode;\n xRange?: readonly [number, number];\n yRange?: readonly [number, number];\n width: number;\n height: number;\n grid?: GridSettings | false | null;\n axes?: AxesSettings | false | null;\n xTicks?: TickSettings | false | null;\n yTicks?: TickSettings | false | null;\n curve?: CurveSettings | false | null;\n fill?: FillSettings | false | null;\n hideNonInteractive?: boolean;\n constrain?: 'none' | 'function';\n history?: boolean;\n minPoints?: number;\n maxPoints?: number;\n addPointMode?: AddPointMode;\n renderAnchor?: (info: AnchorRenderProps) => ReactNode;\n /** Extra SVG content rendered behind the curve and anchors. */\n decorations?: ReactNode;\n className?: string;\n style?: CSSProperties;\n}\n\n/**\n * Editable curve through a list of control points: drag anchors, click to\n * insert, right-click to delete, with optional built-in undo.\n *\n * This is the single-curve API, implemented as one `createFunctionLayer` on\n * a `LayeredCurveEditor`. Reach for those directly to stack several curves\n * on one plot.\n */\nexport function CurveEditor(props: CurveEditorProps) {\n const {\n value, onInput, onChange,\n domain, endpoints, interpolation, constrain, addPointMode,\n minPoints, maxPoints, curve, fill, hideNonInteractive, renderAnchor,\n width, height, xRange, yRange, grid, axes, xTicks, yTicks, history,\n className, style, decorations,\n } = props;\n\n // Layer is config-only; rebuild only when *config* changes.\n const layer = useMemo(() => createFunctionLayer({\n id: 'function',\n domain, endpoints, interpolation, constrain, addPointMode,\n minPoints, maxPoints, curve, fill, hideNonInteractive, renderAnchor,\n }), [\n domain, endpoints, interpolation, constrain, addPointMode,\n minPoints, maxPoints, curve, fill, hideNonInteractive, renderAnchor,\n ]);\n\n // Hybrid controlled-state: the wrapper holds the full FunctionLayerState\n // locally so that consumers using the original CurveEditor API don't\n // have to wire their `value` prop back through on every drag tick to\n // keep the in-flight gesture advancing. We adopt the consumer's\n // `value` prop when it changes externally (and no gesture is active).\n const [layerState, setLayerState] = useState<FunctionLayerState>(\n () => ({ points: value, activeIndex: null }),\n );\n // Ref mirror so the layerChange callback can compare against the\n // freshest state without re-binding on every render.\n const layerStateRef = useLatest(layerState);\n\n // Sync from controlled `value` when the consumer updates it externally\n // and we're idle. Skip the update during a gesture so drag-in-flight\n // points aren't clobbered by a stale `value`.\n useEffect(() => {\n if (layerStateRef.current.activeIndex !== null) return;\n if (layerStateRef.current.points === value) return;\n setLayerState({ points: value, activeIndex: null });\n }, [value, layerStateRef]);\n\n const handleLayerChange = useCallback((_id: string, nextUnknown: unknown) => {\n const next = nextUnknown as FunctionLayerState;\n const prevPoints = layerStateRef.current.points;\n setLayerState(next);\n if (next.points !== prevPoints) {\n onInput(next.points as ControlPoint[]);\n }\n }, [onInput, layerStateRef]);\n\n const handleLayerCommit = useCallback((_id: string, nextUnknown: unknown, prevUnknown: unknown) => {\n if (!onChange) return;\n const next = nextUnknown as FunctionLayerState;\n const prev = prevUnknown as FunctionLayerState;\n if (next.points !== prev.points) {\n onChange?.(next.points as ControlPoint[], prev.points);\n }\n }, [onChange]);\n\n // Right-click on an anchor → delete (preserves original CurveEditor\n // behavior). LayeredCurveEditor doesn't model contextmenu as a\n // gesture; we intercept at the wrapper level by walking the DOM\n // target for `data-anchor-index`.\n const onContextMenu = useCallback((e: ReactMouseEvent<HTMLDivElement>) => {\n const target = e.target as Element | null;\n const node = target?.closest?.('[data-anchor-index]') as Element | null;\n if (!node) return;\n e.preventDefault();\n e.stopPropagation();\n const idxAttr = node.getAttribute('data-anchor-index');\n if (idxAttr === null) return;\n const idx = Number(idxAttr);\n const points = layerStateRef.current.points;\n if (!Number.isInteger(idx) || idx < 0 || idx >= points.length) return;\n if (points[idx]?.locked) return;\n if (endpoints !== undefined && endpoints !== 'free' && (idx === 0 || idx === points.length - 1)) return;\n if (minPoints !== undefined && points.length <= minPoints) return;\n const next = points.filter((_, i) => i !== idx);\n setLayerState({ points: next, activeIndex: null });\n onInput(next as ControlPoint[]);\n onChange?.(next as ControlPoint[], points);\n }, [endpoints, minPoints, onInput, onChange, layerStateRef]);\n\n const layers = useMemo(() => [{ layer, state: layerState }], [layer, layerState]);\n\n return (\n <div\n onContextMenu={onContextMenu}\n style={{ display: 'contents' }}\n >\n <LayeredCurveEditor\n layers={layers}\n onLayerChange={handleLayerChange}\n onLayerCommit={handleLayerCommit}\n width={width}\n height={height}\n xRange={xRange}\n yRange={yRange}\n grid={grid}\n axes={axes}\n xTicks={xTicks}\n yTicks={yTicks}\n history={history}\n className={className}\n style={style}\n >\n {decorations}\n </LayeredCurveEditor>\n </div>\n );\n}\n","import type { Op } from '@weasel-js/core';\nimport type { ControlPoint } from './CurveEditor';\n\n/**\n * The adapter contract for {@link createSetCurveOp}. Consumers wire\n * this to whatever state container holds the curve's value — typically\n * a small callback that calls a React state setter or dispatches a\n * Redux/Zustand action.\n */\nexport interface SetCurveAdapter {\n setValue(id: string, next: readonly ControlPoint[]): void;\n}\n\n/**\n * Arguments for {@link createSetCurveOp}. `coalesceKey` merges consecutive\n * edits sharing it into one history entry.\n */\nexport interface CreateSetCurveOpArgs {\n /** Stable id the consumer uses for this curve. Used so a single\n * adapter can handle multiple curves; the op's apply routes by id. */\n id: string;\n from: readonly ControlPoint[];\n to: readonly ControlPoint[];\n label?: string;\n coalesceKey?: string;\n}\n\n/**\n * Op factory for editing a CurveEditor's value through weasel-history.\n *\n * history.applyOps(\n * [createSetCurveOp({ id: 'easing', from, to, label: 'Edit easing' })],\n * 'Edit easing',\n * );\n *\n * The op's `apply(adapter)` calls `adapter.setValue(id, to)`. `invert()`\n * returns a mirror op with `from`/`to` swapped. When `from` and `to`\n * are structurally equal, `apply` returns `false` so history can skip\n * the entry — same convention as `createSetPathOp`.\n */\nexport function createSetCurveOp(args: CreateSetCurveOpArgs): Op {\n const { id, from, to, label, coalesceKey } = args;\n return {\n name: 'setCurve',\n args: { id, from, to, label, coalesceKey },\n label,\n coalesceKey,\n apply(adapter) {\n if (controlPointsEqual(from, to)) return false;\n (adapter as SetCurveAdapter).setValue(id, to);\n return undefined;\n },\n invert() {\n return createSetCurveOp({ id, from: to, to: from, label, coalesceKey });\n },\n };\n}\n\nfunction controlPointsEqual(\n a: readonly ControlPoint[],\n b: readonly ControlPoint[],\n): boolean {\n if (a === b) return true;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (a[i].x !== b[i].x || a[i].y !== b[i].y) return false;\n }\n return true;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"CurveEditor-0EE00D6s.js","names":["_Fragment","s","dlog","CurveEditor_module_default","LayeredCurveEditor","hitTestAnchor","hitTestCurve","handleHalf","knot","dx","b","a","dy","alpha","segmentSampleAt","t1","p0","p1","t2","p2","t3","p3","t","u","a1x","a1y","a2x","a2y","a3x","a3y","b1x","b1y","b2x","b2y","sampleCatmullRom","anchors","samplesPerSegment","n","out","reflectStart","reflectEnd","i","tStart","s","tangents","ys","m","avg","mag","hermite","m0","m1","sampleMonotone","xs","tx","ty","startStep","sampleLinear","sampleByInterpolation","mode","SAMPLES_PER_SEGMENT","ENDPOINT_HALF","LOCKED_HALF","ANCHOR_SNAP_PX","CURVE_HIT_PX","KEY_STEP_FRACTION","SHIFT_MULTIPLIER","formatCoord","clampToModelRange","ctx","xLo","xHi","yLo","yHi","p","isPinnedEndpointAt","endpoints","index","count","projectAnchors","points","segmentSamples","all","interpolation","start","end","slice","resolveInterpolation","cfg","createFunctionLayer","id","domain","constrain","addPointMode","interactive","curveVisible","render","state","plotAnchors","pathD","fillD","samples","parts","closingY","first","last","fillParts","Fragment","pinned","locked","active","activeIndex","isEndpoint","a11y","node","anchorCls","half","hitTest","plot","segs","c","moveAnchor","mr","xMin","xMax","nx","model","ny","epsilon","left","right","clamped","next","makeDragGesture","onPointerDown","hit","e","extra","insertIndex","segIdx","modelPt","nextPoints","onEmptyPointerDown","onKeyDown","target","el","mult","xSpan","moved","functionLayerState","CurveEditor","props","layer","useMemo","minPoints","maxPoints","curve","fill","hideNonInteractive","renderAnchor","layerState","setLayerState","useState","value","layerStateRef","useLatest","handleLayerChange","useCallback","nextUnknown","prevPoints","onInput","handleLayerCommit","onChange","prev","prevUnknown","onContextMenu","idxAttr","idx","layers","decorations","createSetCurveOp","args","controlPointsEqual","from","to"],"sources":["../../src/components/CurveEditor/catmullRom.ts","../../src/components/CurveEditor/monotone.ts","../../src/components/CurveEditor/interpolation.ts","../../src/components/CurveEditor/createFunctionLayer.tsx","../../src/components/CurveEditor/CurveEditor.tsx","../../src/components/CurveEditor/setCurveOp.ts"],"sourcesContent":["/**\n * Catmull-Rom spline sampling, parameterized by α.\n *\n * α = 0 → uniform parameterization (original Catmull-Rom). Fast, can\n * produce cusps and self-intersections at sharp 2D angles.\n * α = 0.5 → centripetal (Yuksel/Schneider 2011). No cusps, no\n * self-intersection. The \"safe default\" for 2D.\n * α = 1 → chordal. Smoother than uniform, different artifacts.\n *\n * `sampleCurve(...)` is the convenience alias for centripetal (α=0.5).\n */\n\nexport interface Point {\n x: number;\n y: number;\n}\n\nfunction knot(a: Point, b: Point, alpha: number): number {\n const dx = b.x - a.x;\n const dy = b.y - a.y;\n return Math.pow(Math.sqrt(dx * dx + dy * dy), alpha);\n}\n\n/**\n * Sample the Catmull-Rom segment between p1 and p2 at parameter\n * t ∈ [0, 1]. p0 and p3 are the surrounding control points (use phantom\n * reflection at endpoints — see sampleCatmullRom below).\n *\n * Returns p1 at t=0 and p2 at t=1. `alpha` controls parameterization.\n */\nexport function segmentSampleAt(\n p0: Point, p1: Point, p2: Point, p3: Point, t: number,\n alpha: number = 0.5,\n): Point {\n const t0 = 0;\n const t1 = t0 + knot(p0, p1, alpha);\n const t2 = t1 + knot(p1, p2, alpha);\n const t3 = t2 + knot(p2, p3, alpha);\n\n // Degenerate: coincident p1/p2 → linear interpolation fallback.\n if (t2 - t1 === 0) {\n return { x: p1.x + (p2.x - p1.x) * t, y: p1.y + (p2.y - p1.y) * t };\n }\n\n const u = t1 + t * (t2 - t1);\n\n const a1x = ((t1 - u) * p0.x + (u - t0) * p1.x) / (t1 - t0);\n const a1y = ((t1 - u) * p0.y + (u - t0) * p1.y) / (t1 - t0);\n const a2x = ((t2 - u) * p1.x + (u - t1) * p2.x) / (t2 - t1);\n const a2y = ((t2 - u) * p1.y + (u - t1) * p2.y) / (t2 - t1);\n const a3x = ((t3 - u) * p2.x + (u - t2) * p3.x) / (t3 - t2);\n const a3y = ((t3 - u) * p2.y + (u - t2) * p3.y) / (t3 - t2);\n\n const b1x = ((t2 - u) * a1x + (u - t0) * a2x) / (t2 - t0);\n const b1y = ((t2 - u) * a1y + (u - t0) * a2y) / (t2 - t0);\n const b2x = ((t3 - u) * a2x + (u - t1) * a3x) / (t3 - t1);\n const b2y = ((t3 - u) * a2y + (u - t1) * a3y) / (t3 - t1);\n\n const cx = ((t2 - u) * b1x + (u - t1) * b2x) / (t2 - t1);\n const cy = ((t2 - u) * b1y + (u - t1) * b2y) / (t2 - t1);\n return { x: cx, y: cy };\n}\n\n/**\n * Sample the whole curve through `anchors` using Catmull-Rom with the\n * given α. Returns `(n-1) * samplesPerSegment + 1` points. Phantom\n * endpoints by reflection so the first/last segments are tangent to\n * the chord into/out of the endpoints. Returns `[]` for fewer than 2\n * anchors.\n */\nexport function sampleCatmullRom(\n anchors: readonly Point[],\n samplesPerSegment: number,\n alpha: number = 0.5,\n): Point[] {\n if (anchors.length < 2) return [];\n if (samplesPerSegment < 1) samplesPerSegment = 1;\n\n const n = anchors.length;\n const out: Point[] = [];\n\n const reflectStart: Point = {\n x: 2 * anchors[0].x - anchors[1].x,\n y: 2 * anchors[0].y - anchors[1].y,\n };\n const reflectEnd: Point = {\n x: 2 * anchors[n - 1].x - anchors[n - 2].x,\n y: 2 * anchors[n - 1].y - anchors[n - 2].y,\n };\n\n for (let i = 0; i < n - 1; i++) {\n const p0 = i === 0 ? reflectStart : anchors[i - 1];\n const p1 = anchors[i];\n const p2 = anchors[i + 1];\n const p3 = i + 2 < n ? anchors[i + 2] : reflectEnd;\n const tStart = i === 0 ? 0 : 1;\n for (let s = tStart; s <= samplesPerSegment; s++) {\n if (s === 0) {\n out.push(p1);\n continue;\n }\n if (s === samplesPerSegment) {\n out.push(p2);\n continue;\n }\n const t = s / samplesPerSegment;\n out.push(segmentSampleAt(p0, p1, p2, p3, t, alpha));\n }\n }\n return out;\n}\n\n/** Convenience alias for centripetal Catmull-Rom (α=0.5). The most\n * common choice; safe default for 2D paths. */\nexport function sampleCurve(\n anchors: readonly Point[],\n samplesPerSegment: number,\n): Point[] {\n return sampleCatmullRom(anchors, samplesPerSegment, 0.5);\n}\n","/**\n * Monotone cubic interpolation (Fritsch-Carlson / Steffen variant).\n *\n * Passes through every anchor and guarantees the curve never\n * overshoots `min(neighbors)` / `max(neighbors)` along either axis.\n * The right choice for animation timing curves (no anticipation) or\n * audio envelopes (no out-of-range values).\n *\n * Parameterized by index — works for both 1D (function-shaped) and\n * 2D (parametric) anchor sequences. The x and y coordinates are\n * interpolated independently as functions of the anchor index.\n *\n * Reference: Fritsch & Carlson 1980, \"Monotone piecewise cubic\n * interpolation.\" Steffen's 1990 refinement is conceptually the same;\n * we use a min-clamp variant that's monotonicity-preserving by\n * construction.\n */\n\nimport type { Point } from './catmullRom';\n\n/** Per-axis tangent computation for a 1D series of y-values sampled at\n * unit intervals (index-parameterized). Returns one tangent per\n * anchor. */\nfunction tangents(ys: readonly number[]): number[] {\n const n = ys.length;\n if (n === 0) return [];\n if (n === 1) return [0];\n\n // Secant slopes (dx = 1 in index parameterization).\n const m: number[] = new Array(n - 1);\n for (let i = 0; i < n - 1; i++) m[i] = ys[i + 1] - ys[i];\n\n const t: number[] = new Array(n);\n t[0] = m[0];\n t[n - 1] = m[n - 2];\n\n for (let i = 1; i < n - 1; i++) {\n if (m[i - 1] * m[i] <= 0) {\n // Sign change or flat → local extremum; tangent is zero (forces\n // the curve to settle without overshoot).\n t[i] = 0;\n } else {\n // Weighted average of adjacent secants (with dx_i = dx_{i-1} = 1,\n // this is just the arithmetic mean). Clamped by Steffen's rule\n // so the cubic stays monotone within each segment.\n const avg = (m[i - 1] + m[i]) / 2;\n const mag = Math.min(Math.abs(m[i - 1]), Math.abs(m[i]), Math.abs(avg));\n t[i] = Math.sign(avg) * mag;\n }\n }\n return t;\n}\n\n/** Cubic Hermite basis at parameter t ∈ [0, 1] given endpoint values\n * p0, p1 and endpoint tangents m0, m1. */\nfunction hermite(\n p0: number, p1: number, m0: number, m1: number, t: number,\n): number {\n const t2 = t * t;\n const t3 = t2 * t;\n return (\n (2 * t3 - 3 * t2 + 1) * p0 +\n (t3 - 2 * t2 + t) * m0 +\n (-2 * t3 + 3 * t2) * p1 +\n (t3 - t2) * m1\n );\n}\n\n/**\n * Sample the monotone-cubic curve through `anchors`. Returns\n * `(n-1) * samplesPerSegment + 1` points (same shape as\n * `sampleCatmullRom`). `[]` for fewer than 2 anchors.\n */\nexport function sampleMonotone(\n anchors: readonly Point[],\n samplesPerSegment: number,\n): Point[] {\n if (anchors.length < 2) return [];\n if (samplesPerSegment < 1) samplesPerSegment = 1;\n\n const n = anchors.length;\n const xs = anchors.map((a) => a.x);\n const ys = anchors.map((a) => a.y);\n const tx = tangents(xs);\n const ty = tangents(ys);\n\n const out: Point[] = [];\n for (let i = 0; i < n - 1; i++) {\n const startStep = i === 0 ? 0 : 1; // skip duplicate boundary\n for (let s = startStep; s <= samplesPerSegment; s++) {\n if (s === 0) { out.push(anchors[i]); continue; }\n if (s === samplesPerSegment) { out.push(anchors[i + 1]); continue; }\n const t = s / samplesPerSegment;\n out.push({\n x: hermite(xs[i], xs[i + 1], tx[i], tx[i + 1], t),\n y: hermite(ys[i], ys[i + 1], ty[i], ty[i + 1], t),\n });\n }\n }\n return out;\n}\n","/**\n * Interpolation mode dispatcher for CurveEditor. The vocabulary of\n * supported algorithms and the unified `sample(...)` entry point that\n * picks the right one. All algorithms produce\n * `(n-1) * samplesPerSegment + 1` points and pass through every\n * anchor — CurveEditor is strictly interpolating, never approximating.\n */\n\nimport { sampleCatmullRom, type Point } from './catmullRom';\nimport { sampleMonotone } from './monotone';\n\n/** Supported interpolation modes. All are interpolating (pass through\n * every anchor); the differences are in tangent computation and\n * parameterization. */\nexport type InterpolationMode =\n | 'linear'\n | 'catmull-rom' // centripetal (α=0.5) — safe default\n | 'catmull-rom-uniform' // α=0; can cusp on sharp 2D angles\n | 'catmull-rom-chordal' // α=1; smoother than uniform\n | 'monotone'; // Fritsch-Carlson / Steffen; no overshoot\n\n/** Sample anchors as a polyline. Produces the same sample count shape\n * as the Catmull-Rom variants so downstream segment-slicing logic\n * (hit testing, fill path construction) works uniformly. */\nfunction sampleLinear(\n anchors: readonly Point[],\n samplesPerSegment: number,\n): Point[] {\n if (anchors.length < 2) return [];\n if (samplesPerSegment < 1) samplesPerSegment = 1;\n\n const out: Point[] = [];\n for (let i = 0; i < anchors.length - 1; i++) {\n const a = anchors[i];\n const b = anchors[i + 1];\n const startStep = i === 0 ? 0 : 1;\n for (let s = startStep; s <= samplesPerSegment; s++) {\n if (s === 0) { out.push(a); continue; }\n if (s === samplesPerSegment) { out.push(b); continue; }\n const t = s / samplesPerSegment;\n out.push({ x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t });\n }\n }\n return out;\n}\n\n/** Sample `anchors` using the chosen interpolation mode. */\nexport function sampleByInterpolation(\n anchors: readonly Point[],\n samplesPerSegment: number,\n mode: InterpolationMode,\n): Point[] {\n switch (mode) {\n case 'linear':\n return sampleLinear(anchors, samplesPerSegment);\n case 'catmull-rom-uniform':\n return sampleCatmullRom(anchors, samplesPerSegment, 0);\n case 'catmull-rom-chordal':\n return sampleCatmullRom(anchors, samplesPerSegment, 1);\n case 'monotone':\n return sampleMonotone(anchors, samplesPerSegment);\n case 'catmull-rom':\n default:\n return sampleCatmullRom(anchors, samplesPerSegment, 0.5);\n }\n}\n","/**\n * Built-in function/curve layer for LayeredCurveEditor. Bundles the\n * curve-render + anchor-render + drag/insert/delete gestures that were\n * the entirety of the original `CurveEditor`. Multiple instances coexist\n * by id; consumers can compose several to plot e.g. a primary function\n * and a derived (read-only) curve on the same chart.\n */\nimport type { ReactNode } from 'react';\nimport { sampleByInterpolation, type InterpolationMode } from './interpolation';\nimport { hitTestAnchor, hitTestCurve } from './hitTest';\nimport { handleHalf } from '../../handles';\nimport { dlog } from '../../dlog';\nimport s from './CurveEditor.module.css';\nimport type {\n CurveLayer, LayerCtx, LayerGesture, LayerRenderCtx, ModelPoint, PlotPoint,\n EmptyDownArgs, KeyDownArgs,\n} from './layerTypes';\n\n// ── Public types ─────────────────────────────────────────────────────\n\n/** One anchor of a curve, in model space. */\nexport interface ControlPoint {\n x: number;\n y: number;\n /** When true, this control point can't be moved or deleted by the\n * user. Render as a smaller diamond with locked styling. */\n locked?: boolean;\n}\n\n/**\n * Whether a curve's points keep their x order. In `'1d'` an anchor cannot be\n * dragged past its neighbors and a new point is inserted at its x position;\n * in `'2d'` points are free to move anywhere and keep their list order.\n */\nexport type CurveDomain = '1d' | '2d';\n/**\n * How much of a curve's first and last anchor the user may move: everything,\n * only their y, or nothing.\n */\nexport type EndpointMode = 'free' | 'pinned-x' | 'pinned-both';\n/**\n * Where a click adds a new anchor — on the curve itself, anywhere in the\n * plot, or nowhere.\n */\nexport type AddPointMode = 'click-curve' | 'click-empty' | 'never';\n\n/**\n * What a `renderAnchor` function is given for one anchor: the point, its\n * index, its position in plot space (`cx`/`cy`), and the states that change\n * how it should look.\n */\nexport interface AnchorRenderProps {\n point: ControlPoint;\n index: number;\n cx: number;\n cy: number;\n isActive: boolean;\n isLocked: boolean;\n isPinnedEndpoint: boolean;\n isEndpoint: boolean;\n}\n\n/**\n * Per-instance curve-rendering overrides. Currently empty — passing `{}`\n * means \"draw the curve\", and `false`/`null` means don't.\n */\nexport type CurveSettings = object;\n\n/** Shades the region between the curve and one edge of the plot. */\nexport interface FillSettings {\n side: 'below' | 'above';\n color?: string;\n}\n\n/** Configuration for {@link createFunctionLayer}. */\nexport interface FunctionLayerConfig {\n /** Layer id; default `'function'`. Two instances on the same editor\n * must have distinct ids. */\n id?: string;\n domain?: CurveDomain;\n endpoints?: EndpointMode;\n interpolation?: InterpolationMode;\n constrain?: 'none' | 'function';\n addPointMode?: AddPointMode;\n minPoints?: number;\n maxPoints?: number;\n curve?: CurveSettings | false | null;\n fill?: FillSettings | false | null;\n hideNonInteractive?: boolean;\n renderAnchor?: (info: AnchorRenderProps) => ReactNode;\n /** When false, the layer renders the curve+fill but doesn't render\n * anchors and doesn't accept pointer input. For derived read-only\n * curves (e.g. plotting f'(x) alongside f(x)). Default true. */\n interactive?: boolean;\n /** Override x clamp range for drags and pinned endpoints. Defaults\n * to the editor's modelRange x. Use this to constrain a layer to a\n * sub-range of the plot (e.g. a bevel curve occupying x ∈ [0, b]\n * while the editor's xRange is [0, halfWidth]). */\n xClamp?: readonly [number, number];\n /** Names the curve in each anchor's accessible name. Default `'Point'`. */\n label?: string;\n}\n\n/**\n * The state a function layer owns. Treat it as opaque and round-trip it\n * through `LayeredCurveEditor`'s `onLayerChange`; `points` is the part worth\n * reading.\n */\nexport interface FunctionLayerState {\n points: readonly ControlPoint[];\n /** Layer-internal: the index of the anchor currently being dragged.\n * Cleared between gestures. Consumers treat the whole state as\n * opaque and round-trip it via onLayerChange. */\n activeIndex: number | null;\n}\n\nconst SAMPLES_PER_SEGMENT = 16;\nconst ENDPOINT_HALF = handleHalf('--wzl-handle-size-lg');\nconst LOCKED_HALF = handleHalf('--wzl-handle-size-sm');\nconst ANCHOR_SNAP_PX = 12;\nconst CURVE_HIT_PX = 8;\n/** An arrow-key nudge, as a fraction of the range; shift takes ten. */\nconst KEY_STEP_FRACTION = 0.01;\nconst SHIFT_MULTIPLIER = 10;\n\nconst formatCoord = (n: number): string => String(Number(n.toFixed(3)));\n\n// ── Helpers ───────────────────────────────────────────────────────────\n\nfunction clampToModelRange(p: ModelPoint, ctx: LayerCtx): ModelPoint {\n const m = ctx.modelRange;\n const xLo = Math.min(m.xMin, m.xMax);\n const xHi = Math.max(m.xMin, m.xMax);\n const yLo = Math.min(m.yMin, m.yMax);\n const yHi = Math.max(m.yMin, m.yMax);\n return {\n x: Math.max(xLo, Math.min(xHi, p.x)),\n y: Math.max(yLo, Math.min(yHi, p.y)),\n };\n}\n\nfunction isPinnedEndpointAt(index: number, count: number, endpoints: EndpointMode): boolean {\n if (endpoints === 'free') return false;\n return index === 0 || index === count - 1;\n}\n\nfunction projectAnchors(points: readonly ControlPoint[], ctx: LayerCtx): PlotPoint[] {\n return points.map((p) => ctx.toPlot(p));\n}\n\nfunction segmentSamples(\n points: readonly ControlPoint[],\n interpolation: InterpolationMode,\n ctx: LayerCtx,\n): PlotPoint[][] {\n if (points.length < 2) return [];\n const all = sampleByInterpolation(points, SAMPLES_PER_SEGMENT, interpolation);\n const out: PlotPoint[][] = [];\n for (let i = 0; i < points.length - 1; i++) {\n const start = i * SAMPLES_PER_SEGMENT;\n const end = start + SAMPLES_PER_SEGMENT;\n const slice = all.slice(start, end + 1);\n out.push(slice.map((p) => ctx.toPlot(p)));\n }\n return out;\n}\n\nfunction resolveInterpolation(cfg: FunctionLayerConfig): InterpolationMode {\n return (cfg.constrain ?? 'none') === 'function'\n ? 'monotone'\n : (cfg.interpolation ?? 'catmull-rom');\n}\n\n// ── Factory ───────────────────────────────────────────────────────────\n\n/**\n * Builds the built-in curve layer for `LayeredCurveEditor`: it draws the\n * interpolated curve and its anchors, and handles dragging, inserting and\n * deleting them.\n *\n * Several can coexist on one editor as long as their ids differ — plotting a\n * derived read-only curve beside an editable one is the case it is shaped\n * for, via `interactive: false`.\n */\nexport function createFunctionLayer(cfg: FunctionLayerConfig = {}): CurveLayer<FunctionLayerState> {\n const id = cfg.id ?? 'function';\n const domain = cfg.domain ?? '2d';\n const endpoints = cfg.endpoints ?? 'free';\n const constrain = cfg.constrain ?? 'none';\n const addPointMode = cfg.addPointMode ?? 'click-curve';\n const interpolation = resolveInterpolation(cfg);\n const interactive = cfg.interactive !== false;\n const curveVisible = cfg.curve !== false && cfg.curve !== null;\n\n // ── render ──────────────────────────────────────────────────────────\n function render(state: FunctionLayerState, ctx: LayerRenderCtx): ReactNode {\n const { points, activeIndex } = state;\n const plotAnchors = projectAnchors(points, ctx);\n\n let pathD = '';\n let fillD: string | null = null;\n if (points.length >= 2) {\n const samples = sampleByInterpolation(points, SAMPLES_PER_SEGMENT, interpolation)\n .map((p) => ctx.toPlot(p));\n const parts: string[] = [`M${samples[0].x.toFixed(2)},${samples[0].y.toFixed(2)}`];\n for (let i = 1; i < samples.length; i++) {\n parts.push(`L${samples[i].x.toFixed(2)},${samples[i].y.toFixed(2)}`);\n }\n pathD = parts.join('');\n\n if (cfg.fill) {\n const closingY = cfg.fill.side === 'below' ? ctx.plotSize.height : 0;\n const first = samples[0];\n const last = samples[samples.length - 1];\n const fillParts = [...parts];\n fillParts.push(`L${last.x.toFixed(2)},${closingY.toFixed(2)}`);\n fillParts.push(`L${first.x.toFixed(2)},${closingY.toFixed(2)}`);\n fillParts.push('Z');\n fillD = fillParts.join('');\n }\n }\n\n return (\n <>\n {fillD && (\n <path\n data-curve-element=\"fill\"\n className={s.fill}\n d={fillD}\n fill={cfg.fill ? cfg.fill.color : undefined}\n />\n )}\n {pathD && curveVisible && (\n <path\n className={s.curve}\n d={pathD}\n fill=\"none\"\n />\n )}\n {interactive && plotAnchors.map((a, i) => {\n const pinned = isPinnedEndpointAt(i, points.length, endpoints);\n const locked = points[i]?.locked === true;\n if ((locked || pinned) && cfg.hideNonInteractive) return null;\n const active = activeIndex === i;\n const isEndpoint = curveVisible && (i === 0 || i === points.length - 1);\n const focus = !locked && !(endpoints === 'pinned-both' && pinned)\n ? { tabIndex: 0 }\n : {};\n const a11y = {\n ...focus,\n role: 'button',\n 'aria-label': `${cfg.label ?? 'Point'} ${i + 1} at ${formatCoord(points[i].x)}, ${formatCoord(points[i].y)}`,\n };\n if (cfg.renderAnchor) {\n const node = cfg.renderAnchor({\n point: points[i], index: i,\n cx: a.x, cy: a.y,\n isActive: active, isLocked: locked,\n isPinnedEndpoint: pinned, isEndpoint,\n });\n if (node !== null && node !== undefined) {\n return <g key={i} data-anchor-index={i} {...a11y}>{node}</g>;\n }\n }\n const anchorCls = [\n s.anchor,\n isEndpoint && s.endpoint,\n (pinned || locked) && s.pinned,\n locked && s.locked,\n active && s.active,\n ].filter(Boolean).join(' ');\n const renderAsDiamond = isEndpoint || locked;\n if (renderAsDiamond) {\n const half = locked ? LOCKED_HALF : ENDPOINT_HALF;\n return (\n <rect\n key={i}\n className={anchorCls}\n x={a.x - half}\n y={a.y - half}\n width={half * 2}\n height={half * 2}\n transform={`rotate(45 ${a.x} ${a.y})`}\n data-anchor-index={i}\n {...a11y}\n />\n );\n }\n return (\n <circle\n key={i}\n className={anchorCls}\n cx={a.x}\n cy={a.y}\n r={4}\n data-anchor-index={i}\n {...a11y}\n />\n );\n })}\n </>\n );\n }\n\n // ── hit test ────────────────────────────────────────────────────────\n function hitTest(state: FunctionLayerState, plot: PlotPoint, ctx: LayerCtx) {\n if (!interactive) return null;\n const plotAnchors = projectAnchors(state.points, ctx);\n const a = hitTestAnchor(plotAnchors, plot, ANCHOR_SNAP_PX);\n if (a) return { kind: 'anchor' as const, payload: { index: a.index } };\n if (addPointMode === 'click-curve' && curveVisible) {\n const segs = segmentSamples(state.points, interpolation, ctx);\n const c = hitTestCurve(segs, plot, CURVE_HIT_PX);\n if (c) return { kind: 'curve' as const, payload: { segIdx: c.segIdx, t: c.t } };\n }\n return null;\n }\n\n // ── move constraint, shared by drag and keyboard ────────────────────\n function moveAnchor(state: FunctionLayerState, index: number, model: ModelPoint, ctx: LayerCtx): FunctionLayerState {\n const points = state.points;\n if (index < 0 || index >= points.length) return state;\n const mr = ctx.modelRange;\n const xMin = cfg.xClamp ? cfg.xClamp[0] : mr.xMin;\n const xMax = cfg.xClamp ? cfg.xClamp[1] : mr.xMax;\n let nx = model.x;\n let ny = model.y;\n const isEndpoint = index === 0 || index === points.length - 1;\n\n if (endpoints === 'pinned-both' && isEndpoint) {\n nx = points[index].x;\n ny = points[index].y;\n } else if (endpoints === 'pinned-x' && isEndpoint) {\n nx = index === 0 ? xMin : xMax;\n } else if (constrain === 'function' || domain === '1d') {\n const epsilon = constrain === 'function'\n ? (xMax - xMin) / 1000\n : 0;\n const left = index > 0 ? points[index - 1].x + epsilon : xMin;\n const right = index < points.length - 1 ? points[index + 1].x - epsilon : xMax;\n nx = Math.max(left, Math.min(right, nx));\n }\n\n // Always clamp x to the layer's own range (so free-mode\n // interior anchors of a sub-range layer can't escape it).\n // Normalize bounds for inverted ranges.\n {\n const lo = Math.min(xMin, xMax);\n const hi = Math.max(xMin, xMax);\n nx = Math.max(lo, Math.min(hi, nx));\n }\n const clamped = clampToModelRange({ x: nx, y: ny }, ctx);\n const next = points.slice();\n next[index] = { ...points[index], x: clamped.x, y: clamped.y };\n return { ...state, points: next, activeIndex: index };\n }\n\n // ── drag gesture ────────────────────────────────────────────────────\n function makeDragGesture(index: number): LayerGesture<FunctionLayerState> {\n return {\n onMove(state, model, _e, ctx) { return moveAnchor(state, index, model, ctx); },\n onCommit(state) { return { ...state, activeIndex: null }; },\n onCancel(state) { return { ...state, activeIndex: null }; },\n };\n }\n\n // ── pointerdown on a positive hit ───────────────────────────────────\n function onPointerDown(\n state: FunctionLayerState,\n hit: { kind: string; payload?: unknown },\n e: PointerEvent,\n ctx: LayerCtx,\n extra: EmptyDownArgs<FunctionLayerState>,\n ): LayerGesture<FunctionLayerState> | void {\n if (hit.kind === 'anchor') {\n const { index } = hit.payload as { index: number };\n dlog('function-layer', 'anchor-down', { id, index, shift: ctx.modifiers.shift, button: e.button });\n if (state.points[index]?.locked) return;\n if (ctx.modifiers.shift) {\n if (isPinnedEndpointAt(index, state.points.length, endpoints)) return;\n if (cfg.minPoints !== undefined && state.points.length <= cfg.minPoints) return;\n extra.commit({\n ...state,\n points: state.points.filter((_, i) => i !== index),\n activeIndex: null,\n });\n return;\n }\n if (e.button === 2) return;\n extra.commit({ ...state, activeIndex: index });\n return makeDragGesture(index);\n }\n if (hit.kind === 'curve') {\n const { segIdx } = hit.payload as { segIdx: number; t: number };\n if (cfg.maxPoints !== undefined && state.points.length >= cfg.maxPoints) return;\n const insertIndex = segIdx + 1;\n const modelPt = clampToModelRange(extra.model, ctx);\n\n // function/1d: refuse insert that would be immediately clamped\n // against a neighbor (mouse jitter would otherwise drop a phantom\n // point directly above an existing anchor). Snap to dragging the\n // neighbor instead.\n if (constrain === 'function' || domain === '1d') {\n const epsilon = constrain === 'function'\n ? (ctx.modelRange.xMax - ctx.modelRange.xMin) / 1000\n : 0;\n const left = insertIndex > 0 ? state.points[insertIndex - 1] : null;\n const right = insertIndex < state.points.length ? state.points[insertIndex] : null;\n if (left && Math.abs(modelPt.x - left.x) < epsilon * 2) {\n extra.commit({ ...state, activeIndex: insertIndex - 1 });\n return makeDragGesture(insertIndex - 1);\n }\n if (right && Math.abs(modelPt.x - right.x) < epsilon * 2) {\n extra.commit({ ...state, activeIndex: insertIndex });\n return makeDragGesture(insertIndex);\n }\n }\n\n const nextPoints = [...state.points.slice(0, insertIndex), modelPt, ...state.points.slice(insertIndex)];\n extra.commit({ ...state, points: nextPoints, activeIndex: insertIndex });\n return makeDragGesture(insertIndex);\n }\n }\n\n // ── empty-space click ───────────────────────────────────────────────\n function onEmptyPointerDown(\n state: FunctionLayerState,\n model: ModelPoint,\n _e: PointerEvent,\n ctx: LayerCtx,\n extra: EmptyDownArgs<FunctionLayerState>,\n ): LayerGesture<FunctionLayerState> | void {\n if (!interactive) return;\n if (addPointMode !== 'click-empty') return;\n if (cfg.maxPoints !== undefined && state.points.length >= cfg.maxPoints) return;\n\n const clamped = clampToModelRange(model, ctx);\n let insertIndex = state.points.length;\n if (domain === '1d') {\n for (let i = 0; i < state.points.length; i++) {\n if (state.points[i].x > clamped.x) { insertIndex = i; break; }\n }\n }\n const nextPoints = [...state.points.slice(0, insertIndex), clamped, ...state.points.slice(insertIndex)];\n extra.commit({ ...state, points: nextPoints, activeIndex: null });\n return;\n }\n\n // ── keyboard ────────────────────────────────────────────────────────\n function onKeyDown(\n state: FunctionLayerState,\n e: KeyboardEvent,\n ctx: LayerCtx,\n extra: KeyDownArgs<FunctionLayerState>,\n ): void {\n if (!interactive) return;\n const target = e.target as Element | null;\n if (target?.closest?.('[data-layer-id]')?.getAttribute('data-layer-id') !== id) return;\n const el = target.closest('[data-anchor-index]');\n if (!el) return;\n const dx = e.key === 'ArrowRight' ? 1 : e.key === 'ArrowLeft' ? -1 : 0;\n const dy = e.key === 'ArrowUp' ? 1 : e.key === 'ArrowDown' ? -1 : 0;\n if (dx === 0 && dy === 0) return;\n e.preventDefault();\n const index = Number(el.getAttribute('data-anchor-index'));\n const p = state.points[index];\n if (!p || p.locked) return;\n const m = ctx.modelRange;\n const mult = e.shiftKey ? SHIFT_MULTIPLIER : 1;\n const xSpan = cfg.xClamp ? cfg.xClamp[1] - cfg.xClamp[0] : m.xMax - m.xMin;\n const next = moveAnchor(state, index, {\n x: p.x + dx * Math.abs(xSpan) * KEY_STEP_FRACTION * mult,\n y: p.y + dy * Math.abs(m.yMax - m.yMin) * KEY_STEP_FRACTION * mult,\n }, ctx);\n const moved = next.points[index];\n if (moved.x === p.x && moved.y === p.y) return;\n extra.commit({ ...next, activeIndex: null });\n }\n\n return {\n id,\n render,\n hitTest,\n onPointerDown,\n onEmptyPointerDown,\n onKeyDown,\n };\n}\n\n/** Convenience: build a function-layer state from a points array. */\nexport function functionLayerState(points: readonly ControlPoint[]): FunctionLayerState {\n return { points, activeIndex: null };\n}\n","/**\n * Back-compat wrapper around `LayeredCurveEditor` that preserves the\n * original single-curve API. New consumers wanting multi-layer\n * composition should reach for `LayeredCurveEditor` + `createFunctionLayer`\n * directly.\n */\nimport {\n useCallback, useEffect, useMemo, useState,\n type CSSProperties,\n type MouseEvent as ReactMouseEvent,\n type ReactNode,\n} from 'react';\nimport {\n type GridSettings,\n type AxesSettings,\n type TickSettings,\n} from '../Plot2D';\nimport type { InterpolationMode } from './interpolation';\nimport {\n createFunctionLayer,\n type ControlPoint,\n type CurveDomain,\n type EndpointMode,\n type AddPointMode,\n type CurveSettings,\n type FillSettings,\n type AnchorRenderProps,\n type FunctionLayerState,\n} from './createFunctionLayer';\nimport { LayeredCurveEditor } from './LayeredCurveEditor';\nimport { useLatest } from '@weasel-js/core';\n\nexport type { GridSettings, AxesSettings, TickSettings };\nexport type {\n ControlPoint, CurveDomain, EndpointMode, AddPointMode,\n CurveSettings, FillSettings, AnchorRenderProps,\n};\n\n/**\n * Props for {@link CurveEditor}. `onInput` fires throughout a gesture and\n * `onChange` once at its end, with the pre-gesture points as `prev`.\n */\nexport interface CurveEditorProps {\n value: readonly ControlPoint[];\n onInput: (next: ControlPoint[]) => void;\n onChange?: (next: ControlPoint[], prev: readonly ControlPoint[]) => void;\n domain?: CurveDomain;\n interpolation?: InterpolationMode;\n endpoints?: EndpointMode;\n xRange?: readonly [number, number];\n yRange?: readonly [number, number];\n width: number;\n height: number;\n grid?: GridSettings | false | null;\n axes?: AxesSettings | false | null;\n xTicks?: TickSettings | false | null;\n yTicks?: TickSettings | false | null;\n curve?: CurveSettings | false | null;\n fill?: FillSettings | false | null;\n hideNonInteractive?: boolean;\n constrain?: 'none' | 'function';\n history?: boolean;\n minPoints?: number;\n maxPoints?: number;\n addPointMode?: AddPointMode;\n renderAnchor?: (info: AnchorRenderProps) => ReactNode;\n /** Extra SVG content rendered behind the curve and anchors. */\n decorations?: ReactNode;\n className?: string;\n style?: CSSProperties;\n}\n\n/**\n * Editable curve through a list of control points: drag anchors, click to\n * insert, right-click to delete, with optional built-in undo.\n *\n * This is the single-curve API, implemented as one `createFunctionLayer` on\n * a `LayeredCurveEditor`. Reach for those directly to stack several curves\n * on one plot.\n */\nexport function CurveEditor(props: CurveEditorProps) {\n const {\n value, onInput, onChange,\n domain, endpoints, interpolation, constrain, addPointMode,\n minPoints, maxPoints, curve, fill, hideNonInteractive, renderAnchor,\n width, height, xRange, yRange, grid, axes, xTicks, yTicks, history,\n className, style, decorations,\n } = props;\n\n // Layer is config-only; rebuild only when *config* changes.\n const layer = useMemo(() => createFunctionLayer({\n id: 'function',\n domain, endpoints, interpolation, constrain, addPointMode,\n minPoints, maxPoints, curve, fill, hideNonInteractive, renderAnchor,\n }), [\n domain, endpoints, interpolation, constrain, addPointMode,\n minPoints, maxPoints, curve, fill, hideNonInteractive, renderAnchor,\n ]);\n\n // Hybrid controlled-state: the wrapper holds the full FunctionLayerState\n // locally so that consumers using the original CurveEditor API don't\n // have to wire their `value` prop back through on every drag tick to\n // keep the in-flight gesture advancing. We adopt the consumer's\n // `value` prop when it changes externally (and no gesture is active).\n const [layerState, setLayerState] = useState<FunctionLayerState>(\n () => ({ points: value, activeIndex: null }),\n );\n // Ref mirror so the layerChange callback can compare against the\n // freshest state without re-binding on every render.\n const layerStateRef = useLatest(layerState);\n\n // Sync from controlled `value` when the consumer updates it externally\n // and we're idle. Skip the update during a gesture so drag-in-flight\n // points aren't clobbered by a stale `value`.\n useEffect(() => {\n if (layerStateRef.current.activeIndex !== null) return;\n if (layerStateRef.current.points === value) return;\n setLayerState({ points: value, activeIndex: null });\n }, [value, layerStateRef]);\n\n const handleLayerChange = useCallback((_id: string, nextUnknown: unknown) => {\n const next = nextUnknown as FunctionLayerState;\n const prevPoints = layerStateRef.current.points;\n setLayerState(next);\n if (next.points !== prevPoints) {\n onInput(next.points as ControlPoint[]);\n }\n }, [onInput, layerStateRef]);\n\n const handleLayerCommit = useCallback((_id: string, nextUnknown: unknown, prevUnknown: unknown) => {\n if (!onChange) return;\n const next = nextUnknown as FunctionLayerState;\n const prev = prevUnknown as FunctionLayerState;\n if (next.points !== prev.points) {\n onChange?.(next.points as ControlPoint[], prev.points);\n }\n }, [onChange]);\n\n // Right-click on an anchor → delete (preserves original CurveEditor\n // behavior). LayeredCurveEditor doesn't model contextmenu as a\n // gesture; we intercept at the wrapper level by walking the DOM\n // target for `data-anchor-index`.\n const onContextMenu = useCallback((e: ReactMouseEvent<HTMLDivElement>) => {\n const target = e.target as Element | null;\n const node = target?.closest?.('[data-anchor-index]') as Element | null;\n if (!node) return;\n e.preventDefault();\n e.stopPropagation();\n const idxAttr = node.getAttribute('data-anchor-index');\n if (idxAttr === null) return;\n const idx = Number(idxAttr);\n const points = layerStateRef.current.points;\n if (!Number.isInteger(idx) || idx < 0 || idx >= points.length) return;\n if (points[idx]?.locked) return;\n if (endpoints !== undefined && endpoints !== 'free' && (idx === 0 || idx === points.length - 1)) return;\n if (minPoints !== undefined && points.length <= minPoints) return;\n const next = points.filter((_, i) => i !== idx);\n setLayerState({ points: next, activeIndex: null });\n onInput(next as ControlPoint[]);\n onChange?.(next as ControlPoint[], points);\n }, [endpoints, minPoints, onInput, onChange, layerStateRef]);\n\n const layers = useMemo(() => [{ layer, state: layerState }], [layer, layerState]);\n\n return (\n <div\n onContextMenu={onContextMenu}\n style={{ display: 'contents' }}\n >\n <LayeredCurveEditor\n layers={layers}\n onLayerChange={handleLayerChange}\n onLayerCommit={handleLayerCommit}\n width={width}\n height={height}\n xRange={xRange}\n yRange={yRange}\n grid={grid}\n axes={axes}\n xTicks={xTicks}\n yTicks={yTicks}\n history={history}\n className={className}\n style={style}\n >\n {decorations}\n </LayeredCurveEditor>\n </div>\n );\n}\n","import type { Op } from '@weasel-js/core';\nimport type { ControlPoint } from './CurveEditor';\n\n/**\n * The adapter contract for {@link createSetCurveOp}. Consumers wire\n * this to whatever state container holds the curve's value — typically\n * a small callback that calls a React state setter or dispatches a\n * Redux/Zustand action.\n */\nexport interface SetCurveAdapter {\n setValue(id: string, next: readonly ControlPoint[]): void;\n}\n\n/**\n * Arguments for {@link createSetCurveOp}. `coalesceKey` merges consecutive\n * edits sharing it into one history entry.\n */\nexport interface CreateSetCurveOpArgs {\n /** Stable id the consumer uses for this curve. Used so a single\n * adapter can handle multiple curves; the op's apply routes by id. */\n id: string;\n from: readonly ControlPoint[];\n to: readonly ControlPoint[];\n label?: string;\n coalesceKey?: string;\n}\n\n/**\n * Op factory for editing a CurveEditor's value through weasel-history.\n *\n * history.applyOps(\n * [createSetCurveOp({ id: 'easing', from, to, label: 'Edit easing' })],\n * 'Edit easing',\n * );\n *\n * The op's `apply(adapter)` calls `adapter.setValue(id, to)`. `invert()`\n * returns a mirror op with `from`/`to` swapped. When `from` and `to`\n * are structurally equal, `apply` returns `false` so history can skip\n * the entry — same convention as `createSetPathOp`.\n */\nexport function createSetCurveOp(args: CreateSetCurveOpArgs): Op {\n const { id, from, to, label, coalesceKey } = args;\n return {\n name: 'setCurve',\n args: { id, from, to, label, coalesceKey },\n label,\n coalesceKey,\n apply(adapter) {\n if (controlPointsEqual(from, to)) return false;\n (adapter as SetCurveAdapter).setValue(id, to);\n return undefined;\n },\n invert() {\n return createSetCurveOp({ id, from: to, to: from, label, coalesceKey });\n },\n };\n}\n\nfunction controlPointsEqual(\n a: readonly ControlPoint[],\n b: readonly ControlPoint[],\n): boolean {\n if (a === b) return true;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (a[i].x !== b[i].x || a[i].y !== b[i].y) return false;\n }\n return true;\n}\n"],"mappings":"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"}