@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
@@ -10,6 +10,17 @@ function deviceBox(entry, css) {
10
10
  if (!device || Math.abs(device.inlineSize - css.inlineSize * dpr) > 1) return void 0;
11
11
  return Math.abs(device.blockSize - css.blockSize * dpr) > 1 ? void 0 : device;
12
12
  }
13
+ function canTakeColour(context, colour) {
14
+ const kept = context.fillStyle;
15
+ const isKept = (sentinel) => {
16
+ context.fillStyle = sentinel;
17
+ context.fillStyle = colour;
18
+ return context.fillStyle === sentinel;
19
+ };
20
+ const isRefused = isKept("#000000") && isKept("#ffffff");
21
+ context.fillStyle = kept;
22
+ return !isRefused;
23
+ }
13
24
  function readEach(properties, read) {
14
25
  return Object.fromEntries(Object.entries(properties).flatMap(([name, property]) => property === void 0 ? [] : [[name, read(property)]]));
15
26
  }
@@ -181,7 +192,7 @@ var CanvasSurface = class {
181
192
  const { canvas, colours, lengths = {}, numbers = {} } = this.#options;
182
193
  const styles = getComputedStyle(canvas);
183
194
  return {
184
- colours: readEach(colours, (property) => styles.getPropertyValue(property).trim().replaceAll("currentcolor", () => styles.color)),
195
+ colours: readEach(colours, (property) => this.#resolve(styles.getPropertyValue(property).trim().replaceAll("currentcolor", () => styles.color))),
185
196
  isReducedMotion: this.#reducedMotion.matches,
186
197
  lengths: readEach(lengths, (property) => readPxProperty(styles, property, 0)),
187
198
  numbers: readEach(numbers, (property) => readPxProperty(styles, property, 0))
@@ -209,6 +220,16 @@ var CanvasSurface = class {
209
220
  if (previous.width === 0 || previous.height === 0) this.rebuild();
210
221
  else this.invalidate();
211
222
  }
223
+ #resolve(colour) {
224
+ const context = this.#context;
225
+ if (!context || canTakeColour(context, colour)) return colour;
226
+ const probe = document.createElement("span");
227
+ probe.style.color = colour;
228
+ this.#options.canvas.append(probe);
229
+ const resolved = getComputedStyle(probe).color;
230
+ probe.remove();
231
+ return resolved;
232
+ }
212
233
  #schedule() {
213
234
  if (this.#options.signal.aborted || this.#frame !== void 0 || !this.#isVisible) return;
214
235
  this.#frame = requestAnimationFrame((frameMs) => {
@@ -1 +1 @@
1
- {"version":3,"file":"canvas-surface.js","names":["named"],"sources":["../../../src/lib/canvas-surface.ts"],"sourcesContent":["import { readPxProperty } from '#lib/read-px-property.ts';\n\ntype PrivateProperty = `--_sonic-${string}`;\n\nexport interface SurfaceSize {\n\tdpr: number;\n\theight: number;\n\twidth: number;\n}\n\nexport interface SurfaceLook<\n\tColour extends string,\n\tLength extends string = never,\n\tNumeric extends string = never,\n> {\n\tcolours: Record<Colour, string>;\n\tisReducedMotion: boolean;\n\tlengths: Record<Length, number>;\n\tnumbers: Record<Numeric, number>;\n}\n\nexport interface SurfaceFrame<\n\tColour extends string,\n\tLength extends string = never,\n\tNumeric extends string = never,\n> {\n\tframeMs: number;\n\tisDirty: boolean;\n\tisRebuilt: boolean;\n\tlook: SurfaceLook<Colour, Length, Numeric>;\n\tsize: SurfaceSize;\n}\n\nexport interface Surface {\n\tinvalidate: () => void;\n\treadonly isVisible: boolean;\n\trebuild: () => void;\n\trequestFrame: () => void;\n\tsetFill: (isFilling: boolean) => void;\n\treadonly size: SurfaceSize;\n}\n\ninterface SurfaceOptions<Colour extends string, Length extends string, Numeric extends string> {\n\tcanvas: HTMLCanvasElement;\n\tcolours: Record<Colour, PrivateProperty>;\n\tfill?: { control: HTMLElement; sizeProperty: PrivateProperty };\n\tlengths?: Record<Length, PrivateProperty>;\n\tnumbers?: Record<Numeric, PrivateProperty>;\n\tpaint: (context: CanvasRenderingContext2D, frame: SurfaceFrame<Colour, Length, Numeric>) => void;\n\tresize?: (size: SurfaceSize) => void;\n\tsignal: AbortSignal;\n}\n\nfunction deviceRatio(): number {\n\treturn globalThis.devicePixelRatio || 1;\n}\n\n// WebKit reports none, and an emulated device scale reports it in CSS px, so it counts only where it agrees\nfunction deviceBox(\n\tentry: ResizeObserverEntry,\n\tcss: ResizeObserverSize,\n): ResizeObserverSize | undefined {\n\tconst device =\n\t\t'devicePixelContentBoxSize' in entry ? entry.devicePixelContentBoxSize[0] : undefined;\n\tconst dpr = deviceRatio();\n\tif (!device || Math.abs(device.inlineSize - css.inlineSize * dpr) > 1) return undefined;\n\n\treturn Math.abs(device.blockSize - css.blockSize * dpr) > 1 ? undefined : device;\n}\n\n// `Object.fromEntries` widens the keys to `string`\nfunction readEach<Name extends string, Value>(\n\tproperties: Partial<Record<Name, PrivateProperty>>,\n\tread: (property: PrivateProperty) => Value,\n): Record<Name, Value> {\n\tconst named: Readonly<Record<string, PrivateProperty | undefined>> = properties;\n\n\treturn Object.fromEntries(\n\t\tObject.entries(named).flatMap(([name, property]) =>\n\t\t\tproperty === undefined ? [] : [[name, read(property)]],\n\t\t),\n\t) as Record<Name, Value>;\n}\n\nclass CanvasSurface<\n\tColour extends string,\n\tLength extends string,\n\tNumeric extends string,\n> implements Surface {\n\tget isVisible(): boolean {\n\t\treturn this.#isVisible;\n\t}\n\n\tget size(): SurfaceSize {\n\t\treturn this.#size;\n\t}\n\n\t// happy-dom has no canvas context\n\treadonly #context: CanvasRenderingContext2D | undefined;\n\n\t#cssBox: undefined | { height: number; width: number };\n\n\t#fillWatch: ResizeObserver | undefined;\n\n\t#frame: number | undefined;\n\n\t#isDirty = true;\n\n\t#isRebuildWanted = true;\n\n\t#isVisible = true;\n\n\t#look: SurfaceLook<Colour, Length, Numeric> | undefined;\n\n\treadonly #options: SurfaceOptions<Colour, Length, Numeric>;\n\n\treadonly #reducedMotion = matchMedia('(prefers-reduced-motion: reduce)');\n\n\t#size: SurfaceSize = { dpr: deviceRatio(), height: 0, width: 0 };\n\n\tconstructor(options: SurfaceOptions<Colour, Length, Numeric>) {\n\t\tconst { canvas, signal } = options;\n\n\t\tthis.#options = options;\n\t\tthis.#context = canvas.getContext('2d') ?? undefined;\n\t\tthis.#armTransitions();\n\t\tthis.#observeSize();\n\t\tthis.#observeView();\n\t\tthis.#watchResolution();\n\t\tthis.#reducedMotion.addEventListener(\n\t\t\t'change',\n\t\t\t() => {\n\t\t\t\tthis.rebuild();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\t// `transitionstart` drops events when a change replaces a pending transition in WebKit\n\t\tcanvas.addEventListener(\n\t\t\t'transitionrun',\n\t\t\t() => {\n\t\t\t\tthis.rebuild();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\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.#stopFill();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t\tthis.#schedule();\n\t}\n\n\tinvalidate(): void {\n\t\tthis.#isDirty = true;\n\t\tthis.#schedule();\n\t}\n\n\trebuild(): void {\n\t\tthis.#isRebuildWanted = true;\n\t\tthis.invalidate();\n\t}\n\n\trequestFrame(): void {\n\t\tthis.#schedule();\n\t}\n\n\tsetFill(isFilling: boolean): void {\n\t\tconst { fill, signal } = this.#options;\n\t\tif (!fill || signal.aborted || isFilling === (this.#fillWatch !== undefined)) return;\n\n\t\tconst { style } = fill.control;\n\n\t\tthis.#stopFill();\n\t\tif (!isFilling) {\n\t\t\tstyle.removeProperty(fill.sizeProperty);\n\t\t\treturn;\n\t\t}\n\n\t\tconst started = new ResizeObserver((entries) => {\n\t\t\tconst box = entries.at(-1)?.borderBoxSize[0];\n\t\t\tif (!box) return;\n\n\t\t\t// Written a frame on, or the canvas resizes inside this delivery and WebKit reports a loop\n\t\t\trequestAnimationFrame(() => {\n\t\t\t\tif (this.#fillWatch === started) {\n\t\t\t\t\tstyle.setProperty(fill.sizeProperty, `${String(box.blockSize)}px`);\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\n\t\tthis.#fillWatch = started;\n\t\tstarted.observe(fill.control);\n\t}\n\n\t#armTransitions(): void {\n\t\tconst { canvas, colours, numbers = {} } = this.#options;\n\t\tconst { style } = canvas;\n\t\t// A colour mixed from `currentcolor` computes to the same text whatever `color` is\n\t\tconst watched = ['color', ...Object.values(colours), ...Object.values(numbers)];\n\n\t\tstyle.setProperty('transition-property', watched.join(', '));\n\t\tstyle.setProperty('transition-duration', '0.001ms');\n\t\tstyle.setProperty('transition-timing-function', 'step-start');\n\t\tstyle.setProperty('transition-behavior', 'allow-discrete');\n\t}\n\n\t#current(): { isRebuilt: boolean; look: SurfaceLook<Colour, Length, Numeric> } {\n\t\tif (this.#look && !this.#isRebuildWanted) return { isRebuilt: false, look: this.#look };\n\n\t\treturn { isRebuilt: true, look: this.#read() };\n\t}\n\n\t#draw(frameMs: number): void {\n\t\tthis.#frame = undefined;\n\n\t\tconst size = this.#size;\n\t\tif (size.width === 0 || size.height === 0) return;\n\n\t\tconst { canvas, paint } = this.#options;\n\t\tconst { isRebuilt, look } = this.#current();\n\t\tconst isDirty = this.#isDirty;\n\n\t\tthis.#look = look;\n\t\tthis.#isDirty = false;\n\t\tthis.#isRebuildWanted = false;\n\t\t// Assigning either clears the canvas and its state\n\t\tif (canvas.width !== size.width) canvas.width = size.width;\n\t\tif (canvas.height !== size.height) canvas.height = size.height;\n\t\tif (this.#context) paint(this.#context, { frameMs, isDirty, isRebuilt, look, size });\n\t}\n\n\t#measure(entry: ResizeObserverEntry): void {\n\t\tconst [box] = entry.contentBoxSize;\n\t\tif (!box) return;\n\n\t\tconst device = deviceBox(entry, box);\n\n\t\tif (device) {\n\t\t\tthis.#cssBox = undefined;\n\t\t\tthis.#resize(device.inlineSize, device.blockSize);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#cssBox = { height: box.blockSize, width: box.inlineSize };\n\t\tthis.#rescale();\n\t}\n\n\t#observeSize(): void {\n\t\tconst { canvas, signal } = this.#options;\n\t\tconst observer = new ResizeObserver((entries) => {\n\t\t\tconst entry = entries.at(-1);\n\n\t\t\tif (entry) this.#measure(entry);\n\t\t});\n\n\t\ttry {\n\t\t\tobserver.observe(canvas, { box: 'device-pixel-content-box' });\n\t\t} catch {\n\t\t\t// WebKit throws a TypeError on the option\n\t\t\tobserver.observe(canvas, { box: 'content-box' });\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},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\t#observeView(): void {\n\t\tconst { canvas, signal } = this.#options;\n\t\tconst observer = new IntersectionObserver((entries) => {\n\t\t\tconst entry = entries.at(-1);\n\t\t\tif (!entry) return;\n\n\t\t\tconst wasVisible = this.#isVisible;\n\n\t\t\tthis.#isVisible = entry.isIntersecting;\n\t\t\tif (!entry.isIntersecting && this.#frame !== undefined) {\n\t\t\t\tcancelAnimationFrame(this.#frame);\n\t\t\t\tthis.#frame = undefined;\n\t\t\t}\n\n\t\t\t// Chromium fires no transition inside a skipped `content-visibility` subtree\n\t\t\tif (!wasVisible && entry.isIntersecting) this.rebuild();\n\t\t});\n\n\t\tobserver.observe(canvas);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tobserver.disconnect();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\t#read(): SurfaceLook<Colour, Length, Numeric> {\n\t\tconst { canvas, colours, lengths = {}, numbers = {} } = this.#options;\n\t\tconst styles = getComputedStyle(canvas);\n\n\t\treturn {\n\t\t\tcolours: readEach(colours, (property) =>\n\t\t\t\tstyles\n\t\t\t\t\t.getPropertyValue(property)\n\t\t\t\t\t.trim()\n\t\t\t\t\t.replaceAll('currentcolor', () => styles.color),\n\t\t\t),\n\t\t\tisReducedMotion: this.#reducedMotion.matches,\n\t\t\tlengths: readEach<Length, number>(lengths, (property) => readPxProperty(styles, property, 0)),\n\t\t\tnumbers: readEach<Numeric, number>(numbers, (property) =>\n\t\t\t\treadPxProperty(styles, property, 0),\n\t\t\t),\n\t\t};\n\t}\n\n\t#rescale(): void {\n\t\tconst box = this.#cssBox;\n\t\tif (!box) {\n\t\t\tthis.#resize(this.#size.width, this.#size.height);\n\t\t\treturn;\n\t\t}\n\n\t\tconst dpr = deviceRatio();\n\n\t\tthis.#resize(Math.round(box.width * dpr), Math.round(box.height * dpr));\n\t}\n\n\t#resize(width: number, height: number): void {\n\t\tconst previous = this.#size;\n\t\tconst dpr = deviceRatio();\n\t\tif (previous.width === width && previous.height === height && previous.dpr === dpr) return;\n\n\t\tthis.#size = { dpr, height, width };\n\t\tthis.#options.resize?.(this.#size);\n\t\tif (previous.width === 0 || previous.height === 0) this.rebuild();\n\t\telse this.invalidate();\n\t}\n\n\t#schedule(): void {\n\t\tif (this.#options.signal.aborted || this.#frame !== undefined || !this.#isVisible) return;\n\n\t\tthis.#frame = requestAnimationFrame((frameMs) => {\n\t\t\tthis.#draw(frameMs);\n\t\t});\n\t}\n\n\t#stopFill(): void {\n\t\tthis.#fillWatch?.disconnect();\n\t\tthis.#fillWatch = undefined;\n\t}\n\n\t#watchResolution(): void {\n\t\tconst { signal } = this.#options;\n\n\t\tmatchMedia(`(resolution: ${String(deviceRatio())}dppx)`).addEventListener(\n\t\t\t'change',\n\t\t\t() => {\n\t\t\t\tthis.#watchResolution();\n\t\t\t\tthis.#rescale();\n\t\t\t},\n\t\t\t{ once: true, signal },\n\t\t);\n\t}\n}\n\nexport function bindSurface<\n\tColour extends string,\n\tLength extends string = never,\n\tNumeric extends string = never,\n>(options: SurfaceOptions<Colour, Length, Numeric>): Surface {\n\treturn new CanvasSurface(options);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"canvas-surface.js","names":["named"],"sources":["../../../src/lib/canvas-surface.ts"],"sourcesContent":["import { readPxProperty } from '#lib/read-px-property.ts';\n\ntype PrivateProperty = `--_sonic-${string}`;\n\nexport interface SurfaceSize {\n\tdpr: number;\n\theight: number;\n\twidth: number;\n}\n\nexport interface SurfaceLook<\n\tColour extends string,\n\tLength extends string = never,\n\tNumeric extends string = never,\n> {\n\tcolours: Record<Colour, string>;\n\tisReducedMotion: boolean;\n\tlengths: Record<Length, number>;\n\tnumbers: Record<Numeric, number>;\n}\n\nexport interface SurfaceFrame<\n\tColour extends string,\n\tLength extends string = never,\n\tNumeric extends string = never,\n> {\n\tframeMs: number;\n\tisDirty: boolean;\n\tisRebuilt: boolean;\n\tlook: SurfaceLook<Colour, Length, Numeric>;\n\tsize: SurfaceSize;\n}\n\nexport interface Surface {\n\tinvalidate: () => void;\n\treadonly isVisible: boolean;\n\trebuild: () => void;\n\trequestFrame: () => void;\n\tsetFill: (isFilling: boolean) => void;\n\treadonly size: SurfaceSize;\n}\n\ninterface SurfaceOptions<Colour extends string, Length extends string, Numeric extends string> {\n\tcanvas: HTMLCanvasElement;\n\tcolours: Record<Colour, PrivateProperty>;\n\tfill?: { control: HTMLElement; sizeProperty: PrivateProperty };\n\tlengths?: Record<Length, PrivateProperty>;\n\tnumbers?: Record<Numeric, PrivateProperty>;\n\tpaint: (context: CanvasRenderingContext2D, frame: SurfaceFrame<Colour, Length, Numeric>) => void;\n\tresize?: (size: SurfaceSize) => void;\n\tsignal: AbortSignal;\n}\n\nfunction deviceRatio(): number {\n\treturn globalThis.devicePixelRatio || 1;\n}\n\n// WebKit reports none, and an emulated device scale reports it in CSS px, so it counts only where it agrees\nfunction deviceBox(\n\tentry: ResizeObserverEntry,\n\tcss: ResizeObserverSize,\n): ResizeObserverSize | undefined {\n\tconst device =\n\t\t'devicePixelContentBoxSize' in entry ? entry.devicePixelContentBoxSize[0] : undefined;\n\tconst dpr = deviceRatio();\n\tif (!device || Math.abs(device.inlineSize - css.inlineSize * dpr) > 1) return undefined;\n\n\treturn Math.abs(device.blockSize - css.blockSize * dpr) > 1 ? undefined : device;\n}\n\nfunction canTakeColour(context: CanvasRenderingContext2D, colour: string): boolean {\n\tconst kept = context.fillStyle;\n\tconst isKept = (sentinel: string): boolean => {\n\t\tcontext.fillStyle = sentinel;\n\t\tcontext.fillStyle = colour;\n\n\t\treturn context.fillStyle === sentinel;\n\t};\n\tconst isRefused = isKept('#000000') && isKept('#ffffff');\n\n\tcontext.fillStyle = kept;\n\n\treturn !isRefused;\n}\n\n// `Object.fromEntries` widens the keys to `string`\nfunction readEach<Name extends string, Value>(\n\tproperties: Partial<Record<Name, PrivateProperty>>,\n\tread: (property: PrivateProperty) => Value,\n): Record<Name, Value> {\n\tconst named: Readonly<Record<string, PrivateProperty | undefined>> = properties;\n\n\treturn Object.fromEntries(\n\t\tObject.entries(named).flatMap(([name, property]) =>\n\t\t\tproperty === undefined ? [] : [[name, read(property)]],\n\t\t),\n\t) as Record<Name, Value>;\n}\n\nclass CanvasSurface<\n\tColour extends string,\n\tLength extends string,\n\tNumeric extends string,\n> implements Surface {\n\tget isVisible(): boolean {\n\t\treturn this.#isVisible;\n\t}\n\n\tget size(): SurfaceSize {\n\t\treturn this.#size;\n\t}\n\n\t// happy-dom has no canvas context\n\treadonly #context: CanvasRenderingContext2D | undefined;\n\n\t#cssBox: undefined | { height: number; width: number };\n\n\t#fillWatch: ResizeObserver | undefined;\n\n\t#frame: number | undefined;\n\n\t#isDirty = true;\n\n\t#isRebuildWanted = true;\n\n\t#isVisible = true;\n\n\t#look: SurfaceLook<Colour, Length, Numeric> | undefined;\n\n\treadonly #options: SurfaceOptions<Colour, Length, Numeric>;\n\n\treadonly #reducedMotion = matchMedia('(prefers-reduced-motion: reduce)');\n\n\t#size: SurfaceSize = { dpr: deviceRatio(), height: 0, width: 0 };\n\n\tconstructor(options: SurfaceOptions<Colour, Length, Numeric>) {\n\t\tconst { canvas, signal } = options;\n\n\t\tthis.#options = options;\n\t\tthis.#context = canvas.getContext('2d') ?? undefined;\n\t\tthis.#armTransitions();\n\t\tthis.#observeSize();\n\t\tthis.#observeView();\n\t\tthis.#watchResolution();\n\t\tthis.#reducedMotion.addEventListener(\n\t\t\t'change',\n\t\t\t() => {\n\t\t\t\tthis.rebuild();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\n\t\t// `transitionstart` drops events when a change replaces a pending transition in WebKit\n\t\tcanvas.addEventListener(\n\t\t\t'transitionrun',\n\t\t\t() => {\n\t\t\t\tthis.rebuild();\n\t\t\t},\n\t\t\t{ signal },\n\t\t);\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.#stopFill();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t\tthis.#schedule();\n\t}\n\n\tinvalidate(): void {\n\t\tthis.#isDirty = true;\n\t\tthis.#schedule();\n\t}\n\n\trebuild(): void {\n\t\tthis.#isRebuildWanted = true;\n\t\tthis.invalidate();\n\t}\n\n\trequestFrame(): void {\n\t\tthis.#schedule();\n\t}\n\n\tsetFill(isFilling: boolean): void {\n\t\tconst { fill, signal } = this.#options;\n\t\tif (!fill || signal.aborted || isFilling === (this.#fillWatch !== undefined)) return;\n\n\t\tconst { style } = fill.control;\n\n\t\tthis.#stopFill();\n\t\tif (!isFilling) {\n\t\t\tstyle.removeProperty(fill.sizeProperty);\n\t\t\treturn;\n\t\t}\n\n\t\tconst started = new ResizeObserver((entries) => {\n\t\t\tconst box = entries.at(-1)?.borderBoxSize[0];\n\t\t\tif (!box) return;\n\n\t\t\t// Written a frame on, or the canvas resizes inside this delivery and WebKit reports a loop\n\t\t\trequestAnimationFrame(() => {\n\t\t\t\tif (this.#fillWatch === started) {\n\t\t\t\t\tstyle.setProperty(fill.sizeProperty, `${String(box.blockSize)}px`);\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\n\t\tthis.#fillWatch = started;\n\t\tstarted.observe(fill.control);\n\t}\n\n\t#armTransitions(): void {\n\t\tconst { canvas, colours, numbers = {} } = this.#options;\n\t\tconst { style } = canvas;\n\t\t// A colour mixed from `currentcolor` computes to the same text whatever `color` is\n\t\tconst watched = ['color', ...Object.values(colours), ...Object.values(numbers)];\n\n\t\tstyle.setProperty('transition-property', watched.join(', '));\n\t\tstyle.setProperty('transition-duration', '0.001ms');\n\t\tstyle.setProperty('transition-timing-function', 'step-start');\n\t\tstyle.setProperty('transition-behavior', 'allow-discrete');\n\t}\n\n\t#current(): { isRebuilt: boolean; look: SurfaceLook<Colour, Length, Numeric> } {\n\t\tif (this.#look && !this.#isRebuildWanted) return { isRebuilt: false, look: this.#look };\n\n\t\treturn { isRebuilt: true, look: this.#read() };\n\t}\n\n\t#draw(frameMs: number): void {\n\t\tthis.#frame = undefined;\n\n\t\tconst size = this.#size;\n\t\tif (size.width === 0 || size.height === 0) return;\n\n\t\tconst { canvas, paint } = this.#options;\n\t\tconst { isRebuilt, look } = this.#current();\n\t\tconst isDirty = this.#isDirty;\n\n\t\tthis.#look = look;\n\t\tthis.#isDirty = false;\n\t\tthis.#isRebuildWanted = false;\n\t\t// Assigning either clears the canvas and its state\n\t\tif (canvas.width !== size.width) canvas.width = size.width;\n\t\tif (canvas.height !== size.height) canvas.height = size.height;\n\t\tif (this.#context) paint(this.#context, { frameMs, isDirty, isRebuilt, look, size });\n\t}\n\n\t#measure(entry: ResizeObserverEntry): void {\n\t\tconst [box] = entry.contentBoxSize;\n\t\tif (!box) return;\n\n\t\tconst device = deviceBox(entry, box);\n\n\t\tif (device) {\n\t\t\tthis.#cssBox = undefined;\n\t\t\tthis.#resize(device.inlineSize, device.blockSize);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#cssBox = { height: box.blockSize, width: box.inlineSize };\n\t\tthis.#rescale();\n\t}\n\n\t#observeSize(): void {\n\t\tconst { canvas, signal } = this.#options;\n\t\tconst observer = new ResizeObserver((entries) => {\n\t\t\tconst entry = entries.at(-1);\n\n\t\t\tif (entry) this.#measure(entry);\n\t\t});\n\n\t\ttry {\n\t\t\tobserver.observe(canvas, { box: 'device-pixel-content-box' });\n\t\t} catch {\n\t\t\t// WebKit throws a TypeError on the option\n\t\t\tobserver.observe(canvas, { box: 'content-box' });\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},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\t#observeView(): void {\n\t\tconst { canvas, signal } = this.#options;\n\t\tconst observer = new IntersectionObserver((entries) => {\n\t\t\tconst entry = entries.at(-1);\n\t\t\tif (!entry) return;\n\n\t\t\tconst wasVisible = this.#isVisible;\n\n\t\t\tthis.#isVisible = entry.isIntersecting;\n\t\t\tif (!entry.isIntersecting && this.#frame !== undefined) {\n\t\t\t\tcancelAnimationFrame(this.#frame);\n\t\t\t\tthis.#frame = undefined;\n\t\t\t}\n\n\t\t\t// Chromium fires no transition inside a skipped `content-visibility` subtree\n\t\t\tif (!wasVisible && entry.isIntersecting) this.rebuild();\n\t\t});\n\n\t\tobserver.observe(canvas);\n\t\tsignal.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tobserver.disconnect();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t}\n\n\t#read(): SurfaceLook<Colour, Length, Numeric> {\n\t\tconst { canvas, colours, lengths = {}, numbers = {} } = this.#options;\n\t\tconst styles = getComputedStyle(canvas);\n\n\t\treturn {\n\t\t\tcolours: readEach(colours, (property) =>\n\t\t\t\tthis.#resolve(\n\t\t\t\t\tstyles\n\t\t\t\t\t\t.getPropertyValue(property)\n\t\t\t\t\t\t.trim()\n\t\t\t\t\t\t.replaceAll('currentcolor', () => styles.color),\n\t\t\t\t),\n\t\t\t),\n\t\t\tisReducedMotion: this.#reducedMotion.matches,\n\t\t\tlengths: readEach<Length, number>(lengths, (property) => readPxProperty(styles, property, 0)),\n\t\t\tnumbers: readEach<Numeric, number>(numbers, (property) =>\n\t\t\t\treadPxProperty(styles, property, 0),\n\t\t\t),\n\t\t};\n\t}\n\n\t#rescale(): void {\n\t\tconst box = this.#cssBox;\n\t\tif (!box) {\n\t\t\tthis.#resize(this.#size.width, this.#size.height);\n\t\t\treturn;\n\t\t}\n\n\t\tconst dpr = deviceRatio();\n\n\t\tthis.#resize(Math.round(box.width * dpr), Math.round(box.height * dpr));\n\t}\n\n\t#resize(width: number, height: number): void {\n\t\tconst previous = this.#size;\n\t\tconst dpr = deviceRatio();\n\t\tif (previous.width === width && previous.height === height && previous.dpr === dpr) return;\n\n\t\tthis.#size = { dpr, height, width };\n\t\tthis.#options.resize?.(this.#size);\n\t\tif (previous.width === 0 || previous.height === 0) this.rebuild();\n\t\telse this.invalidate();\n\t}\n\n\t#resolve(colour: string): string {\n\t\tconst context = this.#context;\n\t\tif (!context || canTakeColour(context, colour)) return colour;\n\n\t\tconst probe = document.createElement('span');\n\n\t\tprobe.style.color = colour;\n\t\tthis.#options.canvas.append(probe);\n\n\t\tconst resolved = getComputedStyle(probe).color;\n\n\t\tprobe.remove();\n\n\t\treturn resolved;\n\t}\n\n\t#schedule(): void {\n\t\tif (this.#options.signal.aborted || this.#frame !== undefined || !this.#isVisible) return;\n\n\t\tthis.#frame = requestAnimationFrame((frameMs) => {\n\t\t\tthis.#draw(frameMs);\n\t\t});\n\t}\n\n\t#stopFill(): void {\n\t\tthis.#fillWatch?.disconnect();\n\t\tthis.#fillWatch = undefined;\n\t}\n\n\t#watchResolution(): void {\n\t\tconst { signal } = this.#options;\n\n\t\tmatchMedia(`(resolution: ${String(deviceRatio())}dppx)`).addEventListener(\n\t\t\t'change',\n\t\t\t() => {\n\t\t\t\tthis.#watchResolution();\n\t\t\t\tthis.#rescale();\n\t\t\t},\n\t\t\t{ once: true, signal },\n\t\t);\n\t}\n}\n\nexport function bindSurface<\n\tColour extends string,\n\tLength extends string = never,\n\tNumeric extends string = never,\n>(options: SurfaceOptions<Colour, Length, Numeric>): Surface {\n\treturn new CanvasSurface(options);\n}\n"],"mappings":"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"}
@@ -3,7 +3,7 @@ function bindKeyPress(root, signal) {
3
3
  let pressed;
4
4
  const release = () => {
5
5
  if (!pressed) return;
6
- delete pressed.dataset.sonicPressed;
6
+ delete pressed.dataset.sonicActive;
7
7
  pressed = void 0;
8
8
  };
9
9
  root.addEventListener("keydown", (event) => {
@@ -12,7 +12,7 @@ function bindKeyPress(root, signal) {
12
12
  if (!(part instanceof HTMLButtonElement) || part.disabled) return;
13
13
  release();
14
14
  pressed = part;
15
- part.dataset.sonicPressed = "";
15
+ part.dataset.sonicActive = "";
16
16
  }, { signal });
17
17
  root.addEventListener("keyup", (event) => {
18
18
  if (event.key === " " || event.key === "Meta") release();
@@ -1 +1 @@
1
- {"version":3,"file":"key-press.js","names":[],"sources":["../../../src/lib/key-press.ts"],"sourcesContent":["// Firefox never matches `:active` on a button that a key holds\nexport function bindKeyPress(root: HTMLElement, signal: AbortSignal): void {\n\tlet pressed: HTMLButtonElement | undefined;\n\tconst release = (): void => {\n\t\tif (!pressed) return;\n\n\t\tdelete pressed.dataset.sonicPressed;\n\t\tpressed = undefined;\n\t};\n\n\troot.addEventListener(\n\t\t'keydown',\n\t\t(event) => {\n\t\t\tif (event.key !== ' ' || event.repeat || event.defaultPrevented) return;\n\n\t\t\tconst part = event.target;\n\t\t\tif (!(part instanceof HTMLButtonElement) || part.disabled) return;\n\n\t\t\trelease();\n\t\t\tpressed = part;\n\t\t\tpart.dataset.sonicPressed = '';\n\t\t},\n\t\t{ signal },\n\t);\n\troot.addEventListener(\n\t\t'keyup',\n\t\t(event) => {\n\t\t\t// macOS sends no `keyup` for a key let go while Cmd is down\n\t\t\tif (event.key === ' ' || event.key === 'Meta') release();\n\t\t},\n\t\t{ signal },\n\t);\n\troot.addEventListener(\n\t\t'focusout',\n\t\t(event) => {\n\t\t\tif (event.target === pressed) release();\n\t\t},\n\t\t{ signal },\n\t);\n\tsignal.addEventListener('abort', release, { once: true });\n}\n"],"mappings":";AACA,SAAgB,aAAa,MAAmB,QAA2B;CAC1E,IAAI;CACJ,MAAM,gBAAsB;EAC3B,IAAI,CAAC,SAAS;EAEd,OAAO,QAAQ,QAAQ;EACvB,UAAU;CACX;CAEA,KAAK,iBACJ,YACC,UAAU;EACV,IAAI,MAAM,QAAQ,OAAO,MAAM,UAAU,MAAM,kBAAkB;EAEjE,MAAM,OAAO,MAAM;EACnB,IAAI,EAAE,gBAAgB,sBAAsB,KAAK,UAAU;EAE3D,QAAQ;EACR,UAAU;EACV,KAAK,QAAQ,eAAe;CAC7B,GACA,EAAE,OAAO,CACV;CACA,KAAK,iBACJ,UACC,UAAU;EAEV,IAAI,MAAM,QAAQ,OAAO,MAAM,QAAQ,QAAQ,QAAQ;CACxD,GACA,EAAE,OAAO,CACV;CACA,KAAK,iBACJ,aACC,UAAU;EACV,IAAI,MAAM,WAAW,SAAS,QAAQ;CACvC,GACA,EAAE,OAAO,CACV;CACA,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AACzD"}
1
+ {"version":3,"file":"key-press.js","names":[],"sources":["../../../src/lib/key-press.ts"],"sourcesContent":["// Firefox never matches `:active` on a button that a key holds\nexport function bindKeyPress(root: HTMLElement, signal: AbortSignal): void {\n\tlet pressed: HTMLButtonElement | undefined;\n\tconst release = (): void => {\n\t\tif (!pressed) return;\n\n\t\tdelete pressed.dataset.sonicActive;\n\t\tpressed = undefined;\n\t};\n\n\troot.addEventListener(\n\t\t'keydown',\n\t\t(event) => {\n\t\t\tif (event.key !== ' ' || event.repeat || event.defaultPrevented) return;\n\n\t\t\tconst part = event.target;\n\t\t\tif (!(part instanceof HTMLButtonElement) || part.disabled) return;\n\n\t\t\trelease();\n\t\t\tpressed = part;\n\t\t\tpart.dataset.sonicActive = '';\n\t\t},\n\t\t{ signal },\n\t);\n\troot.addEventListener(\n\t\t'keyup',\n\t\t(event) => {\n\t\t\t// macOS sends no `keyup` for a key let go while Cmd is down\n\t\t\tif (event.key === ' ' || event.key === 'Meta') release();\n\t\t},\n\t\t{ signal },\n\t);\n\troot.addEventListener(\n\t\t'focusout',\n\t\t(event) => {\n\t\t\tif (event.target === pressed) release();\n\t\t},\n\t\t{ signal },\n\t);\n\tsignal.addEventListener('abort', release, { once: true });\n}\n"],"mappings":";AACA,SAAgB,aAAa,MAAmB,QAA2B;CAC1E,IAAI;CACJ,MAAM,gBAAsB;EAC3B,IAAI,CAAC,SAAS;EAEd,OAAO,QAAQ,QAAQ;EACvB,UAAU;CACX;CAEA,KAAK,iBACJ,YACC,UAAU;EACV,IAAI,MAAM,QAAQ,OAAO,MAAM,UAAU,MAAM,kBAAkB;EAEjE,MAAM,OAAO,MAAM;EACnB,IAAI,EAAE,gBAAgB,sBAAsB,KAAK,UAAU;EAE3D,QAAQ;EACR,UAAU;EACV,KAAK,QAAQ,cAAc;CAC5B,GACA,EAAE,OAAO,CACV;CACA,KAAK,iBACJ,UACC,UAAU;EAEV,IAAI,MAAM,QAAQ,OAAO,MAAM,QAAQ,QAAQ,QAAQ;CACxD,GACA,EAAE,OAAO,CACV;CACA,KAAK,iBACJ,aACC,UAAU;EACV,IAAI,MAAM,WAAW,SAAS,QAAQ;CACvC,GACA,EAAE,OAAO,CACV;CACA,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AACzD"}
@@ -130,7 +130,7 @@
130
130
  .sonic-button:is(
131
131
  [aria-pressed='true'],
132
132
  [aria-expanded='true'],
133
- :where([momentary]:state(pressed)) > *
133
+ :where([momentary]) > [data-sonic-pressed]
134
134
  ) {
135
135
  --_sonic-keycap-shift: calc(var(--_sonic-keycap-depth) / 2);
136
136
  --_sonic-keycap-scale: var(--_sonic-button-latched-scale);
@@ -158,7 +158,7 @@
158
158
  );
159
159
  }
160
160
 
161
- .sonic-button:is([aria-pressed='true'],[aria-expanded='true'],:where([momentary]:state(pressed)) > *) .sonic-button-cap > [data-sonic-when='released'] {
161
+ .sonic-button:is([aria-pressed='true'],[aria-expanded='true'],:where([momentary]) > [data-sonic-pressed]) .sonic-button-cap > [data-sonic-when='released'] {
162
162
  display: none;
163
163
  }
164
164
 
@@ -167,19 +167,19 @@
167
167
  .sonic-button:is(
168
168
  [aria-pressed='true'],
169
169
  [aria-expanded='true'],
170
- :where([momentary]:state(pressed)) > *
170
+ :where([momentary]) > [data-sonic-pressed]
171
171
  ) {
172
172
  --_sonic-skirt: var(--_sonic-lit);
173
173
  --_sonic-face: var(--_sonic-lit);
174
174
  --_sonic-ink: HighlightText;
175
175
  }
176
176
 
177
- .sonic-button:is([aria-pressed='true'],[aria-expanded='true'],:where([momentary]:state(pressed)) > *):is(:disabled,[aria-disabled='true']) {
177
+ .sonic-button:is([aria-pressed='true'],[aria-expanded='true'],:where([momentary]) > [data-sonic-pressed]):is(:disabled,[aria-disabled='true']) {
178
178
  --_sonic-ink: ButtonFace;
179
179
  }
180
180
  }
181
181
 
182
- .sonic-button:is(:active, [data-sonic-pressed], :where([momentary]:state(pressed)) > *):where(
182
+ .sonic-button:is(:active, [data-sonic-active], :where([momentary]) > [data-sonic-pressed]):where(
183
183
  :not(:disabled, [aria-disabled='true'])
184
184
  ) {
185
185
  --_sonic-keycap-shift: calc(var(--_sonic-keycap-depth) / 2);
@@ -199,9 +199,10 @@
199
199
  [aria-expanded='true'],
200
200
  :disabled,
201
201
  [aria-disabled='true'],
202
- :where([momentary]:state(pressed), [armed]) > *,
202
+ :where([momentary]) > [data-sonic-pressed],
203
+ :where([armed]) > *,
203
204
  :where([busy]) > :not(:has(.sonic-led))
204
- ):is(:focus-visible, :active, [data-sonic-pressed]) {
205
+ ):is(:focus-visible, :active, [data-sonic-active]) {
205
206
  color: var(--_sonic-ink-lift);
206
207
  }
207
208
 
@@ -212,7 +213,8 @@
212
213
  [aria-expanded='true'],
213
214
  :disabled,
214
215
  [aria-disabled='true'],
215
- :where([momentary]:state(pressed), [armed]) > *,
216
+ :where([momentary]) > [data-sonic-pressed],
217
+ :where([armed]) > *,
216
218
  :where([busy]) > :not(:has(.sonic-led))
217
219
  ):hover {
218
220
  color: var(--_sonic-ink-lift);
@@ -330,7 +332,7 @@
330
332
  .sonic-button:not(
331
333
  [aria-pressed='true'],
332
334
  [aria-expanded='true'],
333
- :where([momentary]:state(pressed)) > *
335
+ :where([momentary]) > [data-sonic-pressed]
334
336
  )
335
337
  .sonic-button-cap
336
338
  > [data-sonic-when='pressed'] {
@@ -88,7 +88,7 @@
88
88
  border-radius: 50%;
89
89
  }
90
90
 
91
- :state(dragging) > .sonic-dial {
91
+ .sonic-dial[data-sonic-dragging] {
92
92
  --_sonic-cap: var(--_sonic-cap-hover);
93
93
  }
94
94
 
@@ -251,7 +251,7 @@
251
251
  display: none;
252
252
  }
253
253
 
254
- :state(modulated) > .sonic-dial > .sonic-dial-modulation {
254
+ .sonic-dial[data-sonic-modulated] > .sonic-dial-modulation {
255
255
  --_sonic-dial-modulation-value-angle: calc(
256
256
  var(--_sonic-dial-modulation-value, 0) * var(--_sonic-dial-sweep)
257
257
  );
@@ -272,14 +272,14 @@
272
272
  mask: none;
273
273
  }
274
274
 
275
- :is(:state(modulated) > .sonic-dial > .sonic-dial-modulation)::before,:is(:state(modulated) > .sonic-dial > .sonic-dial-modulation)::after {
275
+ :is(.sonic-dial[data-sonic-modulated] > .sonic-dial-modulation)::before,:is(.sonic-dial[data-sonic-modulated] > .sonic-dial-modulation)::after {
276
276
  position: absolute;
277
277
  inset: 0;
278
278
  content: '';
279
279
  border-radius: 50%;
280
280
  }
281
281
 
282
- :is(:state(modulated) > .sonic-dial > .sonic-dial-modulation)::before {
282
+ :is(.sonic-dial[data-sonic-modulated] > .sonic-dial-modulation)::before {
283
283
  background:
284
284
  conic-gradient(
285
285
  from var(--_sonic-dial-start),
@@ -299,7 +299,7 @@
299
299
  mask: var(--_sonic-arc-mask);
300
300
  }
301
301
 
302
- :is(:state(modulated) > .sonic-dial > .sonic-dial-modulation)::after {
302
+ :is(.sonic-dial[data-sonic-modulated] > .sonic-dial-modulation)::after {
303
303
  background: linear-gradient(
304
304
  var(--_sonic-dial-modulation-tick),
305
305
  var(--_sonic-dial-modulation-tick)
@@ -414,7 +414,7 @@
414
414
  --_sonic-dial-modulation: CanvasText;
415
415
  }
416
416
 
417
- :state(modulated) > .sonic-dial > .sonic-dial-modulation {
417
+ .sonic-dial[data-sonic-modulated] > .sonic-dial-modulation {
418
418
  --_sonic-dial-modulation-range: GrayText;
419
419
  --_sonic-dial-modulation-tick: CanvasText;
420
420
  }
@@ -6,6 +6,12 @@
6
6
  initial-value: 80px;
7
7
  }
8
8
 
9
+ @property --_sonic-point-edge {
10
+ syntax: '<length>';
11
+ inherits: true;
12
+ initial-value: 1px;
13
+ }
14
+
9
15
  @layer sonic.control {
10
16
  :is(sonic-envelope, .sonic-envelope) {
11
17
  --_sonic-envelope-size: var(--sonic-envelope-size, 5rem);
@@ -75,7 +75,7 @@
75
75
  [data-sonic-lit]:where(:not([data-sonic-lit='idle'])),
76
76
  :where(
77
77
  .sonic-button:is([aria-pressed='true'], [aria-expanded='true']),
78
- [momentary]:state(pressed) > .sonic-button,
78
+ [momentary] > .sonic-button[data-sonic-pressed],
79
79
  .sonic-segmented-option[aria-checked='true'],
80
80
  .sonic-toggle:has(
81
81
  > [role='switch'][aria-checked='true'],
@@ -83,7 +83,7 @@
83
83
  )
84
84
  )
85
85
  *,
86
- :where(:state(at-origin):not(:disabled) > .sonic-led)
86
+ :where(:not(:disabled):has(> [data-sonic-at-origin]) > .sonic-led)
87
87
  ) {
88
88
  background:
89
89
  radial-gradient(
@@ -95,8 +95,8 @@
95
95
  var(--_sonic-lens-bezel);
96
96
  }
97
97
 
98
- .sonic-led:is([data-sonic-lit]:where(:not([data-sonic-lit='idle'])),:where(.sonic-button:is([aria-pressed='true'],[aria-expanded='true']),[momentary]:state(pressed) > .sonic-button,.sonic-segmented-option[aria-checked='true'],.sonic-toggle:has( > [role='switch'][aria-checked='true'], > [aria-checked='true'] [data-sonic-lit]))
99
- *,:where(:state(at-origin):not(:disabled) > .sonic-led))::after {
98
+ .sonic-led:is([data-sonic-lit]:where(:not([data-sonic-lit='idle'])),:where(.sonic-button:is([aria-pressed='true'],[aria-expanded='true']),[momentary] > .sonic-button[data-sonic-pressed],.sonic-segmented-option[aria-checked='true'],.sonic-toggle:has( > [role='switch'][aria-checked='true'], > [aria-checked='true'] [data-sonic-lit]))
99
+ *,:where(:not(:disabled):has( > [data-sonic-at-origin]) > .sonic-led))::after {
100
100
  background: radial-gradient(
101
101
  closest-side,
102
102
  color-mix(
@@ -114,7 +114,7 @@
114
114
  [data-sonic-lit]:where(:not([data-sonic-lit='idle'])),
115
115
  :where(
116
116
  .sonic-button:is([aria-pressed='true'], [aria-expanded='true']),
117
- [momentary]:state(pressed) > .sonic-button,
117
+ [momentary] > .sonic-button[data-sonic-pressed],
118
118
  .sonic-segmented-option[aria-checked='true'],
119
119
  .sonic-toggle:has(
120
120
  > [role='switch'][aria-checked='true'],
@@ -122,13 +122,13 @@
122
122
  )
123
123
  )
124
124
  *,
125
- :where(:state(at-origin):not(:disabled) > .sonic-led)
125
+ :where(:not(:disabled):has(> [data-sonic-at-origin]) > .sonic-led)
126
126
  ) {
127
127
  background: none;
128
128
  }
129
129
 
130
- .sonic-led:is([data-sonic-lit]:where(:not([data-sonic-lit='idle'])),:where(.sonic-button:is([aria-pressed='true'],[aria-expanded='true']),[momentary]:state(pressed) > .sonic-button,.sonic-segmented-option[aria-checked='true'],.sonic-toggle:has( > [role='switch'][aria-checked='true'], > [aria-checked='true'] [data-sonic-lit]))
131
- *,:where(:state(at-origin):not(:disabled) > .sonic-led))::after {
130
+ .sonic-led:is([data-sonic-lit]:where(:not([data-sonic-lit='idle'])),:where(.sonic-button:is([aria-pressed='true'],[aria-expanded='true']),[momentary] > .sonic-button[data-sonic-pressed],.sonic-segmented-option[aria-checked='true'],.sonic-toggle:has( > [role='switch'][aria-checked='true'], > [aria-checked='true'] [data-sonic-lit]))
131
+ *,:where(:not(:disabled):has( > [data-sonic-at-origin]) > .sonic-led))::after {
132
132
  background: var(--_sonic-lit);
133
133
  box-shadow: 0 0 0 1px CanvasText;
134
134
  }
@@ -1,3 +1,9 @@
1
+ @property --_sonic-rim-width {
2
+ syntax: '<length>';
3
+ inherits: true;
4
+ initial-value: 1px;
5
+ }
6
+
1
7
  @layer sonic.material {
2
8
  :is(
3
9
  .sonic-dial,
@@ -183,8 +189,13 @@
183
189
  }
184
190
  }
185
191
 
186
- :state(editing)
187
- > :is(.sonic-dial, .sonic-number, .sonic-slider, .sonic-waveform, .sonic-wavestrip) {
192
+ :is(
193
+ .sonic-dial,
194
+ .sonic-number,
195
+ .sonic-slider,
196
+ .sonic-waveform,
197
+ .sonic-wavestrip
198
+ )[data-sonic-editing] {
188
199
  --_sonic-cap: var(--_sonic-cap-hover);
189
200
  --_sonic-focus-shadow: var(--_sonic-focus-glow);
190
201
  }
@@ -1,3 +1,9 @@
1
+ @property --_sonic-glass-pitch {
2
+ syntax: '<length>';
3
+ inherits: true;
4
+ initial-value: 2px;
5
+ }
6
+
1
7
  @keyframes sonic-glass-breathe {
2
8
  to {
3
9
  opacity: calc(0.4 * min(1, var(--_sonic-glass-texture)));
@@ -84,7 +90,7 @@
84
90
  }
85
91
 
86
92
  @media (prefers-reduced-motion: no-preference) {
87
- .sonic-screen:is(:focus-visible,:focus-within)::before,.sonic-screen:has(:state(dragging))::before {
93
+ .sonic-screen:is(:focus-visible,:focus-within)::before,.sonic-screen:has([data-sonic-dragging])::before {
88
94
  animation: sonic-glass-breathe 3s ease-in-out infinite alternate;
89
95
  }
90
96
 
@@ -4,6 +4,12 @@
4
4
  initial-value: 24px;
5
5
  }
6
6
 
7
+ @property --_sonic-readout-shine-width {
8
+ syntax: '<length>';
9
+ inherits: true;
10
+ initial-value: 1px;
11
+ }
12
+
7
13
  @layer sonic.material {
8
14
  :is(.sonic-dial, .sonic-envelope, .sonic-slider, .sonic-waveform, .sonic-wavestrip, .sonic-xy) {
9
15
  --_sonic-readout-size: var(--sonic-readout-size, 1.5rem);
@@ -26,8 +32,10 @@
26
32
  --_sonic-glass-own: var(--sonic-readout-glass);
27
33
  --_sonic-glass-edge-colour-own: var(--sonic-readout-edge);
28
34
  --_sonic-glass-edge-width-own: var(--sonic-readout-edge-width);
35
+ --_sonic-readout-shine-width: max(1px, round(var(--_sonic-unit), var(--_sonic-snap)));
29
36
 
30
37
  box-sizing: border-box;
38
+ display: none;
31
39
  place-items: center;
32
40
  min-inline-size: calc(1.75 * var(--_sonic-readout-size));
33
41
  block-size: var(--_sonic-readout-size);
@@ -56,7 +64,7 @@
56
64
  box-shadow:
57
65
  calc(2 * var(--_sonic-cast-x)) calc(2 * var(--_sonic-cast-y)) calc(6 * var(--_sonic-unit))
58
66
  color-mix(in srgb, var(--_sonic-shade) calc(55% * var(--_sonic-readout-depth)), transparent),
59
- inset 0 0 0 max(1px, round(var(--_sonic-unit), var(--_sonic-snap)))
67
+ inset 0 0 0 var(--_sonic-readout-shine-width)
60
68
  color-mix(in srgb, var(--_sonic-shine) calc(8% * var(--_sonic-readout-depth)), transparent);
61
69
  opacity: 0;
62
70
  translate: 0 calc(0.15 * var(--_sonic-readout-size));
@@ -101,7 +109,7 @@
101
109
  }
102
110
  }
103
111
 
104
- :state(editing) > :is(.sonic-dial,.sonic-slider,.sonic-waveform,.sonic-wavestrip) > :is(.sonic-dial-readout,.sonic-envelope-readout,.sonic-slider-readout,.sonic-waveform-readout,.sonic-wavestrip-readout,.sonic-xy-readout) {
112
+ [data-sonic-editing]:is(.sonic-dial,.sonic-slider,.sonic-waveform,.sonic-wavestrip) > :is(.sonic-dial-readout,.sonic-envelope-readout,.sonic-slider-readout,.sonic-waveform-readout,.sonic-wavestrip-readout,.sonic-xy-readout) {
105
113
  pointer-events: auto;
106
114
  cursor: text;
107
115
  }
@@ -30,8 +30,8 @@
30
30
  scale var(--_sonic-fade-duration) ease-out;
31
31
  }
32
32
 
33
- :state(dragging) > * > .sonic-xy-reticle,
34
- :state(dragging) > * > .sonic-envelope-reticle::before {
33
+ [data-sonic-dragging] > .sonic-xy-reticle,
34
+ [data-sonic-dragging] > .sonic-envelope-reticle::before {
35
35
  opacity: calc(1 - var(--_sonic-relief));
36
36
  scale: 1;
37
37
  }
@@ -84,11 +84,11 @@
84
84
  block-size: var(--_sonic-meter-size);
85
85
  }
86
86
 
87
- :is([value], :state(ladder)) > .sonic-meter {
87
+ :is([value] > .sonic-meter, .sonic-meter[data-sonic-ladder]) {
88
88
  --_sonic-meter-clip-space: 0px;
89
89
  }
90
90
 
91
- :state(ladder) > .sonic-meter {
91
+ .sonic-meter[data-sonic-ladder] {
92
92
  --_sonic-meter-gap: 0px;
93
93
  }
94
94
 
@@ -173,7 +173,7 @@
173
173
  mask-composite: add;
174
174
  }
175
175
 
176
- :state(ladder) > .sonic-meter > :is(.sonic-meter-segments, .sonic-meter-bar) {
176
+ .sonic-meter[data-sonic-ladder] > :is(.sonic-meter-segments, .sonic-meter-bar) {
177
177
  --_sonic-meter-pitch: calc(
178
178
  (var(--_sonic-meter-length) - 2 * var(--_sonic-meter-inset)) / var(--_sonic-meter-count, 1)
179
179
  );
@@ -251,8 +251,8 @@
251
251
  block-size: auto;
252
252
  }
253
253
 
254
- [orientation='horizontal']:state(ladder)
255
- > .sonic-meter
254
+ [orientation='horizontal']
255
+ > .sonic-meter[data-sonic-ladder]
256
256
  > :is(.sonic-meter-segments, .sonic-meter-bar) {
257
257
  --_sonic-meter-segment-mask: radial-gradient(
258
258
  circle var(--_sonic-meter-ladder-radius),
@@ -262,14 +262,14 @@
262
262
  0 0 / var(--_sonic-meter-pitch) 100% repeat no-repeat;
263
263
  }
264
264
 
265
- :state(ladder) > .sonic-meter > :is(.sonic-meter-segments, .sonic-meter-bar)::after {
265
+ .sonic-meter[data-sonic-ladder] > :is(.sonic-meter-segments, .sonic-meter-bar)::after {
266
266
  position: absolute;
267
267
  inset: 0;
268
268
  content: '';
269
269
  background: var(--_sonic-lens-glint) 0 100% / 100% var(--_sonic-meter-pitch) repeat-y;
270
270
  }
271
271
 
272
- :state(ladder) > .sonic-meter > .sonic-meter-bar::after {
272
+ .sonic-meter[data-sonic-ladder] > .sonic-meter-bar::after {
273
273
  background: radial-gradient(
274
274
  circle var(--_sonic-meter-ladder-radius),
275
275
  color-mix(in srgb, var(--_sonic-shine) calc(45% * var(--_sonic-relief)), transparent),
@@ -278,8 +278,8 @@
278
278
  0 100% / 100% var(--_sonic-meter-pitch) repeat-y;
279
279
  }
280
280
 
281
- [orientation='horizontal']:state(ladder)
282
- > .sonic-meter
281
+ [orientation='horizontal']
282
+ > .sonic-meter[data-sonic-ladder]
283
283
  > :is(.sonic-meter-segments, .sonic-meter-bar)::after {
284
284
  background-repeat: repeat-x;
285
285
  background-position: 0 0;
@@ -368,7 +368,7 @@
368
368
  opacity: var(--_sonic-meter-clipped, 0);
369
369
  }
370
370
 
371
- :is([value], :state(ladder)) > .sonic-meter > .sonic-meter-clip {
371
+ :is([value] > .sonic-meter, .sonic-meter[data-sonic-ladder]) > .sonic-meter-clip {
372
372
  display: none;
373
373
  }
374
374
 
@@ -402,7 +402,7 @@
402
402
  background: Mark;
403
403
  }
404
404
 
405
- :state(ladder) > .sonic-meter > :is(.sonic-meter-segments, .sonic-meter-bar)::after {
405
+ .sonic-meter[data-sonic-ladder] > :is(.sonic-meter-segments, .sonic-meter-bar)::after {
406
406
  display: none;
407
407
  }
408
408
  }
@@ -68,7 +68,7 @@
68
68
  white-space: nowrap;
69
69
  }
70
70
 
71
- :state(editing) > .sonic-number > .sonic-number-value {
71
+ .sonic-number[data-sonic-editing] > .sonic-number-value {
72
72
  visibility: hidden;
73
73
  }
74
74
 
@@ -1,5 +1,11 @@
1
1
  @layer sonic.material, sonic.control;
2
2
 
3
+ @property --_sonic-region-bracket-width {
4
+ syntax: '<length>';
5
+ inherits: true;
6
+ initial-value: 1px;
7
+ }
8
+
3
9
  @layer sonic.control {
4
10
  sonic-region {
5
11
  display: none;
@@ -99,14 +99,14 @@
99
99
  transition: color var(--_sonic-fade-duration) ease-out;
100
100
  }
101
101
 
102
- .sonic-segmented-option:where([data-sonic-pressed]):not(:disabled),
102
+ .sonic-segmented-option:where([data-sonic-active]):not(:disabled),
103
103
  .sonic-segmented-option[aria-checked='true'] {
104
104
  --_sonic-keycap-shift: calc(var(--_sonic-keycap-depth) / 2);
105
105
  --_sonic-keycap-scale: var(--_sonic-segmented-latched-scale);
106
106
  --_sonic-keycap-drop: var(--_sonic-press-drop);
107
107
  }
108
108
 
109
- .sonic-segmented-option:where([data-sonic-pressed]):not(:disabled) {
109
+ .sonic-segmented-option:where([data-sonic-active]):not(:disabled) {
110
110
  --_sonic-keycap-scale: var(--_sonic-segmented-press-scale);
111
111
  }
112
112
 
@@ -128,7 +128,7 @@
128
128
  .sonic-segmented-option:not([aria-checked='true'], :disabled):is(
129
129
  :focus-visible,
130
130
  :active,
131
- [data-sonic-pressed]
131
+ [data-sonic-active]
132
132
  ) {
133
133
  color: var(--_sonic-ink-lift);
134
134
  }