@xsynaptic/sonic-ui 0.25.1 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +1 -1
  3. package/dist/default/elements/{display-element.js → analyser-element.js} +10 -12
  4. package/dist/default/elements/button.js +12 -6
  5. package/dist/default/elements/dial.js +9 -10
  6. package/dist/default/elements/envelope.js +26 -20
  7. package/dist/default/elements/field-gesture.js +49 -8
  8. package/dist/default/elements/meter.js +6 -5
  9. package/dist/default/elements/number.js +5 -11
  10. package/dist/default/elements/position-group.js +4 -4
  11. package/dist/default/elements/radio-group.js +7 -7
  12. package/dist/default/elements/region-layer.js +3 -1
  13. package/dist/default/elements/region.js +5 -1
  14. package/dist/default/elements/segmented.js +7 -4
  15. package/dist/default/elements/slider.js +12 -10
  16. package/dist/default/elements/sonic-element.js +10 -13
  17. package/dist/default/elements/spectrum.js +3 -3
  18. package/dist/default/elements/switch.js +11 -6
  19. package/dist/default/elements/toggle.js +16 -10
  20. package/dist/default/elements/value-element.js +26 -4
  21. package/dist/default/elements/wave-element.js +11 -9
  22. package/dist/default/elements/waveform.js +1 -4
  23. package/dist/default/elements/wavestrip.js +1 -1
  24. package/dist/default/elements/xy.js +34 -44
  25. package/dist/default/lib/canvas-surface.js +35 -2
  26. package/dist/default/lib/key-press.js +2 -2
  27. package/dist/default/lib/render.js +2 -2
  28. package/dist/dev/elements/{display-element.d.ts → analyser-element.d.ts} +3 -3
  29. package/dist/dev/elements/{display-element.js → analyser-element.js} +11 -14
  30. package/dist/dev/elements/analyser-element.js.map +1 -0
  31. package/dist/dev/elements/button.d.ts +4 -0
  32. package/dist/dev/elements/button.js +12 -11
  33. package/dist/dev/elements/button.js.map +1 -1
  34. package/dist/dev/elements/dial.d.ts +4 -1
  35. package/dist/dev/elements/dial.js +9 -14
  36. package/dist/dev/elements/dial.js.map +1 -1
  37. package/dist/dev/elements/envelope.d.ts +5 -0
  38. package/dist/dev/elements/envelope.js +26 -24
  39. package/dist/dev/elements/envelope.js.map +1 -1
  40. package/dist/dev/elements/field-gesture.js +49 -8
  41. package/dist/dev/elements/field-gesture.js.map +1 -1
  42. package/dist/dev/elements/meter.d.ts +2 -0
  43. package/dist/dev/elements/meter.js +6 -6
  44. package/dist/dev/elements/meter.js.map +1 -1
  45. package/dist/dev/elements/number.d.ts +2 -1
  46. package/dist/dev/elements/number.js +5 -12
  47. package/dist/dev/elements/number.js.map +1 -1
  48. package/dist/dev/elements/position-group.js +4 -4
  49. package/dist/dev/elements/position-group.js.map +1 -1
  50. package/dist/dev/elements/radio-group.d.ts +1 -1
  51. package/dist/dev/elements/radio-group.js +7 -7
  52. package/dist/dev/elements/radio-group.js.map +1 -1
  53. package/dist/dev/elements/region-layer.js +3 -1
  54. package/dist/dev/elements/region-layer.js.map +1 -1
  55. package/dist/dev/elements/region.d.ts +1 -0
  56. package/dist/dev/elements/region.js +5 -1
  57. package/dist/dev/elements/region.js.map +1 -1
  58. package/dist/dev/elements/segmented.d.ts +4 -2
  59. package/dist/dev/elements/segmented.js +7 -9
  60. package/dist/dev/elements/segmented.js.map +1 -1
  61. package/dist/dev/elements/slider.d.ts +4 -1
  62. package/dist/dev/elements/slider.js +12 -14
  63. package/dist/dev/elements/slider.js.map +1 -1
  64. package/dist/dev/elements/sonic-element.d.ts +6 -7
  65. package/dist/dev/elements/sonic-element.js +16 -91
  66. package/dist/dev/elements/sonic-element.js.map +1 -1
  67. package/dist/dev/elements/spectrum.d.ts +2 -2
  68. package/dist/dev/elements/spectrum.js +3 -3
  69. package/dist/dev/elements/spectrum.js.map +1 -1
  70. package/dist/dev/elements/switch.d.ts +4 -1
  71. package/dist/dev/elements/switch.js +11 -10
  72. package/dist/dev/elements/switch.js.map +1 -1
  73. package/dist/dev/elements/toggle.d.ts +4 -1
  74. package/dist/dev/elements/toggle.js +16 -15
  75. package/dist/dev/elements/toggle.js.map +1 -1
  76. package/dist/dev/elements/value-element.d.ts +4 -3
  77. package/dist/dev/elements/value-element.js +26 -4
  78. package/dist/dev/elements/value-element.js.map +1 -1
  79. package/dist/dev/elements/wave-element.d.ts +2 -1
  80. package/dist/dev/elements/wave-element.js +11 -13
  81. package/dist/dev/elements/wave-element.js.map +1 -1
  82. package/dist/dev/elements/waveform.d.ts +0 -1
  83. package/dist/dev/elements/waveform.js +1 -4
  84. package/dist/dev/elements/waveform.js.map +1 -1
  85. package/dist/dev/elements/wavestrip.js +1 -1
  86. package/dist/dev/elements/wavestrip.js.map +1 -1
  87. package/dist/dev/elements/xy.d.ts +4 -0
  88. package/dist/dev/elements/xy.js +34 -48
  89. package/dist/dev/elements/xy.js.map +1 -1
  90. package/dist/dev/index.d.ts +3 -1
  91. package/dist/dev/lib/canvas-surface.js +35 -2
  92. package/dist/dev/lib/canvas-surface.js.map +1 -1
  93. package/dist/dev/lib/check-styles.d.ts +8 -0
  94. package/dist/dev/lib/check-styles.js +87 -0
  95. package/dist/dev/lib/check-styles.js.map +1 -0
  96. package/dist/dev/lib/key-press.js +2 -2
  97. package/dist/dev/lib/key-press.js.map +1 -1
  98. package/dist/dev/lib/render.js +2 -2
  99. package/dist/dev/lib/render.js.map +1 -1
  100. package/dist/styles/button.css +11 -9
  101. package/dist/styles/dial.css +6 -6
  102. package/dist/styles/envelope.css +6 -0
  103. package/dist/styles/led.css +8 -8
  104. package/dist/styles/material/cap.css +13 -2
  105. package/dist/styles/material/pane.css +7 -1
  106. package/dist/styles/material/readout.css +10 -2
  107. package/dist/styles/material/reticle.css +2 -2
  108. package/dist/styles/meter.css +11 -11
  109. package/dist/styles/number.css +1 -1
  110. package/dist/styles/region.css +6 -0
  111. package/dist/styles/segmented.css +3 -3
  112. package/dist/styles/slider.css +12 -12
  113. package/dist/styles/switch.css +6 -0
  114. package/dist/styles/toggle.css +3 -3
  115. package/dist/styles/waveform.css +7 -1
  116. package/dist/styles/wavestrip.css +6 -0
  117. package/dist/styles/xy.css +15 -3
  118. package/package.json +1 -1
  119. package/dist/dev/elements/display-element.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"spectrum.js","names":[],"sources":["../../../src/elements/spectrum.ts"],"sourcesContent":["import type { SurfaceFrame, SurfaceLook, SurfaceSize } from '#lib/canvas-surface.ts';\nimport type { FrequencyAxis } from '#lib/spectrum-columns.ts';\n\nimport { SonicDisplayElement } from '#elements/display-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport {\n\tcolumnEdges,\n\tcolumnLevels,\n\tgridColumns,\n\tgridRows,\n\tlevelRows,\n\tloudestBin,\n\tzoneRows,\n} from '#lib/spectrum-columns.ts';\nimport { areSettled, createLevels, receiveLevels, stepLevels } from '#lib/spectrum-levels.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-spectrum': SonicSpectrum;\n\t}\n}\n\ntype Colour = keyof typeof colours;\n\ntype Numeric = keyof typeof numbers;\n\ntype Look = SurfaceLook<Colour, never, Numeric>;\n\ninterface SpectrumPeak {\n\tdecibels: number;\n\tfrequency: number;\n}\n\ninterface Scene extends SurfaceSize {\n\tlook: Look;\n\trange: { max: number; min: number };\n\trowOf: (level: number) => number;\n}\n\ninterface Columns {\n\tbars: Float64Array;\n\tedges: Float64Array;\n\tholds: Float64Array;\n\tkey: string;\n}\n\nconst colours = {\n\tclip: '--_sonic-spectrum-clip',\n\tgrid: '--_sonic-spectrum-grid',\n\thot: '--_sonic-spectrum-hot',\n\tlit: '--_sonic-spectrum-lit',\n} as const;\n\nconst numbers = {\n\tclipFrom: '--_sonic-spectrum-clip-from',\n\thotFrom: '--_sonic-spectrum-hot-from',\n} as const;\n\nconst defaultFrequencies: [number, number] = [20, 20_000];\nconst defaultSampleRate = 48_000;\n\nconst renderSpectrum = template(\n\t/* HTML */ `\n\t\t<div aria-hidden=\"true\" class=\"sonic-spectrum\">\n\t\t\t<canvas class=\"sonic-spectrum-canvas\"></canvas>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\n// One path, since a crossing filled twice would show brighter than its lines\nfunction paintGrid(\n\tcontext: CanvasRenderingContext2D,\n\t{ dpr, height, look, range, rowOf, width }: Scene,\n\t[frequencyMin, frequencyMax]: [number, number],\n): void {\n\tconst line = Math.max(1, Math.round(dpr));\n\tconst columns = gridColumns({ frequencyMax, frequencyMin }, width);\n\tconst rows = gridRows(range, rowOf);\n\n\tcontext.beginPath();\n\tfor (const column of columns) context.rect(column, 0, line, height);\n\tfor (const row of rows) context.rect(0, row, width, line);\n\tcontext.fillStyle = look.colours.grid;\n\tcontext.fill();\n}\n\nfunction zoneFill(\n\tcontext: CanvasRenderingContext2D,\n\t{ height, look, rowOf }: Scene,\n): CanvasGradient {\n\tconst gradient = context.createLinearGradient(0, 0, 0, height);\n\tconst { clip, hot } = zoneRows(look.numbers, rowOf);\n\n\tgradient.addColorStop(clip / height, look.colours.clip);\n\tgradient.addColorStop(clip / height, look.colours.hot);\n\tgradient.addColorStop(hot / height, look.colours.hot);\n\tgradient.addColorStop(hot / height, look.colours.lit);\n\n\treturn gradient;\n}\n\n// Whole device pixels, so nothing is antialiased\nfunction paintBars(context: CanvasRenderingContext2D, scene: Scene, bars: Float64Array): void {\n\tconst { height, rowOf } = scene;\n\tlet previous = height;\n\n\tcontext.beginPath();\n\tcontext.moveTo(0, height);\n\tfor (const [column, level] of bars.entries()) {\n\t\tconst row = rowOf(level);\n\n\t\tbars[column] = row;\n\t\tif (row === previous) continue;\n\n\t\tcontext.lineTo(column, previous);\n\t\tcontext.lineTo(column, row);\n\t\tprevious = row;\n\t}\n\tcontext.lineTo(bars.length, previous);\n\tcontext.lineTo(bars.length, height);\n\tcontext.closePath();\n\tcontext.fillStyle = zoneFill(context, scene);\n\tcontext.fill();\n}\n\nfunction paintHolds(\n\tcontext: CanvasRenderingContext2D,\n\t{ look, rowOf }: Scene,\n\t{ bars, holds }: { bars: Float64Array; holds: Float64Array },\n): void {\n\tcontext.beginPath();\n\tfor (const [column, level] of holds.entries()) {\n\t\tconst row = rowOf(level);\n\n\t\tif (row < (bars[column] ?? 0)) context.rect(column, row, 1, 1);\n\t}\n\tcontext.fillStyle = look.colours.lit;\n\tcontext.fill();\n}\n\nexport class SonicSpectrum extends SonicDisplayElement<Colour, Numeric> {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicDisplayElement.observedAttributes,\n\t\t'frequency-max',\n\t\t'frequency-min',\n\t\t'grid',\n\t\t'peak-hold',\n\t\t'sample-rate',\n\t];\n\n\tget frequencyMax(): number {\n\t\treturn this.numberAttribute('frequency-max', defaultFrequencies[1]);\n\t}\n\n\tset frequencyMax(hertz: number | undefined) {\n\t\tthis.reflect('frequency-max', hertz);\n\t}\n\n\tget frequencyMin(): number {\n\t\treturn this.numberAttribute('frequency-min', defaultFrequencies[0]);\n\t}\n\n\tset frequencyMin(hertz: number | undefined) {\n\t\tthis.reflect('frequency-min', hertz);\n\t}\n\n\tget grid(): 'none' | 'on' {\n\t\treturn this.getAttribute('grid') === 'none' ? 'none' : 'on';\n\t}\n\n\tset grid(grid: 'none' | 'on' | undefined) {\n\t\tthis.reflect('grid', grid);\n\t}\n\n\tget peak(): SpectrumPeak | undefined {\n\t\treturn this.#peak && { ...this.#peak };\n\t}\n\n\tget peakHold(): boolean {\n\t\treturn this.hasAttribute('peak-hold');\n\t}\n\n\tset peakHold(isHeld: boolean) {\n\t\tthis.reflect('peak-hold', isHeld);\n\t}\n\n\tget sampleRate(): number {\n\t\tconst sampleRate = this.numberAttribute('sample-rate', defaultSampleRate);\n\n\t\treturn sampleRate > 0 ? sampleRate : defaultSampleRate;\n\t}\n\n\tset sampleRate(hertz: number | undefined) {\n\t\tthis.reflect('sample-rate', hertz);\n\t}\n\n\tprotected readonly control = renderSpectrum();\n\n\tprotected readonly canvas = requireChild(\n\t\tthis.control,\n\t\t'.sonic-spectrum-canvas',\n\t\tHTMLCanvasElement,\n\t);\n\n\tprotected readonly colours = colours;\n\n\tprotected readonly numbers = numbers;\n\n\tprotected readonly sheet = 'spectrum.css';\n\n\tprotected readonly sizeProperty = '--_sonic-spectrum-size';\n\n\t#columns: Columns | undefined;\n\n\t#isSettled = true;\n\n\t#lastFrameMs: number | undefined;\n\n\t#levels = createLevels(0);\n\n\t#peak: SpectrumPeak | undefined;\n\n\t#receivedRate: number | undefined;\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('frequencyMax', 'frequencyMin', 'grid', 'peakHold', 'sampleRate');\n\t\tsuper.connectedCallback();\n\t}\n\n\tresetPeak(): void {\n\t\tthis.#peak = undefined;\n\t}\n\n\tprotected paint(\n\t\tcontext: CanvasRenderingContext2D,\n\t\t{ frameMs, isDirty, look, size }: SurfaceFrame<Colour, never, Numeric>,\n\t): void {\n\t\tif (this.isDisabled()) {\n\t\t\tif (isDirty) this.#draw(context, look, size);\n\t\t\treturn;\n\t\t}\n\n\t\tconst wasSettled = this.#isSettled;\n\n\t\tthis.#step(frameMs, look.isReducedMotion);\n\t\tif (isDirty || !wasSettled || !this.#isSettled) this.#draw(context, look, size);\n\t\tif (!this.#isSettled) this.surface()?.requestFrame();\n\t}\n\n\tprotected pull(analyser: AnalyserNode, buffer: Float32Array<ArrayBuffer>): void {\n\t\tanalyser.getFloatFrequencyData(buffer);\n\t}\n\n\tprotected receive(frame: Float32Array): void {\n\t\tif (frame.length !== this.#levels.bars.length) this.#levels = createLevels(frame.length);\n\t\tthis.#receivedRate = this.analyser?.context.sampleRate ?? this.sampleRate;\n\t\treceiveLevels(this.#levels, frame, this.min);\n\n\t\tconst loudest = loudestBin(frame, this.#axis());\n\n\t\tif (loudest && loudest.decibels > (this.#peak?.decibels ?? -Infinity)) this.#peak = loudest;\n\t}\n\n\t#axis(): FrequencyAxis {\n\t\tconst [frequencyMin, frequencyMax] = this.#frequencies();\n\n\t\treturn {\n\t\t\tbinCount: this.#levels.bars.length,\n\t\t\tfrequencyMax,\n\t\t\tfrequencyMin,\n\t\t\t// The bars on show keep the rate they arrived at once the analyser is unset\n\t\t\tsampleRate: this.#receivedRate ?? this.sampleRate,\n\t\t};\n\t}\n\n\t#columnsFor(width: number): Columns {\n\t\tconst axis = this.#axis();\n\t\tconst key = [width, axis.binCount, axis.sampleRate, axis.frequencyMin, axis.frequencyMax].join(\n\t\t\t' ',\n\t\t);\n\n\t\tif (this.#columns?.key !== key) {\n\t\t\tthis.#columns = {\n\t\t\t\tbars: new Float64Array(width),\n\t\t\t\tedges: columnEdges(axis, width),\n\t\t\t\tholds: new Float64Array(width),\n\t\t\t\tkey,\n\t\t\t};\n\t\t}\n\n\t\treturn this.#columns;\n\t}\n\n\t#draw(context: CanvasRenderingContext2D, look: Look, size: SurfaceSize): void {\n\t\tconst scene = {\n\t\t\t...size,\n\t\t\tlook,\n\t\t\trange: { max: this.max, min: this.min },\n\t\t\trowOf: levelRows({ max: this.max, min: this.min }, size.height),\n\t\t};\n\n\t\tcontext.clearRect(0, 0, size.width, size.height);\n\t\tif (this.grid === 'on') paintGrid(context, scene, this.#frequencies());\n\t\tif (this.isDisabled()) return;\n\n\t\tconst { bars, edges, holds } = this.#columnsFor(size.width);\n\t\tconst spread = { edges, floor: scene.range.min };\n\n\t\tcolumnLevels(bars, this.#levels.bars, spread);\n\t\tpaintBars(context, scene, bars);\n\t\tif (!this.peakHold) return;\n\n\t\tcolumnLevels(holds, this.#levels.holds, spread);\n\t\tpaintHolds(context, scene, { bars, holds });\n\t}\n\n\t#frequencies(): [number, number] {\n\t\tconst { frequencyMax, frequencyMin } = this;\n\n\t\treturn frequencyMin > 0 && frequencyMax > frequencyMin\n\t\t\t? [frequencyMin, frequencyMax]\n\t\t\t: defaultFrequencies;\n\t}\n\n\t#step(frameMs: number, isReducedMotion: boolean): void {\n\t\tconst floor = this.min;\n\n\t\tstepLevels(this.#levels, {\n\t\t\telapsedMs: Math.max(0, frameMs - (this.#lastFrameMs ?? frameMs)),\n\t\t\tfloor,\n\t\t\tisHeld: this.peakHold,\n\t\t\tisHoldStepped: isReducedMotion,\n\t\t\tnowMs: frameMs,\n\t\t});\n\t\tthis.#isSettled = areSettled(this.#levels, floor);\n\t\tthis.#lastFrameMs = this.#isSettled ? undefined : frameMs;\n\t}\n}\n"],"mappings":";;;;;;AA8CA,MAAM,UAAU;CACf,MAAM;CACN,MAAM;CACN,KAAK;CACL,KAAK;AACN;AAEA,MAAM,UAAU;CACf,UAAU;CACV,SAAS;AACV;AAEA,MAAM,qBAAuC,CAAC,IAAI,GAAM;AACxD,MAAM,oBAAoB;AAE1B,MAAM,iBAAiB,SACX;;;;IAKX,cACD;AAGA,SAAS,UACR,SACA,EAAE,KAAK,QAAQ,MAAM,OAAO,OAAO,SACnC,CAAC,cAAc,eACR;CACP,MAAM,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,GAAG,CAAC;CACxC,MAAM,UAAU,YAAY;EAAE;EAAc;CAAa,GAAG,KAAK;CACjE,MAAM,OAAO,SAAS,OAAO,KAAK;CAElC,QAAQ,UAAU;CAClB,KAAK,MAAM,UAAU,SAAS,QAAQ,KAAK,QAAQ,GAAG,MAAM,MAAM;CAClE,KAAK,MAAM,OAAO,MAAM,QAAQ,KAAK,GAAG,KAAK,OAAO,IAAI;CACxD,QAAQ,YAAY,KAAK,QAAQ;CACjC,QAAQ,KAAK;AACd;AAEA,SAAS,SACR,SACA,EAAE,QAAQ,MAAM,SACC;CACjB,MAAM,WAAW,QAAQ,qBAAqB,GAAG,GAAG,GAAG,MAAM;CAC7D,MAAM,EAAE,MAAM,QAAQ,SAAS,KAAK,SAAS,KAAK;CAElD,SAAS,aAAa,OAAO,QAAQ,KAAK,QAAQ,IAAI;CACtD,SAAS,aAAa,OAAO,QAAQ,KAAK,QAAQ,GAAG;CACrD,SAAS,aAAa,MAAM,QAAQ,KAAK,QAAQ,GAAG;CACpD,SAAS,aAAa,MAAM,QAAQ,KAAK,QAAQ,GAAG;CAEpD,OAAO;AACR;AAGA,SAAS,UAAU,SAAmC,OAAc,MAA0B;CAC7F,MAAM,EAAE,QAAQ,UAAU;CAC1B,IAAI,WAAW;CAEf,QAAQ,UAAU;CAClB,QAAQ,OAAO,GAAG,MAAM;CACxB,KAAK,MAAM,CAAC,QAAQ,UAAU,KAAK,QAAQ,GAAG;EAC7C,MAAM,MAAM,MAAM,KAAK;EAEvB,KAAK,UAAU;EACf,IAAI,QAAQ,UAAU;EAEtB,QAAQ,OAAO,QAAQ,QAAQ;EAC/B,QAAQ,OAAO,QAAQ,GAAG;EAC1B,WAAW;CACZ;CACA,QAAQ,OAAO,KAAK,QAAQ,QAAQ;CACpC,QAAQ,OAAO,KAAK,QAAQ,MAAM;CAClC,QAAQ,UAAU;CAClB,QAAQ,YAAY,SAAS,SAAS,KAAK;CAC3C,QAAQ,KAAK;AACd;AAEA,SAAS,WACR,SACA,EAAE,MAAM,SACR,EAAE,MAAM,SACD;CACP,QAAQ,UAAU;CAClB,KAAK,MAAM,CAAC,QAAQ,UAAU,MAAM,QAAQ,GAAG;EAC9C,MAAM,MAAM,MAAM,KAAK;EAEvB,IAAI,OAAO,KAAK,WAAW,IAAI,QAAQ,KAAK,QAAQ,KAAK,GAAG,CAAC;CAC9D;CACA,QAAQ,YAAY,KAAK,QAAQ;CACjC,QAAQ,KAAK;AACd;AAEA,IAAa,gBAAb,cAAmC,oBAAqC;CACvE,OAAyB,qBAAqB;EAC7C,GAAG,oBAAoB;EACvB;EACA;EACA;EACA;EACA;CACD;CAEA,IAAI,eAAuB;EAC1B,OAAO,KAAK,gBAAgB,iBAAiB,mBAAmB,EAAE;CACnE;CAEA,IAAI,aAAa,OAA2B;EAC3C,KAAK,QAAQ,iBAAiB,KAAK;CACpC;CAEA,IAAI,eAAuB;EAC1B,OAAO,KAAK,gBAAgB,iBAAiB,mBAAmB,EAAE;CACnE;CAEA,IAAI,aAAa,OAA2B;EAC3C,KAAK,QAAQ,iBAAiB,KAAK;CACpC;CAEA,IAAI,OAAsB;EACzB,OAAO,KAAK,aAAa,MAAM,MAAM,SAAS,SAAS;CACxD;CAEA,IAAI,KAAK,MAAiC;EACzC,KAAK,QAAQ,QAAQ,IAAI;CAC1B;CAEA,IAAI,OAAiC;EACpC,OAAO,KAAK,SAAS,EAAE,GAAG,KAAK,MAAM;CACtC;CAEA,IAAI,WAAoB;EACvB,OAAO,KAAK,aAAa,WAAW;CACrC;CAEA,IAAI,SAAS,QAAiB;EAC7B,KAAK,QAAQ,aAAa,MAAM;CACjC;CAEA,IAAI,aAAqB;EACxB,MAAM,aAAa,KAAK,gBAAgB,eAAe,iBAAiB;EAExE,OAAO,aAAa,IAAI,aAAa;CACtC;CAEA,IAAI,WAAW,OAA2B;EACzC,KAAK,QAAQ,eAAe,KAAK;CAClC;CAEA,AAAmB,UAAU,eAAe;CAE5C,AAAmB,SAAS,aAC3B,KAAK,SACL,0BACA,iBACD;CAEA,AAAmB,UAAU;CAE7B,AAAmB,UAAU;CAE7B,AAAmB,QAAQ;CAE3B,AAAmB,eAAe;CAElC;CAEA,aAAa;CAEb;CAEA,UAAU,aAAa,CAAC;CAExB;CAEA;CAEA,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,gBAAgB,gBAAgB,QAAQ,YAAY,YAAY;EACvF,MAAM,kBAAkB;CACzB;CAEA,YAAkB;EACjB,KAAK,QAAQ;CACd;CAEA,AAAU,MACT,SACA,EAAE,SAAS,SAAS,MAAM,QACnB;EACP,IAAI,KAAK,WAAW,GAAG;GACtB,IAAI,SAAS,KAAK,MAAM,SAAS,MAAM,IAAI;GAC3C;EACD;EAEA,MAAM,aAAa,KAAK;EAExB,KAAK,MAAM,SAAS,KAAK,eAAe;EACxC,IAAI,WAAW,CAAC,cAAc,CAAC,KAAK,YAAY,KAAK,MAAM,SAAS,MAAM,IAAI;EAC9E,IAAI,CAAC,KAAK,YAAY,KAAK,QAAQ,CAAC,EAAE,aAAa;CACpD;CAEA,AAAU,KAAK,UAAwB,QAAyC;EAC/E,SAAS,sBAAsB,MAAM;CACtC;CAEA,AAAU,QAAQ,OAA2B;EAC5C,IAAI,MAAM,WAAW,KAAK,QAAQ,KAAK,QAAQ,KAAK,UAAU,aAAa,MAAM,MAAM;EACvF,KAAK,gBAAgB,KAAK,UAAU,QAAQ,cAAc,KAAK;EAC/D,cAAc,KAAK,SAAS,OAAO,KAAK,GAAG;EAE3C,MAAM,UAAU,WAAW,OAAO,KAAK,MAAM,CAAC;EAE9C,IAAI,WAAW,QAAQ,YAAY,KAAK,OAAO,YAAY,YAAY,KAAK,QAAQ;CACrF;CAEA,QAAuB;EACtB,MAAM,CAAC,cAAc,gBAAgB,KAAK,aAAa;EAEvD,OAAO;GACN,UAAU,KAAK,QAAQ,KAAK;GAC5B;GACA;GAEA,YAAY,KAAK,iBAAiB,KAAK;EACxC;CACD;CAEA,YAAY,OAAwB;EACnC,MAAM,OAAO,KAAK,MAAM;EACxB,MAAM,MAAM;GAAC;GAAO,KAAK;GAAU,KAAK;GAAY,KAAK;GAAc,KAAK;EAAY,CAAC,CAAC,KACzF,GACD;EAEA,IAAI,KAAK,UAAU,QAAQ,KAC1B,KAAK,WAAW;GACf,MAAM,IAAI,aAAa,KAAK;GAC5B,OAAO,YAAY,MAAM,KAAK;GAC9B,OAAO,IAAI,aAAa,KAAK;GAC7B;EACD;EAGD,OAAO,KAAK;CACb;CAEA,MAAM,SAAmC,MAAY,MAAyB;EAC7E,MAAM,QAAQ;GACb,GAAG;GACH;GACA,OAAO;IAAE,KAAK,KAAK;IAAK,KAAK,KAAK;GAAI;GACtC,OAAO,UAAU;IAAE,KAAK,KAAK;IAAK,KAAK,KAAK;GAAI,GAAG,KAAK,MAAM;EAC/D;EAEA,QAAQ,UAAU,GAAG,GAAG,KAAK,OAAO,KAAK,MAAM;EAC/C,IAAI,KAAK,SAAS,MAAM,UAAU,SAAS,OAAO,KAAK,aAAa,CAAC;EACrE,IAAI,KAAK,WAAW,GAAG;EAEvB,MAAM,EAAE,MAAM,OAAO,UAAU,KAAK,YAAY,KAAK,KAAK;EAC1D,MAAM,SAAS;GAAE;GAAO,OAAO,MAAM,MAAM;EAAI;EAE/C,aAAa,MAAM,KAAK,QAAQ,MAAM,MAAM;EAC5C,UAAU,SAAS,OAAO,IAAI;EAC9B,IAAI,CAAC,KAAK,UAAU;EAEpB,aAAa,OAAO,KAAK,QAAQ,OAAO,MAAM;EAC9C,WAAW,SAAS,OAAO;GAAE;GAAM;EAAM,CAAC;CAC3C;CAEA,eAAiC;EAChC,MAAM,EAAE,cAAc,iBAAiB;EAEvC,OAAO,eAAe,KAAK,eAAe,eACvC,CAAC,cAAc,YAAY,IAC3B;CACJ;CAEA,MAAM,SAAiB,iBAAgC;EACtD,MAAM,QAAQ,KAAK;EAEnB,WAAW,KAAK,SAAS;GACxB,WAAW,KAAK,IAAI,GAAG,WAAW,KAAK,gBAAgB,QAAQ;GAC/D;GACA,QAAQ,KAAK;GACb,eAAe;GACf,OAAO;EACR,CAAC;EACD,KAAK,aAAa,WAAW,KAAK,SAAS,KAAK;EAChD,KAAK,eAAe,KAAK,aAAa,SAAY;CACnD;AACD"}
1
+ {"version":3,"file":"spectrum.js","names":[],"sources":["../../../src/elements/spectrum.ts"],"sourcesContent":["import type { SurfaceFrame, SurfaceLook, SurfaceSize } from '#lib/canvas-surface.ts';\nimport type { FrequencyAxis } from '#lib/spectrum-columns.ts';\n\nimport { SonicAnalyserElement } from '#elements/analyser-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport {\n\tcolumnEdges,\n\tcolumnLevels,\n\tgridColumns,\n\tgridRows,\n\tlevelRows,\n\tloudestBin,\n\tzoneRows,\n} from '#lib/spectrum-columns.ts';\nimport { areSettled, createLevels, receiveLevels, stepLevels } from '#lib/spectrum-levels.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-spectrum': SonicSpectrum;\n\t}\n}\n\ntype Colour = keyof typeof colours;\n\ntype Numeric = keyof typeof numbers;\n\ntype Look = SurfaceLook<Colour, never, Numeric>;\n\ninterface SpectrumPeak {\n\tdecibels: number;\n\tfrequency: number;\n}\n\ninterface Scene extends SurfaceSize {\n\tlook: Look;\n\trange: { max: number; min: number };\n\trowOf: (level: number) => number;\n}\n\ninterface Columns {\n\tbars: Float64Array;\n\tedges: Float64Array;\n\tholds: Float64Array;\n\tkey: string;\n}\n\nconst colours = {\n\tclip: '--_sonic-spectrum-clip',\n\tgrid: '--_sonic-spectrum-grid',\n\thot: '--_sonic-spectrum-hot',\n\tlit: '--_sonic-spectrum-lit',\n} as const;\n\nconst numbers = {\n\tclipFrom: '--_sonic-spectrum-clip-from',\n\thotFrom: '--_sonic-spectrum-hot-from',\n} as const;\n\nconst defaultFrequencies: [number, number] = [20, 20_000];\nconst defaultSampleRate = 48_000;\n\nconst renderSpectrum = template(\n\t/* HTML */ `\n\t\t<div aria-hidden=\"true\" class=\"sonic-spectrum\">\n\t\t\t<canvas class=\"sonic-spectrum-canvas\"></canvas>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\n// One path, since a crossing filled twice would show brighter than its lines\nfunction paintGrid(\n\tcontext: CanvasRenderingContext2D,\n\t{ dpr, height, look, range, rowOf, width }: Scene,\n\t[frequencyMin, frequencyMax]: [number, number],\n): void {\n\tconst line = Math.max(1, Math.round(dpr));\n\tconst columns = gridColumns({ frequencyMax, frequencyMin }, width);\n\tconst rows = gridRows(range, rowOf);\n\n\tcontext.beginPath();\n\tfor (const column of columns) context.rect(column, 0, line, height);\n\tfor (const row of rows) context.rect(0, row, width, line);\n\tcontext.fillStyle = look.colours.grid;\n\tcontext.fill();\n}\n\nfunction zoneFill(\n\tcontext: CanvasRenderingContext2D,\n\t{ height, look, rowOf }: Scene,\n): CanvasGradient {\n\tconst gradient = context.createLinearGradient(0, 0, 0, height);\n\tconst { clip, hot } = zoneRows(look.numbers, rowOf);\n\n\tgradient.addColorStop(clip / height, look.colours.clip);\n\tgradient.addColorStop(clip / height, look.colours.hot);\n\tgradient.addColorStop(hot / height, look.colours.hot);\n\tgradient.addColorStop(hot / height, look.colours.lit);\n\n\treturn gradient;\n}\n\n// Whole device pixels, so nothing is antialiased\nfunction paintBars(context: CanvasRenderingContext2D, scene: Scene, bars: Float64Array): void {\n\tconst { height, rowOf } = scene;\n\tlet previous = height;\n\n\tcontext.beginPath();\n\tcontext.moveTo(0, height);\n\tfor (const [column, level] of bars.entries()) {\n\t\tconst row = rowOf(level);\n\n\t\tbars[column] = row;\n\t\tif (row === previous) continue;\n\n\t\tcontext.lineTo(column, previous);\n\t\tcontext.lineTo(column, row);\n\t\tprevious = row;\n\t}\n\tcontext.lineTo(bars.length, previous);\n\tcontext.lineTo(bars.length, height);\n\tcontext.closePath();\n\tcontext.fillStyle = zoneFill(context, scene);\n\tcontext.fill();\n}\n\nfunction paintHolds(\n\tcontext: CanvasRenderingContext2D,\n\t{ look, rowOf }: Scene,\n\t{ bars, holds }: { bars: Float64Array; holds: Float64Array },\n): void {\n\tcontext.beginPath();\n\tfor (const [column, level] of holds.entries()) {\n\t\tconst row = rowOf(level);\n\n\t\tif (row < (bars[column] ?? 0)) context.rect(column, row, 1, 1);\n\t}\n\tcontext.fillStyle = look.colours.lit;\n\tcontext.fill();\n}\n\nexport class SonicSpectrum extends SonicAnalyserElement<Colour, Numeric> {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicAnalyserElement.observedAttributes,\n\t\t'frequency-max',\n\t\t'frequency-min',\n\t\t'grid',\n\t\t'peak-hold',\n\t\t'sample-rate',\n\t];\n\n\tget frequencyMax(): number {\n\t\treturn this.numberAttribute('frequency-max', defaultFrequencies[1]);\n\t}\n\n\tset frequencyMax(hertz: number | undefined) {\n\t\tthis.reflect('frequency-max', hertz);\n\t}\n\n\tget frequencyMin(): number {\n\t\treturn this.numberAttribute('frequency-min', defaultFrequencies[0]);\n\t}\n\n\tset frequencyMin(hertz: number | undefined) {\n\t\tthis.reflect('frequency-min', hertz);\n\t}\n\n\tget grid(): 'none' | 'on' {\n\t\treturn this.getAttribute('grid') === 'none' ? 'none' : 'on';\n\t}\n\n\tset grid(grid: 'none' | 'on' | undefined) {\n\t\tthis.reflect('grid', grid);\n\t}\n\n\tget peak(): SpectrumPeak | undefined {\n\t\treturn this.#peak && { ...this.#peak };\n\t}\n\n\tget peakHold(): boolean {\n\t\treturn this.hasAttribute('peak-hold');\n\t}\n\n\tset peakHold(isHeld: boolean) {\n\t\tthis.reflect('peak-hold', isHeld);\n\t}\n\n\tget sampleRate(): number {\n\t\tconst sampleRate = this.numberAttribute('sample-rate', defaultSampleRate);\n\n\t\treturn sampleRate > 0 ? sampleRate : defaultSampleRate;\n\t}\n\n\tset sampleRate(hertz: number | undefined) {\n\t\tthis.reflect('sample-rate', hertz);\n\t}\n\n\tprotected override readonly control = renderSpectrum();\n\n\tprotected readonly canvas = requireChild(\n\t\tthis.control,\n\t\t'.sonic-spectrum-canvas',\n\t\tHTMLCanvasElement,\n\t);\n\n\tprotected readonly colours = colours;\n\n\tprotected readonly numbers = numbers;\n\n\tprotected override readonly sheet = 'spectrum.css';\n\n\tprotected readonly sizeProperty = '--_sonic-spectrum-size';\n\n\t#columns: Columns | undefined;\n\n\t#isSettled = true;\n\n\t#lastFrameMs: number | undefined;\n\n\t#levels = createLevels(0);\n\n\t#peak: SpectrumPeak | undefined;\n\n\t#receivedRate: number | undefined;\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('frequencyMax', 'frequencyMin', 'grid', 'peakHold', 'sampleRate');\n\t\tsuper.connectedCallback();\n\t}\n\n\tresetPeak(): void {\n\t\tthis.#peak = undefined;\n\t}\n\n\tprotected paint(\n\t\tcontext: CanvasRenderingContext2D,\n\t\t{ frameMs, isDirty, look, size }: SurfaceFrame<Colour, never, Numeric>,\n\t): void {\n\t\tif (this.isDisabled()) {\n\t\t\tif (isDirty) this.#draw(context, look, size);\n\t\t\treturn;\n\t\t}\n\n\t\tconst wasSettled = this.#isSettled;\n\n\t\tthis.#step(frameMs, look.isReducedMotion);\n\t\tif (isDirty || !wasSettled || !this.#isSettled) this.#draw(context, look, size);\n\t\tif (!this.#isSettled) this.surface()?.requestFrame();\n\t}\n\n\tprotected pull(analyser: AnalyserNode, buffer: Float32Array<ArrayBuffer>): void {\n\t\tanalyser.getFloatFrequencyData(buffer);\n\t}\n\n\tprotected receive(frame: Float32Array): void {\n\t\tif (frame.length !== this.#levels.bars.length) this.#levels = createLevels(frame.length);\n\t\tthis.#receivedRate = this.analyser?.context.sampleRate ?? this.sampleRate;\n\t\treceiveLevels(this.#levels, frame, this.min);\n\n\t\tconst loudest = loudestBin(frame, this.#axis());\n\n\t\tif (loudest && loudest.decibels > (this.#peak?.decibels ?? -Infinity)) this.#peak = loudest;\n\t}\n\n\t#axis(): FrequencyAxis {\n\t\tconst [frequencyMin, frequencyMax] = this.#frequencies();\n\n\t\treturn {\n\t\t\tbinCount: this.#levels.bars.length,\n\t\t\tfrequencyMax,\n\t\t\tfrequencyMin,\n\t\t\t// The bars on show keep the rate they arrived at once the analyser is unset\n\t\t\tsampleRate: this.#receivedRate ?? this.sampleRate,\n\t\t};\n\t}\n\n\t#columnsFor(width: number): Columns {\n\t\tconst axis = this.#axis();\n\t\tconst key = [width, axis.binCount, axis.sampleRate, axis.frequencyMin, axis.frequencyMax].join(\n\t\t\t' ',\n\t\t);\n\n\t\tif (this.#columns?.key !== key) {\n\t\t\tthis.#columns = {\n\t\t\t\tbars: new Float64Array(width),\n\t\t\t\tedges: columnEdges(axis, width),\n\t\t\t\tholds: new Float64Array(width),\n\t\t\t\tkey,\n\t\t\t};\n\t\t}\n\n\t\treturn this.#columns;\n\t}\n\n\t#draw(context: CanvasRenderingContext2D, look: Look, size: SurfaceSize): void {\n\t\tconst scene = {\n\t\t\t...size,\n\t\t\tlook,\n\t\t\trange: { max: this.max, min: this.min },\n\t\t\trowOf: levelRows({ max: this.max, min: this.min }, size.height),\n\t\t};\n\n\t\tcontext.clearRect(0, 0, size.width, size.height);\n\t\tif (this.grid === 'on') paintGrid(context, scene, this.#frequencies());\n\t\tif (this.isDisabled()) return;\n\n\t\tconst { bars, edges, holds } = this.#columnsFor(size.width);\n\t\tconst spread = { edges, floor: scene.range.min };\n\n\t\tcolumnLevels(bars, this.#levels.bars, spread);\n\t\tpaintBars(context, scene, bars);\n\t\tif (!this.peakHold) return;\n\n\t\tcolumnLevels(holds, this.#levels.holds, spread);\n\t\tpaintHolds(context, scene, { bars, holds });\n\t}\n\n\t#frequencies(): [number, number] {\n\t\tconst { frequencyMax, frequencyMin } = this;\n\n\t\treturn frequencyMin > 0 && frequencyMax > frequencyMin\n\t\t\t? [frequencyMin, frequencyMax]\n\t\t\t: defaultFrequencies;\n\t}\n\n\t#step(frameMs: number, isReducedMotion: boolean): void {\n\t\tconst floor = this.min;\n\n\t\tstepLevels(this.#levels, {\n\t\t\telapsedMs: Math.max(0, frameMs - (this.#lastFrameMs ?? frameMs)),\n\t\t\tfloor,\n\t\t\tisHeld: this.peakHold,\n\t\t\tisHoldStepped: isReducedMotion,\n\t\t\tnowMs: frameMs,\n\t\t});\n\t\tthis.#isSettled = areSettled(this.#levels, floor);\n\t\tthis.#lastFrameMs = this.#isSettled ? undefined : frameMs;\n\t}\n}\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { BoxProbe } from "../lib/check-styles.js";
1
2
  import { SonicPositionGroupElement } from "./position-group.js";
2
3
  //#region src/elements/switch.d.ts
3
4
  declare global {
@@ -8,8 +9,10 @@ declare global {
8
9
  export declare class SonicSwitch extends SonicPositionGroupElement {
9
10
  #private;
10
11
  protected readonly bare: HTMLButtonElement;
12
+ protected readonly boxProbe: BoxProbe;
13
+ protected readonly control: HTMLDivElement;
11
14
  protected readonly defaultOrientation = "vertical";
12
- protected readonly group: HTMLDivElement;
15
+ protected readonly sheet = "switch.css";
13
16
  protected claimPress(index: number, event: PointerEvent): boolean;
14
17
  protected connect(signal: AbortSignal): void;
15
18
  protected releaseTarget(index: number): number | undefined;
@@ -20,25 +20,26 @@ const renderBare = template(`<button class="sonic-switch-position" role="switch"
20
20
  const pressCenterZone = .08;
21
21
  var SonicSwitch = class extends SonicPositionGroupElement {
22
22
  bare = renderBare();
23
+ boxProbe = {
24
+ property: "padding-top",
25
+ selector: ".sonic-switch-label"
26
+ };
27
+ control = renderSwitch();
23
28
  defaultOrientation = "vertical";
24
- group = renderSwitch();
25
- #bat = requireChild(this.group, ".sonic-switch-bat", HTMLSpanElement);
29
+ sheet = "switch.css";
30
+ #bat = requireChild(this.control, ".sonic-switch-bat", HTMLSpanElement);
26
31
  #isClickSwallowed = false;
27
32
  claimPress(index, event) {
28
33
  if (this.#isOnBat(event)) return true;
29
34
  const target = this.pressTarget(index);
30
35
  if (this.isOptionDisabled(target) || !this.isMomentary(target)) return false;
31
- capturePointer(this.group, event.pointerId);
36
+ capturePointer(this.control, event.pointerId);
32
37
  this.hold(target, event.pointerId);
33
38
  return true;
34
39
  }
35
40
  connect(signal) {
36
- const group = this.group;
41
+ const group = this.control;
37
42
  super.connect(signal);
38
- this.checkStyles(group, "switch.css", {
39
- property: "padding-top",
40
- selector: ".sonic-switch-label"
41
- });
42
43
  this.bare.addEventListener("click", (event) => {
43
44
  const isSwallowed = this.#isClickSwallowed && event instanceof PointerEvent && event.pointerType !== "";
44
45
  this.#isClickSwallowed = false;
@@ -74,7 +75,7 @@ var SonicSwitch = class extends SonicPositionGroupElement {
74
75
  lift: (press, event) => {
75
76
  this.#isClickSwallowed = true;
76
77
  if (press.hasMoved || this.isDisabled()) return;
77
- if (!isUnder(this.group, event.clientX, event.clientY)) return;
78
+ if (!isUnder(this.control, event.clientX, event.clientY)) return;
78
79
  this.#pressBat(event);
79
80
  },
80
81
  move: (press, event) => {
@@ -105,7 +106,7 @@ var SonicSwitch = class extends SonicPositionGroupElement {
105
106
  #renderThrow(index) {
106
107
  const count = this.positions().length;
107
108
  const at = index < 0 || count < 2 ? 0 : 2 * index / (count - 1) - 1;
108
- this.group.style.setProperty("--_sonic-switch-at", String(at));
109
+ this.control.style.setProperty("--_sonic-switch-at", String(at));
109
110
  }
110
111
  #sideStep(from, event) {
111
112
  const box = this.#bat.getBoundingClientRect();
@@ -1 +1 @@
1
- {"version":3,"file":"switch.js","names":[],"sources":["../../../src/elements/switch.ts"],"sourcesContent":["import { SonicPositionGroupElement } from '#elements/position-group.ts';\nimport { isUnder } from '#elements/radio-group.ts';\nimport { bindDrag, capturePointer } from '#lib/pointer-drag.ts';\nimport { soleStep } from '#lib/positions.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-switch': SonicSwitch;\n\t}\n}\n\nconst renderSwitch = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-switch\">\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-switch-bushing\"></span>\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-switch-bat\"></span>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nconst renderPosition = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-switch-position\" role=\"radio\" type=\"button\">\n\t\t\t<span class=\"sonic-switch-label\"></span>\n\t\t</button>\n\t`,\n\tHTMLButtonElement,\n);\n\nconst renderBare = template(\n\t/* HTML */ `<button class=\"sonic-switch-position\" role=\"switch\" type=\"button\"></button>`,\n\tHTMLButtonElement,\n);\n\ninterface BatPress {\n\thasMoved: boolean;\n\tlast: number;\n\twidth: number;\n}\n\nconst pressCenterZone = 0.08;\n\nexport class SonicSwitch extends SonicPositionGroupElement {\n\tprotected readonly bare = renderBare();\n\n\tprotected readonly defaultOrientation = 'vertical';\n\n\tprotected readonly group = renderSwitch();\n\n\treadonly #bat = requireChild(this.group, '.sonic-switch-bat', HTMLSpanElement);\n\n\t#isClickSwallowed = false;\n\n\tprotected override claimPress(index: number, event: PointerEvent): boolean {\n\t\tif (this.#isOnBat(event)) return true;\n\n\t\tconst target = this.pressTarget(index);\n\t\tif (this.isOptionDisabled(target) || !this.isMomentary(target)) return false;\n\n\t\tcapturePointer(this.group, event.pointerId);\n\t\tthis.hold(target, event.pointerId);\n\n\t\treturn true;\n\t}\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tconst group = this.group;\n\n\t\tsuper.connect(signal);\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(group, 'switch.css', {\n\t\t\t\tproperty: 'padding-top',\n\t\t\t\tselector: '.sonic-switch-label',\n\t\t\t});\n\n\t\tthis.bare.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\tconst isSwallowed =\n\t\t\t\t\tthis.#isClickSwallowed && event instanceof PointerEvent && event.pointerType !== '';\n\n\t\t\t\tthis.#isClickSwallowed = false;\n\t\t\t\tif (!isSwallowed) this.setChecked(!this.checked);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.bare.addEventListener(\n\t\t\t'pointerdown',\n\t\t\t() => {\n\t\t\t\tthis.#isClickSwallowed = false;\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindBat(group, signal);\n\t}\n\n\tprotected override releaseTarget(index: number): number | undefined {\n\t\tconst target = this.pressTarget(index);\n\n\t\treturn this.isMomentary(target) || this.isOptionDisabled(target) ? undefined : target;\n\t}\n\n\tprotected override render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tsuper.render();\n\t\tthis.#renderThrow(this.checkedIndex());\n\t}\n\n\tprotected renderOption(): HTMLButtonElement {\n\t\treturn renderPosition();\n\t}\n\n\t#bindBat(group: HTMLElement, signal: AbortSignal): void {\n\t\tbindDrag<BatPress>(\n\t\t\tgroup,\n\t\t\t{\n\t\t\t\tgrab: (event) => {\n\t\t\t\t\tconst isOnPosition = this.pressedIndex(event.target) >= 0;\n\t\t\t\t\tif (!isOnPosition || this.isDisabled() || !this.#isOnBat(event)) return;\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\thasMoved: false,\n\t\t\t\t\t\tlast: this.along(event),\n\t\t\t\t\t\twidth: this.#bat.getBoundingClientRect().width,\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t\tlift: (press, event) => {\n\t\t\t\t\tthis.#isClickSwallowed = true;\n\t\t\t\t\tif (press.hasMoved || this.isDisabled()) return;\n\t\t\t\t\tif (!isUnder(this.group, event.clientX, event.clientY)) return;\n\n\t\t\t\t\tthis.#pressBat(event);\n\t\t\t\t},\n\t\t\t\tmove: (press, event) => {\n\t\t\t\t\tif (this.isDisabled()) return;\n\n\t\t\t\t\tconst delta = this.along(event) - press.last;\n\t\t\t\t\tif (Math.abs(delta) < press.width / 4) return;\n\n\t\t\t\t\tpress.hasMoved = true;\n\t\t\t\t\tpress.last = this.along(event);\n\t\t\t\t\tthis.#throwBy(Math.sign(delta), event.pointerId);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#hasSprungFrom(index: number): boolean {\n\t\tif (!this.isMomentary(index)) return false;\n\n\t\tthis.releaseHold();\n\n\t\treturn !this.isChecked(index);\n\t}\n\n\t#isOnBat(event: PointerEvent): boolean {\n\t\treturn isUnder(this.#bat, event.clientX, event.clientY);\n\t}\n\n\t#pressBat(event: PointerEvent): void {\n\t\tconst from = this.checkedIndex();\n\t\tconst target = soleStep(this.positions(), from) ?? this.#sideStep(from, event);\n\t\tif (target === undefined) return;\n\n\t\tthis.chooseByClick(target);\n\t\tthis.refocusAt(this.checkedIndex());\n\t}\n\n\t#renderThrow(index: number): void {\n\t\tconst count = this.positions().length;\n\t\tconst at = index < 0 || count < 2 ? 0 : (2 * index) / (count - 1) - 1;\n\n\t\tthis.group.style.setProperty('--_sonic-switch-at', String(at));\n\t}\n\n\t#sideStep(from: number, event: PointerEvent): number | undefined {\n\t\tconst box = this.#bat.getBoundingClientRect();\n\t\tconst [start, size] =\n\t\t\tthis.orientation === 'horizontal' ? [box.left, box.width] : [box.top, box.height];\n\t\tconst offset = (this.along(event) - start) / size - 0.5;\n\t\tconst side = Math.abs(offset) < pressCenterZone ? 1 : Math.sign(offset);\n\t\tconst toward = this.stepFrom(from, side);\n\n\t\treturn toward !== undefined && toward !== from ? toward : this.stepFrom(from, -side);\n\t}\n\n\t#throwBy(direction: number, pointerId: number): void {\n\t\tconst from = this.checkedIndex();\n\t\tconst target = this.stepFrom(from, direction);\n\t\tif (target === undefined || target === from || this.#hasSprungFrom(from)) return;\n\n\t\tif (this.isMomentary(target)) this.hold(target, pointerId);\n\t\telse this.select(target);\n\t\tthis.refocusAt(this.checkedIndex());\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"switch.js","names":[],"sources":["../../../src/elements/switch.ts"],"sourcesContent":["import type { BoxProbe } from '#lib/check-styles.ts';\n\nimport { SonicPositionGroupElement } from '#elements/position-group.ts';\nimport { isUnder } from '#elements/radio-group.ts';\nimport { bindDrag, capturePointer } from '#lib/pointer-drag.ts';\nimport { soleStep } from '#lib/positions.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-switch': SonicSwitch;\n\t}\n}\n\nconst renderSwitch = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-switch\">\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-switch-bushing\"></span>\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-switch-bat\"></span>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nconst renderPosition = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-switch-position\" role=\"radio\" type=\"button\">\n\t\t\t<span class=\"sonic-switch-label\"></span>\n\t\t</button>\n\t`,\n\tHTMLButtonElement,\n);\n\nconst renderBare = template(\n\t/* HTML */ `<button class=\"sonic-switch-position\" role=\"switch\" type=\"button\"></button>`,\n\tHTMLButtonElement,\n);\n\ninterface BatPress {\n\thasMoved: boolean;\n\tlast: number;\n\twidth: number;\n}\n\nconst pressCenterZone = 0.08;\n\nexport class SonicSwitch extends SonicPositionGroupElement {\n\tprotected readonly bare = renderBare();\n\n\tprotected override readonly boxProbe: BoxProbe = {\n\t\tproperty: 'padding-top',\n\t\tselector: '.sonic-switch-label',\n\t};\n\n\tprotected override readonly control = renderSwitch();\n\n\tprotected readonly defaultOrientation = 'vertical';\n\n\tprotected override readonly sheet = 'switch.css';\n\n\treadonly #bat = requireChild(this.control, '.sonic-switch-bat', HTMLSpanElement);\n\n\t#isClickSwallowed = false;\n\n\tprotected override claimPress(index: number, event: PointerEvent): boolean {\n\t\tif (this.#isOnBat(event)) return true;\n\n\t\tconst target = this.pressTarget(index);\n\t\tif (this.isOptionDisabled(target) || !this.isMomentary(target)) return false;\n\n\t\tcapturePointer(this.control, event.pointerId);\n\t\tthis.hold(target, event.pointerId);\n\n\t\treturn true;\n\t}\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tconst group = this.control;\n\n\t\tsuper.connect(signal);\n\n\t\tthis.bare.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\tconst isSwallowed =\n\t\t\t\t\tthis.#isClickSwallowed && event instanceof PointerEvent && event.pointerType !== '';\n\n\t\t\t\tthis.#isClickSwallowed = false;\n\t\t\t\tif (!isSwallowed) this.setChecked(!this.checked);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.bare.addEventListener(\n\t\t\t'pointerdown',\n\t\t\t() => {\n\t\t\t\tthis.#isClickSwallowed = false;\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindBat(group, signal);\n\t}\n\n\tprotected override releaseTarget(index: number): number | undefined {\n\t\tconst target = this.pressTarget(index);\n\n\t\treturn this.isMomentary(target) || this.isOptionDisabled(target) ? undefined : target;\n\t}\n\n\tprotected override render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tsuper.render();\n\t\tthis.#renderThrow(this.checkedIndex());\n\t}\n\n\tprotected renderOption(): HTMLButtonElement {\n\t\treturn renderPosition();\n\t}\n\n\t#bindBat(group: HTMLElement, signal: AbortSignal): void {\n\t\tbindDrag<BatPress>(\n\t\t\tgroup,\n\t\t\t{\n\t\t\t\tgrab: (event) => {\n\t\t\t\t\tconst isOnPosition = this.pressedIndex(event.target) >= 0;\n\t\t\t\t\tif (!isOnPosition || this.isDisabled() || !this.#isOnBat(event)) return;\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\thasMoved: false,\n\t\t\t\t\t\tlast: this.along(event),\n\t\t\t\t\t\twidth: this.#bat.getBoundingClientRect().width,\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t\tlift: (press, event) => {\n\t\t\t\t\tthis.#isClickSwallowed = true;\n\t\t\t\t\tif (press.hasMoved || this.isDisabled()) return;\n\t\t\t\t\tif (!isUnder(this.control, event.clientX, event.clientY)) return;\n\n\t\t\t\t\tthis.#pressBat(event);\n\t\t\t\t},\n\t\t\t\tmove: (press, event) => {\n\t\t\t\t\tif (this.isDisabled()) return;\n\n\t\t\t\t\tconst delta = this.along(event) - press.last;\n\t\t\t\t\tif (Math.abs(delta) < press.width / 4) return;\n\n\t\t\t\t\tpress.hasMoved = true;\n\t\t\t\t\tpress.last = this.along(event);\n\t\t\t\t\tthis.#throwBy(Math.sign(delta), event.pointerId);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#hasSprungFrom(index: number): boolean {\n\t\tif (!this.isMomentary(index)) return false;\n\n\t\tthis.releaseHold();\n\n\t\treturn !this.isChecked(index);\n\t}\n\n\t#isOnBat(event: PointerEvent): boolean {\n\t\treturn isUnder(this.#bat, event.clientX, event.clientY);\n\t}\n\n\t#pressBat(event: PointerEvent): void {\n\t\tconst from = this.checkedIndex();\n\t\tconst target = soleStep(this.positions(), from) ?? this.#sideStep(from, event);\n\t\tif (target === undefined) return;\n\n\t\tthis.chooseByClick(target);\n\t\tthis.refocusAt(this.checkedIndex());\n\t}\n\n\t#renderThrow(index: number): void {\n\t\tconst count = this.positions().length;\n\t\tconst at = index < 0 || count < 2 ? 0 : (2 * index) / (count - 1) - 1;\n\n\t\tthis.control.style.setProperty('--_sonic-switch-at', String(at));\n\t}\n\n\t#sideStep(from: number, event: PointerEvent): number | undefined {\n\t\tconst box = this.#bat.getBoundingClientRect();\n\t\tconst [start, size] =\n\t\t\tthis.orientation === 'horizontal' ? [box.left, box.width] : [box.top, box.height];\n\t\tconst offset = (this.along(event) - start) / size - 0.5;\n\t\tconst side = Math.abs(offset) < pressCenterZone ? 1 : Math.sign(offset);\n\t\tconst toward = this.stepFrom(from, side);\n\n\t\treturn toward !== undefined && toward !== from ? toward : this.stepFrom(from, -side);\n\t}\n\n\t#throwBy(direction: number, pointerId: number): void {\n\t\tconst from = this.checkedIndex();\n\t\tconst target = this.stepFrom(from, direction);\n\t\tif (target === undefined || target === from || this.#hasSprungFrom(from)) return;\n\n\t\tif (this.isMomentary(target)) this.hold(target, pointerId);\n\t\telse this.select(target);\n\t\tthis.refocusAt(this.checkedIndex());\n\t}\n}\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { BoxProbe } from "../lib/check-styles.js";
1
2
  import { SonicPositionGroupElement } from "./position-group.js";
2
3
  //#region src/elements/toggle.d.ts
3
4
  declare global {
@@ -8,8 +9,10 @@ declare global {
8
9
  export declare class SonicToggle extends SonicPositionGroupElement {
9
10
  #private;
10
11
  protected readonly bare: HTMLButtonElement;
12
+ protected readonly boxProbe: BoxProbe;
13
+ protected readonly control: HTMLDivElement;
11
14
  protected readonly defaultOrientation = "horizontal";
12
- protected readonly group: HTMLDivElement;
15
+ protected readonly sheet = "toggle.css";
13
16
  protected claimPress(): boolean;
14
17
  protected connect(signal: AbortSignal): void;
15
18
  protected isMirrored(child: Node): boolean;
@@ -22,22 +22,23 @@ const renderBare = template(`<button class="sonic-toggle-position" role="switch"
22
22
  const pressTolerance = 2;
23
23
  var SonicToggle = class extends SonicPositionGroupElement {
24
24
  bare = renderBare();
25
+ boxProbe = {
26
+ property: "margin-top",
27
+ ratio: "--_sonic-toggle-gap-ratio",
28
+ selector: ".sonic-toggle-cap"
29
+ };
30
+ control = renderToggle();
25
31
  defaultOrientation = "horizontal";
26
- group = renderToggle();
27
- #cap = requireChild(this.group, ".sonic-toggle-cap", HTMLSpanElement);
32
+ sheet = "toggle.css";
33
+ #cap = requireChild(this.control, ".sonic-toggle-cap", HTMLSpanElement);
28
34
  #draggedTo;
29
35
  #hasWarnedOfLegends = false;
30
- #well = requireChild(this.group, ".sonic-toggle-well", HTMLSpanElement);
36
+ #well = requireChild(this.control, ".sonic-toggle-well", HTMLSpanElement);
31
37
  claimPress() {
32
38
  return true;
33
39
  }
34
40
  connect(signal) {
35
41
  super.connect(signal);
36
- this.checkStyles(this.group, "toggle.css", {
37
- property: "margin-top",
38
- ratio: "--_sonic-toggle-gap-ratio",
39
- selector: ".sonic-toggle-cap"
40
- });
41
42
  this.bare.addEventListener("click", (event) => {
42
43
  if (event instanceof PointerEvent && event.pointerType !== "") return;
43
44
  this.setChecked(!this.checked);
@@ -55,7 +56,7 @@ var SonicToggle = class extends SonicPositionGroupElement {
55
56
  render() {
56
57
  if (!this.isBound()) return;
57
58
  this.#checkLegends();
58
- this.group.style.setProperty("--_sonic-toggle-count", String(this.positions().length));
59
+ this.control.style.setProperty("--_sonic-toggle-count", String(this.positions().length));
59
60
  this.#placeCap(this.#draggedTo ?? this.#restingAt());
60
61
  super.render();
61
62
  for (const [index, option] of this.options().entries()) option.style.setProperty("--_sonic-toggle-index", String(index));
@@ -64,11 +65,11 @@ var SonicToggle = class extends SonicPositionGroupElement {
64
65
  return renderPosition();
65
66
  }
66
67
  #bindCap(signal) {
67
- bindDrag(this.group, {
68
+ bindDrag(this.control, {
68
69
  grab: (event) => this.#grab(event),
69
70
  lift: (drag, event) => {
70
71
  if (!drag.isPress || drag.isHolding || this.isDisabled()) return;
71
- if (!isUnder(this.group, event.clientX, event.clientY)) return;
72
+ if (!isUnder(this.control, event.clientX, event.clientY)) return;
72
73
  this.#press(drag.position);
73
74
  },
74
75
  move: (drag, event) => {
@@ -82,7 +83,7 @@ var SonicToggle = class extends SonicPositionGroupElement {
82
83
  if (this.#slideTo(positionAt(drag.travel, pointer), event.pointerId)) drag.isPress = false;
83
84
  },
84
85
  release: (drag) => {
85
- delete drag.marked.dataset.sonicPressed;
86
+ delete drag.marked.dataset.sonicActive;
86
87
  this.#draggedTo = void 0;
87
88
  this.toggleState("dragging", false);
88
89
  this.render();
@@ -97,7 +98,7 @@ var SonicToggle = class extends SonicPositionGroupElement {
97
98
  console.warn(`<sonic-toggle> lays out legends="outside" for two or three positions, and this one has ${String(count)}`);
98
99
  }
99
100
  #fixedLegend() {
100
- return [...this.children].find((child) => child !== this.group && !super.isMirrored(child));
101
+ return [...this.children].find((child) => child !== this.control && !super.isMirrored(child));
101
102
  }
102
103
  #grab(event) {
103
104
  const position = this.pressedIndex(event.target);
@@ -106,7 +107,7 @@ var SonicToggle = class extends SonicPositionGroupElement {
106
107
  const travel = this.#travel(event, isOnCap);
107
108
  if (!travel || !isOnCap && this.isOptionDisabled(position)) return void 0;
108
109
  const marked = this.options()[position] ?? this.bare;
109
- marked.toggleAttribute("data-sonic-pressed", true);
110
+ marked.toggleAttribute("data-sonic-active", true);
110
111
  return {
111
112
  hasMoved: false,
112
113
  isHolding: this.#holdFrom(position, event.pointerId),
@@ -125,7 +126,7 @@ var SonicToggle = class extends SonicPositionGroupElement {
125
126
  return true;
126
127
  }
127
128
  #placeCap(at) {
128
- this.group.style.setProperty("--_sonic-toggle-at", String(at));
129
+ this.control.style.setProperty("--_sonic-toggle-at", String(at));
129
130
  }
130
131
  #press(position) {
131
132
  const target = this.pressTarget(position);
@@ -1 +1 @@
1
- {"version":3,"file":"toggle.js","names":[],"sources":["../../../src/elements/toggle.ts"],"sourcesContent":["import type { ToggleTravel } from '#lib/toggle-travel.ts';\n\nimport { SonicPositionGroupElement } from '#elements/position-group.ts';\nimport { isUnder } from '#elements/radio-group.ts';\nimport { dragThresholdPx } from '#lib/drag-step.ts';\nimport { bindDrag } from '#lib/pointer-drag.ts';\nimport { enabledEnds } from '#lib/positions.ts';\nimport { placeChildren, requireChild, template } from '#lib/render.ts';\nimport { positionAt, travelFrom } from '#lib/toggle-travel.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-toggle': SonicToggle;\n\t}\n}\n\n// fallow-ignore-next-line code-duplication -- each control draws its own parts\nconst renderToggle = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-toggle\">\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-toggle-well\"></span>\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-toggle-cap\"></span>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nconst renderPosition = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-toggle-position\" role=\"radio\" type=\"button\">\n\t\t\t<span class=\"sonic-toggle-legend\"></span>\n\t\t</button>\n\t`,\n\tHTMLButtonElement,\n);\n\nconst renderBare = template(\n\t/* HTML */ `<button class=\"sonic-toggle-position\" role=\"switch\" type=\"button\"></button>`,\n\tHTMLButtonElement,\n);\n\nconst pressTolerance = 2;\n\ninterface CapDrag {\n\thasMoved: boolean;\n\tisHolding: boolean;\n\tisPress: boolean;\n\tmarked: HTMLButtonElement;\n\tposition: number;\n\tstart: number;\n\tthresholdPx: number;\n\ttravel: ToggleTravel;\n}\n\nexport class SonicToggle extends SonicPositionGroupElement {\n\tprotected readonly bare = renderBare();\n\n\tprotected readonly defaultOrientation = 'horizontal';\n\n\tprotected readonly group = renderToggle();\n\n\treadonly #cap = requireChild(this.group, '.sonic-toggle-cap', HTMLSpanElement);\n\n\t#draggedTo: number | undefined;\n\n\t#hasWarnedOfLegends = false;\n\n\treadonly #well = requireChild(this.group, '.sonic-toggle-well', HTMLSpanElement);\n\n\t// Every press is the toggle's own, so the base's slide-off never starts\n\tprotected override claimPress(): boolean {\n\t\treturn true;\n\t}\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tsuper.connect(signal);\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(this.group, 'toggle.css', {\n\t\t\t\tproperty: 'margin-top',\n\t\t\t\tratio: '--_sonic-toggle-gap-ratio',\n\t\t\t\tselector: '.sonic-toggle-cap',\n\t\t\t});\n\t\tthis.bare.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\t// A pointer's press is taken on its release, before the click it sends\n\t\t\t\tif (event instanceof PointerEvent && event.pointerType !== '') return;\n\n\t\t\t\tthis.setChecked(!this.checked);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindCap(signal);\n\t}\n\n\tprotected override isMirrored(child: Node): boolean {\n\t\treturn super.isMirrored(child) || child === this.#fixedLegend();\n\t}\n\n\tprotected override placeCopies(copies: Array<Node>): void {\n\t\tconst positions = copies.filter((copy) => super.isMirrored(copy));\n\n\t\tplaceChildren(\n\t\t\tthis.#cap,\n\t\t\tcopies.filter((copy) => !positions.includes(copy)),\n\t\t);\n\t\tsuper.placeCopies(positions);\n\t}\n\n\tprotected override render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tif (__DEV__) this.#checkLegends();\n\t\tthis.group.style.setProperty('--_sonic-toggle-count', String(this.positions().length));\n\t\tthis.#placeCap(this.#draggedTo ?? this.#restingAt());\n\t\tsuper.render();\n\t\tfor (const [index, option] of this.options().entries()) {\n\t\t\toption.style.setProperty('--_sonic-toggle-index', String(index));\n\t\t}\n\t}\n\n\tprotected renderOption(): HTMLButtonElement {\n\t\treturn renderPosition();\n\t}\n\n\t#bindCap(signal: AbortSignal): void {\n\t\tbindDrag<CapDrag>(\n\t\t\tthis.group,\n\t\t\t{\n\t\t\t\tgrab: (event) => this.#grab(event),\n\t\t\t\tlift: (drag, event) => {\n\t\t\t\t\tif (!drag.isPress || drag.isHolding || this.isDisabled()) return;\n\t\t\t\t\tif (!isUnder(this.group, event.clientX, event.clientY)) return;\n\n\t\t\t\t\tthis.#press(drag.position);\n\t\t\t\t},\n\t\t\t\tmove: (drag, event) => {\n\t\t\t\t\t// A held finger wobbles, and a drag back across halfway would let go\n\t\t\t\t\tif (drag.isHolding || this.isDisabled()) return;\n\t\t\t\t\tconst pointer = this.along(event);\n\t\t\t\t\tconst moved = Math.abs(pointer - drag.start);\n\t\t\t\t\tif (!drag.hasMoved && moved < drag.thresholdPx) return;\n\n\t\t\t\t\tdrag.hasMoved = true;\n\t\t\t\t\tthis.toggleState('dragging', true);\n\t\t\t\t\tif (moved >= pressTolerance * drag.thresholdPx) drag.isPress = false;\n\t\t\t\t\tif (this.#slideTo(positionAt(drag.travel, pointer), event.pointerId))\n\t\t\t\t\t\tdrag.isPress = false;\n\t\t\t\t},\n\t\t\t\trelease: (drag) => {\n\t\t\t\t\tdelete drag.marked.dataset.sonicPressed;\n\t\t\t\t\tthis.#draggedTo = undefined;\n\t\t\t\t\tthis.toggleState('dragging', false);\n\t\t\t\t\tthis.render();\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#checkLegends(): void {\n\t\tif (!__DEV__ || this.#hasWarnedOfLegends) return;\n\n\t\tconst count = this.options().length;\n\t\tif (count <= 3 || this.getAttribute('legends') !== 'outside') return;\n\n\t\tthis.#hasWarnedOfLegends = true;\n\t\tconsole.warn(\n\t\t\t`<sonic-toggle> lays out legends=\"outside\" for two or three positions, and this one has ${String(count)}`,\n\t\t);\n\t}\n\n\t#fixedLegend(): Element | undefined {\n\t\treturn [...this.children].find((child) => child !== this.group && !super.isMirrored(child));\n\t}\n\n\t#grab(event: PointerEvent): CapDrag | undefined {\n\t\tconst position = this.pressedIndex(event.target);\n\t\tif (this.isDisabled() || position < 0) return undefined;\n\n\t\tconst isOnCap = isUnder(this.#cap, event.clientX, event.clientY);\n\t\tconst travel = this.#travel(event, isOnCap);\n\t\tif (!travel || (!isOnCap && this.isOptionDisabled(position))) return undefined;\n\n\t\tconst marked = this.options()[position] ?? this.bare;\n\n\t\tmarked.toggleAttribute('data-sonic-pressed', true);\n\n\t\treturn {\n\t\t\thasMoved: false,\n\t\t\tisHolding: this.#holdFrom(position, event.pointerId),\n\t\t\tisPress: true,\n\t\t\tmarked,\n\t\t\tposition,\n\t\t\tstart: this.along(event),\n\t\t\tthresholdPx: dragThresholdPx(event.pointerType),\n\t\t\ttravel,\n\t\t};\n\t}\n\n\t#holdFrom(position: number, pointerId: number): boolean {\n\t\tconst target = this.pressTarget(position);\n\t\tif (!this.isMomentary(target) || this.isOptionDisabled(target)) return false;\n\n\t\tthis.hold(target, pointerId);\n\n\t\treturn true;\n\t}\n\n\t#placeCap(at: number): void {\n\t\tthis.group.style.setProperty('--_sonic-toggle-at', String(at));\n\t}\n\n\t#press(position: number): void {\n\t\tconst target = this.pressTarget(position);\n\t\tif (this.isOptionDisabled(target)) return;\n\n\t\tthis.select(target);\n\t\tthis.refocusAt(target);\n\t}\n\n\t#restingAt(): number {\n\t\treturn Math.max(0, this.checkedIndex());\n\t}\n\n\t#slideTo(at: number, pointerId: number): boolean {\n\t\tconst crossed = Math.round(at);\n\n\t\tthis.#draggedTo = at;\n\t\tthis.#placeCap(at);\n\t\tif (this.isChecked(crossed)) return false;\n\n\t\tthis.releaseHold();\n\t\tif (this.isOptionDisabled(crossed) || this.isChecked(crossed)) return false;\n\n\t\tif (this.isMomentary(crossed)) this.hold(crossed, pointerId);\n\t\telse this.select(crossed);\n\t\tthis.refocusAt(crossed);\n\n\t\treturn true;\n\t}\n\n\t#travel(event: PointerEvent, isOnCap: boolean): ToggleTravel | undefined {\n\t\tconst [first, last] = enabledEnds(this.positions());\n\t\tif (first < 0) return undefined;\n\n\t\tconst box = this.#well.getBoundingClientRect();\n\t\tconst [start, length, thickness] =\n\t\t\tthis.orientation === 'vertical'\n\t\t\t\t? [box.top, box.height, box.width]\n\t\t\t\t: [box.left, box.width, box.height];\n\n\t\treturn travelFrom({\n\t\t\tat: this.#restingAt(),\n\t\t\tcount: this.positions().length,\n\t\t\tfirst,\n\t\t\tisOnCap,\n\t\t\tlast,\n\t\t\tpointer: this.along(event),\n\t\t\twell: { length, start, thickness },\n\t\t});\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"toggle.js","names":[],"sources":["../../../src/elements/toggle.ts"],"sourcesContent":["import type { BoxProbe } from '#lib/check-styles.ts';\nimport type { ToggleTravel } from '#lib/toggle-travel.ts';\n\nimport { SonicPositionGroupElement } from '#elements/position-group.ts';\nimport { isUnder } from '#elements/radio-group.ts';\nimport { dragThresholdPx } from '#lib/drag-step.ts';\nimport { bindDrag } from '#lib/pointer-drag.ts';\nimport { enabledEnds } from '#lib/positions.ts';\nimport { placeChildren, requireChild, template } from '#lib/render.ts';\nimport { positionAt, travelFrom } from '#lib/toggle-travel.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-toggle': SonicToggle;\n\t}\n}\n\n// fallow-ignore-next-line code-duplication -- each control draws its own parts\nconst renderToggle = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-toggle\">\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-toggle-well\"></span>\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-toggle-cap\"></span>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nconst renderPosition = template(\n\t/* HTML */ `\n\t\t<button class=\"sonic-toggle-position\" role=\"radio\" type=\"button\">\n\t\t\t<span class=\"sonic-toggle-legend\"></span>\n\t\t</button>\n\t`,\n\tHTMLButtonElement,\n);\n\nconst renderBare = template(\n\t/* HTML */ `<button class=\"sonic-toggle-position\" role=\"switch\" type=\"button\"></button>`,\n\tHTMLButtonElement,\n);\n\nconst pressTolerance = 2;\n\ninterface CapDrag {\n\thasMoved: boolean;\n\tisHolding: boolean;\n\tisPress: boolean;\n\tmarked: HTMLButtonElement;\n\tposition: number;\n\tstart: number;\n\tthresholdPx: number;\n\ttravel: ToggleTravel;\n}\n\nexport class SonicToggle extends SonicPositionGroupElement {\n\tprotected readonly bare = renderBare();\n\n\tprotected override readonly boxProbe: BoxProbe = {\n\t\tproperty: 'margin-top',\n\t\tratio: '--_sonic-toggle-gap-ratio',\n\t\tselector: '.sonic-toggle-cap',\n\t};\n\n\tprotected override readonly control = renderToggle();\n\n\tprotected readonly defaultOrientation = 'horizontal';\n\n\tprotected override readonly sheet = 'toggle.css';\n\n\treadonly #cap = requireChild(this.control, '.sonic-toggle-cap', HTMLSpanElement);\n\n\t#draggedTo: number | undefined;\n\n\t#hasWarnedOfLegends = false;\n\n\treadonly #well = requireChild(this.control, '.sonic-toggle-well', HTMLSpanElement);\n\n\t// Every press is the toggle's own, so the base's slide-off never starts\n\tprotected override claimPress(): boolean {\n\t\treturn true;\n\t}\n\n\tprotected override connect(signal: AbortSignal): void {\n\t\tsuper.connect(signal);\n\t\tthis.bare.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\t// A pointer's press is taken on its release, before the click it sends\n\t\t\t\tif (event instanceof PointerEvent && event.pointerType !== '') return;\n\n\t\t\t\tthis.setChecked(!this.checked);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindCap(signal);\n\t}\n\n\tprotected override isMirrored(child: Node): boolean {\n\t\treturn super.isMirrored(child) || child === this.#fixedLegend();\n\t}\n\n\tprotected override placeCopies(copies: Array<Node>): void {\n\t\tconst positions = copies.filter((copy) => super.isMirrored(copy));\n\n\t\tplaceChildren(\n\t\t\tthis.#cap,\n\t\t\tcopies.filter((copy) => !positions.includes(copy)),\n\t\t);\n\t\tsuper.placeCopies(positions);\n\t}\n\n\tprotected override render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tif (__DEV__) this.#checkLegends();\n\t\tthis.control.style.setProperty('--_sonic-toggle-count', String(this.positions().length));\n\t\tthis.#placeCap(this.#draggedTo ?? this.#restingAt());\n\t\tsuper.render();\n\t\tfor (const [index, option] of this.options().entries()) {\n\t\t\toption.style.setProperty('--_sonic-toggle-index', String(index));\n\t\t}\n\t}\n\n\tprotected renderOption(): HTMLButtonElement {\n\t\treturn renderPosition();\n\t}\n\n\t#bindCap(signal: AbortSignal): void {\n\t\tbindDrag<CapDrag>(\n\t\t\tthis.control,\n\t\t\t{\n\t\t\t\tgrab: (event) => this.#grab(event),\n\t\t\t\tlift: (drag, event) => {\n\t\t\t\t\tif (!drag.isPress || drag.isHolding || this.isDisabled()) return;\n\t\t\t\t\tif (!isUnder(this.control, event.clientX, event.clientY)) return;\n\n\t\t\t\t\tthis.#press(drag.position);\n\t\t\t\t},\n\t\t\t\tmove: (drag, event) => {\n\t\t\t\t\t// A held finger wobbles, and a drag back across halfway would let go\n\t\t\t\t\tif (drag.isHolding || this.isDisabled()) return;\n\t\t\t\t\tconst pointer = this.along(event);\n\t\t\t\t\tconst moved = Math.abs(pointer - drag.start);\n\t\t\t\t\tif (!drag.hasMoved && moved < drag.thresholdPx) return;\n\n\t\t\t\t\tdrag.hasMoved = true;\n\t\t\t\t\tthis.toggleState('dragging', true);\n\t\t\t\t\tif (moved >= pressTolerance * drag.thresholdPx) drag.isPress = false;\n\t\t\t\t\tif (this.#slideTo(positionAt(drag.travel, pointer), event.pointerId))\n\t\t\t\t\t\tdrag.isPress = false;\n\t\t\t\t},\n\t\t\t\trelease: (drag) => {\n\t\t\t\t\tdelete drag.marked.dataset.sonicActive;\n\t\t\t\t\tthis.#draggedTo = undefined;\n\t\t\t\t\tthis.toggleState('dragging', false);\n\t\t\t\t\tthis.render();\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#checkLegends(): void {\n\t\tif (!__DEV__ || this.#hasWarnedOfLegends) return;\n\n\t\tconst count = this.options().length;\n\t\tif (count <= 3 || this.getAttribute('legends') !== 'outside') return;\n\n\t\tthis.#hasWarnedOfLegends = true;\n\t\tconsole.warn(\n\t\t\t`<sonic-toggle> lays out legends=\"outside\" for two or three positions, and this one has ${String(count)}`,\n\t\t);\n\t}\n\n\t#fixedLegend(): Element | undefined {\n\t\treturn [...this.children].find((child) => child !== this.control && !super.isMirrored(child));\n\t}\n\n\t#grab(event: PointerEvent): CapDrag | undefined {\n\t\tconst position = this.pressedIndex(event.target);\n\t\tif (this.isDisabled() || position < 0) return undefined;\n\n\t\tconst isOnCap = isUnder(this.#cap, event.clientX, event.clientY);\n\t\tconst travel = this.#travel(event, isOnCap);\n\t\tif (!travel || (!isOnCap && this.isOptionDisabled(position))) return undefined;\n\n\t\tconst marked = this.options()[position] ?? this.bare;\n\n\t\tmarked.toggleAttribute('data-sonic-active', true);\n\n\t\treturn {\n\t\t\thasMoved: false,\n\t\t\tisHolding: this.#holdFrom(position, event.pointerId),\n\t\t\tisPress: true,\n\t\t\tmarked,\n\t\t\tposition,\n\t\t\tstart: this.along(event),\n\t\t\tthresholdPx: dragThresholdPx(event.pointerType),\n\t\t\ttravel,\n\t\t};\n\t}\n\n\t#holdFrom(position: number, pointerId: number): boolean {\n\t\tconst target = this.pressTarget(position);\n\t\tif (!this.isMomentary(target) || this.isOptionDisabled(target)) return false;\n\n\t\tthis.hold(target, pointerId);\n\n\t\treturn true;\n\t}\n\n\t#placeCap(at: number): void {\n\t\tthis.control.style.setProperty('--_sonic-toggle-at', String(at));\n\t}\n\n\t#press(position: number): void {\n\t\tconst target = this.pressTarget(position);\n\t\tif (this.isOptionDisabled(target)) return;\n\n\t\tthis.select(target);\n\t\tthis.refocusAt(target);\n\t}\n\n\t#restingAt(): number {\n\t\treturn Math.max(0, this.checkedIndex());\n\t}\n\n\t#slideTo(at: number, pointerId: number): boolean {\n\t\tconst crossed = Math.round(at);\n\n\t\tthis.#draggedTo = at;\n\t\tthis.#placeCap(at);\n\t\tif (this.isChecked(crossed)) return false;\n\n\t\tthis.releaseHold();\n\t\tif (this.isOptionDisabled(crossed) || this.isChecked(crossed)) return false;\n\n\t\tif (this.isMomentary(crossed)) this.hold(crossed, pointerId);\n\t\telse this.select(crossed);\n\t\tthis.refocusAt(crossed);\n\n\t\treturn true;\n\t}\n\n\t#travel(event: PointerEvent, isOnCap: boolean): ToggleTravel | undefined {\n\t\tconst [first, last] = enabledEnds(this.positions());\n\t\tif (first < 0) return undefined;\n\n\t\tconst box = this.#well.getBoundingClientRect();\n\t\tconst [start, length, thickness] =\n\t\t\tthis.orientation === 'vertical'\n\t\t\t\t? [box.top, box.height, box.width]\n\t\t\t\t: [box.left, box.width, box.height];\n\n\t\treturn travelFrom({\n\t\t\tat: this.#restingAt(),\n\t\t\tcount: this.positions().length,\n\t\t\tfirst,\n\t\t\tisOnCap,\n\t\t\tlast,\n\t\t\tpointer: this.along(event),\n\t\t\twell: { length, start, thickness },\n\t\t});\n\t}\n}\n"],"mappings":";;;;;;;;;AAoBA,MAAM,eAAe,SACT;;;;;IAMX,cACD;AAEA,MAAM,iBAAiB,SACX;;;;IAKX,iBACD;AAEA,MAAM,aAAa,SACP,+EACX,iBACD;AAEA,MAAM,iBAAiB;AAavB,IAAa,cAAb,cAAiC,0BAA0B;CAC1D,AAAmB,OAAO,WAAW;CAErC,AAA4B,WAAqB;EAChD,UAAU;EACV,OAAO;EACP,UAAU;CACX;CAEA,AAA4B,UAAU,aAAa;CAEnD,AAAmB,qBAAqB;CAExC,AAA4B,QAAQ;CAEpC,AAAS,OAAO,aAAa,KAAK,SAAS,qBAAqB,eAAe;CAE/E;CAEA,sBAAsB;CAEtB,AAAS,QAAQ,aAAa,KAAK,SAAS,sBAAsB,eAAe;CAGjF,AAAmB,aAAsB;EACxC,OAAO;CACR;CAEA,AAAmB,QAAQ,QAA2B;EACrD,MAAM,QAAQ,MAAM;EACpB,KAAK,KAAK,iBACT,UACC,UAAU;GAEV,IAAI,iBAAiB,gBAAgB,MAAM,gBAAgB,IAAI;GAE/D,KAAK,WAAW,CAAC,KAAK,OAAO;EAC9B,GACA,EAAE,OAAO,CACV;EACA,KAAK,SAAS,MAAM;CACrB;CAEA,AAAmB,WAAW,OAAsB;EACnD,OAAO,MAAM,WAAW,KAAK,KAAK,UAAU,KAAK,aAAa;CAC/D;CAEA,AAAmB,YAAY,QAA2B;EACzD,MAAM,YAAY,OAAO,QAAQ,SAAS,MAAM,WAAW,IAAI,CAAC;EAEhE,cACC,KAAK,MACL,OAAO,QAAQ,SAAS,CAAC,UAAU,SAAS,IAAI,CAAC,CAClD;EACA,MAAM,YAAY,SAAS;CAC5B;CAEA,AAAmB,SAAe;EACjC,IAAI,CAAC,KAAK,QAAQ,GAAG;EAER,KAAK,cAAc;EAChC,KAAK,QAAQ,MAAM,YAAY,yBAAyB,OAAO,KAAK,UAAU,CAAC,CAAC,MAAM,CAAC;EACvF,KAAK,UAAU,KAAK,cAAc,KAAK,WAAW,CAAC;EACnD,MAAM,OAAO;EACb,KAAK,MAAM,CAAC,OAAO,WAAW,KAAK,QAAQ,CAAC,CAAC,QAAQ,GACpD,OAAO,MAAM,YAAY,yBAAyB,OAAO,KAAK,CAAC;CAEjE;CAEA,AAAU,eAAkC;EAC3C,OAAO,eAAe;CACvB;CAEA,SAAS,QAA2B;EACnC,SACC,KAAK,SACL;GACC,OAAO,UAAU,KAAK,MAAM,KAAK;GACjC,OAAO,MAAM,UAAU;IACtB,IAAI,CAAC,KAAK,WAAW,KAAK,aAAa,KAAK,WAAW,GAAG;IAC1D,IAAI,CAAC,QAAQ,KAAK,SAAS,MAAM,SAAS,MAAM,OAAO,GAAG;IAE1D,KAAK,OAAO,KAAK,QAAQ;GAC1B;GACA,OAAO,MAAM,UAAU;IAEtB,IAAI,KAAK,aAAa,KAAK,WAAW,GAAG;IACzC,MAAM,UAAU,KAAK,MAAM,KAAK;IAChC,MAAM,QAAQ,KAAK,IAAI,UAAU,KAAK,KAAK;IAC3C,IAAI,CAAC,KAAK,YAAY,QAAQ,KAAK,aAAa;IAEhD,KAAK,WAAW;IAChB,KAAK,YAAY,YAAY,IAAI;IACjC,IAAI,SAAS,iBAAiB,KAAK,aAAa,KAAK,UAAU;IAC/D,IAAI,KAAK,SAAS,WAAW,KAAK,QAAQ,OAAO,GAAG,MAAM,SAAS,GAClE,KAAK,UAAU;GACjB;GACA,UAAU,SAAS;IAClB,OAAO,KAAK,OAAO,QAAQ;IAC3B,KAAK,aAAa;IAClB,KAAK,YAAY,YAAY,KAAK;IAClC,KAAK,OAAO;GACb;EACD,GACA,MACD;CACD;CAEA,gBAAsB;EACrB,IAAgB,KAAK,qBAAqB;EAE1C,MAAM,QAAQ,KAAK,QAAQ,CAAC,CAAC;EAC7B,IAAI,SAAS,KAAK,KAAK,aAAa,SAAS,MAAM,WAAW;EAE9D,KAAK,sBAAsB;EAC3B,QAAQ,KACP,0FAA0F,OAAO,KAAK,GACvG;CACD;CAEA,eAAoC;EACnC,OAAO,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,MAAM,UAAU,UAAU,KAAK,WAAW,CAAC,MAAM,WAAW,KAAK,CAAC;CAC7F;CAEA,MAAM,OAA0C;EAC/C,MAAM,WAAW,KAAK,aAAa,MAAM,MAAM;EAC/C,IAAI,KAAK,WAAW,KAAK,WAAW,GAAG,OAAO;EAE9C,MAAM,UAAU,QAAQ,KAAK,MAAM,MAAM,SAAS,MAAM,OAAO;EAC/D,MAAM,SAAS,KAAK,QAAQ,OAAO,OAAO;EAC1C,IAAI,CAAC,UAAW,CAAC,WAAW,KAAK,iBAAiB,QAAQ,GAAI,OAAO;EAErE,MAAM,SAAS,KAAK,QAAQ,CAAC,CAAC,aAAa,KAAK;EAEhD,OAAO,gBAAgB,qBAAqB,IAAI;EAEhD,OAAO;GACN,UAAU;GACV,WAAW,KAAK,UAAU,UAAU,MAAM,SAAS;GACnD,SAAS;GACT;GACA;GACA,OAAO,KAAK,MAAM,KAAK;GACvB,aAAa,gBAAgB,MAAM,WAAW;GAC9C;EACD;CACD;CAEA,UAAU,UAAkB,WAA4B;EACvD,MAAM,SAAS,KAAK,YAAY,QAAQ;EACxC,IAAI,CAAC,KAAK,YAAY,MAAM,KAAK,KAAK,iBAAiB,MAAM,GAAG,OAAO;EAEvE,KAAK,KAAK,QAAQ,SAAS;EAE3B,OAAO;CACR;CAEA,UAAU,IAAkB;EAC3B,KAAK,QAAQ,MAAM,YAAY,sBAAsB,OAAO,EAAE,CAAC;CAChE;CAEA,OAAO,UAAwB;EAC9B,MAAM,SAAS,KAAK,YAAY,QAAQ;EACxC,IAAI,KAAK,iBAAiB,MAAM,GAAG;EAEnC,KAAK,OAAO,MAAM;EAClB,KAAK,UAAU,MAAM;CACtB;CAEA,aAAqB;EACpB,OAAO,KAAK,IAAI,GAAG,KAAK,aAAa,CAAC;CACvC;CAEA,SAAS,IAAY,WAA4B;EAChD,MAAM,UAAU,KAAK,MAAM,EAAE;EAE7B,KAAK,aAAa;EAClB,KAAK,UAAU,EAAE;EACjB,IAAI,KAAK,UAAU,OAAO,GAAG,OAAO;EAEpC,KAAK,YAAY;EACjB,IAAI,KAAK,iBAAiB,OAAO,KAAK,KAAK,UAAU,OAAO,GAAG,OAAO;EAEtE,IAAI,KAAK,YAAY,OAAO,GAAG,KAAK,KAAK,SAAS,SAAS;OACtD,KAAK,OAAO,OAAO;EACxB,KAAK,UAAU,OAAO;EAEtB,OAAO;CACR;CAEA,QAAQ,OAAqB,SAA4C;EACxE,MAAM,CAAC,OAAO,QAAQ,YAAY,KAAK,UAAU,CAAC;EAClD,IAAI,QAAQ,GAAG,OAAO;EAEtB,MAAM,MAAM,KAAK,MAAM,sBAAsB;EAC7C,MAAM,CAAC,OAAO,QAAQ,aACrB,KAAK,gBAAgB,aAClB;GAAC,IAAI;GAAK,IAAI;GAAQ,IAAI;EAAK,IAC/B;GAAC,IAAI;GAAM,IAAI;GAAO,IAAI;EAAM;EAEpC,OAAO,WAAW;GACjB,IAAI,KAAK,WAAW;GACpB,OAAO,KAAK,UAAU,CAAC,CAAC;GACxB;GACA;GACA;GACA,SAAS,KAAK,MAAM,KAAK;GACzB,MAAM;IAAE;IAAQ;IAAO;GAAU;EAClC,CAAC;CACF;AACD"}
@@ -73,16 +73,17 @@ export declare abstract class SonicValueElement extends SonicFormElement {
73
73
  get value(): number;
74
74
  set value(next: null | number | undefined);
75
75
  get valueText(): string;
76
+ protected abstract readonly control: HTMLElement;
76
77
  protected readonly notation: ValueNotation;
77
78
  attributeChangedCallback(name: string): void;
78
79
  connectedCallback(): void;
79
80
  protected abandonHold(): void;
80
- protected bindGestures(control: HTMLElement, signal: AbortSignal, grab: (event: PointerEvent) => undefined | ValueAxis): void;
81
+ protected bindGestures(signal: AbortSignal, grab: (event: PointerEvent) => undefined | ValueAxis): void;
81
82
  protected bindHover(control: HTMLElement, signal: AbortSignal, { canShow, changed, place, valueAt }: HoverBinding): void;
82
83
  protected controlOrientation(): 'horizontal' | 'vertical' | undefined;
83
84
  protected controlRole(): 'slider' | 'spinbutton';
84
85
  protected abstract draw(): void;
85
- protected abstract focusTarget(): HTMLElement;
86
+ protected focusTarget(): HTMLElement;
86
87
  protected input(next: number): boolean;
87
88
  protected isWrapping(): boolean;
88
89
  protected mapping(): ValueMapping;
@@ -98,7 +99,7 @@ export declare abstract class SonicValueElement extends SonicFormElement {
98
99
  protected spokenText(value: number): string | undefined;
99
100
  protected springTarget(): number | undefined;
100
101
  protected tapTarget(): number | undefined;
101
- protected travelPx(control: HTMLElement, property: `--_sonic-${string}`): number;
102
+ protected upwardAxis(travelProperty: `--_sonic-${string}`): ValueAxis;
102
103
  }
103
104
  //#endregion
104
105
  //# sourceMappingURL=value-element.d.ts.map
@@ -233,6 +233,7 @@ var SonicValueElement = class extends SonicFormElement {
233
233
  #model = createValueModel(() => this.#spec());
234
234
  #parseValue;
235
235
  #readout;
236
+ #said;
236
237
  attributeChangedCallback(name) {
237
238
  if (name === "value" && this.#isHeld()) {
238
239
  this.#writeHeld(this.numberAttribute("value", this.min));
@@ -250,7 +251,8 @@ var SonicValueElement = class extends SonicFormElement {
250
251
  abandonHold() {
251
252
  this.#gestures?.abandon();
252
253
  }
253
- bindGestures(control, signal, grab) {
254
+ bindGestures(signal, grab) {
255
+ const control = this.control;
254
256
  const bubble = control.querySelector("[popover]");
255
257
  if (bubble) this.#readout = {
256
258
  anchor: control,
@@ -315,6 +317,9 @@ var SonicValueElement = class extends SonicFormElement {
315
317
  controlRole() {
316
318
  return "slider";
317
319
  }
320
+ focusTarget() {
321
+ return this.control;
322
+ }
318
323
  input(next) {
319
324
  const previous = this.#model.value;
320
325
  if (this.#model.input(next)) this.render();
@@ -373,8 +378,12 @@ var SonicValueElement = class extends SonicFormElement {
373
378
  }
374
379
  springTarget() {}
375
380
  tapTarget() {}
376
- travelPx(control, property) {
377
- return Math.max(1, readPxProperty(getComputedStyle(control), property, fallbackTravelPx));
381
+ upwardAxis(travelProperty) {
382
+ const styles = getComputedStyle(this.control);
383
+ return {
384
+ position: (event) => -event.clientY,
385
+ travelPx: Math.max(1, readPxProperty(styles, travelProperty, fallbackTravelPx))
386
+ };
378
387
  }
379
388
  #entryText() {
380
389
  const value = this.#model.value;
@@ -407,7 +416,7 @@ var SonicValueElement = class extends SonicFormElement {
407
416
  writeAttribute(control, "aria-valuemax", String(high));
408
417
  writeAttribute(control, "aria-valuenow", String(this.#spokenNow()));
409
418
  if (orientation) writeAttribute(control, "aria-orientation", orientation);
410
- writeAttribute(control, "aria-valuetext", this.spokenText(this.#spokenNow()));
419
+ writeAttribute(control, "aria-valuetext", this.#spokenValueText());
411
420
  this.forwardNaming(control, true);
412
421
  this.#renderDisabled(control);
413
422
  this.renderReadout();
@@ -460,6 +469,19 @@ var SonicValueElement = class extends SonicFormElement {
460
469
  const step = this.spokenStep;
461
470
  return step !== void 0 && step > 0 ? trimFloat(Math.round(this.#model.value / step) * step) : this.#model.value;
462
471
  }
472
+ #spokenValueText() {
473
+ const format = this.#formatSpokenValue ?? this.#formatValue ?? this.notation.speak;
474
+ const value = this.#spokenNow();
475
+ const said = this.#said;
476
+ if (said && said.format === format && said.value === value) return said.text;
477
+ const text = format?.(value);
478
+ this.#said = {
479
+ format,
480
+ text,
481
+ value
482
+ };
483
+ return text;
484
+ }
463
485
  #textFor(value) {
464
486
  return (this.#formatValue ?? this.notation.format)(value);
465
487
  }
@@ -1 +1 @@
1
- {"version":3,"file":"value-element.js","names":[],"sources":["../../../src/elements/value-element.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.ts';\nimport type { HoverPreview } from '#lib/hover-preview.ts';\nimport type { ScrubState } from '#lib/scrub.ts';\nimport type { ValueMapping, ValueSpec } from '#lib/value-mapping.ts';\nimport type { ValueModel } from '#lib/value-model.ts';\n\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { isUnanchored, Readout } from '#elements/readout.ts';\nimport { ValueGestures } from '#elements/value-gestures.ts';\nimport { bindHoverPreview } from '#lib/hover-preview.ts';\nimport { toNumber, trimFloat } from '#lib/math.ts';\nimport { parseNumberList } from '#lib/number-list.ts';\nimport { readPxProperty } from '#lib/read-px-property.ts';\nimport { createValueModel } from '#lib/value-model.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementEventMap {\n\t\t'sonic-hover': Event;\n\t\t'sonic-reveal': Event;\n\t}\n}\n\ninterface HoverBinding {\n\tcanShow?: () => boolean;\n\tchanged?: () => void;\n\tplace: () => void;\n\tvalueAt: (event: PointerEvent) => number | undefined;\n}\n\nexport interface ValueNotation {\n\tformat: (value: number) => string;\n\tparse: (text: string) => number;\n\tspeak?: (value: number) => string;\n}\n\nconst fallbackTravelPx = 160;\n\nlet instanceCount = 0;\n\nconst proportionAttributes = new Set(['max', 'midpoint', 'min', 'notched', 'positions', 'taper']);\n\nconst roleAttributes = [\n\t'aria-disabled',\n\t'aria-orientation',\n\t'aria-valuemax',\n\t'aria-valuemin',\n\t'aria-valuenow',\n\t'aria-valuetext',\n\t'role',\n\t'tabindex',\n];\n\nexport interface ValueLink {\n\tchange: () => void;\n\tinput: (next: number, isMover?: boolean) => boolean;\n\tisDisabled: () => boolean;\n\tisHeld: () => boolean;\n\tmodel: ValueModel;\n}\n\n// Filled from inside the class, so it reaches protected members and nothing lands on the element's public type\nlet link: (element: SonicValueElement) => ValueLink;\n\nexport function linkValue(element: SonicValueElement): ValueLink {\n\treturn link(element);\n}\n\nexport abstract class SonicValueElement extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t'detent',\n\t\t'max',\n\t\t'midpoint',\n\t\t'min',\n\t\t'notched',\n\t\t'origin',\n\t\t'readout',\n\t\t'spoken-step',\n\t\t'step',\n\t\t'tabindex',\n\t\t'taper',\n\t\t'value',\n\t\t'positions',\n\t];\n\n\tstatic {\n\t\tlink = (element) => ({\n\t\t\tchange: () => {\n\t\t\t\telement.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t},\n\t\t\tinput: (next, isMover = false) => (isMover || !element.#isHeld()) && element.input(next),\n\t\t\tisDisabled: () => element.isDisabled(),\n\t\t\tisHeld: () => element.#isHeld(),\n\t\t\tmodel: element.#model,\n\t\t});\n\t}\n\n\tget cancelling(): boolean {\n\t\treturn this.hasState('cancelling');\n\t}\n\n\tget default(): number | undefined {\n\t\treturn this.optionalNumberAttribute('default');\n\t}\n\n\tset default(value: number | undefined) {\n\t\tthis.reflect('default', value);\n\t}\n\n\tget detent(): number | undefined {\n\t\treturn this.optionalNumberAttribute('detent');\n\t}\n\n\tset detent(value: number | undefined) {\n\t\tthis.reflect('detent', value);\n\t}\n\n\tget dimmed(): boolean {\n\t\treturn this.hasAttribute('dimmed');\n\t}\n\n\tset dimmed(isDimmed: boolean) {\n\t\tthis.reflect('dimmed', isDimmed);\n\t}\n\n\tget doublePress(): 'entry' | 'none' | 'reset' {\n\t\tconst gesture = this.getAttribute('double-press');\n\n\t\treturn gesture === 'none' || gesture === 'reset' ? gesture : 'entry';\n\t}\n\n\tset doublePress(gesture: 'entry' | 'none' | 'reset' | undefined) {\n\t\tthis.reflect('double-press', gesture);\n\t}\n\n\tget dragging(): boolean {\n\t\treturn this.hasState('dragging');\n\t}\n\n\tget entry(): 'none' | undefined {\n\t\treturn this.getAttribute('entry') === 'none' ? 'none' : undefined;\n\t}\n\n\tset entry(gesture: 'none' | undefined) {\n\t\tthis.reflect('entry', gesture);\n\t}\n\n\tget formatEntry(): ((value: number) => string) | undefined {\n\t\treturn this.#formatEntry;\n\t}\n\n\tset formatEntry(format: ((value: number) => string) | undefined) {\n\t\tthis.#formatEntry = format;\n\t}\n\n\tget formatSpokenValue(): ((value: number) => string) | undefined {\n\t\treturn this.#formatSpokenValue;\n\t}\n\n\tset formatSpokenValue(format: ((value: number) => string) | undefined) {\n\t\tthis.#formatSpokenValue = format;\n\t\tthis.render();\n\t}\n\n\tget formatValue(): ((value: number) => string) | undefined {\n\t\treturn this.#formatValue;\n\t}\n\n\tset formatValue(format: ((value: number) => string) | undefined) {\n\t\tthis.#formatValue = format;\n\t\tthis.render();\n\t}\n\n\tget hoverValue(): number | undefined {\n\t\treturn this.#hover?.value();\n\t}\n\n\tget keyStep(): number | undefined {\n\t\treturn this.optionalNumberAttribute('key-step');\n\t}\n\n\tset keyStep(value: number | undefined) {\n\t\tthis.reflect('key-step', value);\n\t}\n\n\tget max(): number {\n\t\treturn this.numberAttribute('max', 100);\n\t}\n\n\tset max(value: number | undefined) {\n\t\tthis.reflect('max', value);\n\t}\n\n\tget midpoint(): number | undefined {\n\t\treturn this.optionalNumberAttribute('midpoint');\n\t}\n\n\tset midpoint(value: number | undefined) {\n\t\tthis.reflect('midpoint', value);\n\t}\n\n\tget min(): number {\n\t\treturn this.numberAttribute('min', 0);\n\t}\n\n\tset min(value: number | undefined) {\n\t\tthis.reflect('min', value);\n\t}\n\n\tget notched(): boolean {\n\t\treturn this.hasAttribute('notched');\n\t}\n\n\tset notched(isNotched: boolean) {\n\t\tthis.reflect('notched', isNotched);\n\t}\n\n\tget origin(): number | undefined {\n\t\treturn this.optionalNumberAttribute('origin');\n\t}\n\n\tset origin(value: number | undefined) {\n\t\tthis.reflect('origin', value);\n\t}\n\n\tget parseValue(): ((text: string) => number) | undefined {\n\t\treturn this.#parseValue;\n\t}\n\n\tset parseValue(parse: ((text: string) => number) | undefined) {\n\t\tthis.#parseValue = parse;\n\t}\n\n\tget pointerType(): string | undefined {\n\t\treturn this.#gestures?.pointerType();\n\t}\n\n\t/** The values a stepped control stops at, spaced evenly along the travel whatever their spacing */\n\tget positions(): Array<number> | undefined {\n\t\treturn parseNumberList(this.getAttribute('positions'));\n\t}\n\n\tset positions(positions: Array<number> | undefined) {\n\t\tthis.reflect('positions', positions?.join(' '));\n\t}\n\n\tget readout(): boolean {\n\t\treturn this.hasAttribute('readout');\n\t}\n\n\tset readout(isShown: boolean) {\n\t\tthis.reflect('readout', isShown);\n\t}\n\n\tget revealed(): boolean {\n\t\treturn this.hasState('revealed');\n\t}\n\n\t/** Rounds the value assistive technology hears, in value units; the value itself is untouched */\n\tget spokenStep(): number | undefined {\n\t\treturn this.optionalNumberAttribute('spoken-step');\n\t}\n\n\tset spokenStep(value: number | undefined) {\n\t\tthis.reflect('spoken-step', value);\n\t}\n\n\tget step(): number {\n\t\treturn this.numberAttribute('step', 1);\n\t}\n\n\tset step(value: number | undefined) {\n\t\tthis.reflect('step', value);\n\t}\n\n\tget taper(): 'linear' | 'log' {\n\t\treturn this.getAttribute('taper') === 'log' ? 'log' : 'linear';\n\t}\n\n\tset taper(curve: 'linear' | 'log' | undefined) {\n\t\tthis.reflect('taper', curve);\n\t}\n\n\tget value(): number {\n\t\treturn this.#model.value;\n\t}\n\n\tset value(next: null | number | undefined) {\n\t\t// A framework removes a prop by setting the property to `undefined`\n\t\tif (next === undefined || next === null) {\n\t\t\tthis.formResetCallback();\n\t\t\treturn;\n\t\t}\n\n\t\tconst value = toNumber(next);\n\n\t\tif (this.#isHeld()) {\n\t\t\tthis.#writeHeld(value);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.#model.write(value)) this.render();\n\t}\n\n\tget valueText(): string {\n\t\treturn this.#textFor(this.#model.value);\n\t}\n\n\t// `Number.parseFloat`, as `Number('')` is 0; an emptied field should leave the value alone\n\tprotected readonly notation: ValueNotation = { format: String, parse: Number.parseFloat };\n\n\t#formatEntry: ((value: number) => string) | undefined;\n\n\t#formatSpokenValue: ((value: number) => string) | undefined;\n\n\t#formatValue: ((value: number) => string) | undefined;\n\n\t#gestures: undefined | ValueGestures;\n\n\t#heldAt: number | undefined;\n\n\t#hover: HoverPreview | undefined;\n\n\treadonly #instance = String((instanceCount += 1));\n\n\t// A subclass adding an input to `isWrapping()` has to observe it\n\treadonly #model = createValueModel(() => this.#spec());\n\n\t#parseValue: ((text: string) => number) | undefined;\n\n\t#readout: undefined | { anchor: HTMLElement; bubble: Readout };\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'value' && this.#isHeld()) {\n\t\t\tthis.#writeHeld(this.numberAttribute('value', this.min));\n\t\t\treturn;\n\t\t}\n\t\tif (name === 'disabled' && this.isDisabled()) this.#gestures?.end();\n\n\t\tthis.#model.respec(this.numberAttribute('value', this.min), { forgetAsk: name === 'value' });\n\t\tif (proportionAttributes.has(name) && this.isBound()) this.proportionsChanged();\n\t\tthis.render();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'default',\n\t\t\t'dimmed',\n\t\t\t'doublePress',\n\t\t\t'entry',\n\t\t\t'max',\n\t\t\t'midpoint',\n\t\t\t'min',\n\t\t\t'notched',\n\t\t\t'origin',\n\t\t\t'readout',\n\t\t\t'spokenStep',\n\t\t\t'step',\n\t\t\t'taper',\n\t\t\t'positions',\n\t\t\t'detent',\n\t\t\t'formatEntry',\n\t\t\t'formatValue',\n\t\t\t'formatSpokenValue',\n\t\t\t'keyStep',\n\t\t\t'parseValue',\n\t\t\t'value',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected abandonHold(): void {\n\t\tthis.#gestures?.abandon();\n\t}\n\n\tprotected bindGestures(\n\t\tcontrol: HTMLElement,\n\t\tsignal: AbortSignal,\n\t\tgrab: (event: PointerEvent) => undefined | ValueAxis,\n\t): void {\n\t\tconst bubble = control.querySelector<HTMLElement>('[popover]');\n\n\t\tif (bubble) this.#readout = { anchor: control, bubble: new Readout(bubble) };\n\t\tthis.#gestures = new ValueGestures(\n\t\t\t{\n\t\t\t\tdefault: () => this.default,\n\t\t\t\tdetent: () => this.detent,\n\t\t\t\tdispatch: (type) => {\n\t\t\t\t\tthis.dispatchEvent(new Event(type, { bubbles: true }));\n\t\t\t\t},\n\t\t\t\tdoublePress: () => this.doublePress,\n\t\t\t\tentryId: `sonic-entry-${this.#instance}`,\n\t\t\t\tentryText: () => this.#entryText(),\n\t\t\t\tforwardNaming: (target, isNamed) => {\n\t\t\t\t\tthis.forwardNaming(target, isNamed);\n\t\t\t\t},\n\t\t\t\thasEntry: () => !isUnanchored && this.entry !== 'none',\n\t\t\t\tinput: (next) => this.input(next),\n\t\t\t\tisDisabled: () => this.isDisabled(),\n\t\t\t\tkeyStep: () => this.keyStep,\n\t\t\t\tmodel: this.#model,\n\t\t\t\tparse: (text) => (this.#parseValue ?? this.notation.parse)(text),\n\t\t\t\trender: () => {\n\t\t\t\t\tthis.render();\n\t\t\t\t},\n\t\t\t\tscrubChanged: () => {\n\t\t\t\t\tthis.#renderHold();\n\t\t\t\t},\n\t\t\t\tscrubsKeyRepeat: () => this.scrubsKeyRepeat(),\n\t\t\t\tspringTarget: () => this.springTarget(),\n\t\t\t\ttapTarget: () => this.tapTarget(),\n\t\t\t\ttoggleState: (state, isOn) => {\n\t\t\t\t\tthis.toggleState(state, isOn);\n\t\t\t\t},\n\t\t\t},\n\t\t\tcontrol,\n\t\t\tthis.focusTarget(),\n\t\t);\n\t\tthis.#gestures.bind(signal, grab);\n\t\tcontrol.addEventListener(\n\t\t\t'pointerup',\n\t\t\t(event) => {\n\t\t\t\tthis.#hover?.lift(event);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\tprotected bindHover(\n\t\tcontrol: HTMLElement,\n\t\tsignal: AbortSignal,\n\t\t{ canShow = () => true, changed, place, valueAt }: HoverBinding,\n\t): void {\n\t\tthis.#hover = bindHoverPreview(\n\t\t\tcontrol,\n\t\t\t{\n\t\t\t\tcanShow,\n\t\t\t\tchanged: () => {\n\t\t\t\t\tchanged?.();\n\t\t\t\t\tthis.dispatchEvent(new Event('sonic-hover', { bubbles: true }));\n\t\t\t\t},\n\t\t\t\tdismiss: () => {\n\t\t\t\t\tthis.render();\n\t\t\t\t},\n\t\t\t\tisTaken: () => this.#isEditing() || this.#isRevealed(),\n\t\t\t\tshow: () => {\n\t\t\t\t\tif (!this.readout) return;\n\n\t\t\t\t\tthis.renderReadout();\n\t\t\t\t\tplace();\n\t\t\t\t},\n\t\t\t\tvalueAt,\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\tprotected controlOrientation(): 'horizontal' | 'vertical' | undefined {\n\t\treturn undefined;\n\t}\n\n\tprotected controlRole(): 'slider' | 'spinbutton' {\n\t\treturn 'slider';\n\t}\n\n\tprotected abstract draw(): void;\n\n\tprotected abstract override focusTarget(): HTMLElement;\n\n\tprotected input(next: number): boolean {\n\t\tconst previous = this.#model.value;\n\n\t\tif (this.#model.input(next)) this.render();\n\t\tif (this.#model.value === previous) return false;\n\n\t\tthis.dispatchEvent(new Event('input', { bubbles: true, composed: true }));\n\n\t\treturn true;\n\t}\n\n\tprotected isWrapping(): boolean {\n\t\treturn false;\n\t}\n\n\tprotected mapping(): ValueMapping {\n\t\treturn this.#model.mapping();\n\t}\n\n\tprotected originValue(): number {\n\t\treturn this.origin ?? this.mapping().bounds[0];\n\t}\n\n\tprotected proportionsChanged(): void {\n\t\t// Runs when every proportion moves, never per input\n\t}\n\n\tprotected readoutValue(): number {\n\t\treturn this.#shown().value ?? this.#model.value;\n\t}\n\n\tprotected render(): void {\n\t\tif (this.isBound()) this.#renderControl();\n\t\tthis.#model.notify();\n\t}\n\n\tprotected renderReadout(): void {\n\t\tconst readout = this.#readout;\n\t\tif (!readout) return;\n\n\t\tconst { isOpen, value } = this.#shown();\n\t\tconst text = value === undefined ? undefined : this.#textFor(value);\n\n\t\treadout.bubble.show({ anchor: readout.anchor, isOpen: isOpen && text !== '', text });\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tthis.value = Number(state);\n\t}\n\n\tprotected scrubChanged(): void {\n\t\t// Only a subclass draws a scrub\n\t}\n\n\tprotected scrubsKeyRepeat(): boolean {\n\t\treturn false;\n\t}\n\n\tprotected scrubState(): ScrubState {\n\t\tconst from = this.#gestures?.scrubFrom();\n\n\t\treturn {\n\t\t\tfrom,\n\t\t\tisRevealed: this.#isRevealed(),\n\t\t\tplayed: from === undefined ? this.#model.value : (this.#heldAt ?? from),\n\t\t};\n\t}\n\n\tprotected spokenText(value: number): string | undefined {\n\t\treturn (this.#formatSpokenValue ?? this.#formatValue ?? this.notation.speak)?.(value);\n\t}\n\n\tprotected springTarget(): number | undefined {\n\t\treturn undefined;\n\t}\n\n\tprotected tapTarget(): number | undefined {\n\t\treturn undefined;\n\t}\n\n\tprotected travelPx(control: HTMLElement, property: `--_sonic-${string}`): number {\n\t\treturn Math.max(1, readPxProperty(getComputedStyle(control), property, fallbackTravelPx));\n\t}\n\n\t#entryText(): string {\n\t\tconst value = this.#model.value;\n\n\t\tif (this.#formatEntry) return this.#formatEntry(value);\n\n\t\t// The notation's own parser may not read a consumer's `formatValue`\n\t\tif (!this.#parseValue) return this.notation.format(value);\n\n\t\tconst text = this.valueText;\n\n\t\tif (__DEV__ && !Number.isFinite(this.#parseValue(text))) {\n\t\t\tconsole.warn(\n\t\t\t\t`<${this.localName}> opens its entry with \"${text}\", which parseValue cannot read; set formatEntry`,\n\t\t\t);\n\t\t}\n\n\t\treturn text;\n\t}\n\n\t#isEditing(): boolean {\n\t\treturn this.#gestures?.isEditing() === true;\n\t}\n\n\t#isHeld(): boolean {\n\t\treturn this.#gestures?.isHeld() === true;\n\t}\n\n\t#isRevealed(): boolean {\n\t\treturn this.#gestures?.isRevealed() === true;\n\t}\n\n\t#renderAria(control: HTMLElement, orientation?: 'horizontal' | 'vertical'): void {\n\t\tthis.writeFormValue(String(this.#model.value), String(this.#model.value));\n\t\tif (this.#isEditing()) {\n\t\t\tfor (const name of roleAttributes) control.removeAttribute(name);\n\t\t\tthis.forwardNaming(control, false);\n\t\t\tthis.renderReadout();\n\t\t\treturn;\n\t\t}\n\n\t\tconst [low, high] = this.mapping().bounds;\n\n\t\twriteAttribute(control, 'role', this.controlRole());\n\t\twriteAttribute(control, 'aria-valuemin', String(low));\n\t\twriteAttribute(control, 'aria-valuemax', String(high));\n\t\twriteAttribute(control, 'aria-valuenow', String(this.#spokenNow()));\n\t\tif (orientation) writeAttribute(control, 'aria-orientation', orientation);\n\t\twriteAttribute(control, 'aria-valuetext', this.spokenText(this.#spokenNow()));\n\t\tthis.forwardNaming(control, true);\n\t\tthis.#renderDisabled(control);\n\t\tthis.renderReadout();\n\t}\n\n\t#renderControl(): void {\n\t\t// A reveal or typed entry takes the readout from a hover\n\t\tif (this.#isEditing() || this.#isRevealed()) this.#hover?.clear();\n\t\tthis.draw();\n\t\tthis.toggleState(\n\t\t\t'at-origin',\n\t\t\t!this.isWrapping() && this.#model.value === this.mapping().snap(this.originValue()),\n\t\t);\n\t\tthis.#renderAria(this.focusTarget(), this.controlOrientation());\n\t}\n\n\t#renderDisabled(control: HTMLElement): void {\n\t\tconst isDisabled = this.isDisabled();\n\n\t\twriteAttribute(control, 'aria-disabled', isDisabled ? 'true' : undefined);\n\t\t// The host is `display: contents`, so its tab stop is the control's\n\t\tconst stop = this.getAttribute('tabindex') === '-1' ? '-1' : '0';\n\n\t\twriteAttribute(control, 'tabindex', isDisabled ? undefined : stop);\n\t}\n\n\t#renderHold(): void {\n\t\tif (this.#gestures?.scrubFrom() === undefined) this.#heldAt = undefined;\n\t\tthis.#hover?.restore();\n\t\tthis.renderReadout();\n\t\tthis.scrubChanged();\n\t}\n\n\t#shown(): { isOpen: boolean; value: number | undefined } {\n\t\tif (this.#isEditing()) return { isOpen: true, value: undefined };\n\n\t\tconst isRevealed = this.#isRevealed();\n\t\tconst preview = isRevealed ? undefined : this.#hover?.value();\n\n\t\treturn {\n\t\t\tisOpen: (isRevealed || preview !== undefined) && this.readout,\n\t\t\tvalue: preview ?? this.#model.value,\n\t\t};\n\t}\n\n\t#spec(): ValueSpec {\n\t\tconst { detent, midpoint, positions } = this;\n\n\t\treturn {\n\t\t\t...(detent === undefined ? {} : { detent }),\n\t\t\t...(positions ? { positions } : {}),\n\t\t\tmax: this.max,\n\t\t\t...(midpoint === undefined ? {} : { midpoint }),\n\t\t\tisNotched: this.notched,\n\t\t\tisWrapping: this.isWrapping(),\n\t\t\tmin: this.min,\n\t\t\tstep: this.step,\n\t\t\ttaper: this.taper,\n\t\t};\n\t}\n\n\t#spokenNow(): number {\n\t\tconst step = this.spokenStep;\n\n\t\treturn step !== undefined && step > 0\n\t\t\t? trimFloat(Math.round(this.#model.value / step) * step)\n\t\t\t: this.#model.value;\n\t}\n\n\t#textFor(value: number): string {\n\t\treturn (this.#formatValue ?? this.notation.format)(value);\n\t}\n\n\t#writeHeld(next: number): void {\n\t\tthis.#heldAt = next;\n\t\tthis.scrubChanged();\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"value-element.js","names":[],"sources":["../../../src/elements/value-element.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.ts';\nimport type { HoverPreview } from '#lib/hover-preview.ts';\nimport type { ScrubState } from '#lib/scrub.ts';\nimport type { ValueMapping, ValueSpec } from '#lib/value-mapping.ts';\nimport type { ValueModel } from '#lib/value-model.ts';\n\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { isUnanchored, Readout } from '#elements/readout.ts';\nimport { ValueGestures } from '#elements/value-gestures.ts';\nimport { bindHoverPreview } from '#lib/hover-preview.ts';\nimport { toNumber, trimFloat } from '#lib/math.ts';\nimport { parseNumberList } from '#lib/number-list.ts';\nimport { readPxProperty } from '#lib/read-px-property.ts';\nimport { createValueModel } from '#lib/value-model.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementEventMap {\n\t\t'sonic-hover': Event;\n\t\t'sonic-reveal': Event;\n\t}\n}\n\ninterface HoverBinding {\n\tcanShow?: () => boolean;\n\tchanged?: () => void;\n\tplace: () => void;\n\tvalueAt: (event: PointerEvent) => number | undefined;\n}\n\nexport interface ValueNotation {\n\tformat: (value: number) => string;\n\tparse: (text: string) => number;\n\tspeak?: (value: number) => string;\n}\n\nconst fallbackTravelPx = 160;\n\nlet instanceCount = 0;\n\nconst proportionAttributes = new Set(['max', 'midpoint', 'min', 'notched', 'positions', 'taper']);\n\nconst roleAttributes = [\n\t'aria-disabled',\n\t'aria-orientation',\n\t'aria-valuemax',\n\t'aria-valuemin',\n\t'aria-valuenow',\n\t'aria-valuetext',\n\t'role',\n\t'tabindex',\n];\n\nexport interface ValueLink {\n\tchange: () => void;\n\tinput: (next: number, isMover?: boolean) => boolean;\n\tisDisabled: () => boolean;\n\tisHeld: () => boolean;\n\tmodel: ValueModel;\n}\n\n// Filled from inside the class, so it reaches protected members and nothing lands on the element's public type\nlet link: (element: SonicValueElement) => ValueLink;\n\nexport function linkValue(element: SonicValueElement): ValueLink {\n\treturn link(element);\n}\n\nexport abstract class SonicValueElement extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t'detent',\n\t\t'max',\n\t\t'midpoint',\n\t\t'min',\n\t\t'notched',\n\t\t'origin',\n\t\t'readout',\n\t\t'spoken-step',\n\t\t'step',\n\t\t'tabindex',\n\t\t'taper',\n\t\t'value',\n\t\t'positions',\n\t];\n\n\tstatic {\n\t\tlink = (element) => ({\n\t\t\tchange: () => {\n\t\t\t\telement.dispatchEvent(new Event('change', { bubbles: true }));\n\t\t\t},\n\t\t\tinput: (next, isMover = false) => (isMover || !element.#isHeld()) && element.input(next),\n\t\t\tisDisabled: () => element.isDisabled(),\n\t\t\tisHeld: () => element.#isHeld(),\n\t\t\tmodel: element.#model,\n\t\t});\n\t}\n\n\tget cancelling(): boolean {\n\t\treturn this.hasState('cancelling');\n\t}\n\n\tget default(): number | undefined {\n\t\treturn this.optionalNumberAttribute('default');\n\t}\n\n\tset default(value: number | undefined) {\n\t\tthis.reflect('default', value);\n\t}\n\n\tget detent(): number | undefined {\n\t\treturn this.optionalNumberAttribute('detent');\n\t}\n\n\tset detent(value: number | undefined) {\n\t\tthis.reflect('detent', value);\n\t}\n\n\tget dimmed(): boolean {\n\t\treturn this.hasAttribute('dimmed');\n\t}\n\n\tset dimmed(isDimmed: boolean) {\n\t\tthis.reflect('dimmed', isDimmed);\n\t}\n\n\tget doublePress(): 'entry' | 'none' | 'reset' {\n\t\tconst gesture = this.getAttribute('double-press');\n\n\t\treturn gesture === 'none' || gesture === 'reset' ? gesture : 'entry';\n\t}\n\n\tset doublePress(gesture: 'entry' | 'none' | 'reset' | undefined) {\n\t\tthis.reflect('double-press', gesture);\n\t}\n\n\tget dragging(): boolean {\n\t\treturn this.hasState('dragging');\n\t}\n\n\tget entry(): 'none' | undefined {\n\t\treturn this.getAttribute('entry') === 'none' ? 'none' : undefined;\n\t}\n\n\tset entry(gesture: 'none' | undefined) {\n\t\tthis.reflect('entry', gesture);\n\t}\n\n\tget formatEntry(): ((value: number) => string) | undefined {\n\t\treturn this.#formatEntry;\n\t}\n\n\tset formatEntry(format: ((value: number) => string) | undefined) {\n\t\tthis.#formatEntry = format;\n\t}\n\n\tget formatSpokenValue(): ((value: number) => string) | undefined {\n\t\treturn this.#formatSpokenValue;\n\t}\n\n\tset formatSpokenValue(format: ((value: number) => string) | undefined) {\n\t\tthis.#formatSpokenValue = format;\n\t\tthis.render();\n\t}\n\n\tget formatValue(): ((value: number) => string) | undefined {\n\t\treturn this.#formatValue;\n\t}\n\n\tset formatValue(format: ((value: number) => string) | undefined) {\n\t\tthis.#formatValue = format;\n\t\tthis.render();\n\t}\n\n\tget hoverValue(): number | undefined {\n\t\treturn this.#hover?.value();\n\t}\n\n\tget keyStep(): number | undefined {\n\t\treturn this.optionalNumberAttribute('key-step');\n\t}\n\n\tset keyStep(value: number | undefined) {\n\t\tthis.reflect('key-step', value);\n\t}\n\n\tget max(): number {\n\t\treturn this.numberAttribute('max', 100);\n\t}\n\n\tset max(value: number | undefined) {\n\t\tthis.reflect('max', value);\n\t}\n\n\tget midpoint(): number | undefined {\n\t\treturn this.optionalNumberAttribute('midpoint');\n\t}\n\n\tset midpoint(value: number | undefined) {\n\t\tthis.reflect('midpoint', value);\n\t}\n\n\tget min(): number {\n\t\treturn this.numberAttribute('min', 0);\n\t}\n\n\tset min(value: number | undefined) {\n\t\tthis.reflect('min', value);\n\t}\n\n\tget notched(): boolean {\n\t\treturn this.hasAttribute('notched');\n\t}\n\n\tset notched(isNotched: boolean) {\n\t\tthis.reflect('notched', isNotched);\n\t}\n\n\tget origin(): number | undefined {\n\t\treturn this.optionalNumberAttribute('origin');\n\t}\n\n\tset origin(value: number | undefined) {\n\t\tthis.reflect('origin', value);\n\t}\n\n\tget parseValue(): ((text: string) => number) | undefined {\n\t\treturn this.#parseValue;\n\t}\n\n\tset parseValue(parse: ((text: string) => number) | undefined) {\n\t\tthis.#parseValue = parse;\n\t}\n\n\tget pointerType(): string | undefined {\n\t\treturn this.#gestures?.pointerType();\n\t}\n\n\t/** The values a stepped control stops at, spaced evenly along the travel whatever their spacing */\n\tget positions(): Array<number> | undefined {\n\t\treturn parseNumberList(this.getAttribute('positions'));\n\t}\n\n\tset positions(positions: Array<number> | undefined) {\n\t\tthis.reflect('positions', positions?.join(' '));\n\t}\n\n\tget readout(): boolean {\n\t\treturn this.hasAttribute('readout');\n\t}\n\n\tset readout(isShown: boolean) {\n\t\tthis.reflect('readout', isShown);\n\t}\n\n\tget revealed(): boolean {\n\t\treturn this.hasState('revealed');\n\t}\n\n\t/** Rounds the value assistive technology hears, in value units; the value itself is untouched */\n\tget spokenStep(): number | undefined {\n\t\treturn this.optionalNumberAttribute('spoken-step');\n\t}\n\n\tset spokenStep(value: number | undefined) {\n\t\tthis.reflect('spoken-step', value);\n\t}\n\n\tget step(): number {\n\t\treturn this.numberAttribute('step', 1);\n\t}\n\n\tset step(value: number | undefined) {\n\t\tthis.reflect('step', value);\n\t}\n\n\tget taper(): 'linear' | 'log' {\n\t\treturn this.getAttribute('taper') === 'log' ? 'log' : 'linear';\n\t}\n\n\tset taper(curve: 'linear' | 'log' | undefined) {\n\t\tthis.reflect('taper', curve);\n\t}\n\n\tget value(): number {\n\t\treturn this.#model.value;\n\t}\n\n\tset value(next: null | number | undefined) {\n\t\t// A framework removes a prop by setting the property to `undefined`\n\t\tif (next === undefined || next === null) {\n\t\t\tthis.formResetCallback();\n\t\t\treturn;\n\t\t}\n\n\t\tconst value = toNumber(next);\n\n\t\tif (this.#isHeld()) {\n\t\t\tthis.#writeHeld(value);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.#model.write(value)) this.render();\n\t}\n\n\tget valueText(): string {\n\t\treturn this.#textFor(this.#model.value);\n\t}\n\n\tprotected abstract override readonly control: HTMLElement;\n\n\t// `Number.parseFloat`, as `Number('')` is 0; an emptied field should leave the value alone\n\tprotected readonly notation: ValueNotation = { format: String, parse: Number.parseFloat };\n\n\t#formatEntry: ((value: number) => string) | undefined;\n\n\t#formatSpokenValue: ((value: number) => string) | undefined;\n\n\t#formatValue: ((value: number) => string) | undefined;\n\n\t#gestures: undefined | ValueGestures;\n\n\t#heldAt: number | undefined;\n\n\t#hover: HoverPreview | undefined;\n\n\treadonly #instance = String((instanceCount += 1));\n\n\t// A subclass adding an input to `isWrapping()` has to observe it\n\treadonly #model = createValueModel(() => this.#spec());\n\n\t#parseValue: ((text: string) => number) | undefined;\n\n\t#readout: undefined | { anchor: HTMLElement; bubble: Readout };\n\n\t#said:\n\t\t| undefined\n\t\t| { format: ((value: number) => string) | undefined; text: string | undefined; value: number };\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'value' && this.#isHeld()) {\n\t\t\tthis.#writeHeld(this.numberAttribute('value', this.min));\n\t\t\treturn;\n\t\t}\n\t\tif (name === 'disabled' && this.isDisabled()) this.#gestures?.end();\n\n\t\tthis.#model.respec(this.numberAttribute('value', this.min), { forgetAsk: name === 'value' });\n\t\tif (proportionAttributes.has(name) && this.isBound()) this.proportionsChanged();\n\t\tthis.render();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'default',\n\t\t\t'dimmed',\n\t\t\t'doublePress',\n\t\t\t'entry',\n\t\t\t'max',\n\t\t\t'midpoint',\n\t\t\t'min',\n\t\t\t'notched',\n\t\t\t'origin',\n\t\t\t'readout',\n\t\t\t'spokenStep',\n\t\t\t'step',\n\t\t\t'taper',\n\t\t\t'positions',\n\t\t\t'detent',\n\t\t\t'formatEntry',\n\t\t\t'formatValue',\n\t\t\t'formatSpokenValue',\n\t\t\t'keyStep',\n\t\t\t'parseValue',\n\t\t\t'value',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected abandonHold(): void {\n\t\tthis.#gestures?.abandon();\n\t}\n\n\tprotected bindGestures(\n\t\tsignal: AbortSignal,\n\t\tgrab: (event: PointerEvent) => undefined | ValueAxis,\n\t): void {\n\t\tconst control = this.control;\n\t\tconst bubble = control.querySelector<HTMLElement>('[popover]');\n\n\t\tif (bubble) this.#readout = { anchor: control, bubble: new Readout(bubble) };\n\t\tthis.#gestures = new ValueGestures(\n\t\t\t{\n\t\t\t\tdefault: () => this.default,\n\t\t\t\tdetent: () => this.detent,\n\t\t\t\tdispatch: (type) => {\n\t\t\t\t\tthis.dispatchEvent(new Event(type, { bubbles: true }));\n\t\t\t\t},\n\t\t\t\tdoublePress: () => this.doublePress,\n\t\t\t\tentryId: `sonic-entry-${this.#instance}`,\n\t\t\t\tentryText: () => this.#entryText(),\n\t\t\t\tforwardNaming: (target, isNamed) => {\n\t\t\t\t\tthis.forwardNaming(target, isNamed);\n\t\t\t\t},\n\t\t\t\thasEntry: () => !isUnanchored && this.entry !== 'none',\n\t\t\t\tinput: (next) => this.input(next),\n\t\t\t\tisDisabled: () => this.isDisabled(),\n\t\t\t\tkeyStep: () => this.keyStep,\n\t\t\t\tmodel: this.#model,\n\t\t\t\tparse: (text) => (this.#parseValue ?? this.notation.parse)(text),\n\t\t\t\trender: () => {\n\t\t\t\t\tthis.render();\n\t\t\t\t},\n\t\t\t\tscrubChanged: () => {\n\t\t\t\t\tthis.#renderHold();\n\t\t\t\t},\n\t\t\t\tscrubsKeyRepeat: () => this.scrubsKeyRepeat(),\n\t\t\t\tspringTarget: () => this.springTarget(),\n\t\t\t\ttapTarget: () => this.tapTarget(),\n\t\t\t\ttoggleState: (state, isOn) => {\n\t\t\t\t\tthis.toggleState(state, isOn);\n\t\t\t\t},\n\t\t\t},\n\t\t\tcontrol,\n\t\t\tthis.focusTarget(),\n\t\t);\n\t\tthis.#gestures.bind(signal, grab);\n\t\tcontrol.addEventListener(\n\t\t\t'pointerup',\n\t\t\t(event) => {\n\t\t\t\tthis.#hover?.lift(event);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\tprotected bindHover(\n\t\tcontrol: HTMLElement,\n\t\tsignal: AbortSignal,\n\t\t{ canShow = () => true, changed, place, valueAt }: HoverBinding,\n\t): void {\n\t\tthis.#hover = bindHoverPreview(\n\t\t\tcontrol,\n\t\t\t{\n\t\t\t\tcanShow,\n\t\t\t\tchanged: () => {\n\t\t\t\t\tchanged?.();\n\t\t\t\t\tthis.dispatchEvent(new Event('sonic-hover', { bubbles: true }));\n\t\t\t\t},\n\t\t\t\tdismiss: () => {\n\t\t\t\t\tthis.render();\n\t\t\t\t},\n\t\t\t\tisTaken: () => this.#isEditing() || this.#isRevealed(),\n\t\t\t\tshow: () => {\n\t\t\t\t\tif (!this.readout) return;\n\n\t\t\t\t\tthis.renderReadout();\n\t\t\t\t\tplace();\n\t\t\t\t},\n\t\t\t\tvalueAt,\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\tprotected controlOrientation(): 'horizontal' | 'vertical' | undefined {\n\t\treturn undefined;\n\t}\n\n\tprotected controlRole(): 'slider' | 'spinbutton' {\n\t\treturn 'slider';\n\t}\n\n\tprotected abstract draw(): void;\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.control;\n\t}\n\n\tprotected input(next: number): boolean {\n\t\tconst previous = this.#model.value;\n\n\t\tif (this.#model.input(next)) this.render();\n\t\tif (this.#model.value === previous) return false;\n\n\t\tthis.dispatchEvent(new Event('input', { bubbles: true, composed: true }));\n\n\t\treturn true;\n\t}\n\n\tprotected isWrapping(): boolean {\n\t\treturn false;\n\t}\n\n\tprotected mapping(): ValueMapping {\n\t\treturn this.#model.mapping();\n\t}\n\n\tprotected originValue(): number {\n\t\treturn this.origin ?? this.mapping().bounds[0];\n\t}\n\n\tprotected proportionsChanged(): void {\n\t\t// Runs when every proportion moves, never per input\n\t}\n\n\tprotected readoutValue(): number {\n\t\treturn this.#shown().value ?? this.#model.value;\n\t}\n\n\tprotected render(): void {\n\t\tif (this.isBound()) this.#renderControl();\n\t\tthis.#model.notify();\n\t}\n\n\tprotected renderReadout(): void {\n\t\tconst readout = this.#readout;\n\t\tif (!readout) return;\n\n\t\tconst { isOpen, value } = this.#shown();\n\t\tconst text = value === undefined ? undefined : this.#textFor(value);\n\n\t\treadout.bubble.show({ anchor: readout.anchor, isOpen: isOpen && text !== '', text });\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tthis.value = Number(state);\n\t}\n\n\tprotected scrubChanged(): void {\n\t\t// Only a subclass draws a scrub\n\t}\n\n\tprotected scrubsKeyRepeat(): boolean {\n\t\treturn false;\n\t}\n\n\tprotected scrubState(): ScrubState {\n\t\tconst from = this.#gestures?.scrubFrom();\n\n\t\treturn {\n\t\t\tfrom,\n\t\t\tisRevealed: this.#isRevealed(),\n\t\t\tplayed: from === undefined ? this.#model.value : (this.#heldAt ?? from),\n\t\t};\n\t}\n\n\tprotected spokenText(value: number): string | undefined {\n\t\treturn (this.#formatSpokenValue ?? this.#formatValue ?? this.notation.speak)?.(value);\n\t}\n\n\tprotected springTarget(): number | undefined {\n\t\treturn undefined;\n\t}\n\n\tprotected tapTarget(): number | undefined {\n\t\treturn undefined;\n\t}\n\n\tprotected upwardAxis(travelProperty: `--_sonic-${string}`): ValueAxis {\n\t\tconst styles = getComputedStyle(this.control);\n\n\t\treturn {\n\t\t\tposition: (event) => -event.clientY,\n\t\t\ttravelPx: Math.max(1, readPxProperty(styles, travelProperty, fallbackTravelPx)),\n\t\t};\n\t}\n\n\t#entryText(): string {\n\t\tconst value = this.#model.value;\n\n\t\tif (this.#formatEntry) return this.#formatEntry(value);\n\n\t\t// The notation's own parser may not read a consumer's `formatValue`\n\t\tif (!this.#parseValue) return this.notation.format(value);\n\n\t\tconst text = this.valueText;\n\n\t\tif (__DEV__ && !Number.isFinite(this.#parseValue(text))) {\n\t\t\tconsole.warn(\n\t\t\t\t`<${this.localName}> opens its entry with \"${text}\", which parseValue cannot read; set formatEntry`,\n\t\t\t);\n\t\t}\n\n\t\treturn text;\n\t}\n\n\t#isEditing(): boolean {\n\t\treturn this.#gestures?.isEditing() === true;\n\t}\n\n\t#isHeld(): boolean {\n\t\treturn this.#gestures?.isHeld() === true;\n\t}\n\n\t#isRevealed(): boolean {\n\t\treturn this.#gestures?.isRevealed() === true;\n\t}\n\n\t#renderAria(control: HTMLElement, orientation?: 'horizontal' | 'vertical'): void {\n\t\tthis.writeFormValue(String(this.#model.value), String(this.#model.value));\n\t\tif (this.#isEditing()) {\n\t\t\tfor (const name of roleAttributes) control.removeAttribute(name);\n\t\t\tthis.forwardNaming(control, false);\n\t\t\tthis.renderReadout();\n\t\t\treturn;\n\t\t}\n\n\t\tconst [low, high] = this.mapping().bounds;\n\n\t\twriteAttribute(control, 'role', this.controlRole());\n\t\twriteAttribute(control, 'aria-valuemin', String(low));\n\t\twriteAttribute(control, 'aria-valuemax', String(high));\n\t\twriteAttribute(control, 'aria-valuenow', String(this.#spokenNow()));\n\t\tif (orientation) writeAttribute(control, 'aria-orientation', orientation);\n\t\twriteAttribute(control, 'aria-valuetext', this.#spokenValueText());\n\t\tthis.forwardNaming(control, true);\n\t\tthis.#renderDisabled(control);\n\t\tthis.renderReadout();\n\t}\n\n\t#renderControl(): void {\n\t\t// A reveal or typed entry takes the readout from a hover\n\t\tif (this.#isEditing() || this.#isRevealed()) this.#hover?.clear();\n\t\tthis.draw();\n\t\tthis.toggleState(\n\t\t\t'at-origin',\n\t\t\t!this.isWrapping() && this.#model.value === this.mapping().snap(this.originValue()),\n\t\t);\n\t\tthis.#renderAria(this.focusTarget(), this.controlOrientation());\n\t}\n\n\t#renderDisabled(control: HTMLElement): void {\n\t\tconst isDisabled = this.isDisabled();\n\n\t\twriteAttribute(control, 'aria-disabled', isDisabled ? 'true' : undefined);\n\t\t// The host is `display: contents`, so its tab stop is the control's\n\t\tconst stop = this.getAttribute('tabindex') === '-1' ? '-1' : '0';\n\n\t\twriteAttribute(control, 'tabindex', isDisabled ? undefined : stop);\n\t}\n\n\t#renderHold(): void {\n\t\tif (this.#gestures?.scrubFrom() === undefined) this.#heldAt = undefined;\n\t\tthis.#hover?.restore();\n\t\tthis.renderReadout();\n\t\tthis.scrubChanged();\n\t}\n\n\t#shown(): { isOpen: boolean; value: number | undefined } {\n\t\tif (this.#isEditing()) return { isOpen: true, value: undefined };\n\n\t\tconst isRevealed = this.#isRevealed();\n\t\tconst preview = isRevealed ? undefined : this.#hover?.value();\n\n\t\treturn {\n\t\t\tisOpen: (isRevealed || preview !== undefined) && this.readout,\n\t\t\tvalue: preview ?? this.#model.value,\n\t\t};\n\t}\n\n\t#spec(): ValueSpec {\n\t\tconst { detent, midpoint, positions } = this;\n\n\t\treturn {\n\t\t\t...(detent === undefined ? {} : { detent }),\n\t\t\t...(positions ? { positions } : {}),\n\t\t\tmax: this.max,\n\t\t\t...(midpoint === undefined ? {} : { midpoint }),\n\t\t\tisNotched: this.notched,\n\t\t\tisWrapping: this.isWrapping(),\n\t\t\tmin: this.min,\n\t\t\tstep: this.step,\n\t\t\ttaper: this.taper,\n\t\t};\n\t}\n\n\t#spokenNow(): number {\n\t\tconst step = this.spokenStep;\n\n\t\treturn step !== undefined && step > 0\n\t\t\t? trimFloat(Math.round(this.#model.value / step) * step)\n\t\t\t: this.#model.value;\n\t}\n\n\t#spokenValueText(): string | undefined {\n\t\tconst format = this.#formatSpokenValue ?? this.#formatValue ?? this.notation.speak;\n\t\tconst value = this.#spokenNow();\n\t\tconst said = this.#said;\n\t\tif (said && said.format === format && said.value === value) return said.text;\n\n\t\tconst text = format?.(value);\n\n\t\tthis.#said = { format, text, value };\n\n\t\treturn text;\n\t}\n\n\t#textFor(value: number): string {\n\t\treturn (this.#formatValue ?? this.notation.format)(value);\n\t}\n\n\t#writeHeld(next: number): void {\n\t\tthis.#heldAt = next;\n\t\tthis.scrubChanged();\n\t}\n}\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { BoxProbe } from "../lib/check-styles.js";
1
2
  import { SonicValueElement, ValueNotation } from "./value-element.js";
2
3
  import { Seconds } from "../lib/units.js";
3
4
  import { Surface, SurfaceFrame } from "../lib/canvas-surface.js";
@@ -26,9 +27,9 @@ export declare abstract class SonicWaveElement<Colour extends string, Length ext
26
27
  set fill(isFilling: boolean);
27
28
  get markers(): Array<WaveMarker>;
28
29
  set markers(list: Iterable<WaveMarker> | undefined);
30
+ protected readonly boxProbe: BoxProbe;
29
31
  protected abstract readonly canvas: HTMLCanvasElement;
30
32
  protected abstract readonly colours: Record<Colour, `--_sonic-${string}`>;
31
- protected abstract readonly control: HTMLElement;
32
33
  protected abstract readonly lengths: Record<Length, `--_sonic-${string}`>;
33
34
  protected readonly notation: ValueNotation;
34
35
  protected abstract readonly numbers: Record<Numeric, `--_sonic-${string}`>;