@xsynaptic/sonic-ui 0.19.0 → 0.21.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.
- package/CHANGELOG.md +37 -0
- package/README.md +27 -112
- package/dist/default/elements/button.js +2 -1
- package/dist/default/elements/field-gesture.js +3 -0
- package/dist/default/elements/sonic-element.js +1 -1
- package/dist/default/elements/split.js +3 -1
- package/dist/default/elements/switch.js +5 -3
- package/dist/default/elements/toggle.js +10 -5
- package/dist/default/elements/value-entry.js +5 -18
- package/dist/default/elements/waveform.js +15 -4
- package/dist/default/elements/xy.js +33 -5
- package/dist/default/lib/canvas-surface.js +5 -1
- package/dist/default/lib/double-press.js +30 -0
- package/dist/dev/elements/button.js +5 -11
- package/dist/dev/elements/button.js.map +1 -1
- package/dist/dev/elements/dial.js +2 -12
- package/dist/dev/elements/dial.js.map +1 -1
- package/dist/dev/elements/display-element.js +1 -5
- package/dist/dev/elements/display-element.js.map +1 -1
- package/dist/dev/elements/envelope.js +2 -15
- package/dist/dev/elements/envelope.js.map +1 -1
- package/dist/dev/elements/field-gesture.js +3 -0
- package/dist/dev/elements/field-gesture.js.map +1 -1
- package/dist/dev/elements/meter.js +1 -1
- package/dist/dev/elements/meter.js.map +1 -1
- package/dist/dev/elements/number.js +1 -13
- package/dist/dev/elements/number.js.map +1 -1
- package/dist/dev/elements/segmented.js +3 -10
- package/dist/dev/elements/segmented.js.map +1 -1
- package/dist/dev/elements/slider.js +2 -12
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/elements/sonic-element.d.ts +1 -22
- package/dist/dev/elements/sonic-element.js +7 -34
- package/dist/dev/elements/sonic-element.js.map +1 -1
- package/dist/dev/elements/split.js +3 -1
- package/dist/dev/elements/split.js.map +1 -1
- package/dist/dev/elements/switch.js +7 -8
- package/dist/dev/elements/switch.js.map +1 -1
- package/dist/dev/elements/toggle.js +13 -15
- package/dist/dev/elements/toggle.js.map +1 -1
- package/dist/dev/elements/value-entry.js +5 -18
- package/dist/dev/elements/value-entry.js.map +1 -1
- package/dist/dev/elements/wave-element.js +2 -12
- package/dist/dev/elements/wave-element.js.map +1 -1
- package/dist/dev/elements/waveform.d.ts +2 -0
- package/dist/dev/elements/waveform.js +15 -4
- package/dist/dev/elements/waveform.js.map +1 -1
- package/dist/dev/elements/xy.d.ts +4 -0
- package/dist/dev/elements/xy.js +35 -17
- package/dist/dev/elements/xy.js.map +1 -1
- package/dist/dev/lib/canvas-surface.js +5 -1
- package/dist/dev/lib/canvas-surface.js.map +1 -1
- package/dist/dev/lib/double-press.js +31 -0
- package/dist/dev/lib/double-press.js.map +1 -0
- package/dist/skins/amber.css +0 -1
- package/dist/skins/ivory.css +0 -1
- package/dist/skins/lime.css +0 -1
- package/dist/skins/slate.css +0 -1
- package/dist/styles/button.css +1 -0
- package/dist/styles/dial.css +1 -1
- package/dist/styles/envelope.css +16 -14
- package/dist/styles/led.css +7 -19
- package/dist/styles/material/bracket.css +1 -1
- package/dist/styles/material/cap.css +27 -4
- package/dist/styles/material/core.css +9 -1
- package/dist/styles/material/glass.css +1 -7
- package/dist/styles/material/lens.css +18 -1
- package/dist/styles/material/pane.css +2 -12
- package/dist/styles/material/readout.css +7 -1
- package/dist/styles/material/well.css +8 -12
- package/dist/styles/meter.css +6 -4
- package/dist/styles/number.css +8 -9
- package/dist/styles/screen.css +1 -3
- package/dist/styles/spectrum.css +8 -22
- package/dist/styles/toggle.css +4 -0
- package/dist/styles/waveform.css +12 -24
- package/dist/styles/wavestrip.css +12 -33
- package/dist/styles/xy.css +30 -28
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# @xsynaptic/sonic-ui
|
|
2
2
|
|
|
3
|
+
## 0.21.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- - Breaking: the number box is plain: its digits alone in the text colour, with no glass, edge or corner. Wrap it in `.sonic-screen` for the box (`--sonic-screen-size` set to the number's size and `--sonic-screen-inset-ratio: 0` gives the old one). `--sonic-number-radius-ratio` and `--sonic-number-inset-ratio` are removed, `--sonic-radius` and the glass tokens no longer reach it, and `number.css` needs only the core, cap and value material sheets.
|
|
8
|
+
- With no skin a meter no longer draws a near-black bar: its bed is clear and its unlit segments are a faint tint of the text colour, like the LED. Skins are unchanged.
|
|
9
|
+
- A screen now shrinks in a flex row; give one that holds a text readout a `min-inline-size`.
|
|
10
|
+
- A button sets `user-select: none`, as every other control does, so a text legend no longer selects under a long press on touch.
|
|
11
|
+
- A held XY puck stays solid, as a held envelope handle does; the hold shows as the glow, or as the bracket on a skin with no relief. It no longer turns hollow.
|
|
12
|
+
- `--sonic-focus-radius` rounds the focus outline and glow of the plain boxes (number box, waveform, wave strip), which have no corner of their own. Unset, they stay square.
|
|
13
|
+
- - With no skin, every unlit part follows the text colour at 30%, as the LED and the meter did: a dial's ring, a slider's groove, the ring, the pad's lines, the wave strip. Glass, a disabled envelope handle and a disabled puck stay solid. Skins are unchanged.
|
|
14
|
+
- Tokens: `--sonic-touch-action` reaches the dial and the number box, so one set on a wrapper for a slider now reaches dials inside it. `--sonic-radius` reaches every readout; `--sonic-readout-radius-ratio` is new and wins when set. `--sonic-focus-radius` also rounds the focus of a slider and a switch position.
|
|
15
|
+
- Focus: a visible `--sonic-focus-outline` is drawn on the cap of a button, a segmented option and a dial, where the cap covered it.
|
|
16
|
+
- Waveform: a repeating key scrubs, as on the wave strip: `input` on each repeat and one `change` on keyup.
|
|
17
|
+
- Split: `disabled` freezes the members against gestures; a written `value` still lands.
|
|
18
|
+
- XY pad: `double-press="reset"` (default `none`), `:state(revealed)`, `dragging` and `revealed` getters, and `sonic-reveal`. A form reset during a drag is dropped.
|
|
19
|
+
- Toggle: a press that slides a few pixels is still a press. A coloured lit toggle takes system colours in forced colours.
|
|
20
|
+
- Button and switch: a key prevented in a capture listener no longer holds a momentary button, a bat press let go off the switch is cancelled, and an LED in a disabled latched button or option dims where it stayed lit.
|
|
21
|
+
- Screen: its text can be selected (a screen that is itself a button sets its own `user-select`), and its `--sonic-glass-depth` no longer flattens the well of a button inside it.
|
|
22
|
+
- The keycap sheen follows `--sonic-light-tilt`.
|
|
23
|
+
|
|
24
|
+
## 0.20.0
|
|
25
|
+
|
|
26
|
+
### Minor Changes
|
|
27
|
+
|
|
28
|
+
- - Breaking: the wave strip, waveform, spectrum, envelope and XY pad draw no glass. Each is plain, in the text colour around it, with no fill, edge or corner. For the encased look wrap one in `<div class="sonic-screen">`, whose padding is the inset.
|
|
29
|
+
- Breaking: `.sonic-screen` is sized like a `div`. It fills a block, hugs in a flex row and is never shorter than `--sonic-screen-size`; `--sonic-screen-aspect-ratio` is removed, so a text readout sets its own width. A control with `fill` needs the screen given a height.
|
|
30
|
+
- Removed tokens: `--sonic-wavestrip-inset-ratio`, `--sonic-waveform-inset-ratio`, `--sonic-spectrum-inset-ratio`, `--sonic-xy-inset-ratio`, and the `-radius-ratio` of the wave strip, waveform, spectrum, envelope and XY pad. `--sonic-radius` no longer reaches those five, and `--sonic-glass` with its depth, texture, edge, text and font reaches only the screen, the number box and the readouts. The four relief skins drop their wave strip inset.
|
|
31
|
+
- New token: `--sonic-wavestrip-marker-edge` colours the ring round a marker, which is clear when unset. A held puck and the gap in its focus ring are clear too, and the pad's lines stop at the puck. A held or disabled envelope handle is filled in its ring's colour.
|
|
32
|
+
- A plain control takes its colour from `color`, where it took `--sonic-glass-text`, and its canvas repaints when `color` changes. It ignores `--sonic-ink`, as before.
|
|
33
|
+
- With no token set: the XY pad and the spectrum lose their default insets (0.04 and 0.06), a wave strip's regions stop at the wave's height, and a screen's well takes the full depth where the waveform's took 0.6.
|
|
34
|
+
- `spectrum.css` needs only `material/core.css`; the envelope, waveform, wave strip and XY pad sheets no longer need `pane` or `well`. Importing sheets one by one, the glass comes back with `screen.css` and `material/glass.css`, `pane.css` and `well.css`.
|
|
35
|
+
|
|
36
|
+
### Patch Changes
|
|
37
|
+
|
|
38
|
+
- - Development warnings name a control's own sheet and `material/core.css` when missing, and no longer any other material sheet; the README table lists what each sheet needs.
|
|
39
|
+
|
|
3
40
|
## 0.19.0
|
|
4
41
|
|
|
5
42
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Native custom elements for audio interfaces. Each control renders in the light DOM, skinned with `--sonic-*` tokens (CSS custom properties). No framework, no runtime dependencies.
|
|
4
4
|
|
|
5
|
-
Early and unstable: any 0.x release may break the API.
|
|
6
|
-
|
|
7
|
-
This README holds what the package's own files do not say. For the rest, read the package:
|
|
5
|
+
Early and unstable: any 0.x release may break the API, and the changelog is where a control's behaviour is described. This README holds setup and the contracts every control shares. For the rest, read the package:
|
|
8
6
|
|
|
9
7
|
- Properties and methods: `dist/dev/elements/<control>.d.ts`. Every attribute has a property of the same name in camel case.
|
|
10
8
|
- Tokens and their defaults: `dist/styles/<control>.css` and `dist/styles/material/`, where each token is read as `var(--sonic-…, default)`.
|
|
@@ -23,6 +21,7 @@ import '@xsynaptic/sonic-ui/define';
|
|
|
23
21
|
```css
|
|
24
22
|
@layer base, sonic, components;
|
|
25
23
|
|
|
24
|
+
@import 'reset.css' layer(base);
|
|
26
25
|
@import '@xsynaptic/sonic-ui/controls.css';
|
|
27
26
|
@import '@xsynaptic/sonic-ui/skins/slate.css';
|
|
28
27
|
```
|
|
@@ -33,116 +32,32 @@ import '@xsynaptic/sonic-ui/define';
|
|
|
33
32
|
</body>
|
|
34
33
|
```
|
|
35
34
|
|
|
36
|
-
- Every sheet sits in the `sonic` layer, so
|
|
37
|
-
- A
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
@layer base, sonic, components;
|
|
41
|
-
|
|
42
|
-
@import 'reset.css' layer(base);
|
|
43
|
-
```
|
|
35
|
+
- Every sheet sits in the `sonic` layer. A rule outside any layer beats every layered one, so an unlayered reset strips padding, borders and type from the controls: import a reset into a layer declared before `sonic`, and keep your own rules in one declared after.
|
|
36
|
+
- A skin sheet does nothing until its class, `sonic-skin-<name>`, is on an ancestor. The skins are `slate`, `amber`, `ivory`, `lime` and `flat`. With no skin a control is bare, drawn in the text colour around it.
|
|
37
|
+
- `./define` registers every control and `./define/<control>` registers one. `.` exports the classes and registers nothing.
|
|
38
|
+
- Under a bundler that sets the `development` condition, as Vite's dev server does, the controls warn in the console about a missing sheet, a reset, a box property on a host and a child they do not use. Read the console before debugging.
|
|
44
39
|
|
|
45
|
-
|
|
46
|
-
- `./define` registers every control. `./define/<control>` registers one, and `.` exports the classes and registers nothing.
|
|
47
|
-
- Under a bundler that sets the `development` condition, as Vite's dev server does, the controls warn in the console about a missing sheet, a padding lost to an unlayered rule, a box property set on a host, a child they do not use and a binding that found nothing. Read the console before debugging.
|
|
40
|
+
## Contracts
|
|
48
41
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
-
|
|
52
|
-
- Name every control with `aria-label` or `aria-labelledby` on the host, or with a `<label>` for a form-associated one
|
|
42
|
+
- The host is `display: contents` and the control is an element inside it. A width, margin or transform on the host does nothing: size a control with its token (`--sonic-dial-size`, `--sonic-slider-length`), set on the host or any ancestor, and place it with its parent's layout.
|
|
43
|
+
- The split is the one host with a box: `<sonic-split>` is inline until given a display, and laying it out is yours.
|
|
44
|
+
- Register the classes under their stock tag names (`sonic-dial`, `sonic-split`). The sheets and the split key on them, so a split under another tag finds no members.
|
|
45
|
+
- Name every control with `aria-label` or `aria-labelledby` on the host, or with a `<label>` for a form-associated one.
|
|
53
46
|
- A boolean attribute is on when present, whatever its value, so `pressed="false"` latches a button. From a framework pass `undefined`, never `false`.
|
|
54
|
-
- A control uses only the children it documents
|
|
55
|
-
- Events bubble and carry no detail: read the property when one fires. A control knows nothing about playback, so wire
|
|
56
|
-
-
|
|
57
|
-
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
The dial, the slider, the number box and both wave controls hold one number, from `min` (0) to `max` (100) in steps of `step` (1).
|
|
62
|
-
|
|
63
|
-
- `input` fires as the value moves and `change` once when a gesture ends, however it ends.
|
|
64
|
-
- A drag owns the value: a write to `value` during one is dropped, so a seek bar fed by `timeupdate` needs no guard of its own.
|
|
65
|
-
- A double press, or Enter, opens typed entry. `double-press="reset"` makes it go back to `default` instead. Cmd-click on Apple platforms, Ctrl-click elsewhere, and Delete reset whichever is chosen, once `default` is set.
|
|
66
|
-
- `formatValue` and `parseValue` are properties holding functions. What the first writes, the second has to read, or typed entry opens on text it cannot take; `formatEntry` covers the case where it cannot.
|
|
67
|
-
- The readout bubble is opt-in, with `readout`. Assistive technology hears the value without it; `formatSpokenValue` words it.
|
|
68
|
-
- `taper="log"` gives even octaves on a range above zero, and `midpoint` bends a range that starts at zero. On a tapered control the arrow keys move by travel; `key-step` makes them move by value.
|
|
69
|
-
- `positions` is a property holding the numbers a stepped control stops at, spaced evenly whatever their values; their labels come from `formatValue`.
|
|
70
|
-
- `modulationValue` is a property only, meant to be written every frame. It moves the light and fires nothing.
|
|
71
|
-
- To take a key from a control, call `preventDefault()` in a capture listener.
|
|
72
|
-
|
|
73
|
-
## Press controls
|
|
74
|
-
|
|
75
|
-
A button's children are its legend, an icon or a label, always an element. They are copied into the cap, so the copy has none of your listeners and any id in it is prefixed; keep them static. A child with `data-sonic-when="pressed"` or `"released"` shows only in that state.
|
|
76
|
-
|
|
77
|
-
```html
|
|
78
|
-
<sonic-button aria-label="Loop" latching>
|
|
79
|
-
<span class="sonic-led"></span>
|
|
80
|
-
<svg aria-hidden="true">…</svg>
|
|
81
|
-
</sonic-button>
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
- A plain button fires `click`. `latching` makes it stay pressed, with `pressed` and a `change` event; `momentary` makes it pressed for as long as it is held, and acts on the press, not the release.
|
|
85
|
-
- `soft-disabled` stops presses but keeps the tab stop, for a button that would otherwise lose focus when it disables itself. `busy` leaves it pressable.
|
|
86
|
-
- The segmented control, the switch and the toggle take one child per position, each with `data-sonic-value`, and hold the chosen one in `value`. They fire `change` only. A switch or a toggle with no children is on or off, held in `checked`.
|
|
87
|
-
- On a position child, `data-sonic-disabled` locks it, `data-sonic-momentary` makes an end position spring back, and on a toggle `data-sonic-lit` lights the well there (`ok`, `warning` or `danger` for the colour). A lock stops the user, not your code: a `value` written to a disabled position is chosen.
|
|
88
|
-
|
|
89
|
-
```html
|
|
90
|
-
<sonic-segmented aria-label="Filter" value="lp">
|
|
91
|
-
<span data-sonic-value="lp">LP</span>
|
|
92
|
-
<span data-sonic-value="bp">BP</span>
|
|
93
|
-
<span data-sonic-value="hp">HP</span>
|
|
94
|
-
</sonic-segmented>
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
## Parts that are only a class
|
|
98
|
-
|
|
99
|
-
The LED, the panel, the ring and the screen have no behaviour, so they are classes on your own elements and need no registration.
|
|
100
|
-
|
|
101
|
-
- `<span class="sonic-led">` is unlit until it carries `data-sonic-lit`, whose value picks the colour: empty, `idle`, `ok`, `warning` or `danger`. Inside a latching button or a toggle's cap it lights with the control.
|
|
102
|
-
- `class="sonic-ring"` draws a lit arc around its one child, from `--sonic-ring-from` to `--sonic-ring-to`, each 0 to 1. Give it your own `role="progressbar"`.
|
|
103
|
-
- `class="sonic-screen"` is a pane of glass for your own text or canvas, and `class="sonic-panel"` groups controls under a `sonic-panel-title`.
|
|
104
|
-
|
|
105
|
-
## Displays
|
|
106
|
-
|
|
107
|
-
A display takes its signal as data and never opens an `AudioContext`.
|
|
108
|
-
|
|
109
|
-
- `<sonic-meter>`: write `level`, a linear peak amplitude, as often as you have one; the meter converts to decibels and applies its `ballistics`. Write `value` instead to show a number in the meter's own units exactly as given. `segments` turns the bar into a ladder. It fires nothing and has no role: read `peak`, call `resetPeak()`, and announce numbers yourself.
|
|
110
|
-
- `<sonic-spectrum>`: set `analyser` to an `AnalyserNode`, or call `push(decibels)` with a `Float32Array` holding one level per bin and set `sample-rate`.
|
|
111
|
-
|
|
112
|
-
## Controls that drive other controls
|
|
113
|
-
|
|
114
|
-
- `<sonic-split>` wraps value controls and keeps their values adding up to `total`. `mode` chooses who gives way (`proportional`, `equal` or `cascade`), and `data-sonic-locked` on a member holds it still. The others move a microtask after the one the user moved, so its `input` arrives first.
|
|
115
|
-
- `<sonic-envelope>` has no value and no events. Each of its attributes (`delay`, `attack`, `hold`, `decay`, `sustain`, `release`, and `attack-curve`, `decay-curve`, `release-curve`) names the id of a dial, a slider or a number box; dragging a handle turns that control, which fires its own `input`. Stages share the width equally, whatever their times.
|
|
116
|
-
- `<sonic-xy>` holds `x` and `y`, each with its own `x-min`, `x-max`, `x-step` and so on, and submits them as `name.x` and `name.y`.
|
|
117
|
-
- `envelopeCurve` and `crossfadeGains` are exported so your DSP computes what the control draws.
|
|
118
|
-
|
|
119
|
-
## Wave controls
|
|
120
|
-
|
|
121
|
-
`<sonic-wavestrip>` is the overview of a track, scrubbed to seek; `<sonic-waveform>` is the close-up that scrolls under a fixed playhead. Both are value controls in seconds that take their data as properties (`peaks`, `markers`); the types list the rest. `peaks` is `{ pairsPerSecond, samples }`, and the full scale follows the array unless `fullScale` is set.
|
|
122
|
-
|
|
123
|
-
A wave control shows and reads a clock (`1:23`, typed or drawn) and speaks a duration in the language around it, the nearest `lang`. Setting `formatValue`, `parseValue` or `formatSpokenValue` replaces that part; `formatClock` and `parseClock` are the same pair, exported so your own time text matches.
|
|
124
|
-
|
|
125
|
-
Their events bubble and carry no detail, so read the property when one fires: `revealed` on `sonic-reveal`, `currentMarker` on `sonic-marker`, and the strip's `hoverValue` on `sonic-hover`, which `clientXOf(value)` turns into a viewport x.
|
|
126
|
-
|
|
127
|
-
The strip draws one part per marker inside `.sonic-wavestrip-markers`, in `start` order: a `.sonic-wavestrip-marker` dot, or a `.sonic-wavestrip-region` when the marker has an `end`. Marker labels are hidden from assistive technology; fold `currentMarker` into `formatSpokenValue` to have one spoken.
|
|
128
|
-
|
|
129
|
-
The waveform parks the current marker's label in its corner and fades it after the next marker crosses the playhead, never before: `--sonic-waveform-label-fade-start` (0) and `--sonic-waveform-label-fade-end` (1) run from that crossing, 0, to the next label reaching the park, 1.
|
|
130
|
-
|
|
131
|
-
The waveform calls `requestPeaks(fromSeconds, toSeconds)` as its window moves, often every frame, so return at once for a span already loaded or on its way. Return a promise, or several, and the waveform repaints as each settles; an `async` function is the wrong shape, since it hands back a new settled promise each call. `pending` is yours to keep: list the spans being fetched, and clear each as it lands.
|
|
132
|
-
|
|
133
|
-
With `zoomable`, a ctrl or cmd wheel, a pinch and the `+` and `-` keys set the waveform's `zoom` and fire `sonic-zoom`; a write to `zoom` fires nothing.
|
|
134
|
-
|
|
135
|
-
With `fill` a control takes its container's height and its ratios follow. `--sonic-wavestrip-marker-size`, `--sonic-wavestrip-groove-size` and `--sonic-waveform-label-font-size` hold an absolute length instead.
|
|
47
|
+
- A control uses only the children it documents and adds one child of its own. Children it draws are copied, so a copy has none of your listeners; keep them static. Under a hydrating framework, register the definitions once hydration is done (in React, from an effect).
|
|
48
|
+
- Events bubble and carry no detail: read the property when one fires. A control knows nothing about playback, so wire its events to your own state and write the property back.
|
|
49
|
+
- A value control fires `input` as its value moves and `change` once when a gesture ends. A drag owns the value: a write to `value` during one is dropped.
|
|
50
|
+
- `formatValue` and `parseValue` are properties holding functions. Once `parseValue` is set it has to read what `formatValue` writes, or typed entry opens on text it cannot take.
|
|
51
|
+
- A display takes its signal as data, and nothing in the library fetches, decodes or opens an `AudioContext`.
|
|
52
|
+
- States a control sets itself are custom states on the host, selected with `:state()`.
|
|
53
|
+
- The LED, the panel, the ring and the screen have no behaviour, so they are classes on your own elements (`sonic-led`, `sonic-panel`, `sonic-ring`, `sonic-screen`). A control draws no glass of its own: wrap one in a screen for the encased look.
|
|
136
54
|
|
|
137
55
|
## Skinning
|
|
138
56
|
|
|
139
|
-
A skin is one rule that sets tokens on a class; copy `dist/skins/flat.css` to start one. Tokens, hook classes (`.sonic-dial
|
|
57
|
+
A skin is one rule that sets tokens on a class; copy `dist/skins/flat.css` to start one. Tokens, hook classes (`.sonic-dial`), `data-sonic-*` attributes and states are the public surface. Anything starting `--_sonic-` is private and changes without notice.
|
|
140
58
|
|
|
141
59
|
- Set tokens, not rules on hook classes. If a look needs a hook-class rule, a token is missing.
|
|
142
|
-
-
|
|
143
|
-
- A length token takes any CSS length except a percentage. For a fluid size use container units.
|
|
144
|
-
- `--sonic-relief` runs from 0, flat, to 1, and scales every bevel and shadow at once. `--sonic-light-tilt` turns the one light they all follow.
|
|
145
|
-
- `--sonic-target-size` widens the region that takes a press past the control's box. It is unset by default.
|
|
60
|
+
- A token ending in `-ratio` is a unitless share of the control's size, so one size token scales the whole control. A length token takes any CSS length except a percentage.
|
|
146
61
|
|
|
147
62
|
## Loading less
|
|
148
63
|
|
|
@@ -159,22 +74,22 @@ A skin is one rule that sets tokens on a class; copy `dist/skins/flat.css` to st
|
|
|
159
74
|
| --------------- | ----------------------------------------------- |
|
|
160
75
|
| `button.css` | core, cap, well, keycap |
|
|
161
76
|
| `dial.css` | core, cap, glass, readout, value, arc, scale |
|
|
162
|
-
| `envelope.css` | core, glass,
|
|
77
|
+
| `envelope.css` | core, glass, readout, bracket |
|
|
163
78
|
| `led.css` | core, lens |
|
|
164
79
|
| `meter.css` | core, lens, groove |
|
|
165
|
-
| `number.css` | core, cap,
|
|
80
|
+
| `number.css` | core, cap, value |
|
|
166
81
|
| `panel.css` | core |
|
|
167
82
|
| `ring.css` | core, arc |
|
|
168
83
|
| `screen.css` | core, glass, pane, well |
|
|
169
84
|
| `segmented.css` | core, cap, well, keycap |
|
|
170
85
|
| `slider.css` | core, cap, glass, readout, value, groove, scale |
|
|
171
|
-
| `spectrum.css` | core
|
|
86
|
+
| `spectrum.css` | core |
|
|
172
87
|
| `switch.css` | core, cap |
|
|
173
88
|
| `toggle.css` | core, cap, well, keycap |
|
|
174
|
-
| `waveform.css` | core, cap, glass,
|
|
175
|
-
| `wavestrip.css` | core, cap, glass,
|
|
176
|
-
| `xy.css` | core, cap, glass,
|
|
89
|
+
| `waveform.css` | core, cap, glass, readout, value |
|
|
90
|
+
| `wavestrip.css` | core, cap, glass, readout, value |
|
|
91
|
+
| `xy.css` | core, cap, glass, readout, bracket |
|
|
177
92
|
|
|
178
|
-
An LED inside a control also needs `led.css`, and a button inside a ring needs `button.css`.
|
|
93
|
+
An LED inside a control also needs `led.css`, and a button inside a ring needs `button.css`. Only a missing `core.css` or control sheet is warned about, so check a control that draws wrong against this table.
|
|
179
94
|
|
|
180
95
|
Targets current Chrome and Edge, Safari 26 and Firefox 147. No control mirrors for right-to-left. MIT licensed.
|
|
@@ -167,7 +167,8 @@ var SonicButton = class extends SonicFormElement {
|
|
|
167
167
|
holding.hold(event.pointerId);
|
|
168
168
|
}, { signal });
|
|
169
169
|
button.addEventListener("keydown", (event) => {
|
|
170
|
-
if (event.repeat ||
|
|
170
|
+
if (event.repeat || event.defaultPrevented) return;
|
|
171
|
+
if (!holdKeys.has(event.key) || !this.#isMomentary()) return;
|
|
171
172
|
holding.hold(event.key);
|
|
172
173
|
}, { signal });
|
|
173
174
|
}
|
|
@@ -58,7 +58,7 @@ var SonicElement = class extends HTMLElement {
|
|
|
58
58
|
}
|
|
59
59
|
if (!this.isDisabled() && !this.#focused()) target.focus(options);
|
|
60
60
|
}
|
|
61
|
-
checkStyles(control, sheet,
|
|
61
|
+
checkStyles(control, sheet, box) {}
|
|
62
62
|
focusTarget() {}
|
|
63
63
|
forwardNaming(target, isNamed) {
|
|
64
64
|
const labels = isNamed ? this.namingLabels() : [];
|
|
@@ -64,6 +64,7 @@ var SonicSplit = class extends SonicElement {
|
|
|
64
64
|
#waiting = /* @__PURE__ */ new Set();
|
|
65
65
|
attributeChangedCallback(name) {
|
|
66
66
|
if ((name === "mode" || name === "total") && this.#signal) this.#bind();
|
|
67
|
+
if (name === "disabled" && this.#signal) this.#limit();
|
|
67
68
|
}
|
|
68
69
|
connectedCallback() {
|
|
69
70
|
this.upgradeProperties("mode", "total");
|
|
@@ -152,11 +153,12 @@ var SonicSplit = class extends SonicElement {
|
|
|
152
153
|
})[index] ?? target;
|
|
153
154
|
}
|
|
154
155
|
#limit() {
|
|
156
|
+
const isFrozen = this.isDisabled();
|
|
155
157
|
const values = this.#members.map(({ seen }) => seen);
|
|
156
158
|
for (const [index, member] of this.#members.entries()) {
|
|
157
159
|
const model = this.#model(values, member);
|
|
158
160
|
const value = values[index] ?? member.seen;
|
|
159
|
-
member.link.model.setLimit(isLocked(member) ? [value, value] : gridLimits(member.link.model.mapping(), splitLimits(model, index, this.#total)));
|
|
161
|
+
member.link.model.setLimit(isFrozen || isLocked(member) ? [value, value] : gridLimits(member.link.model.mapping(), splitLimits(model, index, this.#total)));
|
|
160
162
|
}
|
|
161
163
|
}
|
|
162
164
|
#model(values, mover) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { requireChild, template } from "../lib/render.js";
|
|
2
|
-
import { bindDrag } from "../lib/pointer-drag.js";
|
|
2
|
+
import { bindDrag, capturePointer } from "../lib/pointer-drag.js";
|
|
3
3
|
import { soleStep } from "../lib/positions.js";
|
|
4
4
|
import { isUnder } from "./radio-group.js";
|
|
5
5
|
import { SonicPositionGroupElement } from "./position-group.js";
|
|
@@ -28,7 +28,7 @@ var SonicSwitch = class extends SonicPositionGroupElement {
|
|
|
28
28
|
if (this.#isOnBat(event)) return true;
|
|
29
29
|
const target = this.pressTarget(index);
|
|
30
30
|
if (this.isOptionDisabled(target) || !this.isMomentary(target)) return false;
|
|
31
|
-
this.group
|
|
31
|
+
capturePointer(this.group, event.pointerId);
|
|
32
32
|
this.hold(target, event.pointerId);
|
|
33
33
|
return true;
|
|
34
34
|
}
|
|
@@ -68,8 +68,10 @@ var SonicSwitch = class extends SonicPositionGroupElement {
|
|
|
68
68
|
};
|
|
69
69
|
},
|
|
70
70
|
lift: (press, event) => {
|
|
71
|
-
if (!press.hasMoved && !this.isDisabled()) this.#pressBat(event);
|
|
72
71
|
this.#isClickSwallowed = true;
|
|
72
|
+
if (press.hasMoved || this.isDisabled()) return;
|
|
73
|
+
if (!isUnder(this.group, event.clientX, event.clientY)) return;
|
|
74
|
+
this.#pressBat(event);
|
|
73
75
|
},
|
|
74
76
|
move: (press, event) => {
|
|
75
77
|
if (this.isDisabled()) return;
|
|
@@ -19,6 +19,7 @@ const renderPosition = template(`
|
|
|
19
19
|
</button>
|
|
20
20
|
`, HTMLButtonElement);
|
|
21
21
|
const renderBare = template(`<button class="sonic-toggle-position" role="switch" type="button"></button>`, HTMLButtonElement);
|
|
22
|
+
const pressSlop = 2;
|
|
22
23
|
var SonicToggle = class extends SonicPositionGroupElement {
|
|
23
24
|
bare = renderBare();
|
|
24
25
|
defaultOrientation = "horizontal";
|
|
@@ -60,17 +61,19 @@ var SonicToggle = class extends SonicPositionGroupElement {
|
|
|
60
61
|
bindDrag(this.group, {
|
|
61
62
|
grab: (event) => this.#grab(event),
|
|
62
63
|
lift: (drag, event) => {
|
|
63
|
-
if (drag.
|
|
64
|
+
if (!drag.isPress || drag.isHolding || this.isDisabled()) return;
|
|
64
65
|
if (!isUnder(this.group, event.clientX, event.clientY)) return;
|
|
65
66
|
this.#press(drag.position);
|
|
66
67
|
},
|
|
67
68
|
move: (drag, event) => {
|
|
68
69
|
if (drag.isHolding || this.isDisabled()) return;
|
|
69
70
|
const pointer = this.along(event);
|
|
70
|
-
|
|
71
|
+
const moved = Math.abs(pointer - drag.start);
|
|
72
|
+
if (!drag.hasMoved && moved < drag.thresholdPx) return;
|
|
71
73
|
drag.hasMoved = true;
|
|
72
74
|
this.toggleState("dragging", true);
|
|
73
|
-
|
|
75
|
+
if (moved >= pressSlop * drag.thresholdPx) drag.isPress = false;
|
|
76
|
+
if (this.#slideTo(positionAt(drag.travel, pointer), event.pointerId)) drag.isPress = false;
|
|
74
77
|
},
|
|
75
78
|
release: (drag) => {
|
|
76
79
|
delete drag.marked.dataset.sonicPressed;
|
|
@@ -95,6 +98,7 @@ var SonicToggle = class extends SonicPositionGroupElement {
|
|
|
95
98
|
return {
|
|
96
99
|
hasMoved: false,
|
|
97
100
|
isHolding: this.#holdFrom(position, event.pointerId),
|
|
101
|
+
isPress: true,
|
|
98
102
|
marked,
|
|
99
103
|
position,
|
|
100
104
|
start: this.along(event),
|
|
@@ -124,12 +128,13 @@ var SonicToggle = class extends SonicPositionGroupElement {
|
|
|
124
128
|
const crossed = Math.round(at);
|
|
125
129
|
this.#draggedTo = at;
|
|
126
130
|
this.#placeCap(at);
|
|
127
|
-
if (this.isChecked(crossed)) return;
|
|
131
|
+
if (this.isChecked(crossed)) return false;
|
|
128
132
|
this.releaseHold();
|
|
129
|
-
if (this.isOptionDisabled(crossed) || this.isChecked(crossed)) return;
|
|
133
|
+
if (this.isOptionDisabled(crossed) || this.isChecked(crossed)) return false;
|
|
130
134
|
if (this.isMomentary(crossed)) this.hold(crossed, pointerId);
|
|
131
135
|
else this.select(crossed);
|
|
132
136
|
this.refocusAt(crossed);
|
|
137
|
+
return true;
|
|
133
138
|
}
|
|
134
139
|
#travel(event, isOnCap) {
|
|
135
140
|
const [first, last] = enabledEnds(this.positions());
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
+
import { createDoublePress } from "../lib/double-press.js";
|
|
2
|
+
|
|
1
3
|
//#region src/elements/value-entry.ts
|
|
2
|
-
const doublePressMs = 500;
|
|
3
|
-
const doublePressPx = 4;
|
|
4
|
-
function isDoublePress(previous, next) {
|
|
5
|
-
if (!previous) return false;
|
|
6
|
-
return next.time - previous.time < doublePressMs && Math.hypot(next.x - previous.x, next.y - previous.y) < doublePressPx;
|
|
7
|
-
}
|
|
8
4
|
var ValueEntry = class {
|
|
9
5
|
get isOpen() {
|
|
10
6
|
return this.#isOpen;
|
|
11
7
|
}
|
|
8
|
+
#doublePress = createDoublePress();
|
|
12
9
|
#input;
|
|
13
10
|
#isOpen = false;
|
|
14
|
-
#lastPress;
|
|
15
11
|
#openedText = "";
|
|
16
12
|
#options;
|
|
17
13
|
constructor(input, options) {
|
|
@@ -44,7 +40,7 @@ var ValueEntry = class {
|
|
|
44
40
|
if (isCommitting && this.#input.value !== this.#openedText) this.#options.commit(this.#input.value);
|
|
45
41
|
}
|
|
46
42
|
forgetPress() {
|
|
47
|
-
this.#
|
|
43
|
+
this.#doublePress.forget();
|
|
48
44
|
}
|
|
49
45
|
open() {
|
|
50
46
|
const input = this.#input;
|
|
@@ -57,16 +53,7 @@ var ValueEntry = class {
|
|
|
57
53
|
input.select();
|
|
58
54
|
}
|
|
59
55
|
press(event) {
|
|
60
|
-
|
|
61
|
-
time: event.timeStamp,
|
|
62
|
-
x: event.clientX,
|
|
63
|
-
y: event.clientY
|
|
64
|
-
};
|
|
65
|
-
const previous = this.#lastPress;
|
|
66
|
-
this.#lastPress = press;
|
|
67
|
-
if (!isDoublePress(previous, press)) return false;
|
|
68
|
-
this.#lastPress = void 0;
|
|
69
|
-
return true;
|
|
56
|
+
return this.#doublePress.press(event);
|
|
70
57
|
}
|
|
71
58
|
};
|
|
72
59
|
|
|
@@ -287,6 +287,14 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
287
287
|
}]));
|
|
288
288
|
this.surface()?.rebuild();
|
|
289
289
|
}
|
|
290
|
+
scrubChanged() {
|
|
291
|
+
const isScrubbing = this.scrubState().from !== void 0;
|
|
292
|
+
this.#grabbed = isScrubbing ? this.#grabbed ?? this.#lastDrawn() : void 0;
|
|
293
|
+
super.scrubChanged();
|
|
294
|
+
}
|
|
295
|
+
scrubsKeyRepeat() {
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
290
298
|
#duePending() {
|
|
291
299
|
const due = dueRegions(this.#pending, this.#pendingListedMs, [performance.now(), this.pendingDelay]);
|
|
292
300
|
if (due.length < this.#pending.length) this.surface()?.requestFrame();
|
|
@@ -294,10 +302,7 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
294
302
|
}
|
|
295
303
|
#grab() {
|
|
296
304
|
if (this.#zoomGesture?.isPinching() === true) return void 0;
|
|
297
|
-
const drawn = this.#
|
|
298
|
-
seconds: this.value,
|
|
299
|
-
startSeconds: this.value
|
|
300
|
-
};
|
|
305
|
+
const drawn = this.#lastDrawn();
|
|
301
306
|
this.#grabbed = drawn;
|
|
302
307
|
const mapping = this.mapping();
|
|
303
308
|
const [low, high] = mapping.bounds;
|
|
@@ -317,6 +322,12 @@ var SonicWaveform = class extends SonicWaveElement {
|
|
|
317
322
|
seconds: this.value === from ? grabbed.seconds : this.value
|
|
318
323
|
};
|
|
319
324
|
}
|
|
325
|
+
#lastDrawn() {
|
|
326
|
+
return this.#drawn ?? {
|
|
327
|
+
seconds: this.value,
|
|
328
|
+
startSeconds: this.value
|
|
329
|
+
};
|
|
330
|
+
}
|
|
320
331
|
#readKindColours() {
|
|
321
332
|
const kinds = [...new Set(this.markers.map(({ kind }) => kind).filter((kind) => isKind(kind)))];
|
|
322
333
|
const probes = kinds.map((kind) => {
|
|
@@ -4,6 +4,7 @@ import { SonicFormElement } from "./form-element.js";
|
|
|
4
4
|
import { requireChild, template } from "../lib/render.js";
|
|
5
5
|
import { Readout } from "./readout.js";
|
|
6
6
|
import { ReadoutClaim } from "./readout-claim.js";
|
|
7
|
+
import { createDoublePress } from "../lib/double-press.js";
|
|
7
8
|
import { focusByPointer } from "../lib/focus-by-pointer.js";
|
|
8
9
|
import { isMenuPress, isResetPress } from "../lib/modifier-press.js";
|
|
9
10
|
import { resetKeys } from "../lib/value-mapping.js";
|
|
@@ -66,6 +67,15 @@ var SonicXy = class extends SonicFormElement {
|
|
|
66
67
|
"y-step",
|
|
67
68
|
"y-taper"
|
|
68
69
|
];
|
|
70
|
+
get doublePress() {
|
|
71
|
+
return this.getAttribute("double-press") === "reset" ? "reset" : "none";
|
|
72
|
+
}
|
|
73
|
+
set doublePress(gesture) {
|
|
74
|
+
this.reflect("double-press", gesture);
|
|
75
|
+
}
|
|
76
|
+
get dragging() {
|
|
77
|
+
return this.hasState("dragging");
|
|
78
|
+
}
|
|
69
79
|
get formatValue() {
|
|
70
80
|
return this.#formatValue;
|
|
71
81
|
}
|
|
@@ -82,6 +92,9 @@ var SonicXy = class extends SonicFormElement {
|
|
|
82
92
|
set readout(isEnabled) {
|
|
83
93
|
this.reflect("readout", isEnabled);
|
|
84
94
|
}
|
|
95
|
+
get revealed() {
|
|
96
|
+
return this.hasState("revealed");
|
|
97
|
+
}
|
|
85
98
|
get x() {
|
|
86
99
|
return this.#models.x.value;
|
|
87
100
|
}
|
|
@@ -194,10 +207,12 @@ var SonicXy = class extends SonicFormElement {
|
|
|
194
207
|
this.#renderReadout();
|
|
195
208
|
});
|
|
196
209
|
#currentAxis = "x";
|
|
210
|
+
#doublePress = createDoublePress();
|
|
197
211
|
#xy = renderXy();
|
|
198
212
|
#field = requireChild(this.#xy, ".sonic-xy-field", HTMLDivElement);
|
|
199
213
|
#formatValue;
|
|
200
214
|
#gesture;
|
|
215
|
+
#isRevealed = false;
|
|
201
216
|
#models = {
|
|
202
217
|
x: createValueModel(() => this.#spec("x")),
|
|
203
218
|
y: createValueModel(() => this.#spec("y"))
|
|
@@ -214,10 +229,11 @@ var SonicXy = class extends SonicFormElement {
|
|
|
214
229
|
this.#respec(name);
|
|
215
230
|
}
|
|
216
231
|
connectedCallback() {
|
|
217
|
-
this.upgradeProperties("readout", "xDefault", "xLabel", "xMax", "xMidpoint", "xMin", "xOrigin", "xStep", "xTaper", "yDefault", "yLabel", "yMax", "yMidpoint", "yMin", "yOrigin", "yStep", "yTaper", "formatValue", "x", "y");
|
|
232
|
+
this.upgradeProperties("doublePress", "readout", "xDefault", "xLabel", "xMax", "xMidpoint", "xMin", "xOrigin", "xStep", "xTaper", "yDefault", "yLabel", "yMax", "yMidpoint", "yMin", "yOrigin", "yStep", "yTaper", "formatValue", "x", "y");
|
|
218
233
|
super.connectedCallback();
|
|
219
234
|
}
|
|
220
235
|
formResetCallback() {
|
|
236
|
+
if (this.#gesture?.current()) return;
|
|
221
237
|
this.#respec(...axes);
|
|
222
238
|
}
|
|
223
239
|
connect(signal) {
|
|
@@ -259,23 +275,26 @@ var SonicXy = class extends SonicFormElement {
|
|
|
259
275
|
const move = keyMoves.get(event.key);
|
|
260
276
|
if (!move && !(resetKeys.has(event.key) && this.#hasDefault())) return;
|
|
261
277
|
event.preventDefault();
|
|
262
|
-
if (move) this.#keyTo(...move);
|
|
263
|
-
else this.#reset();
|
|
264
278
|
this.#claim.reveal("keys");
|
|
265
279
|
this.#renderReadout();
|
|
280
|
+
if (move) this.#keyTo(...move);
|
|
281
|
+
else this.#reset();
|
|
266
282
|
}, { signal });
|
|
267
283
|
xy.addEventListener("focusout", (event) => {
|
|
268
284
|
if (event.relatedTarget instanceof Node && xy.contains(event.relatedTarget)) return;
|
|
269
285
|
if (this.#claim.conceal("keys")) this.#renderReadout();
|
|
270
286
|
}, { signal });
|
|
271
287
|
signal.addEventListener("abort", () => {
|
|
272
|
-
this.#claim.conceal("keys");
|
|
288
|
+
if (this.#claim.conceal("keys")) this.#renderReadout();
|
|
273
289
|
}, { once: true });
|
|
274
290
|
}
|
|
275
291
|
#bindPointer(xy, signal) {
|
|
276
292
|
xy.addEventListener("mousedown", (event) => {
|
|
277
293
|
event.preventDefault();
|
|
278
294
|
}, { signal });
|
|
295
|
+
xy.addEventListener("pointercancel", () => {
|
|
296
|
+
this.#doublePress.forget();
|
|
297
|
+
}, { signal });
|
|
279
298
|
this.#gesture = bindFieldGesture(xy, {
|
|
280
299
|
claim: this.#claim,
|
|
281
300
|
grab: (event) => {
|
|
@@ -290,6 +309,10 @@ var SonicXy = class extends SonicFormElement {
|
|
|
290
309
|
input: (_hold, next) => {
|
|
291
310
|
if (!this.#input(next)) this.#renderReadout();
|
|
292
311
|
},
|
|
312
|
+
lift: (_hold, event) => {
|
|
313
|
+
if (!this.#doublePress.press(event) || this.doublePress !== "reset") return;
|
|
314
|
+
this.#reset();
|
|
315
|
+
},
|
|
293
316
|
release: (_hold, moved) => {
|
|
294
317
|
if (moved.length > 0) this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
295
318
|
},
|
|
@@ -393,11 +416,16 @@ var SonicXy = class extends SonicFormElement {
|
|
|
393
416
|
this.writeFormValue(fields, state);
|
|
394
417
|
}
|
|
395
418
|
#renderReadout() {
|
|
419
|
+
const { isRevealed } = this.#claim;
|
|
396
420
|
this.#readout.show({
|
|
397
421
|
anchor: this.#puck,
|
|
398
|
-
isOpen:
|
|
422
|
+
isOpen: isRevealed && this.readout,
|
|
399
423
|
text: this.#valueText("x")
|
|
400
424
|
});
|
|
425
|
+
if (isRevealed === this.#isRevealed) return;
|
|
426
|
+
this.#isRevealed = isRevealed;
|
|
427
|
+
this.toggleState("revealed", isRevealed);
|
|
428
|
+
this.dispatchEvent(new Event("sonic-reveal", { bubbles: true }));
|
|
401
429
|
}
|
|
402
430
|
#reset() {
|
|
403
431
|
this.#commit({
|
|
@@ -89,7 +89,11 @@ var CanvasSurface = class {
|
|
|
89
89
|
#armTransitions() {
|
|
90
90
|
const { canvas, colours, numbers = {} } = this.#options;
|
|
91
91
|
const { style } = canvas;
|
|
92
|
-
const watched = [
|
|
92
|
+
const watched = [
|
|
93
|
+
"color",
|
|
94
|
+
...Object.values(colours),
|
|
95
|
+
...Object.values(numbers)
|
|
96
|
+
];
|
|
93
97
|
style.setProperty("transition-property", watched.join(", "));
|
|
94
98
|
style.setProperty("transition-duration", "0.001ms");
|
|
95
99
|
style.setProperty("transition-timing-function", "step-start");
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/lib/double-press.ts
|
|
2
|
+
const doublePressMs = 500;
|
|
3
|
+
const doublePressPx = 4;
|
|
4
|
+
function isDoublePress(previous, next) {
|
|
5
|
+
if (!previous) return false;
|
|
6
|
+
return next.time - previous.time < doublePressMs && Math.hypot(next.x - previous.x, next.y - previous.y) < doublePressPx;
|
|
7
|
+
}
|
|
8
|
+
function createDoublePress() {
|
|
9
|
+
let last;
|
|
10
|
+
return {
|
|
11
|
+
forget() {
|
|
12
|
+
last = void 0;
|
|
13
|
+
},
|
|
14
|
+
press(event) {
|
|
15
|
+
const press = {
|
|
16
|
+
time: event.timeStamp,
|
|
17
|
+
x: event.clientX,
|
|
18
|
+
y: event.clientY
|
|
19
|
+
};
|
|
20
|
+
const previous = last;
|
|
21
|
+
last = press;
|
|
22
|
+
if (!isDoublePress(previous, press)) return false;
|
|
23
|
+
last = void 0;
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
//#endregion
|
|
30
|
+
export { createDoublePress };
|