@xsynaptic/sonic-ui 0.25.1 → 0.26.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 (80) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +1 -1
  3. package/dist/default/elements/button.js +3 -0
  4. package/dist/default/elements/dial.js +3 -0
  5. package/dist/default/elements/envelope.js +3 -0
  6. package/dist/default/elements/meter.js +3 -0
  7. package/dist/default/elements/number.js +3 -0
  8. package/dist/default/elements/radio-group.js +4 -1
  9. package/dist/default/elements/region-layer.js +3 -1
  10. package/dist/default/elements/region.js +5 -1
  11. package/dist/default/elements/slider.js +3 -0
  12. package/dist/default/elements/sonic-element.js +4 -9
  13. package/dist/default/elements/toggle.js +2 -2
  14. package/dist/default/elements/value-element.js +15 -1
  15. package/dist/default/elements/wave-element.js +3 -0
  16. package/dist/default/elements/xy.js +3 -0
  17. package/dist/default/lib/canvas-surface.js +22 -1
  18. package/dist/default/lib/key-press.js +2 -2
  19. package/dist/dev/elements/button.d.ts +1 -0
  20. package/dist/dev/elements/button.js +3 -0
  21. package/dist/dev/elements/button.js.map +1 -1
  22. package/dist/dev/elements/dial.d.ts +1 -0
  23. package/dist/dev/elements/dial.js +3 -0
  24. package/dist/dev/elements/dial.js.map +1 -1
  25. package/dist/dev/elements/envelope.d.ts +1 -0
  26. package/dist/dev/elements/envelope.js +3 -0
  27. package/dist/dev/elements/envelope.js.map +1 -1
  28. package/dist/dev/elements/meter.d.ts +1 -0
  29. package/dist/dev/elements/meter.js +3 -0
  30. package/dist/dev/elements/meter.js.map +1 -1
  31. package/dist/dev/elements/number.d.ts +1 -0
  32. package/dist/dev/elements/number.js +3 -0
  33. package/dist/dev/elements/number.js.map +1 -1
  34. package/dist/dev/elements/radio-group.d.ts +1 -0
  35. package/dist/dev/elements/radio-group.js +4 -1
  36. package/dist/dev/elements/radio-group.js.map +1 -1
  37. package/dist/dev/elements/region-layer.js +3 -1
  38. package/dist/dev/elements/region-layer.js.map +1 -1
  39. package/dist/dev/elements/region.d.ts +1 -0
  40. package/dist/dev/elements/region.js +5 -1
  41. package/dist/dev/elements/region.js.map +1 -1
  42. package/dist/dev/elements/slider.d.ts +1 -0
  43. package/dist/dev/elements/slider.js +3 -0
  44. package/dist/dev/elements/slider.js.map +1 -1
  45. package/dist/dev/elements/sonic-element.d.ts +1 -0
  46. package/dist/dev/elements/sonic-element.js +4 -9
  47. package/dist/dev/elements/sonic-element.js.map +1 -1
  48. package/dist/dev/elements/toggle.js +2 -2
  49. package/dist/dev/elements/toggle.js.map +1 -1
  50. package/dist/dev/elements/value-element.js +15 -1
  51. package/dist/dev/elements/value-element.js.map +1 -1
  52. package/dist/dev/elements/wave-element.d.ts +1 -0
  53. package/dist/dev/elements/wave-element.js +3 -0
  54. package/dist/dev/elements/wave-element.js.map +1 -1
  55. package/dist/dev/elements/xy.d.ts +1 -0
  56. package/dist/dev/elements/xy.js +3 -0
  57. package/dist/dev/elements/xy.js.map +1 -1
  58. package/dist/dev/lib/canvas-surface.js +22 -1
  59. package/dist/dev/lib/canvas-surface.js.map +1 -1
  60. package/dist/dev/lib/key-press.js +2 -2
  61. package/dist/dev/lib/key-press.js.map +1 -1
  62. package/dist/styles/button.css +11 -9
  63. package/dist/styles/dial.css +6 -6
  64. package/dist/styles/envelope.css +6 -0
  65. package/dist/styles/led.css +8 -8
  66. package/dist/styles/material/cap.css +13 -2
  67. package/dist/styles/material/pane.css +7 -1
  68. package/dist/styles/material/readout.css +10 -2
  69. package/dist/styles/material/reticle.css +2 -2
  70. package/dist/styles/meter.css +11 -11
  71. package/dist/styles/number.css +1 -1
  72. package/dist/styles/region.css +6 -0
  73. package/dist/styles/segmented.css +3 -3
  74. package/dist/styles/slider.css +12 -12
  75. package/dist/styles/switch.css +6 -0
  76. package/dist/styles/toggle.css +3 -3
  77. package/dist/styles/waveform.css +7 -1
  78. package/dist/styles/wavestrip.css +6 -0
  79. package/dist/styles/xy.css +15 -3
  80. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"meter.js","names":[],"sources":["../../../src/elements/meter.ts"],"sourcesContent":["import type { Decibels } from '#lib/units.ts';\n\nimport { SonicElement } from '#elements/sonic-element.ts';\nimport { ballisticsRates, fall, rise, stepNeedle } from '#lib/ballistics.ts';\nimport { toNumber } from '#lib/math.ts';\nimport { parseNumberList } from '#lib/number-list.ts';\nimport { template } from '#lib/render.ts';\nimport { linearTaper } from '#lib/taper.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-meter': SonicMeter;\n\t}\n}\n\nconst renderMeter = template(\n\t/* HTML */ `\n\t\t<div aria-hidden=\"true\" class=\"sonic-meter\">\n\t\t\t<div class=\"sonic-meter-segments\"></div>\n\t\t\t<div class=\"sonic-meter-bar\"></div>\n\t\t\t<div class=\"sonic-meter-clip\"></div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nconst holdMs = 1500;\n\nconst ballisticsModes = ['peak', 'ppm-1', 'ppm-2', 'vu'] as const;\n\nconst settledDecibels = 0.01;\nconst settledAmplitude = 1e-4;\nconst settledPerSecond = 1e-2;\n\nfunction toDecibels(amplitude: number): number {\n\treturn 20 * Math.log10(Math.abs(amplitude));\n}\n\nfunction toAmplitude(decibels: number): number {\n\treturn 10 ** (decibels / 20);\n}\n\n// A `calc()` rather than a count, so the zone's token stays live\nfunction segmentsBelow(segments: Array<number>, zone: 'clip' | 'hot'): string {\n\tconst terms = segments.map(\n\t\t(threshold) => `sign(max(0, var(--_sonic-meter-${zone}-from) - (${String(threshold)})))`,\n\t);\n\n\treturn `calc(${terms.join(' + ')})`;\n}\n\nexport class SonicMeter extends SonicElement {\n\tstatic override readonly observedAttributes = [\n\t\t'ballistics',\n\t\t'disabled',\n\t\t'max',\n\t\t'min',\n\t\t'origin',\n\t\t'segments',\n\t\t'value',\n\t];\n\n\tget ballistics(): (typeof ballisticsModes)[number] {\n\t\tconst name = this.getAttribute('ballistics');\n\n\t\treturn ballisticsModes.find((mode) => mode === name) ?? 'peak';\n\t}\n\n\tset ballistics(mode: (typeof ballisticsModes)[number] | undefined) {\n\t\tthis.reflect('ballistics', mode);\n\t}\n\n\t/** Linear peak amplitude, 1 at full scale; the meter converts to decibels and applies its ballistics */\n\tget level(): number {\n\t\treturn this.#level;\n\t}\n\n\tset level(level: number) {\n\t\tconst amplitude = toNumber(level);\n\t\tif (!Number.isFinite(amplitude)) return;\n\n\t\tconst decibels = toDecibels(amplitude);\n\n\t\tthis.#level = amplitude;\n\t\tthis.#target = decibels;\n\t\tif (this.#isValueShown) return;\n\n\t\tconst now = performance.now();\n\n\t\tthis.#peak = Math.max(this.#peak, decibels);\n\t\tif (decibels >= 0) this.#clipAt = now;\n\t\tthis.#step(now, 0);\n\t\tthis.#render(now);\n\t\tthis.#schedule(now);\n\t}\n\n\tget max(): Decibels {\n\t\treturn this.numberAttribute('max', 0);\n\t}\n\n\tset max(decibels: number | undefined) {\n\t\tthis.reflect('max', decibels);\n\t}\n\n\tget min(): Decibels {\n\t\treturn this.numberAttribute('min', -60);\n\t}\n\n\tset min(decibels: number | undefined) {\n\t\tthis.reflect('min', decibels);\n\t}\n\n\tget orientation(): 'horizontal' | 'vertical' {\n\t\treturn this.getAttribute('orientation') === 'horizontal' ? 'horizontal' : 'vertical';\n\t}\n\n\tset orientation(direction: 'horizontal' | 'vertical' | undefined) {\n\t\tthis.reflect('orientation', direction);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\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\t/** Highest level since `resetPeak()`; `-Infinity` before any signal */\n\tget peak(): Decibels {\n\t\treturn this.#peak;\n\t}\n\n\t/** Ladder thresholds, one per segment; unset draws a bar */\n\tget segments(): Array<Decibels> | undefined {\n\t\treturn parseNumberList(this.getAttribute('segments'));\n\t}\n\n\tset segments(thresholds: Array<Decibels> | undefined) {\n\t\tthis.reflect('segments', thresholds?.join(' '));\n\t}\n\n\t/** A reading in the meter's own units, drawn as given with no ballistics; takes over from `level` while set */\n\tget value(): number | undefined {\n\t\treturn this.optionalNumberAttribute('value');\n\t}\n\n\tset value(value: number | undefined) {\n\t\tthis.reflect('value', value);\n\t}\n\n\t#bar = -Infinity;\n\n\t#carryMs = 0;\n\n\t#clipAt = -Infinity;\n\n\t#frame: number | undefined;\n\n\t#isValueShown = false;\n\n\t#lastFrame: number | undefined;\n\n\t#level = 0;\n\n\treadonly #meter = renderMeter();\n\n\t#peak = -Infinity;\n\n\t#peakHold = -Infinity;\n\n\t#peakHoldAt = -Infinity;\n\n\t#segments: Array<number> | undefined;\n\n\t#target = -Infinity;\n\n\t#velocity = 0;\n\n\tattributeChangedCallback(name: string): void {\n\t\tconst now = performance.now();\n\n\t\tif (name === 'ballistics') this.#velocity = 0;\n\t\telse if (name === 'value') this.#readValue(now);\n\t\tthis.#segments = this.segments;\n\t\tif (name === 'segments') this.#renderSegments();\n\t\tthis.#render(now);\n\t\tthis.#schedule(now);\n\t}\n\n\tresetPeak(): void {\n\t\tthis.#peak = -Infinity;\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst now = performance.now();\n\n\t\tthis.upgradeProperties(\n\t\t\t'ballistics',\n\t\t\t'max',\n\t\t\t'min',\n\t\t\t'orientation',\n\t\t\t'origin',\n\t\t\t'segments',\n\t\t\t'value',\n\t\t\t'level',\n\t\t);\n\t\tthis.keepControl(this.#meter, signal);\n\t\tthis.#renderSegments();\n\t\tthis.#render(now);\n\t\tthis.#schedule(now);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tif (this.#frame !== undefined) cancelAnimationFrame(this.#frame);\n\t\t\t\tthis.#frame = undefined;\n\t\t\t\tthis.#lastFrame = undefined;\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t\tif (__DEV__) this.checkStyles(this.#meter, 'meter.css');\n\t}\n\n\t#isSettled(now: number): boolean {\n\t\tif (this.#isValueShown) return true;\n\t\tif (now - this.#clipAt < holdMs) return false;\n\t\tif (this.ballistics === 'vu') return this.#bar === this.#target && this.#velocity === 0;\n\n\t\tconst floor = this.min;\n\n\t\treturn (\n\t\t\tthis.#bar >= this.#target &&\n\t\t\tthis.#bar <= Math.max(this.#target, floor) &&\n\t\t\tthis.#peakHold <= Math.max(this.#bar, floor)\n\t\t);\n\t}\n\n\t#proportionOf(level: number): number {\n\t\tconst segments = this.#segments;\n\t\tif (segments) {\n\t\t\treturn segments.filter((threshold) => threshold <= level).length / segments.length;\n\t\t}\n\n\t\treturn linearTaper(this.min, this.max).proportionOf(level);\n\t}\n\n\t#readValue(now: number): void {\n\t\tconst value = this.value;\n\t\tconst isValueShown = value !== undefined;\n\n\t\tif (isValueShown !== this.#isValueShown) {\n\t\t\tthis.#isValueShown = isValueShown;\n\t\t\tthis.resetPeak();\n\t\t\tif (!isValueShown) this.#step(now, 0);\n\t\t}\n\t\tif (value !== undefined) this.#peak = Math.max(this.#peak, value);\n\t}\n\n\t#render(now: number): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst meter = this.#meter;\n\n\t\tconst origin = this.origin === undefined ? 0 : this.#proportionOf(this.origin);\n\n\t\tmeter.style.setProperty('--_sonic-meter-min', String(this.min));\n\t\tmeter.style.setProperty('--_sonic-meter-max', String(this.max));\n\t\tmeter.style.setProperty('--_sonic-meter-origin', String(origin));\n\n\t\tif (this.disabled) {\n\t\t\tthis.#renderStill(origin);\n\t\t\treturn;\n\t\t}\n\n\t\tconst value = this.value;\n\n\t\tif (value !== undefined) {\n\t\t\tthis.#renderStill(this.#proportionOf(value));\n\t\t\treturn;\n\t\t}\n\n\t\tconst peakHold = this.ballistics === 'vu' ? 0 : this.#proportionOf(this.#peakHold);\n\n\t\tmeter.style.setProperty('--_sonic-meter-bar', String(this.#proportionOf(this.#bar)));\n\t\tmeter.style.setProperty('--_sonic-meter-peak-hold', String(peakHold));\n\t\tmeter.style.setProperty('--_sonic-meter-clipped', now - this.#clipAt < holdMs ? '1' : '0');\n\t}\n\n\t#renderSegments(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst meter = this.#meter;\n\t\tconst segments = this.#segments;\n\n\t\tthis.toggleState('ladder', segments !== undefined);\n\t\tif (!segments) {\n\t\t\tfor (const name of ['count', 'hot-segments', 'clip-segments']) {\n\t\t\t\tmeter.style.removeProperty(`--_sonic-meter-${name}`);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tmeter.style.setProperty('--_sonic-meter-count', String(segments.length));\n\t\tmeter.style.setProperty('--_sonic-meter-hot-segments', segmentsBelow(segments, 'hot'));\n\t\tmeter.style.setProperty('--_sonic-meter-clip-segments', segmentsBelow(segments, 'clip'));\n\t}\n\n\t#renderStill(bar: number): void {\n\t\tconst { style } = this.#meter;\n\n\t\tstyle.setProperty('--_sonic-meter-bar', String(bar));\n\t\tstyle.setProperty('--_sonic-meter-peak-hold', '0');\n\t\tstyle.setProperty('--_sonic-meter-clipped', '0');\n\t}\n\n\t#risen(timeConstantMs: number, elapsedMs: number): number {\n\t\tconst target = toAmplitude(this.#target);\n\t\tconst risen = toDecibels(rise(toAmplitude(this.#bar), { target, timeConstantMs }, elapsedMs));\n\n\t\treturn this.#target - risen < settledDecibels ? this.#target : risen;\n\t}\n\n\t#schedule(now: number): void {\n\t\tif (this.#frame !== undefined || !this.isConnected || this.#isSettled(now)) return;\n\n\t\tif (this.#lastFrame === undefined) this.#lastFrame = now;\n\t\tthis.#frame = requestAnimationFrame(this.#tick);\n\t}\n\n\t#step(now: number, elapsedMs: number): void {\n\t\tconst mode = this.ballistics;\n\n\t\tif (mode === 'vu') {\n\t\t\tthis.#stepNeedle(elapsedMs);\n\t\t\treturn;\n\t\t}\n\n\t\tconst { fallDecibelsPerSecond, riseTimeConstantMs } = ballisticsRates[mode];\n\t\tconst floor = Math.max(this.#target, this.min);\n\n\t\tthis.#bar =\n\t\t\tthis.#target > this.#bar\n\t\t\t\t? this.#risen(riseTimeConstantMs, elapsedMs)\n\t\t\t\t: fall(this.#bar, { fallDecibelsPerSecond, floor }, elapsedMs);\n\t\tif (this.#bar >= this.#peakHold) {\n\t\t\tthis.#peakHold = this.#bar;\n\t\t\tthis.#peakHoldAt = now;\n\t\t}\n\n\t\tconst pastHoldMs = now - this.#peakHoldAt - holdMs;\n\n\t\tif (pastHoldMs >= 0) {\n\t\t\tthis.#peakHold = fall(\n\t\t\t\tthis.#peakHold,\n\t\t\t\t{ fallDecibelsPerSecond, floor: this.#bar },\n\t\t\t\tMath.min(elapsedMs, pastHoldMs),\n\t\t\t);\n\t\t}\n\t}\n\n\t#stepNeedle(elapsedMs: number): void {\n\t\tconst target = toAmplitude(this.#target);\n\t\tconst needle = stepNeedle(\n\t\t\t{ carryMs: this.#carryMs, position: toAmplitude(this.#bar), velocity: this.#velocity },\n\t\t\ttarget,\n\t\t\telapsedMs,\n\t\t);\n\t\tconst isLanded =\n\t\t\tMath.abs(needle.position - target) < settledAmplitude &&\n\t\t\tMath.abs(needle.velocity) < settledPerSecond;\n\n\t\tthis.#carryMs = needle.carryMs;\n\t\tthis.#velocity = isLanded ? 0 : needle.velocity;\n\t\tthis.#bar = isLanded ? this.#target : toDecibels(Math.max(0, needle.position));\n\t}\n\n\treadonly #tick = (time: number): void => {\n\t\tconst elapsedMs = Math.max(0, time - (this.#lastFrame ?? time));\n\n\t\tthis.#frame = undefined;\n\t\tthis.#lastFrame = time;\n\t\tthis.#step(time, elapsedMs);\n\t\tthis.#render(time);\n\t\tif (this.#isSettled(time)) {\n\t\t\tthis.#lastFrame = undefined;\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#frame = requestAnimationFrame(this.#tick);\n\t};\n}\n"],"mappings":";;;;;;;;AAiBA,MAAM,cAAc,SACR;;;;;;IAOX,cACD;AAEA,MAAM,SAAS;AAEf,MAAM,kBAAkB;CAAC;CAAQ;CAAS;CAAS;AAAI;AAEvD,MAAM,kBAAkB;AACxB,MAAM,mBAAmB;AACzB,MAAM,mBAAmB;AAEzB,SAAS,WAAW,WAA2B;CAC9C,OAAO,KAAK,KAAK,MAAM,KAAK,IAAI,SAAS,CAAC;AAC3C;AAEA,SAAS,YAAY,UAA0B;CAC9C,OAAO,OAAO,WAAW;AAC1B;AAGA,SAAS,cAAc,UAAyB,MAA8B;CAK7E,OAAO,QAJO,SAAS,KACrB,cAAc,kCAAkC,KAAK,YAAY,OAAO,SAAS,EAAE,IAGlE,CAAC,CAAC,KAAK,KAAK,EAAE;AAClC;AAEA,IAAa,aAAb,cAAgC,aAAa;CAC5C,OAAyB,qBAAqB;EAC7C;EACA;EACA;EACA;EACA;EACA;EACA;CACD;CAEA,IAAI,aAA+C;EAClD,MAAM,OAAO,KAAK,aAAa,YAAY;EAE3C,OAAO,gBAAgB,MAAM,SAAS,SAAS,IAAI,KAAK;CACzD;CAEA,IAAI,WAAW,MAAoD;EAClE,KAAK,QAAQ,cAAc,IAAI;CAChC;;CAGA,IAAI,QAAgB;EACnB,OAAO,KAAK;CACb;CAEA,IAAI,MAAM,OAAe;EACxB,MAAM,YAAY,SAAS,KAAK;EAChC,IAAI,CAAC,OAAO,SAAS,SAAS,GAAG;EAEjC,MAAM,WAAW,WAAW,SAAS;EAErC,KAAK,SAAS;EACd,KAAK,UAAU;EACf,IAAI,KAAK,eAAe;EAExB,MAAM,MAAM,YAAY,IAAI;EAE5B,KAAK,QAAQ,KAAK,IAAI,KAAK,OAAO,QAAQ;EAC1C,IAAI,YAAY,GAAG,KAAK,UAAU;EAClC,KAAK,MAAM,KAAK,CAAC;EACjB,KAAK,QAAQ,GAAG;EAChB,KAAK,UAAU,GAAG;CACnB;CAEA,IAAI,MAAgB;EACnB,OAAO,KAAK,gBAAgB,OAAO,CAAC;CACrC;CAEA,IAAI,IAAI,UAA8B;EACrC,KAAK,QAAQ,OAAO,QAAQ;CAC7B;CAEA,IAAI,MAAgB;EACnB,OAAO,KAAK,gBAAgB,OAAO,GAAG;CACvC;CAEA,IAAI,IAAI,UAA8B;EACrC,KAAK,QAAQ,OAAO,QAAQ;CAC7B;CAEA,IAAI,cAAyC;EAC5C,OAAO,KAAK,aAAa,aAAa,MAAM,eAAe,eAAe;CAC3E;CAEA,IAAI,YAAY,WAAkD;EACjE,KAAK,QAAQ,eAAe,SAAS;CACtC;CAGA,IAAI,SAA6B;EAChC,OAAO,KAAK,wBAAwB,QAAQ;CAC7C;CAEA,IAAI,OAAO,OAA2B;EACrC,KAAK,QAAQ,UAAU,KAAK;CAC7B;;CAGA,IAAI,OAAiB;EACpB,OAAO,KAAK;CACb;;CAGA,IAAI,WAAwC;EAC3C,OAAO,gBAAgB,KAAK,aAAa,UAAU,CAAC;CACrD;CAEA,IAAI,SAAS,YAAyC;EACrD,KAAK,QAAQ,YAAY,YAAY,KAAK,GAAG,CAAC;CAC/C;;CAGA,IAAI,QAA4B;EAC/B,OAAO,KAAK,wBAAwB,OAAO;CAC5C;CAEA,IAAI,MAAM,OAA2B;EACpC,KAAK,QAAQ,SAAS,KAAK;CAC5B;CAEA,OAAO;CAEP,WAAW;CAEX,UAAU;CAEV;CAEA,gBAAgB;CAEhB;CAEA,SAAS;CAET,AAAS,SAAS,YAAY;CAE9B,QAAQ;CAER,YAAY;CAEZ,cAAc;CAEd;CAEA,UAAU;CAEV,YAAY;CAEZ,yBAAyB,MAAoB;EAC5C,MAAM,MAAM,YAAY,IAAI;EAE5B,IAAI,SAAS,cAAc,KAAK,YAAY;OACvC,IAAI,SAAS,SAAS,KAAK,WAAW,GAAG;EAC9C,KAAK,YAAY,KAAK;EACtB,IAAI,SAAS,YAAY,KAAK,gBAAgB;EAC9C,KAAK,QAAQ,GAAG;EAChB,KAAK,UAAU,GAAG;CACnB;CAEA,YAAkB;EACjB,KAAK,QAAQ;CACd;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,MAAM,YAAY,IAAI;EAE5B,KAAK,kBACJ,cACA,OACA,OACA,eACA,UACA,YACA,SACA,OACD;EACA,KAAK,YAAY,KAAK,QAAQ,MAAM;EACpC,KAAK,gBAAgB;EACrB,KAAK,QAAQ,GAAG;EAChB,KAAK,UAAU,GAAG;EAClB,OAAO,iBACN,eACM;GACL,IAAI,KAAK,WAAW,QAAW,qBAAqB,KAAK,MAAM;GAC/D,KAAK,SAAS;GACd,KAAK,aAAa;EACnB,GACA,EAAE,MAAM,KAAK,CACd;EACa,KAAK,YAAY,KAAK,QAAQ,WAAW;CACvD;CAEA,WAAW,KAAsB;EAChC,IAAI,KAAK,eAAe,OAAO;EAC/B,IAAI,MAAM,KAAK,UAAU,QAAQ,OAAO;EACxC,IAAI,KAAK,eAAe,MAAM,OAAO,KAAK,SAAS,KAAK,WAAW,KAAK,cAAc;EAEtF,MAAM,QAAQ,KAAK;EAEnB,OACC,KAAK,QAAQ,KAAK,WAClB,KAAK,QAAQ,KAAK,IAAI,KAAK,SAAS,KAAK,KACzC,KAAK,aAAa,KAAK,IAAI,KAAK,MAAM,KAAK;CAE7C;CAEA,cAAc,OAAuB;EACpC,MAAM,WAAW,KAAK;EACtB,IAAI,UACH,OAAO,SAAS,QAAQ,cAAc,aAAa,KAAK,CAAC,CAAC,SAAS,SAAS;EAG7E,OAAO,YAAY,KAAK,KAAK,KAAK,GAAG,CAAC,CAAC,aAAa,KAAK;CAC1D;CAEA,WAAW,KAAmB;EAC7B,MAAM,QAAQ,KAAK;EACnB,MAAM,eAAe,UAAU;EAE/B,IAAI,iBAAiB,KAAK,eAAe;GACxC,KAAK,gBAAgB;GACrB,KAAK,UAAU;GACf,IAAI,CAAC,cAAc,KAAK,MAAM,KAAK,CAAC;EACrC;EACA,IAAI,UAAU,QAAW,KAAK,QAAQ,KAAK,IAAI,KAAK,OAAO,KAAK;CACjE;CAEA,QAAQ,KAAmB;EAC1B,IAAI,CAAC,KAAK,QAAQ,GAAG;EAErB,MAAM,QAAQ,KAAK;EAEnB,MAAM,SAAS,KAAK,WAAW,SAAY,IAAI,KAAK,cAAc,KAAK,MAAM;EAE7E,MAAM,MAAM,YAAY,sBAAsB,OAAO,KAAK,GAAG,CAAC;EAC9D,MAAM,MAAM,YAAY,sBAAsB,OAAO,KAAK,GAAG,CAAC;EAC9D,MAAM,MAAM,YAAY,yBAAyB,OAAO,MAAM,CAAC;EAE/D,IAAI,KAAK,UAAU;GAClB,KAAK,aAAa,MAAM;GACxB;EACD;EAEA,MAAM,QAAQ,KAAK;EAEnB,IAAI,UAAU,QAAW;GACxB,KAAK,aAAa,KAAK,cAAc,KAAK,CAAC;GAC3C;EACD;EAEA,MAAM,WAAW,KAAK,eAAe,OAAO,IAAI,KAAK,cAAc,KAAK,SAAS;EAEjF,MAAM,MAAM,YAAY,sBAAsB,OAAO,KAAK,cAAc,KAAK,IAAI,CAAC,CAAC;EACnF,MAAM,MAAM,YAAY,4BAA4B,OAAO,QAAQ,CAAC;EACpE,MAAM,MAAM,YAAY,0BAA0B,MAAM,KAAK,UAAU,SAAS,MAAM,GAAG;CAC1F;CAEA,kBAAwB;EACvB,IAAI,CAAC,KAAK,QAAQ,GAAG;EAErB,MAAM,QAAQ,KAAK;EACnB,MAAM,WAAW,KAAK;EAEtB,KAAK,YAAY,UAAU,aAAa,MAAS;EACjD,IAAI,CAAC,UAAU;GACd,KAAK,MAAM,QAAQ;IAAC;IAAS;IAAgB;GAAe,GAC3D,MAAM,MAAM,eAAe,kBAAkB,MAAM;GAEpD;EACD;EAEA,MAAM,MAAM,YAAY,wBAAwB,OAAO,SAAS,MAAM,CAAC;EACvE,MAAM,MAAM,YAAY,+BAA+B,cAAc,UAAU,KAAK,CAAC;EACrF,MAAM,MAAM,YAAY,gCAAgC,cAAc,UAAU,MAAM,CAAC;CACxF;CAEA,aAAa,KAAmB;EAC/B,MAAM,EAAE,UAAU,KAAK;EAEvB,MAAM,YAAY,sBAAsB,OAAO,GAAG,CAAC;EACnD,MAAM,YAAY,4BAA4B,GAAG;EACjD,MAAM,YAAY,0BAA0B,GAAG;CAChD;CAEA,OAAO,gBAAwB,WAA2B;EACzD,MAAM,SAAS,YAAY,KAAK,OAAO;EACvC,MAAM,QAAQ,WAAW,KAAK,YAAY,KAAK,IAAI,GAAG;GAAE;GAAQ;EAAe,GAAG,SAAS,CAAC;EAE5F,OAAO,KAAK,UAAU,QAAQ,kBAAkB,KAAK,UAAU;CAChE;CAEA,UAAU,KAAmB;EAC5B,IAAI,KAAK,WAAW,UAAa,CAAC,KAAK,eAAe,KAAK,WAAW,GAAG,GAAG;EAE5E,IAAI,KAAK,eAAe,QAAW,KAAK,aAAa;EACrD,KAAK,SAAS,sBAAsB,KAAK,KAAK;CAC/C;CAEA,MAAM,KAAa,WAAyB;EAC3C,MAAM,OAAO,KAAK;EAElB,IAAI,SAAS,MAAM;GAClB,KAAK,YAAY,SAAS;GAC1B;EACD;EAEA,MAAM,EAAE,uBAAuB,uBAAuB,gBAAgB;EACtE,MAAM,QAAQ,KAAK,IAAI,KAAK,SAAS,KAAK,GAAG;EAE7C,KAAK,OACJ,KAAK,UAAU,KAAK,OACjB,KAAK,OAAO,oBAAoB,SAAS,IACzC,KAAK,KAAK,MAAM;GAAE;GAAuB;EAAM,GAAG,SAAS;EAC/D,IAAI,KAAK,QAAQ,KAAK,WAAW;GAChC,KAAK,YAAY,KAAK;GACtB,KAAK,cAAc;EACpB;EAEA,MAAM,aAAa,MAAM,KAAK,cAAc;EAE5C,IAAI,cAAc,GACjB,KAAK,YAAY,KAChB,KAAK,WACL;GAAE;GAAuB,OAAO,KAAK;EAAK,GAC1C,KAAK,IAAI,WAAW,UAAU,CAC/B;CAEF;CAEA,YAAY,WAAyB;EACpC,MAAM,SAAS,YAAY,KAAK,OAAO;EACvC,MAAM,SAAS,WACd;GAAE,SAAS,KAAK;GAAU,UAAU,YAAY,KAAK,IAAI;GAAG,UAAU,KAAK;EAAU,GACrF,QACA,SACD;EACA,MAAM,WACL,KAAK,IAAI,OAAO,WAAW,MAAM,IAAI,oBACrC,KAAK,IAAI,OAAO,QAAQ,IAAI;EAE7B,KAAK,WAAW,OAAO;EACvB,KAAK,YAAY,WAAW,IAAI,OAAO;EACvC,KAAK,OAAO,WAAW,KAAK,UAAU,WAAW,KAAK,IAAI,GAAG,OAAO,QAAQ,CAAC;CAC9E;CAEA,AAAS,SAAS,SAAuB;EACxC,MAAM,YAAY,KAAK,IAAI,GAAG,QAAQ,KAAK,cAAc,KAAK;EAE9D,KAAK,SAAS;EACd,KAAK,aAAa;EAClB,KAAK,MAAM,MAAM,SAAS;EAC1B,KAAK,QAAQ,IAAI;EACjB,IAAI,KAAK,WAAW,IAAI,GAAG;GAC1B,KAAK,aAAa;GAClB;EACD;EAEA,KAAK,SAAS,sBAAsB,KAAK,KAAK;CAC/C;AACD"}
1
+ {"version":3,"file":"meter.js","names":[],"sources":["../../../src/elements/meter.ts"],"sourcesContent":["import type { Decibels } from '#lib/units.ts';\n\nimport { SonicElement } from '#elements/sonic-element.ts';\nimport { ballisticsRates, fall, rise, stepNeedle } from '#lib/ballistics.ts';\nimport { toNumber } from '#lib/math.ts';\nimport { parseNumberList } from '#lib/number-list.ts';\nimport { template } from '#lib/render.ts';\nimport { linearTaper } from '#lib/taper.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-meter': SonicMeter;\n\t}\n}\n\nconst renderMeter = template(\n\t/* HTML */ `\n\t\t<div aria-hidden=\"true\" class=\"sonic-meter\">\n\t\t\t<div class=\"sonic-meter-segments\"></div>\n\t\t\t<div class=\"sonic-meter-bar\"></div>\n\t\t\t<div class=\"sonic-meter-clip\"></div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nconst holdMs = 1500;\n\nconst ballisticsModes = ['peak', 'ppm-1', 'ppm-2', 'vu'] as const;\n\nconst settledDecibels = 0.01;\nconst settledAmplitude = 1e-4;\nconst settledPerSecond = 1e-2;\n\nfunction toDecibels(amplitude: number): number {\n\treturn 20 * Math.log10(Math.abs(amplitude));\n}\n\nfunction toAmplitude(decibels: number): number {\n\treturn 10 ** (decibels / 20);\n}\n\n// A `calc()` rather than a count, so the zone's token stays live\nfunction segmentsBelow(segments: Array<number>, zone: 'clip' | 'hot'): string {\n\tconst terms = segments.map(\n\t\t(threshold) => `sign(max(0, var(--_sonic-meter-${zone}-from) - (${String(threshold)})))`,\n\t);\n\n\treturn `calc(${terms.join(' + ')})`;\n}\n\nexport class SonicMeter extends SonicElement {\n\tstatic override readonly observedAttributes = [\n\t\t'ballistics',\n\t\t'disabled',\n\t\t'max',\n\t\t'min',\n\t\t'origin',\n\t\t'segments',\n\t\t'value',\n\t];\n\n\tget ballistics(): (typeof ballisticsModes)[number] {\n\t\tconst name = this.getAttribute('ballistics');\n\n\t\treturn ballisticsModes.find((mode) => mode === name) ?? 'peak';\n\t}\n\n\tset ballistics(mode: (typeof ballisticsModes)[number] | undefined) {\n\t\tthis.reflect('ballistics', mode);\n\t}\n\n\t/** Linear peak amplitude, 1 at full scale; the meter converts to decibels and applies its ballistics */\n\tget level(): number {\n\t\treturn this.#level;\n\t}\n\n\tset level(level: number) {\n\t\tconst amplitude = toNumber(level);\n\t\tif (!Number.isFinite(amplitude)) return;\n\n\t\tconst decibels = toDecibels(amplitude);\n\n\t\tthis.#level = amplitude;\n\t\tthis.#target = decibels;\n\t\tif (this.#isValueShown) return;\n\n\t\tconst now = performance.now();\n\n\t\tthis.#peak = Math.max(this.#peak, decibels);\n\t\tif (decibels >= 0) this.#clipAt = now;\n\t\tthis.#step(now, 0);\n\t\tthis.#render(now);\n\t\tthis.#schedule(now);\n\t}\n\n\tget max(): Decibels {\n\t\treturn this.numberAttribute('max', 0);\n\t}\n\n\tset max(decibels: number | undefined) {\n\t\tthis.reflect('max', decibels);\n\t}\n\n\tget min(): Decibels {\n\t\treturn this.numberAttribute('min', -60);\n\t}\n\n\tset min(decibels: number | undefined) {\n\t\tthis.reflect('min', decibels);\n\t}\n\n\tget orientation(): 'horizontal' | 'vertical' {\n\t\treturn this.getAttribute('orientation') === 'horizontal' ? 'horizontal' : 'vertical';\n\t}\n\n\tset orientation(direction: 'horizontal' | 'vertical' | undefined) {\n\t\tthis.reflect('orientation', direction);\n\t}\n\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per reflected attribute\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\t/** Highest level since `resetPeak()`; `-Infinity` before any signal */\n\tget peak(): Decibels {\n\t\treturn this.#peak;\n\t}\n\n\t/** Ladder thresholds, one per segment; unset draws a bar */\n\tget segments(): Array<Decibels> | undefined {\n\t\treturn parseNumberList(this.getAttribute('segments'));\n\t}\n\n\tset segments(thresholds: Array<Decibels> | undefined) {\n\t\tthis.reflect('segments', thresholds?.join(' '));\n\t}\n\n\t/** A reading in the meter's own units, drawn as given with no ballistics; takes over from `level` while set */\n\tget value(): number | undefined {\n\t\treturn this.optionalNumberAttribute('value');\n\t}\n\n\tset value(value: number | undefined) {\n\t\tthis.reflect('value', value);\n\t}\n\n\t#bar = -Infinity;\n\n\t#carryMs = 0;\n\n\t#clipAt = -Infinity;\n\n\t#frame: number | undefined;\n\n\t#isValueShown = false;\n\n\t#lastFrame: number | undefined;\n\n\t#level = 0;\n\n\treadonly #meter = renderMeter();\n\n\t#peak = -Infinity;\n\n\t#peakHold = -Infinity;\n\n\t#peakHoldAt = -Infinity;\n\n\t#segments: Array<number> | undefined;\n\n\t#target = -Infinity;\n\n\t#velocity = 0;\n\n\tattributeChangedCallback(name: string): void {\n\t\tconst now = performance.now();\n\n\t\tif (name === 'ballistics') this.#velocity = 0;\n\t\telse if (name === 'value') this.#readValue(now);\n\t\tthis.#segments = this.segments;\n\t\tif (name === 'segments') this.#renderSegments();\n\t\tthis.#render(now);\n\t\tthis.#schedule(now);\n\t}\n\n\tresetPeak(): void {\n\t\tthis.#peak = -Infinity;\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst now = performance.now();\n\n\t\tthis.upgradeProperties(\n\t\t\t'ballistics',\n\t\t\t'max',\n\t\t\t'min',\n\t\t\t'orientation',\n\t\t\t'origin',\n\t\t\t'segments',\n\t\t\t'value',\n\t\t\t'level',\n\t\t);\n\t\tthis.keepControl(this.#meter, signal);\n\t\tthis.#renderSegments();\n\t\tthis.#render(now);\n\t\tthis.#schedule(now);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tif (this.#frame !== undefined) cancelAnimationFrame(this.#frame);\n\t\t\t\tthis.#frame = undefined;\n\t\t\t\tthis.#lastFrame = undefined;\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t\tif (__DEV__) this.checkStyles(this.#meter, 'meter.css');\n\t}\n\n\tprotected override stateTarget(): HTMLElement {\n\t\treturn this.#meter;\n\t}\n\n\t#isSettled(now: number): boolean {\n\t\tif (this.#isValueShown) return true;\n\t\tif (now - this.#clipAt < holdMs) return false;\n\t\tif (this.ballistics === 'vu') return this.#bar === this.#target && this.#velocity === 0;\n\n\t\tconst floor = this.min;\n\n\t\treturn (\n\t\t\tthis.#bar >= this.#target &&\n\t\t\tthis.#bar <= Math.max(this.#target, floor) &&\n\t\t\tthis.#peakHold <= Math.max(this.#bar, floor)\n\t\t);\n\t}\n\n\t#proportionOf(level: number): number {\n\t\tconst segments = this.#segments;\n\t\tif (segments) {\n\t\t\treturn segments.filter((threshold) => threshold <= level).length / segments.length;\n\t\t}\n\n\t\treturn linearTaper(this.min, this.max).proportionOf(level);\n\t}\n\n\t#readValue(now: number): void {\n\t\tconst value = this.value;\n\t\tconst isValueShown = value !== undefined;\n\n\t\tif (isValueShown !== this.#isValueShown) {\n\t\t\tthis.#isValueShown = isValueShown;\n\t\t\tthis.resetPeak();\n\t\t\tif (!isValueShown) this.#step(now, 0);\n\t\t}\n\t\tif (value !== undefined) this.#peak = Math.max(this.#peak, value);\n\t}\n\n\t#render(now: number): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst meter = this.#meter;\n\n\t\tconst origin = this.origin === undefined ? 0 : this.#proportionOf(this.origin);\n\n\t\tmeter.style.setProperty('--_sonic-meter-min', String(this.min));\n\t\tmeter.style.setProperty('--_sonic-meter-max', String(this.max));\n\t\tmeter.style.setProperty('--_sonic-meter-origin', String(origin));\n\n\t\tif (this.disabled) {\n\t\t\tthis.#renderStill(origin);\n\t\t\treturn;\n\t\t}\n\n\t\tconst value = this.value;\n\n\t\tif (value !== undefined) {\n\t\t\tthis.#renderStill(this.#proportionOf(value));\n\t\t\treturn;\n\t\t}\n\n\t\tconst peakHold = this.ballistics === 'vu' ? 0 : this.#proportionOf(this.#peakHold);\n\n\t\tmeter.style.setProperty('--_sonic-meter-bar', String(this.#proportionOf(this.#bar)));\n\t\tmeter.style.setProperty('--_sonic-meter-peak-hold', String(peakHold));\n\t\tmeter.style.setProperty('--_sonic-meter-clipped', now - this.#clipAt < holdMs ? '1' : '0');\n\t}\n\n\t#renderSegments(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst meter = this.#meter;\n\t\tconst segments = this.#segments;\n\n\t\tthis.toggleState('ladder', segments !== undefined);\n\t\tif (!segments) {\n\t\t\tfor (const name of ['count', 'hot-segments', 'clip-segments']) {\n\t\t\t\tmeter.style.removeProperty(`--_sonic-meter-${name}`);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tmeter.style.setProperty('--_sonic-meter-count', String(segments.length));\n\t\tmeter.style.setProperty('--_sonic-meter-hot-segments', segmentsBelow(segments, 'hot'));\n\t\tmeter.style.setProperty('--_sonic-meter-clip-segments', segmentsBelow(segments, 'clip'));\n\t}\n\n\t#renderStill(bar: number): void {\n\t\tconst { style } = this.#meter;\n\n\t\tstyle.setProperty('--_sonic-meter-bar', String(bar));\n\t\tstyle.setProperty('--_sonic-meter-peak-hold', '0');\n\t\tstyle.setProperty('--_sonic-meter-clipped', '0');\n\t}\n\n\t#risen(timeConstantMs: number, elapsedMs: number): number {\n\t\tconst target = toAmplitude(this.#target);\n\t\tconst risen = toDecibels(rise(toAmplitude(this.#bar), { target, timeConstantMs }, elapsedMs));\n\n\t\treturn this.#target - risen < settledDecibels ? this.#target : risen;\n\t}\n\n\t#schedule(now: number): void {\n\t\tif (this.#frame !== undefined || !this.isConnected || this.#isSettled(now)) return;\n\n\t\tif (this.#lastFrame === undefined) this.#lastFrame = now;\n\t\tthis.#frame = requestAnimationFrame(this.#tick);\n\t}\n\n\t#step(now: number, elapsedMs: number): void {\n\t\tconst mode = this.ballistics;\n\n\t\tif (mode === 'vu') {\n\t\t\tthis.#stepNeedle(elapsedMs);\n\t\t\treturn;\n\t\t}\n\n\t\tconst { fallDecibelsPerSecond, riseTimeConstantMs } = ballisticsRates[mode];\n\t\tconst floor = Math.max(this.#target, this.min);\n\n\t\tthis.#bar =\n\t\t\tthis.#target > this.#bar\n\t\t\t\t? this.#risen(riseTimeConstantMs, elapsedMs)\n\t\t\t\t: fall(this.#bar, { fallDecibelsPerSecond, floor }, elapsedMs);\n\t\tif (this.#bar >= this.#peakHold) {\n\t\t\tthis.#peakHold = this.#bar;\n\t\t\tthis.#peakHoldAt = now;\n\t\t}\n\n\t\tconst pastHoldMs = now - this.#peakHoldAt - holdMs;\n\n\t\tif (pastHoldMs >= 0) {\n\t\t\tthis.#peakHold = fall(\n\t\t\t\tthis.#peakHold,\n\t\t\t\t{ fallDecibelsPerSecond, floor: this.#bar },\n\t\t\t\tMath.min(elapsedMs, pastHoldMs),\n\t\t\t);\n\t\t}\n\t}\n\n\t#stepNeedle(elapsedMs: number): void {\n\t\tconst target = toAmplitude(this.#target);\n\t\tconst needle = stepNeedle(\n\t\t\t{ carryMs: this.#carryMs, position: toAmplitude(this.#bar), velocity: this.#velocity },\n\t\t\ttarget,\n\t\t\telapsedMs,\n\t\t);\n\t\tconst isLanded =\n\t\t\tMath.abs(needle.position - target) < settledAmplitude &&\n\t\t\tMath.abs(needle.velocity) < settledPerSecond;\n\n\t\tthis.#carryMs = needle.carryMs;\n\t\tthis.#velocity = isLanded ? 0 : needle.velocity;\n\t\tthis.#bar = isLanded ? this.#target : toDecibels(Math.max(0, needle.position));\n\t}\n\n\treadonly #tick = (time: number): void => {\n\t\tconst elapsedMs = Math.max(0, time - (this.#lastFrame ?? time));\n\n\t\tthis.#frame = undefined;\n\t\tthis.#lastFrame = time;\n\t\tthis.#step(time, elapsedMs);\n\t\tthis.#render(time);\n\t\tif (this.#isSettled(time)) {\n\t\t\tthis.#lastFrame = undefined;\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#frame = requestAnimationFrame(this.#tick);\n\t};\n}\n"],"mappings":"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"}
@@ -14,6 +14,7 @@ export declare class SonicNumber extends SonicValueElement {
14
14
  protected controlRole(): 'spinbutton';
15
15
  protected draw(): void;
16
16
  protected focusTarget(): HTMLElement;
17
+ protected stateTarget(): HTMLElement;
17
18
  protected tapTarget(): number | undefined;
18
19
  }
19
20
  //#endregion
@@ -47,6 +47,9 @@ var SonicNumber = class extends SonicValueElement {
47
47
  focusTarget() {
48
48
  return this.#number;
49
49
  }
50
+ stateTarget() {
51
+ return this.#number;
52
+ }
50
53
  tapTarget() {
51
54
  if (this.press !== "step") return void 0;
52
55
  const mapping = this.mapping();
@@ -1 +1 @@
1
- {"version":3,"file":"number.js","names":[],"sources":["../../../src/elements/number.ts"],"sourcesContent":["import { SonicValueElement } from '#elements/value-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-number': SonicNumber;\n\t}\n}\n\nconst renderNumber = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-number\" role=\"spinbutton\" tabindex=\"0\">\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-number-value\"></span>\n\t\t\t<input\n\t\t\t\tautocomplete=\"off\"\n\t\t\t\tclass=\"sonic-number-entry\"\n\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\thidden\n\t\t\t\tspellcheck=\"false\"\n\t\t\t\ttype=\"text\"\n\t\t\t/>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicNumber extends SonicValueElement {\n\tget press(): 'none' | 'step' {\n\t\treturn this.getAttribute('press') === 'step' ? 'step' : 'none';\n\t}\n\n\tset press(gesture: 'none' | 'step' | undefined) {\n\t\tthis.reflect('press', gesture);\n\t}\n\n\treadonly #number = renderNumber();\n\n\treadonly #digits = requireChild(this.#number, '.sonic-number-value', HTMLSpanElement);\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('press');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst number = this.#number;\n\n\t\tthis.keepControl(number, signal);\n\t\tthis.render();\n\t\tif (__DEV__) this.checkStyles(number, 'number.css');\n\t\tthis.bindGestures(number, signal, () => ({\n\t\t\tposition: (event) => -event.clientY,\n\t\t\ttravelPx: this.travelPx(number, '--_sonic-number-travel'),\n\t\t}));\n\t}\n\n\tprotected override controlRole(): 'spinbutton' {\n\t\treturn 'spinbutton';\n\t}\n\n\tprotected draw(): void {\n\t\tthis.#digits.textContent = this.valueText;\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#number;\n\t}\n\n\tprotected override tapTarget(): number | undefined {\n\t\tif (this.press !== 'step') return undefined;\n\n\t\tconst mapping = this.mapping();\n\t\tconst next = mapping.keyTarget('ArrowUp', this.value);\n\n\t\treturn next === undefined || next === this.value ? mapping.bounds[0] : next;\n\t}\n}\n"],"mappings":";;;;AAWA,MAAM,eAAe,SACT;;;;;;;;;;;;IAaX,cACD;AAEA,IAAa,cAAb,cAAiC,kBAAkB;CAClD,IAAI,QAAyB;EAC5B,OAAO,KAAK,aAAa,OAAO,MAAM,SAAS,SAAS;CACzD;CAEA,IAAI,MAAM,SAAsC;EAC/C,KAAK,QAAQ,SAAS,OAAO;CAC9B;CAEA,AAAS,UAAU,aAAa;CAEhC,AAAS,UAAU,aAAa,KAAK,SAAS,uBAAuB,eAAe;CAEpF,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,OAAO;EAC9B,MAAM,kBAAkB;CACzB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,SAAS,KAAK;EAEpB,KAAK,YAAY,QAAQ,MAAM;EAC/B,KAAK,OAAO;EACC,KAAK,YAAY,QAAQ,YAAY;EAClD,KAAK,aAAa,QAAQ,eAAe;GACxC,WAAW,UAAU,CAAC,MAAM;GAC5B,UAAU,KAAK,SAAS,QAAQ,wBAAwB;EACzD,EAAE;CACH;CAEA,AAAmB,cAA4B;EAC9C,OAAO;CACR;CAEA,AAAU,OAAa;EACtB,KAAK,QAAQ,cAAc,KAAK;CACjC;CAEA,AAAmB,cAA2B;EAC7C,OAAO,KAAK;CACb;CAEA,AAAmB,YAAgC;EAClD,IAAI,KAAK,UAAU,QAAQ,OAAO;EAElC,MAAM,UAAU,KAAK,QAAQ;EAC7B,MAAM,OAAO,QAAQ,UAAU,WAAW,KAAK,KAAK;EAEpD,OAAO,SAAS,UAAa,SAAS,KAAK,QAAQ,QAAQ,OAAO,KAAK;CACxE;AACD"}
1
+ {"version":3,"file":"number.js","names":[],"sources":["../../../src/elements/number.ts"],"sourcesContent":["import { SonicValueElement } from '#elements/value-element.ts';\nimport { requireChild, template } from '#lib/render.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-number': SonicNumber;\n\t}\n}\n\nconst renderNumber = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-number\" role=\"spinbutton\" tabindex=\"0\">\n\t\t\t<span aria-hidden=\"true\" class=\"sonic-number-value\"></span>\n\t\t\t<input\n\t\t\t\tautocomplete=\"off\"\n\t\t\t\tclass=\"sonic-number-entry\"\n\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\thidden\n\t\t\t\tspellcheck=\"false\"\n\t\t\t\ttype=\"text\"\n\t\t\t/>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicNumber extends SonicValueElement {\n\tget press(): 'none' | 'step' {\n\t\treturn this.getAttribute('press') === 'step' ? 'step' : 'none';\n\t}\n\n\tset press(gesture: 'none' | 'step' | undefined) {\n\t\tthis.reflect('press', gesture);\n\t}\n\n\treadonly #number = renderNumber();\n\n\treadonly #digits = requireChild(this.#number, '.sonic-number-value', HTMLSpanElement);\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('press');\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst number = this.#number;\n\n\t\tthis.keepControl(number, signal);\n\t\tthis.render();\n\t\tif (__DEV__) this.checkStyles(number, 'number.css');\n\t\tthis.bindGestures(number, signal, () => ({\n\t\t\tposition: (event) => -event.clientY,\n\t\t\ttravelPx: this.travelPx(number, '--_sonic-number-travel'),\n\t\t}));\n\t}\n\n\tprotected override controlRole(): 'spinbutton' {\n\t\treturn 'spinbutton';\n\t}\n\n\tprotected draw(): void {\n\t\tthis.#digits.textContent = this.valueText;\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#number;\n\t}\n\n\tprotected override stateTarget(): HTMLElement {\n\t\treturn this.#number;\n\t}\n\n\tprotected override tapTarget(): number | undefined {\n\t\tif (this.press !== 'step') return undefined;\n\n\t\tconst mapping = this.mapping();\n\t\tconst next = mapping.keyTarget('ArrowUp', this.value);\n\n\t\treturn next === undefined || next === this.value ? mapping.bounds[0] : next;\n\t}\n}\n"],"mappings":";;;;AAWA,MAAM,eAAe,SACT;;;;;;;;;;;;IAaX,cACD;AAEA,IAAa,cAAb,cAAiC,kBAAkB;CAClD,IAAI,QAAyB;EAC5B,OAAO,KAAK,aAAa,OAAO,MAAM,SAAS,SAAS;CACzD;CAEA,IAAI,MAAM,SAAsC;EAC/C,KAAK,QAAQ,SAAS,OAAO;CAC9B;CAEA,AAAS,UAAU,aAAa;CAEhC,AAAS,UAAU,aAAa,KAAK,SAAS,uBAAuB,eAAe;CAEpF,AAAS,oBAA0B;EAClC,KAAK,kBAAkB,OAAO;EAC9B,MAAM,kBAAkB;CACzB;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,SAAS,KAAK;EAEpB,KAAK,YAAY,QAAQ,MAAM;EAC/B,KAAK,OAAO;EACC,KAAK,YAAY,QAAQ,YAAY;EAClD,KAAK,aAAa,QAAQ,eAAe;GACxC,WAAW,UAAU,CAAC,MAAM;GAC5B,UAAU,KAAK,SAAS,QAAQ,wBAAwB;EACzD,EAAE;CACH;CAEA,AAAmB,cAA4B;EAC9C,OAAO;CACR;CAEA,AAAU,OAAa;EACtB,KAAK,QAAQ,cAAc,KAAK;CACjC;CAEA,AAAmB,cAA2B;EAC7C,OAAO,KAAK;CACb;CAEA,AAAmB,cAA2B;EAC7C,OAAO,KAAK;CACb;CAEA,AAAmB,YAAgC;EAClD,IAAI,KAAK,UAAU,QAAQ,OAAO;EAElC,MAAM,UAAU,KAAK,QAAQ;EAC7B,MAAM,OAAO,QAAQ,UAAU,WAAW,KAAK,KAAK;EAEpD,OAAO,SAAS,UAAa,SAAS,KAAK,QAAQ,QAAQ,OAAO,KAAK;CACxE;AACD"}
@@ -27,6 +27,7 @@ export declare abstract class SonicRadioGroupElement extends SonicFormElement {
27
27
  protected abstract renderOption(): HTMLButtonElement;
28
28
  protected restoreState(state: string): void;
29
29
  protected select(index: number): void;
30
+ protected stateTarget(): HTMLElement;
30
31
  protected stepFrom(from: number, step: number): number | undefined;
31
32
  }
32
33
  //#endregion
@@ -139,6 +139,9 @@ var SonicRadioGroupElement = class extends SonicFormElement {
139
139
  this.value = next;
140
140
  this.dispatchEvent(new Event("change", { bubbles: true }));
141
141
  }
142
+ stateTarget() {
143
+ return this.group;
144
+ }
142
145
  stepFrom(from, step) {
143
146
  return stepFrom(this.positions(), from, {
144
147
  isWrapping: this.isWrapping(),
@@ -192,7 +195,7 @@ var SonicRadioGroupElement = class extends SonicFormElement {
192
195
  if (focused) this.#refocus(focused, focusedValue);
193
196
  }
194
197
  #markPressed(pressed) {
195
- for (const [index, option] of this.options().entries()) option.toggleAttribute("data-sonic-pressed", index === pressed);
198
+ for (const [index, option] of this.options().entries()) option.toggleAttribute("data-sonic-active", index === pressed);
196
199
  }
197
200
  #optionBoxes() {
198
201
  const positions = this.positions();
@@ -1 +1 @@
1
- {"version":3,"file":"radio-group.js","names":[],"sources":["../../../src/elements/radio-group.ts"],"sourcesContent":["import type { Position } from '#lib/positions.ts';\n\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { copyNode } from '#lib/copy-node.ts';\nimport { bindKeyPress } from '#lib/key-press.ts';\nimport { mirrorChildren } from '#lib/mirror-children.ts';\nimport { bindDrag } from '#lib/pointer-drag.ts';\nimport { keyTarget, stepFrom } from '#lib/positions.ts';\nimport { placeChildren } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ninterface Press {\n\tboxes: Array<[number, DOMRect]>;\n\tindex: number | undefined;\n}\n\nfunction isInside(box: DOMRect, x: number, y: number): boolean {\n\treturn x >= box.left && x < box.right && y >= box.top && y < box.bottom;\n}\n\nexport function isUnder(option: Element, x: number, y: number): boolean {\n\treturn isInside(option.getBoundingClientRect(), x, y);\n}\n\nexport function optionValue(option: Element): string | undefined {\n\treturn option.querySelector<HTMLElement | SVGElement>('[data-sonic-value]')?.dataset.sonicValue;\n}\n\nexport abstract class SonicRadioGroupElement extends SonicFormElement {\n\tstatic override readonly observedAttributes = [...SonicFormElement.observedAttributes, 'value'];\n\n\tget value(): string {\n\t\treturn this.#value;\n\t}\n\n\tset value(next: string) {\n\t\tif (next === this.#value) return;\n\n\t\tthis.#value = next;\n\t\tthis.render();\n\t}\n\n\tprotected abstract readonly group: HTMLElement;\n\n\t#value = '';\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'value') this.#value = this.getAttribute('value') ?? '';\n\t\tthis.render();\n\t}\n\n\tprotected chooseByClick(index: number): void {\n\t\tthis.select(index);\n\t}\n\n\tprotected chooseByKey(index: number, _event: KeyboardEvent): void {\n\t\tthis.select(index);\n\t\tthis.options()[index]?.focus();\n\t}\n\n\tprotected claimPress(_index: number, _event: PointerEvent): boolean {\n\t\treturn false;\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst group = this.group;\n\n\t\tthis.upgradeProperties('value');\n\t\tmirrorChildren(\n\t\t\tthis,\n\t\t\t{\n\t\t\t\tcontrol: group,\n\t\t\t\tcopy: copyNode,\n\t\t\t\tisCopied: (child) => this.isMirrored(child),\n\t\t\t\tplace: (copies) => {\n\t\t\t\t\tthis.placeCopies(copies);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\n\t\tgroup.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\tif (event instanceof PointerEvent && event.pointerType !== '') return;\n\n\t\t\t\tconst index = this.indexOf(event.target);\n\t\t\t\tif (index !== -1) this.chooseByClick(index);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindPress(group, signal);\n\t\tbindKeyPress(group, signal);\n\t\tgroup.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (event.defaultPrevented) return;\n\n\t\t\t\tconst index = this.indexOf(event.target);\n\t\t\t\tconst next =\n\t\t\t\t\tindex === -1\n\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t: keyTarget(this.positions(), index, { isWrapping: this.isWrapping(), key: event.key });\n\t\t\t\tif (next === undefined) return;\n\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.chooseByKey(next, event);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\tprotected override focusTarget(): HTMLElement | undefined {\n\t\treturn this.options().find((option) => option.tabIndex === 0);\n\t}\n\n\tprotected indexOf(target: EventTarget | null): number {\n\t\tconst option = this.optionOf(target);\n\n\t\treturn option ? this.options().indexOf(option) : -1;\n\t}\n\n\tprotected isMirrored(child: Node): boolean {\n\t\treturn child instanceof Element && child.matches('[data-sonic-value]');\n\t}\n\n\tprotected isOptionDisabled(index: number): boolean {\n\t\treturn this.positions()[index]?.isDisabled !== false;\n\t}\n\n\tprotected isWrapping(): boolean {\n\t\treturn true;\n\t}\n\n\tprotected optionOf(target: EventTarget | null): HTMLButtonElement | undefined {\n\t\tif (!(target instanceof Element)) return undefined;\n\n\t\tconst option = target.closest('[role=\"radio\"]');\n\n\t\treturn option instanceof HTMLButtonElement && option.parentElement === this.group\n\t\t\t? option\n\t\t\t: undefined;\n\t}\n\n\tprotected options(): Array<HTMLButtonElement> {\n\t\treturn [...this.group.children].filter(\n\t\t\t(child): child is HTMLButtonElement =>\n\t\t\t\tchild instanceof HTMLButtonElement && child.getAttribute('role') === 'radio',\n\t\t);\n\t}\n\n\tprotected placeCopies(copies: Array<Node>): void {\n\t\tthis.#copyOptions(copies);\n\t}\n\n\tprotected positions(): Array<Position> {\n\t\treturn this.options().map((option) => {\n\t\t\tconst child = option.querySelector('[data-sonic-value]');\n\n\t\t\treturn {\n\t\t\t\tisDisabled: child?.hasAttribute('data-sonic-disabled') === true,\n\t\t\t\tisMomentary: child?.hasAttribute('data-sonic-momentary') === true,\n\t\t\t};\n\t\t});\n\t}\n\n\tprotected refocusAt(index: number): void {\n\t\tif (this.group.matches(':focus-within')) this.options()[index]?.focus();\n\t}\n\n\tprotected releaseTarget(index: number): number | undefined {\n\t\treturn index;\n\t}\n\n\tprotected render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst options = this.options();\n\t\tconst positions = this.positions();\n\t\tconst checked = options.find((option) => optionValue(option) === this.#value);\n\t\tconst enabled = this.isDisabled()\n\t\t\t? []\n\t\t\t: options.filter((_option, index) => positions[index]?.isDisabled === false);\n\t\tconst focusable = enabled.find((option) => option === checked) ?? enabled[0];\n\n\t\tfor (const option of options) {\n\t\t\twriteAttribute(option, 'disabled', enabled.includes(option) ? undefined : '');\n\t\t\twriteAttribute(option, 'aria-checked', String(option === checked));\n\t\t\twriteAttribute(option, 'tabindex', option === focusable ? '0' : '-1');\n\t\t}\n\t\tthis.forwardNaming(this.group, true);\n\t\t// eslint-disable-next-line unicorn/no-null -- `null` submits nothing\n\t\tthis.writeFormValue(checked ? this.#value : null, this.#value);\n\t}\n\n\tprotected abstract renderOption(): HTMLButtonElement;\n\n\tprotected restoreState(state: string): void {\n\t\tthis.value = state;\n\t}\n\n\tprotected select(index: number): void {\n\t\tconst option = this.options()[index];\n\t\tconst next = option && optionValue(option);\n\t\tif (next === undefined || next === this.#value) return;\n\n\t\tthis.value = next;\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\tprotected stepFrom(from: number, step: number): number | undefined {\n\t\treturn stepFrom(this.positions(), from, { isWrapping: this.isWrapping(), step });\n\t}\n\n\t#bindPress(group: HTMLElement, signal: AbortSignal): void {\n\t\tbindDrag<Press>(\n\t\t\tgroup,\n\t\t\t{\n\t\t\t\tgrab: (event) => {\n\t\t\t\t\tconst index = this.indexOf(event.target);\n\t\t\t\t\tif (index === -1 || this.isDisabled() || this.isOptionDisabled(index)) return;\n\t\t\t\t\tif (this.claimPress(index, event)) return;\n\n\t\t\t\t\tconst boxes = this.#optionBoxes();\n\n\t\t\t\t\tthis.#markPressed(index);\n\n\t\t\t\t\treturn { boxes, index };\n\t\t\t\t},\n\t\t\t\tlift: (press) => {\n\t\t\t\t\tif (press.index === undefined || this.isDisabled()) return;\n\n\t\t\t\t\tconst index = this.releaseTarget(press.index);\n\t\t\t\t\tif (index === undefined) return;\n\n\t\t\t\t\tthis.select(index);\n\t\t\t\t\tthis.refocusAt(index);\n\t\t\t\t},\n\t\t\t\tmove: (press, event) => {\n\t\t\t\t\tpress.index = press.boxes.find(([, box]) =>\n\t\t\t\t\t\tisInside(box, event.clientX, event.clientY),\n\t\t\t\t\t)?.[0];\n\t\t\t\t\tthis.#markPressed(press.index);\n\t\t\t\t},\n\t\t\t\trelease: () => {\n\t\t\t\t\tthis.#markPressed(undefined);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#copyOptions(copies: Array<Node>): void {\n\t\tconst group = this.group;\n\t\tconst options = this.options();\n\t\tconst focused = this.optionOf(group.querySelector(':scope > :focus'));\n\t\tconst focusedValue = focused && optionValue(focused);\n\n\t\tfor (const [index, copy] of copies.entries()) {\n\t\t\tlet option = options[index];\n\t\t\tif (!option) {\n\t\t\t\toption = this.renderOption();\n\t\t\t\tgroup.append(option);\n\t\t\t}\n\n\t\t\tif (option.firstElementChild) placeChildren(option.firstElementChild, [copy]);\n\t\t}\n\t\tfor (const option of options.slice(copies.length)) option.remove();\n\t\tthis.render();\n\t\tif (focused) this.#refocus(focused, focusedValue);\n\t}\n\n\t#markPressed(pressed: number | undefined): void {\n\t\tfor (const [index, option] of this.options().entries()) {\n\t\t\toption.toggleAttribute('data-sonic-pressed', index === pressed);\n\t\t}\n\t}\n\n\t#optionBoxes(): Array<[number, DOMRect]> {\n\t\tconst positions = this.positions();\n\n\t\treturn this.options().flatMap((option, index) =>\n\t\t\tpositions[index]?.isDisabled === false\n\t\t\t\t? [[index, option.getBoundingClientRect()] satisfies [number, DOMRect]]\n\t\t\t\t: [],\n\t\t);\n\t}\n\n\t#refocus(focused: HTMLButtonElement, value: string | undefined): void {\n\t\tconst options = this.options();\n\t\tconst next =\n\t\t\toptions.find((option) => optionValue(option) === value) ??\n\t\t\toptions.find((option) => option.tabIndex === 0);\n\n\t\tif (next !== focused) next?.focus();\n\t}\n}\n"],"mappings":";;;;;;;;;;AAgBA,SAAS,SAAS,KAAc,GAAW,GAAoB;CAC9D,OAAO,KAAK,IAAI,QAAQ,IAAI,IAAI,SAAS,KAAK,IAAI,OAAO,IAAI,IAAI;AAClE;AAEA,SAAgB,QAAQ,QAAiB,GAAW,GAAoB;CACvE,OAAO,SAAS,OAAO,sBAAsB,GAAG,GAAG,CAAC;AACrD;AAEA,SAAgB,YAAY,QAAqC;CAChE,OAAO,OAAO,cAAwC,oBAAoB,CAAC,EAAE,QAAQ;AACtF;AAEA,IAAsB,yBAAtB,cAAqD,iBAAiB;CACrE,OAAyB,qBAAqB,CAAC,GAAG,iBAAiB,oBAAoB,OAAO;CAE9F,IAAI,QAAgB;EACnB,OAAO,KAAK;CACb;CAEA,IAAI,MAAM,MAAc;EACvB,IAAI,SAAS,KAAK,QAAQ;EAE1B,KAAK,SAAS;EACd,KAAK,OAAO;CACb;CAIA,SAAS;CAET,yBAAyB,MAAoB;EAC5C,IAAI,SAAS,SAAS,KAAK,SAAS,KAAK,aAAa,OAAO,KAAK;EAClE,KAAK,OAAO;CACb;CAEA,AAAU,cAAc,OAAqB;EAC5C,KAAK,OAAO,KAAK;CAClB;CAEA,AAAU,YAAY,OAAe,QAA6B;EACjE,KAAK,OAAO,KAAK;EACjB,KAAK,QAAQ,CAAC,CAAC,MAAM,EAAE,MAAM;CAC9B;CAEA,AAAU,WAAW,QAAgB,QAA+B;EACnE,OAAO;CACR;CAEA,AAAU,QAAQ,QAA2B;EAC5C,MAAM,QAAQ,KAAK;EAEnB,KAAK,kBAAkB,OAAO;EAC9B,eACC,MACA;GACC,SAAS;GACT,MAAM;GACN,WAAW,UAAU,KAAK,WAAW,KAAK;GAC1C,QAAQ,WAAW;IAClB,KAAK,YAAY,MAAM;GACxB;EACD,GACA,MACD;EAEA,MAAM,iBACL,UACC,UAAU;GACV,IAAI,iBAAiB,gBAAgB,MAAM,gBAAgB,IAAI;GAE/D,MAAM,QAAQ,KAAK,QAAQ,MAAM,MAAM;GACvC,IAAI,UAAU,IAAI,KAAK,cAAc,KAAK;EAC3C,GACA,EAAE,OAAO,CACV;EACA,KAAK,WAAW,OAAO,MAAM;EAC7B,aAAa,OAAO,MAAM;EAC1B,MAAM,iBACL,YACC,UAAU;GACV,IAAI,MAAM,kBAAkB;GAE5B,MAAM,QAAQ,KAAK,QAAQ,MAAM,MAAM;GACvC,MAAM,OACL,UAAU,KACP,SACA,UAAU,KAAK,UAAU,GAAG,OAAO;IAAE,YAAY,KAAK,WAAW;IAAG,KAAK,MAAM;GAAI,CAAC;GACxF,IAAI,SAAS,QAAW;GAExB,MAAM,eAAe;GACrB,KAAK,YAAY,MAAM,KAAK;EAC7B,GACA,EAAE,OAAO,CACV;CACD;CAEA,AAAmB,cAAuC;EACzD,OAAO,KAAK,QAAQ,CAAC,CAAC,MAAM,WAAW,OAAO,aAAa,CAAC;CAC7D;CAEA,AAAU,QAAQ,QAAoC;EACrD,MAAM,SAAS,KAAK,SAAS,MAAM;EAEnC,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,QAAQ,MAAM,IAAI;CAClD;CAEA,AAAU,WAAW,OAAsB;EAC1C,OAAO,iBAAiB,WAAW,MAAM,QAAQ,oBAAoB;CACtE;CAEA,AAAU,iBAAiB,OAAwB;EAClD,OAAO,KAAK,UAAU,CAAC,CAAC,MAAM,EAAE,eAAe;CAChD;CAEA,AAAU,aAAsB;EAC/B,OAAO;CACR;CAEA,AAAU,SAAS,QAA2D;EAC7E,IAAI,EAAE,kBAAkB,UAAU,OAAO;EAEzC,MAAM,SAAS,OAAO,QAAQ,kBAAgB;EAE9C,OAAO,kBAAkB,qBAAqB,OAAO,kBAAkB,KAAK,QACzE,SACA;CACJ;CAEA,AAAU,UAAoC;EAC7C,OAAO,CAAC,GAAG,KAAK,MAAM,QAAQ,CAAC,CAAC,QAC9B,UACA,iBAAiB,qBAAqB,MAAM,aAAa,MAAM,MAAM,OACvE;CACD;CAEA,AAAU,YAAY,QAA2B;EAChD,KAAK,aAAa,MAAM;CACzB;CAEA,AAAU,YAA6B;EACtC,OAAO,KAAK,QAAQ,CAAC,CAAC,KAAK,WAAW;GACrC,MAAM,QAAQ,OAAO,cAAc,oBAAoB;GAEvD,OAAO;IACN,YAAY,OAAO,aAAa,qBAAqB,MAAM;IAC3D,aAAa,OAAO,aAAa,sBAAsB,MAAM;GAC9D;EACD,CAAC;CACF;CAEA,AAAU,UAAU,OAAqB;EACxC,IAAI,KAAK,MAAM,QAAQ,eAAe,GAAG,KAAK,QAAQ,CAAC,CAAC,MAAM,EAAE,MAAM;CACvE;CAEA,AAAU,cAAc,OAAmC;EAC1D,OAAO;CACR;CAEA,AAAU,SAAe;EACxB,IAAI,CAAC,KAAK,QAAQ,GAAG;EAErB,MAAM,UAAU,KAAK,QAAQ;EAC7B,MAAM,YAAY,KAAK,UAAU;EACjC,MAAM,UAAU,QAAQ,MAAM,WAAW,YAAY,MAAM,MAAM,KAAK,MAAM;EAC5E,MAAM,UAAU,KAAK,WAAW,IAC7B,CAAC,IACD,QAAQ,QAAQ,SAAS,UAAU,UAAU,MAAM,EAAE,eAAe,KAAK;EAC5E,MAAM,YAAY,QAAQ,MAAM,WAAW,WAAW,OAAO,KAAK,QAAQ;EAE1E,KAAK,MAAM,UAAU,SAAS;GAC7B,eAAe,QAAQ,YAAY,QAAQ,SAAS,MAAM,IAAI,SAAY,EAAE;GAC5E,eAAe,QAAQ,gBAAgB,OAAO,WAAW,OAAO,CAAC;GACjE,eAAe,QAAQ,YAAY,WAAW,YAAY,MAAM,IAAI;EACrE;EACA,KAAK,cAAc,KAAK,OAAO,IAAI;EAEnC,KAAK,eAAe,UAAU,KAAK,SAAS,MAAM,KAAK,MAAM;CAC9D;CAIA,AAAU,aAAa,OAAqB;EAC3C,KAAK,QAAQ;CACd;CAEA,AAAU,OAAO,OAAqB;EACrC,MAAM,SAAS,KAAK,QAAQ,CAAC,CAAC;EAC9B,MAAM,OAAO,UAAU,YAAY,MAAM;EACzC,IAAI,SAAS,UAAa,SAAS,KAAK,QAAQ;EAEhD,KAAK,QAAQ;EACb,KAAK,cAAc,IAAI,MAAM,UAAU,EAAE,SAAS,KAAK,CAAC,CAAC;CAC1D;CAEA,AAAU,SAAS,MAAc,MAAkC;EAClE,OAAO,SAAS,KAAK,UAAU,GAAG,MAAM;GAAE,YAAY,KAAK,WAAW;GAAG;EAAK,CAAC;CAChF;CAEA,WAAW,OAAoB,QAA2B;EACzD,SACC,OACA;GACC,OAAO,UAAU;IAChB,MAAM,QAAQ,KAAK,QAAQ,MAAM,MAAM;IACvC,IAAI,UAAU,MAAM,KAAK,WAAW,KAAK,KAAK,iBAAiB,KAAK,GAAG;IACvE,IAAI,KAAK,WAAW,OAAO,KAAK,GAAG;IAEnC,MAAM,QAAQ,KAAK,aAAa;IAEhC,KAAK,aAAa,KAAK;IAEvB,OAAO;KAAE;KAAO;IAAM;GACvB;GACA,OAAO,UAAU;IAChB,IAAI,MAAM,UAAU,UAAa,KAAK,WAAW,GAAG;IAEpD,MAAM,QAAQ,KAAK,cAAc,MAAM,KAAK;IAC5C,IAAI,UAAU,QAAW;IAEzB,KAAK,OAAO,KAAK;IACjB,KAAK,UAAU,KAAK;GACrB;GACA,OAAO,OAAO,UAAU;IACvB,MAAM,QAAQ,MAAM,MAAM,MAAM,GAAG,SAClC,SAAS,KAAK,MAAM,SAAS,MAAM,OAAO,CAC3C,CAAC,GAAG;IACJ,KAAK,aAAa,MAAM,KAAK;GAC9B;GACA,eAAe;IACd,KAAK,aAAa,MAAS;GAC5B;EACD,GACA,MACD;CACD;CAEA,aAAa,QAA2B;EACvC,MAAM,QAAQ,KAAK;EACnB,MAAM,UAAU,KAAK,QAAQ;EAC7B,MAAM,UAAU,KAAK,SAAS,MAAM,cAAc,iBAAiB,CAAC;EACpE,MAAM,eAAe,WAAW,YAAY,OAAO;EAEnD,KAAK,MAAM,CAAC,OAAO,SAAS,OAAO,QAAQ,GAAG;GAC7C,IAAI,SAAS,QAAQ;GACrB,IAAI,CAAC,QAAQ;IACZ,SAAS,KAAK,aAAa;IAC3B,MAAM,OAAO,MAAM;GACpB;GAEA,IAAI,OAAO,mBAAmB,cAAc,OAAO,mBAAmB,CAAC,IAAI,CAAC;EAC7E;EACA,KAAK,MAAM,UAAU,QAAQ,MAAM,OAAO,MAAM,GAAG,OAAO,OAAO;EACjE,KAAK,OAAO;EACZ,IAAI,SAAS,KAAK,SAAS,SAAS,YAAY;CACjD;CAEA,aAAa,SAAmC;EAC/C,KAAK,MAAM,CAAC,OAAO,WAAW,KAAK,QAAQ,CAAC,CAAC,QAAQ,GACpD,OAAO,gBAAgB,sBAAsB,UAAU,OAAO;CAEhE;CAEA,eAAyC;EACxC,MAAM,YAAY,KAAK,UAAU;EAEjC,OAAO,KAAK,QAAQ,CAAC,CAAC,SAAS,QAAQ,UACtC,UAAU,MAAM,EAAE,eAAe,QAC9B,CAAC,CAAC,OAAO,OAAO,sBAAsB,CAAC,CAA6B,IACpE,CAAC,CACL;CACD;CAEA,SAAS,SAA4B,OAAiC;EACrE,MAAM,UAAU,KAAK,QAAQ;EAC7B,MAAM,OACL,QAAQ,MAAM,WAAW,YAAY,MAAM,MAAM,KAAK,KACtD,QAAQ,MAAM,WAAW,OAAO,aAAa,CAAC;EAE/C,IAAI,SAAS,SAAS,MAAM,MAAM;CACnC;AACD"}
1
+ {"version":3,"file":"radio-group.js","names":[],"sources":["../../../src/elements/radio-group.ts"],"sourcesContent":["import type { Position } from '#lib/positions.ts';\n\nimport { SonicFormElement } from '#elements/form-element.ts';\nimport { copyNode } from '#lib/copy-node.ts';\nimport { bindKeyPress } from '#lib/key-press.ts';\nimport { mirrorChildren } from '#lib/mirror-children.ts';\nimport { bindDrag } from '#lib/pointer-drag.ts';\nimport { keyTarget, stepFrom } from '#lib/positions.ts';\nimport { placeChildren } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ninterface Press {\n\tboxes: Array<[number, DOMRect]>;\n\tindex: number | undefined;\n}\n\nfunction isInside(box: DOMRect, x: number, y: number): boolean {\n\treturn x >= box.left && x < box.right && y >= box.top && y < box.bottom;\n}\n\nexport function isUnder(option: Element, x: number, y: number): boolean {\n\treturn isInside(option.getBoundingClientRect(), x, y);\n}\n\nexport function optionValue(option: Element): string | undefined {\n\treturn option.querySelector<HTMLElement | SVGElement>('[data-sonic-value]')?.dataset.sonicValue;\n}\n\nexport abstract class SonicRadioGroupElement extends SonicFormElement {\n\tstatic override readonly observedAttributes = [...SonicFormElement.observedAttributes, 'value'];\n\n\tget value(): string {\n\t\treturn this.#value;\n\t}\n\n\tset value(next: string) {\n\t\tif (next === this.#value) return;\n\n\t\tthis.#value = next;\n\t\tthis.render();\n\t}\n\n\tprotected abstract readonly group: HTMLElement;\n\n\t#value = '';\n\n\tattributeChangedCallback(name: string): void {\n\t\tif (name === 'value') this.#value = this.getAttribute('value') ?? '';\n\t\tthis.render();\n\t}\n\n\tprotected chooseByClick(index: number): void {\n\t\tthis.select(index);\n\t}\n\n\tprotected chooseByKey(index: number, _event: KeyboardEvent): void {\n\t\tthis.select(index);\n\t\tthis.options()[index]?.focus();\n\t}\n\n\tprotected claimPress(_index: number, _event: PointerEvent): boolean {\n\t\treturn false;\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst group = this.group;\n\n\t\tthis.upgradeProperties('value');\n\t\tmirrorChildren(\n\t\t\tthis,\n\t\t\t{\n\t\t\t\tcontrol: group,\n\t\t\t\tcopy: copyNode,\n\t\t\t\tisCopied: (child) => this.isMirrored(child),\n\t\t\t\tplace: (copies) => {\n\t\t\t\t\tthis.placeCopies(copies);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\n\t\tgroup.addEventListener(\n\t\t\t'click',\n\t\t\t(event) => {\n\t\t\t\tif (event instanceof PointerEvent && event.pointerType !== '') return;\n\n\t\t\t\tconst index = this.indexOf(event.target);\n\t\t\t\tif (index !== -1) this.chooseByClick(index);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.#bindPress(group, signal);\n\t\tbindKeyPress(group, signal);\n\t\tgroup.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tif (event.defaultPrevented) return;\n\n\t\t\t\tconst index = this.indexOf(event.target);\n\t\t\t\tconst next =\n\t\t\t\t\tindex === -1\n\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t: keyTarget(this.positions(), index, { isWrapping: this.isWrapping(), key: event.key });\n\t\t\t\tif (next === undefined) return;\n\n\t\t\t\tevent.preventDefault();\n\t\t\t\tthis.chooseByKey(next, event);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\tprotected override focusTarget(): HTMLElement | undefined {\n\t\treturn this.options().find((option) => option.tabIndex === 0);\n\t}\n\n\tprotected indexOf(target: EventTarget | null): number {\n\t\tconst option = this.optionOf(target);\n\n\t\treturn option ? this.options().indexOf(option) : -1;\n\t}\n\n\tprotected isMirrored(child: Node): boolean {\n\t\treturn child instanceof Element && child.matches('[data-sonic-value]');\n\t}\n\n\tprotected isOptionDisabled(index: number): boolean {\n\t\treturn this.positions()[index]?.isDisabled !== false;\n\t}\n\n\tprotected isWrapping(): boolean {\n\t\treturn true;\n\t}\n\n\tprotected optionOf(target: EventTarget | null): HTMLButtonElement | undefined {\n\t\tif (!(target instanceof Element)) return undefined;\n\n\t\tconst option = target.closest('[role=\"radio\"]');\n\n\t\treturn option instanceof HTMLButtonElement && option.parentElement === this.group\n\t\t\t? option\n\t\t\t: undefined;\n\t}\n\n\tprotected options(): Array<HTMLButtonElement> {\n\t\treturn [...this.group.children].filter(\n\t\t\t(child): child is HTMLButtonElement =>\n\t\t\t\tchild instanceof HTMLButtonElement && child.getAttribute('role') === 'radio',\n\t\t);\n\t}\n\n\tprotected placeCopies(copies: Array<Node>): void {\n\t\tthis.#copyOptions(copies);\n\t}\n\n\tprotected positions(): Array<Position> {\n\t\treturn this.options().map((option) => {\n\t\t\tconst child = option.querySelector('[data-sonic-value]');\n\n\t\t\treturn {\n\t\t\t\tisDisabled: child?.hasAttribute('data-sonic-disabled') === true,\n\t\t\t\tisMomentary: child?.hasAttribute('data-sonic-momentary') === true,\n\t\t\t};\n\t\t});\n\t}\n\n\tprotected refocusAt(index: number): void {\n\t\tif (this.group.matches(':focus-within')) this.options()[index]?.focus();\n\t}\n\n\tprotected releaseTarget(index: number): number | undefined {\n\t\treturn index;\n\t}\n\n\tprotected render(): void {\n\t\tif (!this.isBound()) return;\n\n\t\tconst options = this.options();\n\t\tconst positions = this.positions();\n\t\tconst checked = options.find((option) => optionValue(option) === this.#value);\n\t\tconst enabled = this.isDisabled()\n\t\t\t? []\n\t\t\t: options.filter((_option, index) => positions[index]?.isDisabled === false);\n\t\tconst focusable = enabled.find((option) => option === checked) ?? enabled[0];\n\n\t\tfor (const option of options) {\n\t\t\twriteAttribute(option, 'disabled', enabled.includes(option) ? undefined : '');\n\t\t\twriteAttribute(option, 'aria-checked', String(option === checked));\n\t\t\twriteAttribute(option, 'tabindex', option === focusable ? '0' : '-1');\n\t\t}\n\t\tthis.forwardNaming(this.group, true);\n\t\t// eslint-disable-next-line unicorn/no-null -- `null` submits nothing\n\t\tthis.writeFormValue(checked ? this.#value : null, this.#value);\n\t}\n\n\tprotected abstract renderOption(): HTMLButtonElement;\n\n\tprotected restoreState(state: string): void {\n\t\tthis.value = state;\n\t}\n\n\tprotected select(index: number): void {\n\t\tconst option = this.options()[index];\n\t\tconst next = option && optionValue(option);\n\t\tif (next === undefined || next === this.#value) return;\n\n\t\tthis.value = next;\n\t\tthis.dispatchEvent(new Event('change', { bubbles: true }));\n\t}\n\n\tprotected override stateTarget(): HTMLElement {\n\t\treturn this.group;\n\t}\n\n\tprotected stepFrom(from: number, step: number): number | undefined {\n\t\treturn stepFrom(this.positions(), from, { isWrapping: this.isWrapping(), step });\n\t}\n\n\t#bindPress(group: HTMLElement, signal: AbortSignal): void {\n\t\tbindDrag<Press>(\n\t\t\tgroup,\n\t\t\t{\n\t\t\t\tgrab: (event) => {\n\t\t\t\t\tconst index = this.indexOf(event.target);\n\t\t\t\t\tif (index === -1 || this.isDisabled() || this.isOptionDisabled(index)) return;\n\t\t\t\t\tif (this.claimPress(index, event)) return;\n\n\t\t\t\t\tconst boxes = this.#optionBoxes();\n\n\t\t\t\t\tthis.#markPressed(index);\n\n\t\t\t\t\treturn { boxes, index };\n\t\t\t\t},\n\t\t\t\tlift: (press) => {\n\t\t\t\t\tif (press.index === undefined || this.isDisabled()) return;\n\n\t\t\t\t\tconst index = this.releaseTarget(press.index);\n\t\t\t\t\tif (index === undefined) return;\n\n\t\t\t\t\tthis.select(index);\n\t\t\t\t\tthis.refocusAt(index);\n\t\t\t\t},\n\t\t\t\tmove: (press, event) => {\n\t\t\t\t\tpress.index = press.boxes.find(([, box]) =>\n\t\t\t\t\t\tisInside(box, event.clientX, event.clientY),\n\t\t\t\t\t)?.[0];\n\t\t\t\t\tthis.#markPressed(press.index);\n\t\t\t\t},\n\t\t\t\trelease: () => {\n\t\t\t\t\tthis.#markPressed(undefined);\n\t\t\t\t},\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\t#copyOptions(copies: Array<Node>): void {\n\t\tconst group = this.group;\n\t\tconst options = this.options();\n\t\tconst focused = this.optionOf(group.querySelector(':scope > :focus'));\n\t\tconst focusedValue = focused && optionValue(focused);\n\n\t\tfor (const [index, copy] of copies.entries()) {\n\t\t\tlet option = options[index];\n\t\t\tif (!option) {\n\t\t\t\toption = this.renderOption();\n\t\t\t\tgroup.append(option);\n\t\t\t}\n\n\t\t\tif (option.firstElementChild) placeChildren(option.firstElementChild, [copy]);\n\t\t}\n\t\tfor (const option of options.slice(copies.length)) option.remove();\n\t\tthis.render();\n\t\tif (focused) this.#refocus(focused, focusedValue);\n\t}\n\n\t#markPressed(pressed: number | undefined): void {\n\t\tfor (const [index, option] of this.options().entries()) {\n\t\t\toption.toggleAttribute('data-sonic-active', index === pressed);\n\t\t}\n\t}\n\n\t#optionBoxes(): Array<[number, DOMRect]> {\n\t\tconst positions = this.positions();\n\n\t\treturn this.options().flatMap((option, index) =>\n\t\t\tpositions[index]?.isDisabled === false\n\t\t\t\t? [[index, option.getBoundingClientRect()] satisfies [number, DOMRect]]\n\t\t\t\t: [],\n\t\t);\n\t}\n\n\t#refocus(focused: HTMLButtonElement, value: string | undefined): void {\n\t\tconst options = this.options();\n\t\tconst next =\n\t\t\toptions.find((option) => optionValue(option) === value) ??\n\t\t\toptions.find((option) => option.tabIndex === 0);\n\n\t\tif (next !== focused) next?.focus();\n\t}\n}\n"],"mappings":"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"}
@@ -51,6 +51,7 @@ var RegionLayer = class {
51
51
  this.#release(true);
52
52
  }
53
53
  follow() {
54
+ if (this.#drawn.length === 0) return;
54
55
  if (this.#stripKey() !== this.#key) this.render();
55
56
  }
56
57
  isHeld() {
@@ -158,6 +159,7 @@ var RegionLayer = class {
158
159
  const span = link.span();
159
160
  const [low, high] = regionTravel(span, mapping.bounds);
160
161
  const canMove = this.#isEnabled(entry);
162
+ const stop = region.getAttribute("tabindex") === "-1" ? "-1" : "0";
161
163
  writeProperty(part, "--_sonic-region-from", String(mapping.proportionOf(span.start)));
162
164
  writeProperty(part, "--_sonic-region-to", String(mapping.proportionOf(span.end)));
163
165
  if (!known || entry.kind !== region.kind) {
@@ -170,7 +172,7 @@ var RegionLayer = class {
170
172
  writeAttribute(part, "aria-valuenow", String(span.start));
171
173
  writeAttribute(part, "aria-valuetext", host.speak(span));
172
174
  writeAttribute(part, "aria-disabled", canMove ? void 0 : "true");
173
- writeAttribute(part, "tabindex", canMove ? "0" : void 0);
175
+ writeAttribute(part, "tabindex", canMove ? stop : void 0);
174
176
  link.draw(part);
175
177
  return entry;
176
178
  }
@@ -1 +1 @@
1
- {"version":3,"file":"region-layer.js","names":[],"sources":["../../../src/elements/region-layer.ts"],"sourcesContent":["import type { RegionLink } from '#elements/region.ts';\nimport type { RegionDragState, RegionSpan } from '#lib/region-drag.ts';\nimport type { ValueMapping } from '#lib/value-mapping.ts';\n\nimport { linkRegion, ownRegions, SonicRegion } from '#elements/region.ts';\nimport { dragThresholdPx } from '#lib/drag-step.ts';\nimport { focusByPointer } from '#lib/focus-by-pointer.ts';\nimport { writeKind } from '#lib/marker-band.ts';\nimport { capturePointer } from '#lib/pointer-drag.ts';\nimport {\n\tmoveRegionTo,\n\tregionAt,\n\tregionTravel,\n\tstartRegionDrag,\n\tstepRegionDrag,\n} from '#lib/region-drag.ts';\nimport { placeChildren, template } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ninterface LayerHost {\n\taxis: () => { startPx: number; travelPx: number };\n\tcontrol: HTMLElement;\n\telement: HTMLElement;\n\tisDisabled: () => boolean;\n\tlayer: HTMLElement;\n\tmapping: () => ValueMapping;\n\toutside: () => ((event: PointerEvent) => boolean) | undefined;\n\tseek: (event: PointerEvent) => void;\n\tspeak: (span: RegionSpan) => string;\n}\n\ninterface Drawn {\n\tkind: string | undefined;\n\tlink: RegionLink;\n\tpart: HTMLElement;\n\tregion: SonicRegion;\n}\n\ninterface KeyScrub {\n\tdrawn: Drawn;\n\tfrom: RegionSpan;\n\tkey: string;\n}\n\ninterface PointerHold {\n\tdrawn: Drawn;\n\tfrom: RegionSpan;\n\toutside: ((event: PointerEvent) => boolean) | undefined;\n\tpointerId: number;\n\tpress: PointerEvent;\n\tstate: RegionDragState;\n}\n\nconst renderPart = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-region\" role=\"slider\" aria-orientation=\"horizontal\">\n\t\t\t<div class=\"sonic-region-bracket\"></div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nfunction hasMoved(from: RegionSpan, to: RegionSpan): boolean {\n\treturn from.start !== to.start || from.end !== to.end;\n}\n\nfunction clippedEnds(region: SonicRegion, [low, high]: [number, number]): string | undefined {\n\tconst ends = [\n\t\t...(Math.min(region.start, region.end) < low ? ['start'] : []),\n\t\t...(Math.max(region.start, region.end) > high ? ['end'] : []),\n\t];\n\n\treturn ends.length > 0 ? ends.join(' ') : undefined;\n}\n\nfunction writeProperty(part: HTMLElement, name: string, value: string): void {\n\tif (part.style.getPropertyValue(name) !== value) part.style.setProperty(name, value);\n}\n\nexport class RegionLayer {\n\t#drawn: Array<Drawn> = [];\n\n\t#held: PointerHold | undefined;\n\n\treadonly #host: LayerHost;\n\n\t#key: string | undefined;\n\n\t#keyScrub: KeyScrub | undefined;\n\n\tconstructor(host: LayerHost, signal: AbortSignal) {\n\t\tconst render = (): void => {\n\t\t\tthis.render();\n\t\t};\n\t\tconst observer = new MutationObserver(render);\n\n\t\tthis.#host = host;\n\t\tthis.#bindPointer(signal);\n\t\tthis.#bindKeys(signal);\n\t\tobserver.observe(host.element, { childList: true });\n\t\townRegions(\n\t\t\thost.element,\n\t\t\t(region) => {\n\t\t\t\tthis.#redraw(region);\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tobserver.disconnect();\n\t\t\t\tthis.#release();\n\t\t\t\tthis.#endKeyScrub();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\tabandon(): void {\n\t\tthis.#release(true);\n\t}\n\n\tfollow(): void {\n\t\tif (this.#stripKey() !== this.#key) this.render();\n\t}\n\n\tisHeld(): boolean {\n\t\treturn this.#held !== undefined;\n\t}\n\n\trender(): void {\n\t\tconst host = this.#host;\n\t\tconst regions = [...host.element.children].filter((child) => child instanceof SonicRegion);\n\t\tconst drawn = regions.map((region) => this.#draw(region));\n\n\t\tthis.#drawn = drawn;\n\t\tthis.#key = this.#stripKey();\n\t\tplaceChildren(\n\t\t\thost.layer,\n\t\t\tdrawn.map(({ part }) => part),\n\t\t);\n\t\tthis.#dropGone();\n\t}\n\n\ttake(event: PointerEvent): boolean {\n\t\tconst host = this.#host;\n\t\tconst { startPx, travelPx } = host.axis();\n\t\tconst mapping = host.mapping();\n\t\tconst index = regionAt(\n\t\t\tthis.#drawn.map((entry) => {\n\t\t\t\tif (!this.#isEnabled(entry)) return;\n\n\t\t\t\tconst { end, start } = entry.link.span();\n\n\t\t\t\treturn [mapping.proportionOf(start) * travelPx, mapping.proportionOf(end) * travelPx];\n\t\t\t}),\n\t\t\tevent.clientX - startPx,\n\t\t);\n\t\tconst pressed = index === undefined ? undefined : this.#drawn[index];\n\t\tif (!pressed) return false;\n\n\t\tthis.#endKeyScrub();\n\n\t\tconst from = pressed.link.span();\n\n\t\tthis.#held = {\n\t\t\tdrawn: pressed,\n\t\t\tfrom,\n\t\t\toutside: host.outside(),\n\t\t\tpointerId: event.pointerId,\n\t\t\tpress: event,\n\t\t\tstate: startRegionDrag({\n\t\t\t\tfrom,\n\t\t\t\tposition: event.clientX,\n\t\t\t\tthresholdPx: dragThresholdPx(event.pointerType),\n\t\t\t\twidthPx: travelPx,\n\t\t\t}),\n\t\t};\n\t\tpressed.link.hold('pointer');\n\t\tcapturePointer(host.control, event.pointerId);\n\n\t\treturn true;\n\t}\n\n\t#bindKeys(signal: AbortSignal): void {\n\t\tconst { layer } = this.#host;\n\n\t\tlayer.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tconst entry = this.#drawn.find(({ part }) => part === event.target);\n\t\t\t\tif (!entry || event.defaultPrevented || this.#held) return;\n\n\t\t\t\tif (this.#isEnabled(entry)) this.#keyTo(entry, event);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tlayer.addEventListener(\n\t\t\t'keyup',\n\t\t\t(event) => {\n\t\t\t\t// macOS sends no `keyup` for a key let go while Cmd is down\n\t\t\t\tif (event.key === 'Meta' || event.key === this.#keyScrub?.key) this.#endKeyScrub();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\t// No event fires when a label arrives, so focus picks it up\n\t\tlayer.addEventListener(\n\t\t\t'focusin',\n\t\t\t() => {\n\t\t\t\tthis.render();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tlayer.addEventListener(\n\t\t\t'focusout',\n\t\t\t() => {\n\t\t\t\tthis.#endKeyScrub();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\t#bindPointer(signal: AbortSignal): void {\n\t\tconst whileHeld = (\n\t\t\ttype: string,\n\t\t\thandle: (hold: PointerHold, event: PointerEvent) => void,\n\t\t): void => {\n\t\t\tthis.#host.control.addEventListener(\n\t\t\t\ttype,\n\t\t\t\t(event) => {\n\t\t\t\t\tconst held = this.#held;\n\n\t\t\t\t\tif (held && event instanceof PointerEvent && held.pointerId === event.pointerId) {\n\t\t\t\t\t\thandle(held, event);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{ signal },\n\t\t\t);\n\t\t};\n\n\t\twhileHeld('pointermove', (hold, event) => {\n\t\t\tthis.#dragTo(hold, event);\n\t\t});\n\t\twhileHeld('pointerup', (hold) => {\n\t\t\tconst isPress = !hold.state.isEngaged;\n\n\t\t\tthis.#release();\n\t\t\tif (isPress) this.#host.seek(hold.press);\n\t\t});\n\t\twhileHeld('pointercancel', () => {\n\t\t\tthis.#release(true);\n\t\t});\n\t\twhileHeld('lostpointercapture', () => {\n\t\t\tthis.#release();\n\t\t});\n\t}\n\n\t#dragTo(hold: PointerHold, event: PointerEvent): void {\n\t\tconst wasEngaged = hold.state.isEngaged;\n\t\tconst { span, state } = stepRegionDrag(this.#host.mapping(), hold.state, {\n\t\t\tisFine: event.shiftKey,\n\t\t\tposition: event.clientX,\n\t\t});\n\n\t\thold.state = state;\n\t\tif (!span) return;\n\n\t\tif (!wasEngaged) focusByPointer(hold.drawn.part);\n\t\thold.drawn.link.input(hold.outside?.(event) === true ? hold.from : span);\n\t}\n\n\t#draw(region: SonicRegion): Drawn {\n\t\tconst host = this.#host;\n\t\tconst known = this.#drawn.find((entry) => entry.region === region);\n\t\tconst entry = known ?? {\n\t\t\tkind: undefined,\n\t\t\tlink: linkRegion(region),\n\t\t\tpart: renderPart(),\n\t\t\tregion,\n\t\t};\n\t\tconst { link, part } = entry;\n\t\tconst mapping = host.mapping();\n\t\tconst span = link.span();\n\t\tconst [low, high] = regionTravel(span, mapping.bounds);\n\t\tconst canMove = this.#isEnabled(entry);\n\n\t\twriteProperty(part, '--_sonic-region-from', String(mapping.proportionOf(span.start)));\n\t\twriteProperty(part, '--_sonic-region-to', String(mapping.proportionOf(span.end)));\n\t\tif (!known || entry.kind !== region.kind) {\n\t\t\tentry.kind = region.kind;\n\t\t\twriteKind(part, region.kind, '--_sonic-ink');\n\t\t}\n\t\twriteAttribute(part, 'data-sonic-clipped', clippedEnds(region, mapping.bounds));\n\t\twriteAttribute(part, 'aria-valuemin', String(low));\n\t\twriteAttribute(part, 'aria-valuemax', String(high));\n\t\twriteAttribute(part, 'aria-valuenow', String(span.start));\n\t\twriteAttribute(part, 'aria-valuetext', host.speak(span));\n\t\twriteAttribute(part, 'aria-disabled', canMove ? undefined : 'true');\n\t\twriteAttribute(part, 'tabindex', canMove ? '0' : undefined);\n\t\tlink.draw(part);\n\n\t\treturn entry;\n\t}\n\n\t#dropGone(): void {\n\t\tconst isGone = (entry: Drawn): boolean =>\n\t\t\t!this.#drawn.includes(entry) || !this.#isEnabled(entry);\n\n\t\tif (this.#held && isGone(this.#held.drawn)) this.#release();\n\t\tif (this.#keyScrub && isGone(this.#keyScrub.drawn)) this.#endKeyScrub();\n\t}\n\n\t#endKeyScrub(): void {\n\t\tconst scrub = this.#keyScrub;\n\t\tif (!scrub) return;\n\n\t\tconst { link } = scrub.drawn;\n\n\t\tthis.#keyScrub = undefined;\n\t\tlink.hold(undefined);\n\t\tif (hasMoved(scrub.from, link.span())) link.change();\n\t}\n\n\t#isEnabled({ link }: Drawn): boolean {\n\t\treturn !this.#host.isDisabled() && !link.isDisabled();\n\t}\n\n\t#keyTo(entry: Drawn, event: KeyboardEvent): void {\n\t\tconst mapping = this.#host.mapping();\n\t\tconst from = entry.link.span();\n\t\tconst start = mapping.keyTarget(event.key, from.start);\n\t\tif (start === undefined) return;\n\n\t\tevent.preventDefault();\n\n\t\tconst next = moveRegionTo(mapping, from, start);\n\n\t\tif (!event.repeat) {\n\t\t\tthis.#endKeyScrub();\n\t\t\tif (entry.link.input(next)) entry.link.change();\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.#keyScrub?.drawn !== entry || this.#keyScrub.key !== event.key) {\n\t\t\tthis.#endKeyScrub();\n\t\t\tthis.#keyScrub = { drawn: entry, from, key: event.key };\n\t\t\tentry.link.hold('keys');\n\t\t}\n\t\tentry.link.input(next);\n\t}\n\n\t#redraw(region: SonicRegion): void {\n\t\tif (this.#drawn.every((entry) => entry.region !== region)) {\n\t\t\tthis.render();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#draw(region);\n\t\tthis.#dropGone();\n\t}\n\n\t#release(isRestored = false): void {\n\t\tconst hold = this.#held;\n\t\tif (!hold) return;\n\n\t\tconst { link } = hold.drawn;\n\n\t\tthis.#held = undefined;\n\t\tif (isRestored) link.input(hold.from);\n\t\tlink.hold(undefined);\n\t\tif (hasMoved(hold.from, link.span())) link.change();\n\t}\n\n\t// A changed value formatter is told by what it says of one time\n\t#stripKey(): string {\n\t\tconst host = this.#host;\n\t\tconst mapping = host.mapping();\n\t\tconst [low, high] = mapping.bounds;\n\n\t\treturn [\n\t\t\tlow,\n\t\t\thigh,\n\t\t\tmapping.proportionOf((low + high) / 2),\n\t\t\thost.isDisabled(),\n\t\t\thost.speak({ end: high, start: high }),\n\t\t].join('|');\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"region-layer.js","names":[],"sources":["../../../src/elements/region-layer.ts"],"sourcesContent":["import type { RegionLink } from '#elements/region.ts';\nimport type { RegionDragState, RegionSpan } from '#lib/region-drag.ts';\nimport type { ValueMapping } from '#lib/value-mapping.ts';\n\nimport { linkRegion, ownRegions, SonicRegion } from '#elements/region.ts';\nimport { dragThresholdPx } from '#lib/drag-step.ts';\nimport { focusByPointer } from '#lib/focus-by-pointer.ts';\nimport { writeKind } from '#lib/marker-band.ts';\nimport { capturePointer } from '#lib/pointer-drag.ts';\nimport {\n\tmoveRegionTo,\n\tregionAt,\n\tregionTravel,\n\tstartRegionDrag,\n\tstepRegionDrag,\n} from '#lib/region-drag.ts';\nimport { placeChildren, template } from '#lib/render.ts';\nimport { writeAttribute } from '#lib/write-attribute.ts';\n\ninterface LayerHost {\n\taxis: () => { startPx: number; travelPx: number };\n\tcontrol: HTMLElement;\n\telement: HTMLElement;\n\tisDisabled: () => boolean;\n\tlayer: HTMLElement;\n\tmapping: () => ValueMapping;\n\toutside: () => ((event: PointerEvent) => boolean) | undefined;\n\tseek: (event: PointerEvent) => void;\n\tspeak: (span: RegionSpan) => string;\n}\n\ninterface Drawn {\n\tkind: string | undefined;\n\tlink: RegionLink;\n\tpart: HTMLElement;\n\tregion: SonicRegion;\n}\n\ninterface KeyScrub {\n\tdrawn: Drawn;\n\tfrom: RegionSpan;\n\tkey: string;\n}\n\ninterface PointerHold {\n\tdrawn: Drawn;\n\tfrom: RegionSpan;\n\toutside: ((event: PointerEvent) => boolean) | undefined;\n\tpointerId: number;\n\tpress: PointerEvent;\n\tstate: RegionDragState;\n}\n\nconst renderPart = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-region\" role=\"slider\" aria-orientation=\"horizontal\">\n\t\t\t<div class=\"sonic-region-bracket\"></div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nfunction hasMoved(from: RegionSpan, to: RegionSpan): boolean {\n\treturn from.start !== to.start || from.end !== to.end;\n}\n\nfunction clippedEnds(region: SonicRegion, [low, high]: [number, number]): string | undefined {\n\tconst ends = [\n\t\t...(Math.min(region.start, region.end) < low ? ['start'] : []),\n\t\t...(Math.max(region.start, region.end) > high ? ['end'] : []),\n\t];\n\n\treturn ends.length > 0 ? ends.join(' ') : undefined;\n}\n\nfunction writeProperty(part: HTMLElement, name: string, value: string): void {\n\tif (part.style.getPropertyValue(name) !== value) part.style.setProperty(name, value);\n}\n\nexport class RegionLayer {\n\t#drawn: Array<Drawn> = [];\n\n\t#held: PointerHold | undefined;\n\n\treadonly #host: LayerHost;\n\n\t#key: string | undefined;\n\n\t#keyScrub: KeyScrub | undefined;\n\n\tconstructor(host: LayerHost, signal: AbortSignal) {\n\t\tconst render = (): void => {\n\t\t\tthis.render();\n\t\t};\n\t\tconst observer = new MutationObserver(render);\n\n\t\tthis.#host = host;\n\t\tthis.#bindPointer(signal);\n\t\tthis.#bindKeys(signal);\n\t\tobserver.observe(host.element, { childList: true });\n\t\townRegions(\n\t\t\thost.element,\n\t\t\t(region) => {\n\t\t\t\tthis.#redraw(region);\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tobserver.disconnect();\n\t\t\t\tthis.#release();\n\t\t\t\tthis.#endKeyScrub();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\tabandon(): void {\n\t\tthis.#release(true);\n\t}\n\n\tfollow(): void {\n\t\tif (this.#drawn.length === 0) return;\n\t\tif (this.#stripKey() !== this.#key) this.render();\n\t}\n\n\tisHeld(): boolean {\n\t\treturn this.#held !== undefined;\n\t}\n\n\trender(): void {\n\t\tconst host = this.#host;\n\t\tconst regions = [...host.element.children].filter((child) => child instanceof SonicRegion);\n\t\tconst drawn = regions.map((region) => this.#draw(region));\n\n\t\tthis.#drawn = drawn;\n\t\tthis.#key = this.#stripKey();\n\t\tplaceChildren(\n\t\t\thost.layer,\n\t\t\tdrawn.map(({ part }) => part),\n\t\t);\n\t\tthis.#dropGone();\n\t}\n\n\ttake(event: PointerEvent): boolean {\n\t\tconst host = this.#host;\n\t\tconst { startPx, travelPx } = host.axis();\n\t\tconst mapping = host.mapping();\n\t\tconst index = regionAt(\n\t\t\tthis.#drawn.map((entry) => {\n\t\t\t\tif (!this.#isEnabled(entry)) return;\n\n\t\t\t\tconst { end, start } = entry.link.span();\n\n\t\t\t\treturn [mapping.proportionOf(start) * travelPx, mapping.proportionOf(end) * travelPx];\n\t\t\t}),\n\t\t\tevent.clientX - startPx,\n\t\t);\n\t\tconst pressed = index === undefined ? undefined : this.#drawn[index];\n\t\tif (!pressed) return false;\n\n\t\tthis.#endKeyScrub();\n\n\t\tconst from = pressed.link.span();\n\n\t\tthis.#held = {\n\t\t\tdrawn: pressed,\n\t\t\tfrom,\n\t\t\toutside: host.outside(),\n\t\t\tpointerId: event.pointerId,\n\t\t\tpress: event,\n\t\t\tstate: startRegionDrag({\n\t\t\t\tfrom,\n\t\t\t\tposition: event.clientX,\n\t\t\t\tthresholdPx: dragThresholdPx(event.pointerType),\n\t\t\t\twidthPx: travelPx,\n\t\t\t}),\n\t\t};\n\t\tpressed.link.hold('pointer');\n\t\tcapturePointer(host.control, event.pointerId);\n\n\t\treturn true;\n\t}\n\n\t#bindKeys(signal: AbortSignal): void {\n\t\tconst { layer } = this.#host;\n\n\t\tlayer.addEventListener(\n\t\t\t'keydown',\n\t\t\t(event) => {\n\t\t\t\tconst entry = this.#drawn.find(({ part }) => part === event.target);\n\t\t\t\tif (!entry || event.defaultPrevented || this.#held) return;\n\n\t\t\t\tif (this.#isEnabled(entry)) this.#keyTo(entry, event);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tlayer.addEventListener(\n\t\t\t'keyup',\n\t\t\t(event) => {\n\t\t\t\t// macOS sends no `keyup` for a key let go while Cmd is down\n\t\t\t\tif (event.key === 'Meta' || event.key === this.#keyScrub?.key) this.#endKeyScrub();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\t// No event fires when a label arrives, so focus picks it up\n\t\tlayer.addEventListener(\n\t\t\t'focusin',\n\t\t\t() => {\n\t\t\t\tthis.render();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tlayer.addEventListener(\n\t\t\t'focusout',\n\t\t\t() => {\n\t\t\t\tthis.#endKeyScrub();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t}\n\n\t#bindPointer(signal: AbortSignal): void {\n\t\tconst whileHeld = (\n\t\t\ttype: string,\n\t\t\thandle: (hold: PointerHold, event: PointerEvent) => void,\n\t\t): void => {\n\t\t\tthis.#host.control.addEventListener(\n\t\t\t\ttype,\n\t\t\t\t(event) => {\n\t\t\t\t\tconst held = this.#held;\n\n\t\t\t\t\tif (held && event instanceof PointerEvent && held.pointerId === event.pointerId) {\n\t\t\t\t\t\thandle(held, event);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{ signal },\n\t\t\t);\n\t\t};\n\n\t\twhileHeld('pointermove', (hold, event) => {\n\t\t\tthis.#dragTo(hold, event);\n\t\t});\n\t\twhileHeld('pointerup', (hold) => {\n\t\t\tconst isPress = !hold.state.isEngaged;\n\n\t\t\tthis.#release();\n\t\t\tif (isPress) this.#host.seek(hold.press);\n\t\t});\n\t\twhileHeld('pointercancel', () => {\n\t\t\tthis.#release(true);\n\t\t});\n\t\twhileHeld('lostpointercapture', () => {\n\t\t\tthis.#release();\n\t\t});\n\t}\n\n\t#dragTo(hold: PointerHold, event: PointerEvent): void {\n\t\tconst wasEngaged = hold.state.isEngaged;\n\t\tconst { span, state } = stepRegionDrag(this.#host.mapping(), hold.state, {\n\t\t\tisFine: event.shiftKey,\n\t\t\tposition: event.clientX,\n\t\t});\n\n\t\thold.state = state;\n\t\tif (!span) return;\n\n\t\tif (!wasEngaged) focusByPointer(hold.drawn.part);\n\t\thold.drawn.link.input(hold.outside?.(event) === true ? hold.from : span);\n\t}\n\n\t#draw(region: SonicRegion): Drawn {\n\t\tconst host = this.#host;\n\t\tconst known = this.#drawn.find((entry) => entry.region === region);\n\t\tconst entry = known ?? {\n\t\t\tkind: undefined,\n\t\t\tlink: linkRegion(region),\n\t\t\tpart: renderPart(),\n\t\t\tregion,\n\t\t};\n\t\tconst { link, part } = entry;\n\t\tconst mapping = host.mapping();\n\t\tconst span = link.span();\n\t\tconst [low, high] = regionTravel(span, mapping.bounds);\n\t\tconst canMove = this.#isEnabled(entry);\n\t\tconst stop = region.getAttribute('tabindex') === '-1' ? '-1' : '0';\n\n\t\twriteProperty(part, '--_sonic-region-from', String(mapping.proportionOf(span.start)));\n\t\twriteProperty(part, '--_sonic-region-to', String(mapping.proportionOf(span.end)));\n\t\tif (!known || entry.kind !== region.kind) {\n\t\t\tentry.kind = region.kind;\n\t\t\twriteKind(part, region.kind, '--_sonic-ink');\n\t\t}\n\t\twriteAttribute(part, 'data-sonic-clipped', clippedEnds(region, mapping.bounds));\n\t\twriteAttribute(part, 'aria-valuemin', String(low));\n\t\twriteAttribute(part, 'aria-valuemax', String(high));\n\t\twriteAttribute(part, 'aria-valuenow', String(span.start));\n\t\twriteAttribute(part, 'aria-valuetext', host.speak(span));\n\t\twriteAttribute(part, 'aria-disabled', canMove ? undefined : 'true');\n\t\twriteAttribute(part, 'tabindex', canMove ? stop : undefined);\n\t\tlink.draw(part);\n\n\t\treturn entry;\n\t}\n\n\t#dropGone(): void {\n\t\tconst isGone = (entry: Drawn): boolean =>\n\t\t\t!this.#drawn.includes(entry) || !this.#isEnabled(entry);\n\n\t\tif (this.#held && isGone(this.#held.drawn)) this.#release();\n\t\tif (this.#keyScrub && isGone(this.#keyScrub.drawn)) this.#endKeyScrub();\n\t}\n\n\t#endKeyScrub(): void {\n\t\tconst scrub = this.#keyScrub;\n\t\tif (!scrub) return;\n\n\t\tconst { link } = scrub.drawn;\n\n\t\tthis.#keyScrub = undefined;\n\t\tlink.hold(undefined);\n\t\tif (hasMoved(scrub.from, link.span())) link.change();\n\t}\n\n\t#isEnabled({ link }: Drawn): boolean {\n\t\treturn !this.#host.isDisabled() && !link.isDisabled();\n\t}\n\n\t#keyTo(entry: Drawn, event: KeyboardEvent): void {\n\t\tconst mapping = this.#host.mapping();\n\t\tconst from = entry.link.span();\n\t\tconst start = mapping.keyTarget(event.key, from.start);\n\t\tif (start === undefined) return;\n\n\t\tevent.preventDefault();\n\n\t\tconst next = moveRegionTo(mapping, from, start);\n\n\t\tif (!event.repeat) {\n\t\t\tthis.#endKeyScrub();\n\t\t\tif (entry.link.input(next)) entry.link.change();\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.#keyScrub?.drawn !== entry || this.#keyScrub.key !== event.key) {\n\t\t\tthis.#endKeyScrub();\n\t\t\tthis.#keyScrub = { drawn: entry, from, key: event.key };\n\t\t\tentry.link.hold('keys');\n\t\t}\n\t\tentry.link.input(next);\n\t}\n\n\t#redraw(region: SonicRegion): void {\n\t\tif (this.#drawn.every((entry) => entry.region !== region)) {\n\t\t\tthis.render();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#draw(region);\n\t\tthis.#dropGone();\n\t}\n\n\t#release(isRestored = false): void {\n\t\tconst hold = this.#held;\n\t\tif (!hold) return;\n\n\t\tconst { link } = hold.drawn;\n\n\t\tthis.#held = undefined;\n\t\tif (isRestored) link.input(hold.from);\n\t\tlink.hold(undefined);\n\t\tif (hasMoved(hold.from, link.span())) link.change();\n\t}\n\n\t// A changed value formatter is told by what it says of one time\n\t#stripKey(): string {\n\t\tconst host = this.#host;\n\t\tconst mapping = host.mapping();\n\t\tconst [low, high] = mapping.bounds;\n\n\t\treturn [\n\t\t\tlow,\n\t\t\thigh,\n\t\t\tmapping.proportionOf((low + high) / 2),\n\t\t\thost.isDisabled(),\n\t\t\thost.speak({ end: high, start: high }),\n\t\t].join('|');\n\t}\n}\n"],"mappings":"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"}
@@ -24,6 +24,7 @@ export declare class SonicRegion extends SonicFormElement {
24
24
  protected focusTarget(): HTMLElement | undefined;
25
25
  protected render(): void;
26
26
  protected restoreState(state: string): void;
27
+ protected stateTarget(): HTMLElement | undefined;
27
28
  }
28
29
  //#endregion
29
30
  //# sourceMappingURL=region.d.ts.map
@@ -24,7 +24,8 @@ var SonicRegion = class extends SonicFormElement {
24
24
  "end",
25
25
  "kind",
26
26
  "name",
27
- "start"
27
+ "start",
28
+ "tabindex"
28
29
  ];
29
30
  static {
30
31
  link = (element) => ({
@@ -114,6 +115,9 @@ var SonicRegion = class extends SonicFormElement {
114
115
  };
115
116
  this.render();
116
117
  }
118
+ stateTarget() {
119
+ return this.#part;
120
+ }
117
121
  #draw(part) {
118
122
  this.#part = part;
119
123
  this.forwardNaming(part, true);
@@ -1 +1 @@
1
- {"version":3,"file":"region.js","names":[],"sources":["../../../src/elements/region.ts"],"sourcesContent":["import type { RegionSpan } from '#lib/region-drag.ts';\nimport type { Seconds } from '#lib/units.ts';\n\nimport { SonicFormElement } from '#elements/form-element.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-region': SonicRegion;\n\t}\n}\n\ntype RegionHold = 'keys' | 'pointer';\n\nexport interface RegionLink {\n\tchange: () => void;\n\tdraw: (part: HTMLElement) => void;\n\thold: (by: RegionHold | undefined) => void;\n\tinput: (span: RegionSpan) => boolean;\n\tisDisabled: () => boolean;\n\tspan: () => RegionSpan;\n}\n\nconst owners = new WeakMap<Element, (region: SonicRegion) => void>();\n\nconst warned = new Set<string>();\n\n// Filled from inside the class, so it reaches protected members and nothing lands on the element's public type\nlet link: (element: SonicRegion) => RegionLink;\n\nexport function linkRegion(element: SonicRegion): RegionLink {\n\treturn link(element);\n}\n\nexport function ownRegions(\n\thost: Element,\n\tchanged: (region: SonicRegion) => void,\n\tsignal: AbortSignal,\n): void {\n\towners.set(host, changed);\n\tsignal.addEventListener(\n\t\t'abort',\n\t\t() => {\n\t\t\tif (owners.get(host) === changed) owners.delete(host);\n\t\t},\n\t\t{ once: true },\n\t);\n}\n\nfunction warnOnce(key: string, message: string): void {\n\tif (!__DEV__ || warned.has(key)) return;\n\n\twarned.add(key);\n\tconsole.warn(message);\n}\n\nexport class SonicRegion extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t'end',\n\t\t'kind',\n\t\t'name',\n\t\t'start',\n\t];\n\n\tstatic {\n\t\tlink = (element) => ({\n\t\t\tchange: () => {\n\t\t\t\telement.#report('change');\n\t\t\t},\n\t\t\tdraw: (part) => {\n\t\t\t\telement.#draw(part);\n\t\t\t},\n\t\t\thold: (by) => {\n\t\t\t\telement.#held = by;\n\t\t\t\telement.toggleState('dragging', by === 'pointer');\n\t\t\t},\n\t\t\tinput: (span) => element.#input(span),\n\t\t\tisDisabled: () => element.isDisabled(),\n\t\t\tspan: () => {\n\t\t\t\tconst { end, start } = element.#span;\n\n\t\t\t\treturn { end: Math.max(start, end), start: Math.min(start, end) };\n\t\t\t},\n\t\t});\n\t}\n\n\tget dragging(): boolean {\n\t\treturn this.hasState('dragging');\n\t}\n\n\tget end(): Seconds {\n\t\treturn this.#span.end;\n\t}\n\n\tset end(next: null | Seconds | undefined) {\n\t\tif (!this.#held) this.reflect('end', next);\n\t}\n\n\t/** Lowercase letters, digits and hyphens; colours the region through `--sonic-marker-<kind>` */\n\tget kind(): string | undefined {\n\t\treturn this.getAttribute('kind') ?? undefined;\n\t}\n\n\tset kind(kind: string | undefined) {\n\t\tthis.reflect('kind', kind);\n\t}\n\n\tget start(): Seconds {\n\t\treturn this.#span.start;\n\t}\n\n\tset start(next: null | Seconds | undefined) {\n\t\tif (!this.#held) this.reflect('start', next);\n\t}\n\n\t#formState = '';\n\n\t// A held region owns its values, so a host writing them on every `timeupdate` does not fight the gesture\n\t#held: RegionHold | undefined;\n\n\t#part: HTMLElement | undefined;\n\n\t#span: RegionSpan = { end: 0, start: 0 };\n\n\tattributeChangedCallback(name: string): void {\n\t\tconst isSpan = name === 'end' || name === 'start';\n\n\t\tif (isSpan && this.#held) return;\n\t\tif (isSpan) this.#readSpan(name);\n\t\tthis.render();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('end', 'kind', 'start');\n\t\tsuper.connectedCallback();\n\t}\n\n\toverride formResetCallback(): void {\n\t\tif (this.#held) return;\n\n\t\tthis.#readSpan('start');\n\t\tthis.#readSpan('end');\n\t\tthis.render();\n\t}\n\n\tprotected connect(): void {\n\t\tthis.render();\n\t\tif (this.parentElement?.localName !== 'sonic-wavestrip') {\n\t\t\twarnOnce(\n\t\t\t\t'parent',\n\t\t\t\t'<sonic-region> draws nothing of its own, so it shows only as a child of <sonic-wavestrip>',\n\t\t\t);\n\t\t}\n\t}\n\n\tprotected override focusTarget(): HTMLElement | undefined {\n\t\treturn this.#part;\n\t}\n\n\tprotected render(): void {\n\t\tif (!this.isBound() || !this.parentElement) return;\n\n\t\towners.get(this.parentElement)?.(this);\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tconst [start = NaN, end = NaN] = state.split(',').map(Number);\n\t\tif (!Number.isFinite(start) || !Number.isFinite(end)) return;\n\n\t\tthis.#span = { end, start };\n\t\tthis.render();\n\t}\n\n\t#draw(part: HTMLElement): void {\n\t\tthis.#part = part;\n\t\tthis.forwardNaming(part, true);\n\t\tthis.#writeForm();\n\t\tif (\n\t\t\t__DEV__ &&\n\t\t\t!this.hasAttribute('aria-label') &&\n\t\t\t!this.hasAttribute('aria-labelledby') &&\n\t\t\tthis.namingLabels().length === 0\n\t\t) {\n\t\t\twarnOnce(\n\t\t\t\t'name',\n\t\t\t\t'<sonic-region> is a slider with no name; give it an aria-label or aria-labelledby',\n\t\t\t);\n\t\t}\n\t}\n\n\t#input(span: RegionSpan): boolean {\n\t\tconst hasMoved = span.start !== this.#span.start || span.end !== this.#span.end;\n\n\t\tif (hasMoved) {\n\t\t\tthis.#span = span;\n\t\t\tthis.render();\n\t\t\tthis.#report('input');\n\t\t}\n\n\t\treturn hasMoved;\n\t}\n\n\t// One end at a time, so a write to one leaves the other where a gesture put it\n\t#readSpan(name: 'end' | 'start'): void {\n\t\tconst { end, start } = this.#span;\n\n\t\tif (name === 'end') {\n\t\t\tthis.#span = { end: this.numberAttribute('end', start), start };\n\t\t\treturn;\n\t\t}\n\n\t\tconst next = this.numberAttribute('start', 0);\n\n\t\tthis.#span = { end: this.hasAttribute('end') ? end : next, start: next };\n\t}\n\n\t#report(type: 'change' | 'input'): void {\n\t\tthis.dispatchEvent(new Event(type, { bubbles: true, composed: type === 'input' }));\n\t}\n\n\t#writeForm(): void {\n\t\tconst { end, start } = this.#span;\n\t\tconst state = `${String(start)},${String(end)}`;\n\t\tconst formState = `${this.name}=${state}`;\n\t\tif (formState === this.#formState) return;\n\n\t\tthis.#formState = formState;\n\t\tthis.writeFormFields(\n\t\t\t[\n\t\t\t\t['start', start],\n\t\t\t\t['end', end],\n\t\t\t],\n\t\t\tstate,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"region.js","names":[],"sources":["../../../src/elements/region.ts"],"sourcesContent":["import type { RegionSpan } from '#lib/region-drag.ts';\nimport type { Seconds } from '#lib/units.ts';\n\nimport { SonicFormElement } from '#elements/form-element.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-region': SonicRegion;\n\t}\n}\n\ntype RegionHold = 'keys' | 'pointer';\n\nexport interface RegionLink {\n\tchange: () => void;\n\tdraw: (part: HTMLElement) => void;\n\thold: (by: RegionHold | undefined) => void;\n\tinput: (span: RegionSpan) => boolean;\n\tisDisabled: () => boolean;\n\tspan: () => RegionSpan;\n}\n\nconst owners = new WeakMap<Element, (region: SonicRegion) => void>();\n\nconst warned = new Set<string>();\n\n// Filled from inside the class, so it reaches protected members and nothing lands on the element's public type\nlet link: (element: SonicRegion) => RegionLink;\n\nexport function linkRegion(element: SonicRegion): RegionLink {\n\treturn link(element);\n}\n\nexport function ownRegions(\n\thost: Element,\n\tchanged: (region: SonicRegion) => void,\n\tsignal: AbortSignal,\n): void {\n\towners.set(host, changed);\n\tsignal.addEventListener(\n\t\t'abort',\n\t\t() => {\n\t\t\tif (owners.get(host) === changed) owners.delete(host);\n\t\t},\n\t\t{ once: true },\n\t);\n}\n\nfunction warnOnce(key: string, message: string): void {\n\tif (!__DEV__ || warned.has(key)) return;\n\n\twarned.add(key);\n\tconsole.warn(message);\n}\n\nexport class SonicRegion extends SonicFormElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicFormElement.observedAttributes,\n\t\t'end',\n\t\t'kind',\n\t\t'name',\n\t\t'start',\n\t\t'tabindex',\n\t];\n\n\tstatic {\n\t\tlink = (element) => ({\n\t\t\tchange: () => {\n\t\t\t\telement.#report('change');\n\t\t\t},\n\t\t\tdraw: (part) => {\n\t\t\t\telement.#draw(part);\n\t\t\t},\n\t\t\thold: (by) => {\n\t\t\t\telement.#held = by;\n\t\t\t\telement.toggleState('dragging', by === 'pointer');\n\t\t\t},\n\t\t\tinput: (span) => element.#input(span),\n\t\t\tisDisabled: () => element.isDisabled(),\n\t\t\tspan: () => {\n\t\t\t\tconst { end, start } = element.#span;\n\n\t\t\t\treturn { end: Math.max(start, end), start: Math.min(start, end) };\n\t\t\t},\n\t\t});\n\t}\n\n\tget dragging(): boolean {\n\t\treturn this.hasState('dragging');\n\t}\n\n\tget end(): Seconds {\n\t\treturn this.#span.end;\n\t}\n\n\tset end(next: null | Seconds | undefined) {\n\t\tif (!this.#held) this.reflect('end', next);\n\t}\n\n\t/** Lowercase letters, digits and hyphens; colours the region through `--sonic-marker-<kind>` */\n\tget kind(): string | undefined {\n\t\treturn this.getAttribute('kind') ?? undefined;\n\t}\n\n\tset kind(kind: string | undefined) {\n\t\tthis.reflect('kind', kind);\n\t}\n\n\tget start(): Seconds {\n\t\treturn this.#span.start;\n\t}\n\n\tset start(next: null | Seconds | undefined) {\n\t\tif (!this.#held) this.reflect('start', next);\n\t}\n\n\t#formState = '';\n\n\t// A held region owns its values, so a host writing them on every `timeupdate` does not fight the gesture\n\t#held: RegionHold | undefined;\n\n\t#part: HTMLElement | undefined;\n\n\t#span: RegionSpan = { end: 0, start: 0 };\n\n\tattributeChangedCallback(name: string): void {\n\t\tconst isSpan = name === 'end' || name === 'start';\n\n\t\tif (isSpan && this.#held) return;\n\t\tif (isSpan) this.#readSpan(name);\n\t\tthis.render();\n\t}\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties('end', 'kind', 'start');\n\t\tsuper.connectedCallback();\n\t}\n\n\toverride formResetCallback(): void {\n\t\tif (this.#held) return;\n\n\t\tthis.#readSpan('start');\n\t\tthis.#readSpan('end');\n\t\tthis.render();\n\t}\n\n\tprotected connect(): void {\n\t\tthis.render();\n\t\tif (this.parentElement?.localName !== 'sonic-wavestrip') {\n\t\t\twarnOnce(\n\t\t\t\t'parent',\n\t\t\t\t'<sonic-region> draws nothing of its own, so it shows only as a child of <sonic-wavestrip>',\n\t\t\t);\n\t\t}\n\t}\n\n\tprotected override focusTarget(): HTMLElement | undefined {\n\t\treturn this.#part;\n\t}\n\n\tprotected render(): void {\n\t\tif (!this.isBound() || !this.parentElement) return;\n\n\t\towners.get(this.parentElement)?.(this);\n\t}\n\n\tprotected restoreState(state: string): void {\n\t\tconst [start = NaN, end = NaN] = state.split(',').map(Number);\n\t\tif (!Number.isFinite(start) || !Number.isFinite(end)) return;\n\n\t\tthis.#span = { end, start };\n\t\tthis.render();\n\t}\n\n\tprotected override stateTarget(): HTMLElement | undefined {\n\t\treturn this.#part;\n\t}\n\n\t#draw(part: HTMLElement): void {\n\t\tthis.#part = part;\n\t\tthis.forwardNaming(part, true);\n\t\tthis.#writeForm();\n\t\tif (\n\t\t\t__DEV__ &&\n\t\t\t!this.hasAttribute('aria-label') &&\n\t\t\t!this.hasAttribute('aria-labelledby') &&\n\t\t\tthis.namingLabels().length === 0\n\t\t) {\n\t\t\twarnOnce(\n\t\t\t\t'name',\n\t\t\t\t'<sonic-region> is a slider with no name; give it an aria-label or aria-labelledby',\n\t\t\t);\n\t\t}\n\t}\n\n\t#input(span: RegionSpan): boolean {\n\t\tconst hasMoved = span.start !== this.#span.start || span.end !== this.#span.end;\n\n\t\tif (hasMoved) {\n\t\t\tthis.#span = span;\n\t\t\tthis.render();\n\t\t\tthis.#report('input');\n\t\t}\n\n\t\treturn hasMoved;\n\t}\n\n\t// One end at a time, so a write to one leaves the other where a gesture put it\n\t#readSpan(name: 'end' | 'start'): void {\n\t\tconst { end, start } = this.#span;\n\n\t\tif (name === 'end') {\n\t\t\tthis.#span = { end: this.numberAttribute('end', start), start };\n\t\t\treturn;\n\t\t}\n\n\t\tconst next = this.numberAttribute('start', 0);\n\n\t\tthis.#span = { end: this.hasAttribute('end') ? end : next, start: next };\n\t}\n\n\t#report(type: 'change' | 'input'): void {\n\t\tthis.dispatchEvent(new Event(type, { bubbles: true, composed: type === 'input' }));\n\t}\n\n\t#writeForm(): void {\n\t\tconst { end, start } = this.#span;\n\t\tconst state = `${String(start)},${String(end)}`;\n\t\tconst formState = `${this.name}=${state}`;\n\t\tif (formState === this.#formState) return;\n\n\t\tthis.#formState = formState;\n\t\tthis.writeFormFields(\n\t\t\t[\n\t\t\t\t['start', start],\n\t\t\t\t['end', end],\n\t\t\t],\n\t\t\tstate,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
@@ -34,6 +34,7 @@ export declare class SonicSlider extends SonicValueElement {
34
34
  protected scrubChanged(): void;
35
35
  protected scrubsKeyRepeat(): boolean;
36
36
  protected springTarget(): number | undefined;
37
+ protected stateTarget(): HTMLElement;
37
38
  }
38
39
  //#endregion
39
40
  //# sourceMappingURL=slider.d.ts.map
@@ -156,6 +156,9 @@ var SonicSlider = class extends SonicValueElement {
156
156
  springTarget() {
157
157
  return this.spring ? this.originValue() : void 0;
158
158
  }
159
+ stateTarget() {
160
+ return this.#slider;
161
+ }
159
162
  #axis(slider, cap) {
160
163
  const box = slider.getBoundingClientRect();
161
164
  const capBox = cap.getBoundingClientRect();
@@ -1 +1 @@
1
- {"version":3,"file":"slider.js","names":[],"sources":["../../../src/elements/slider.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.ts';\nimport type { TimeRegions } from '#lib/time-regions.ts';\n\nimport { createModulation } from '#elements/modulation.ts';\nimport { bindScale } from '#elements/scale.ts';\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { clampProportion } from '#lib/math.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { scrubRegions } from '#lib/scrub.ts';\nimport { sortedRegions } from '#lib/time-regions.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-slider': SonicSlider;\n\t}\n}\n\ninterface SliderAxis extends ValueAxis {\n\tstartPx: number;\n}\n\nfunction grooveStop(proportion: number): string {\n\tif (proportion <= 0) return '0%';\n\tif (proportion >= 1) return '100%';\n\n\treturn `calc(${String(proportion)} * var(--_sonic-slider-travel) + var(--_sonic-slider-groove-end))`;\n}\n\nconst renderSlider = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-slider\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-slider-scale\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-slider-notches\"></div>\n\t\t\t<div class=\"sonic-slider-groove\"></div>\n\t\t\t<div class=\"sonic-slider-modulation\"></div>\n\t\t\t<div class=\"sonic-slider-cap\"></div>\n\t\t\t<div class=\"sonic-slider-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-slider-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicSlider extends SonicValueElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicValueElement.observedAttributes,\n\t\t'modulation',\n\t\t'orientation',\n\t\t'scrub',\n\t\t'spring',\n\t];\n\n\t/** Loaded spans, in the slider's own values */\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget buffered(): Array<[number, number]> {\n\t\treturn this.#buffered.map(([start, end]) => [start, end]);\n\t}\n\n\tset buffered(regions: TimeRegions | undefined) {\n\t\tthis.#buffered = sortedRegions(regions);\n\t\tthis.render();\n\t}\n\n\tget groovePress(): 'jump' | 'none' {\n\t\treturn this.getAttribute('groove-press') === 'none' ? 'none' : 'jump';\n\t}\n\n\tset groovePress(gesture: 'jump' | 'none' | undefined) {\n\t\tthis.reflect('groove-press', gesture);\n\t}\n\n\t/** How far a modulation source can push the value, in value units; signed */\n\tget modulation(): number {\n\t\treturn this.numberAttribute('modulation', 0);\n\t}\n\n\tset modulation(amount: number | undefined) {\n\t\tthis.reflect('modulation', amount);\n\t}\n\n\t/** Where the value is right now with modulation applied; a property only, cheap to write every frame, fires nothing */\n\tget modulationValue(): number | undefined {\n\t\treturn this.#modulation.value();\n\t}\n\n\tset modulationValue(value: null | number | undefined) {\n\t\tthis.#modulation.write(value, this.isBound() ? this.mapping() : undefined);\n\t}\n\n\tget orientation(): 'horizontal' | 'vertical' {\n\t\treturn this.getAttribute('orientation') === 'vertical' ? 'vertical' : 'horizontal';\n\t}\n\n\tset orientation(direction: 'horizontal' | 'vertical' | undefined) {\n\t\tthis.reflect('orientation', direction);\n\t}\n\n\tget scrub(): boolean {\n\t\treturn this.hasAttribute('scrub');\n\t}\n\n\tset scrub(isScrubbing: boolean) {\n\t\tthis.reflect('scrub', isScrubbing);\n\t}\n\n\tget spring(): boolean {\n\t\treturn this.hasAttribute('spring');\n\t}\n\n\tset spring(isSpring: boolean) {\n\t\tthis.reflect('spring', isSpring);\n\t}\n\n\t#buffered: Array<[number, number]> = [];\n\n\treadonly #slider = renderSlider();\n\n\treadonly #modulation = createModulation(this.#slider, 'slider', (isModulated) => {\n\t\tthis.toggleState('modulated', isModulated);\n\t});\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'buffered',\n\t\t\t'groovePress',\n\t\t\t'modulationValue',\n\t\t\t'modulation',\n\t\t\t'orientation',\n\t\t\t'scrub',\n\t\t\t'spring',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst slider = this.#slider;\n\t\tconst cap = requireChild(slider, '.sonic-slider-cap', HTMLDivElement);\n\n\t\tbindScale(\n\t\t\tthis,\n\t\t\t{ control: slider, marks: requireChild(slider, '.sonic-slider-scale', HTMLDivElement) },\n\t\t\tsignal,\n\t\t);\n\t\tslider.addEventListener(\n\t\t\t'transitionend',\n\t\t\t(event) => {\n\t\t\t\tif (event.propertyName === '--_sonic-slider-value') this.toggleState('springing', false);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.bindHover(slider, signal, {\n\t\t\tcanShow: () => this.scrub && this.orientation === 'horizontal',\n\t\t\tplace: () => {\n\t\t\t\tthis.#placeReadout(slider);\n\t\t\t},\n\t\t\tvalueAt: (event) => {\n\t\t\t\tconst axis = this.#axis(slider, cap);\n\t\t\t\tconst at = clampProportion((axis.position(event) - axis.startPx) / axis.travelPx);\n\t\t\t\tconst mapping = this.mapping();\n\n\t\t\t\treturn mapping.snap(mapping.valueAt(at));\n\t\t\t},\n\t\t});\n\t\tthis.render();\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(slider, 'slider.css', { property: 'margin-bottom', selector: '[popover]' });\n\t\tthis.bindGestures(slider, signal, (event) => {\n\t\t\tconst axis = this.#axis(slider, cap);\n\t\t\tif (event.target instanceof Node && cap.contains(event.target)) return axis;\n\n\t\t\tif (this.groovePress === 'none') return;\n\n\t\t\tthis.input(this.mapping().valueAt((axis.position(event) - axis.startPx) / axis.travelPx));\n\n\t\t\treturn axis;\n\t\t});\n\t}\n\n\tprotected override controlOrientation(): 'horizontal' | 'vertical' {\n\t\treturn this.orientation;\n\t}\n\n\tprotected draw(): void {\n\t\tthis.#modulation.draw({\n\t\t\tmapping: this.mapping(),\n\t\t\tmodulation: this.modulation,\n\t\t\torigin: this.originValue(),\n\t\t\tvalue: this.value,\n\t\t});\n\t\tthis.#renderBuffered(this.#slider);\n\t\tthis.#renderScrub(this.#slider);\n\t\tthis.#placeReadout(this.#slider);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#slider;\n\t}\n\n\tprotected override scrubChanged(): void {\n\t\tif (this.scrub) this.render();\n\t}\n\n\tprotected override scrubsKeyRepeat(): boolean {\n\t\treturn this.scrub;\n\t}\n\n\tprotected override springTarget(): number | undefined {\n\t\treturn this.spring ? this.originValue() : undefined;\n\t}\n\n\t#axis(slider: HTMLElement, cap: HTMLElement): SliderAxis {\n\t\tconst box = slider.getBoundingClientRect();\n\t\tconst capBox = cap.getBoundingClientRect();\n\n\t\treturn this.orientation === 'vertical'\n\t\t\t? {\n\t\t\t\t\tposition: (event) => -event.clientY,\n\t\t\t\t\tstartPx: capBox.height / 2 - box.bottom,\n\t\t\t\t\ttravelPx: Math.max(1, box.height - capBox.height),\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\tposition: (event) => event.clientX,\n\t\t\t\t\tstartPx: box.left + capBox.width / 2,\n\t\t\t\t\ttravelPx: Math.max(1, box.width - capBox.width),\n\t\t\t\t};\n\t}\n\n\t#placeReadout(slider: HTMLElement): void {\n\t\tif (!this.scrub) {\n\t\t\tslider.style.removeProperty('--_sonic-slider-readout-value');\n\t\t\treturn;\n\t\t}\n\n\t\tslider.style.setProperty(\n\t\t\t'--_sonic-slider-readout-value',\n\t\t\tString(this.mapping().proportionOf(this.readoutValue())),\n\t\t);\n\t}\n\n\t#renderBuffered(slider: HTMLElement): void {\n\t\tconst buffered = this.#buffered;\n\n\t\tif (buffered.length === 0) {\n\t\t\tslider.style.removeProperty('--_sonic-slider-buffered-regions');\n\t\t\treturn;\n\t\t}\n\n\t\tconst { proportionOf } = this.mapping();\n\t\tconst stops = buffered.flatMap(([start, end]) => {\n\t\t\tconst from = grooveStop(proportionOf(Math.min(start, end)));\n\t\t\tconst to = grooveStop(proportionOf(Math.max(start, end)));\n\n\t\t\treturn [\n\t\t\t\t`transparent ${from}`,\n\t\t\t\t`var(--_sonic-slider-buffered) ${from} ${to}`,\n\t\t\t\t`transparent ${to}`,\n\t\t\t];\n\t\t});\n\n\t\tslider.style.setProperty(\n\t\t\t'--_sonic-slider-buffered-regions',\n\t\t\t`linear-gradient(var(--_sonic-slider-toward), ${stops.join(', ')})`,\n\t\t);\n\t}\n\n\t#renderScrub(slider: HTMLElement): void {\n\t\tconst { style } = slider;\n\t\tconst state = this.scrubState();\n\t\tconst { played, scrub } = scrubRegions(state, this.value, this.mapping());\n\n\t\tif (this.scrub && state.from !== undefined) {\n\t\t\tstyle.setProperty('--_sonic-slider-played', String(played));\n\t\t} else style.removeProperty('--_sonic-slider-played');\n\n\t\tif (scrub === undefined || !this.scrub) {\n\t\t\tstyle.removeProperty('--_sonic-slider-scrub-region');\n\t\t\treturn;\n\t\t}\n\n\t\tconst from = grooveStop(Math.min(played, scrub));\n\t\tconst to = grooveStop(Math.max(played, scrub));\n\n\t\tstyle.setProperty(\n\t\t\t'--_sonic-slider-scrub-region',\n\t\t\t`linear-gradient(var(--_sonic-slider-toward), transparent ${from}, var(--_sonic-slider-scrub) ${from} ${to}, transparent ${to})`,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"slider.js","names":[],"sources":["../../../src/elements/slider.ts"],"sourcesContent":["import type { ValueAxis } from '#elements/value-gestures.ts';\nimport type { TimeRegions } from '#lib/time-regions.ts';\n\nimport { createModulation } from '#elements/modulation.ts';\nimport { bindScale } from '#elements/scale.ts';\nimport { SonicValueElement } from '#elements/value-element.ts';\nimport { clampProportion } from '#lib/math.ts';\nimport { requireChild, template } from '#lib/render.ts';\nimport { scrubRegions } from '#lib/scrub.ts';\nimport { sortedRegions } from '#lib/time-regions.ts';\n\ndeclare const __DEV__: boolean;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'sonic-slider': SonicSlider;\n\t}\n}\n\ninterface SliderAxis extends ValueAxis {\n\tstartPx: number;\n}\n\nfunction grooveStop(proportion: number): string {\n\tif (proportion <= 0) return '0%';\n\tif (proportion >= 1) return '100%';\n\n\treturn `calc(${String(proportion)} * var(--_sonic-slider-travel) + var(--_sonic-slider-groove-end))`;\n}\n\nconst renderSlider = template(\n\t/* HTML */ `\n\t\t<div class=\"sonic-slider\" role=\"slider\" tabindex=\"0\">\n\t\t\t<div class=\"sonic-slider-scale\" aria-hidden=\"true\"></div>\n\t\t\t<div class=\"sonic-slider-notches\"></div>\n\t\t\t<div class=\"sonic-slider-groove\"></div>\n\t\t\t<div class=\"sonic-slider-modulation\"></div>\n\t\t\t<div class=\"sonic-slider-cap\"></div>\n\t\t\t<div class=\"sonic-slider-readout\" popover=\"manual\">\n\t\t\t\t<span></span>\n\t\t\t\t<input\n\t\t\t\t\tautocomplete=\"off\"\n\t\t\t\t\tclass=\"sonic-slider-entry\"\n\t\t\t\t\tenterkeyhint=\"done\"\n\t\t\t\t\thidden\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t`,\n\tHTMLDivElement,\n);\n\nexport class SonicSlider extends SonicValueElement {\n\tstatic override readonly observedAttributes = [\n\t\t...SonicValueElement.observedAttributes,\n\t\t'modulation',\n\t\t'orientation',\n\t\t'scrub',\n\t\t'spring',\n\t];\n\n\t/** Loaded spans, in the slider's own values */\n\t// fallow-ignore-next-line code-duplication -- one accessor pair per property\n\tget buffered(): Array<[number, number]> {\n\t\treturn this.#buffered.map(([start, end]) => [start, end]);\n\t}\n\n\tset buffered(regions: TimeRegions | undefined) {\n\t\tthis.#buffered = sortedRegions(regions);\n\t\tthis.render();\n\t}\n\n\tget groovePress(): 'jump' | 'none' {\n\t\treturn this.getAttribute('groove-press') === 'none' ? 'none' : 'jump';\n\t}\n\n\tset groovePress(gesture: 'jump' | 'none' | undefined) {\n\t\tthis.reflect('groove-press', gesture);\n\t}\n\n\t/** How far a modulation source can push the value, in value units; signed */\n\tget modulation(): number {\n\t\treturn this.numberAttribute('modulation', 0);\n\t}\n\n\tset modulation(amount: number | undefined) {\n\t\tthis.reflect('modulation', amount);\n\t}\n\n\t/** Where the value is right now with modulation applied; a property only, cheap to write every frame, fires nothing */\n\tget modulationValue(): number | undefined {\n\t\treturn this.#modulation.value();\n\t}\n\n\tset modulationValue(value: null | number | undefined) {\n\t\tthis.#modulation.write(value, this.isBound() ? this.mapping() : undefined);\n\t}\n\n\tget orientation(): 'horizontal' | 'vertical' {\n\t\treturn this.getAttribute('orientation') === 'vertical' ? 'vertical' : 'horizontal';\n\t}\n\n\tset orientation(direction: 'horizontal' | 'vertical' | undefined) {\n\t\tthis.reflect('orientation', direction);\n\t}\n\n\tget scrub(): boolean {\n\t\treturn this.hasAttribute('scrub');\n\t}\n\n\tset scrub(isScrubbing: boolean) {\n\t\tthis.reflect('scrub', isScrubbing);\n\t}\n\n\tget spring(): boolean {\n\t\treturn this.hasAttribute('spring');\n\t}\n\n\tset spring(isSpring: boolean) {\n\t\tthis.reflect('spring', isSpring);\n\t}\n\n\t#buffered: Array<[number, number]> = [];\n\n\treadonly #slider = renderSlider();\n\n\treadonly #modulation = createModulation(this.#slider, 'slider', (isModulated) => {\n\t\tthis.toggleState('modulated', isModulated);\n\t});\n\n\toverride connectedCallback(): void {\n\t\tthis.upgradeProperties(\n\t\t\t'buffered',\n\t\t\t'groovePress',\n\t\t\t'modulationValue',\n\t\t\t'modulation',\n\t\t\t'orientation',\n\t\t\t'scrub',\n\t\t\t'spring',\n\t\t);\n\t\tsuper.connectedCallback();\n\t}\n\n\tprotected connect(signal: AbortSignal): void {\n\t\tconst slider = this.#slider;\n\t\tconst cap = requireChild(slider, '.sonic-slider-cap', HTMLDivElement);\n\n\t\tbindScale(\n\t\t\tthis,\n\t\t\t{ control: slider, marks: requireChild(slider, '.sonic-slider-scale', HTMLDivElement) },\n\t\t\tsignal,\n\t\t);\n\t\tslider.addEventListener(\n\t\t\t'transitionend',\n\t\t\t(event) => {\n\t\t\t\tif (event.propertyName === '--_sonic-slider-value') this.toggleState('springing', false);\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\tthis.bindHover(slider, signal, {\n\t\t\tcanShow: () => this.scrub && this.orientation === 'horizontal',\n\t\t\tplace: () => {\n\t\t\t\tthis.#placeReadout(slider);\n\t\t\t},\n\t\t\tvalueAt: (event) => {\n\t\t\t\tconst axis = this.#axis(slider, cap);\n\t\t\t\tconst at = clampProportion((axis.position(event) - axis.startPx) / axis.travelPx);\n\t\t\t\tconst mapping = this.mapping();\n\n\t\t\t\treturn mapping.snap(mapping.valueAt(at));\n\t\t\t},\n\t\t});\n\t\tthis.render();\n\t\tif (__DEV__)\n\t\t\tthis.checkStyles(slider, 'slider.css', { property: 'margin-bottom', selector: '[popover]' });\n\t\tthis.bindGestures(slider, signal, (event) => {\n\t\t\tconst axis = this.#axis(slider, cap);\n\t\t\tif (event.target instanceof Node && cap.contains(event.target)) return axis;\n\n\t\t\tif (this.groovePress === 'none') return;\n\n\t\t\tthis.input(this.mapping().valueAt((axis.position(event) - axis.startPx) / axis.travelPx));\n\n\t\t\treturn axis;\n\t\t});\n\t}\n\n\tprotected override controlOrientation(): 'horizontal' | 'vertical' {\n\t\treturn this.orientation;\n\t}\n\n\tprotected draw(): void {\n\t\tthis.#modulation.draw({\n\t\t\tmapping: this.mapping(),\n\t\t\tmodulation: this.modulation,\n\t\t\torigin: this.originValue(),\n\t\t\tvalue: this.value,\n\t\t});\n\t\tthis.#renderBuffered(this.#slider);\n\t\tthis.#renderScrub(this.#slider);\n\t\tthis.#placeReadout(this.#slider);\n\t}\n\n\tprotected override focusTarget(): HTMLElement {\n\t\treturn this.#slider;\n\t}\n\n\tprotected override scrubChanged(): void {\n\t\tif (this.scrub) this.render();\n\t}\n\n\tprotected override scrubsKeyRepeat(): boolean {\n\t\treturn this.scrub;\n\t}\n\n\tprotected override springTarget(): number | undefined {\n\t\treturn this.spring ? this.originValue() : undefined;\n\t}\n\n\tprotected override stateTarget(): HTMLElement {\n\t\treturn this.#slider;\n\t}\n\n\t#axis(slider: HTMLElement, cap: HTMLElement): SliderAxis {\n\t\tconst box = slider.getBoundingClientRect();\n\t\tconst capBox = cap.getBoundingClientRect();\n\n\t\treturn this.orientation === 'vertical'\n\t\t\t? {\n\t\t\t\t\tposition: (event) => -event.clientY,\n\t\t\t\t\tstartPx: capBox.height / 2 - box.bottom,\n\t\t\t\t\ttravelPx: Math.max(1, box.height - capBox.height),\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\tposition: (event) => event.clientX,\n\t\t\t\t\tstartPx: box.left + capBox.width / 2,\n\t\t\t\t\ttravelPx: Math.max(1, box.width - capBox.width),\n\t\t\t\t};\n\t}\n\n\t#placeReadout(slider: HTMLElement): void {\n\t\tif (!this.scrub) {\n\t\t\tslider.style.removeProperty('--_sonic-slider-readout-value');\n\t\t\treturn;\n\t\t}\n\n\t\tslider.style.setProperty(\n\t\t\t'--_sonic-slider-readout-value',\n\t\t\tString(this.mapping().proportionOf(this.readoutValue())),\n\t\t);\n\t}\n\n\t#renderBuffered(slider: HTMLElement): void {\n\t\tconst buffered = this.#buffered;\n\n\t\tif (buffered.length === 0) {\n\t\t\tslider.style.removeProperty('--_sonic-slider-buffered-regions');\n\t\t\treturn;\n\t\t}\n\n\t\tconst { proportionOf } = this.mapping();\n\t\tconst stops = buffered.flatMap(([start, end]) => {\n\t\t\tconst from = grooveStop(proportionOf(Math.min(start, end)));\n\t\t\tconst to = grooveStop(proportionOf(Math.max(start, end)));\n\n\t\t\treturn [\n\t\t\t\t`transparent ${from}`,\n\t\t\t\t`var(--_sonic-slider-buffered) ${from} ${to}`,\n\t\t\t\t`transparent ${to}`,\n\t\t\t];\n\t\t});\n\n\t\tslider.style.setProperty(\n\t\t\t'--_sonic-slider-buffered-regions',\n\t\t\t`linear-gradient(var(--_sonic-slider-toward), ${stops.join(', ')})`,\n\t\t);\n\t}\n\n\t#renderScrub(slider: HTMLElement): void {\n\t\tconst { style } = slider;\n\t\tconst state = this.scrubState();\n\t\tconst { played, scrub } = scrubRegions(state, this.value, this.mapping());\n\n\t\tif (this.scrub && state.from !== undefined) {\n\t\t\tstyle.setProperty('--_sonic-slider-played', String(played));\n\t\t} else style.removeProperty('--_sonic-slider-played');\n\n\t\tif (scrub === undefined || !this.scrub) {\n\t\t\tstyle.removeProperty('--_sonic-slider-scrub-region');\n\t\t\treturn;\n\t\t}\n\n\t\tconst from = grooveStop(Math.min(played, scrub));\n\t\tconst to = grooveStop(Math.max(played, scrub));\n\n\t\tstyle.setProperty(\n\t\t\t'--_sonic-slider-scrub-region',\n\t\t\t`linear-gradient(var(--_sonic-slider-toward), transparent ${from}, var(--_sonic-slider-scrub) ${from} ${to}, transparent ${to})`,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
@@ -27,6 +27,7 @@ export declare abstract class SonicElement extends HTMLElement {
27
27
  protected numberAttribute(name: string, fallback: number): number;
28
28
  protected optionalNumberAttribute(name: string): number | undefined;
29
29
  protected reflect(name: string, value: boolean | null | number | string | undefined): void;
30
+ protected stateTarget(): Element | undefined;
30
31
  protected toggleState(state: string, isOn: boolean): void;
31
32
  protected upgradeProperties(...names: Array<keyof this>): void;
32
33
  protected usesChild(_child: Node): boolean;
@@ -9,7 +9,6 @@ const namingAttributes = [
9
9
  "aria-labelledby"
10
10
  ];
11
11
  const checkedSheets = /* @__PURE__ */ new WeakMap();
12
- const canMatchStates = CSS.supports("selector(:state(a))");
13
12
  const canReferElements = "ariaLabelledByElements" in Element.prototype;
14
13
  function isLost(drawn, { token }) {
15
14
  return getComputedStyle(drawn).getPropertyValue(token) === "";
@@ -57,7 +56,6 @@ var SonicElement = class SonicElement extends HTMLElement {
57
56
  }
58
57
  internals = "attachInternals" in HTMLElement.prototype ? this.attachInternals() : void 0;
59
58
  #connection;
60
- #customStates = canMatchStates ? this.internals?.states : void 0;
61
59
  #states = /* @__PURE__ */ new Set();
62
60
  static #labelId(label) {
63
61
  if (label.id === "") {
@@ -186,14 +184,11 @@ var SonicElement = class SonicElement extends HTMLElement {
186
184
  }
187
185
  this.setAttribute(name, value === true ? "" : value.toString());
188
186
  }
187
+ stateTarget() {}
189
188
  toggleState(state, isOn) {
190
- if (isOn) {
191
- this.#states.add(state);
192
- this.#customStates?.add(state);
193
- return;
194
- }
195
- this.#states.delete(state);
196
- this.#customStates?.delete(state);
189
+ if (isOn) this.#states.add(state);
190
+ else this.#states.delete(state);
191
+ this.stateTarget()?.toggleAttribute(`data-sonic-${state}`, isOn);
197
192
  }
198
193
  upgradeProperties(...names) {
199
194
  for (const name of names) {