@threadlabs/looma 0.35.0 → 0.36.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/components/ui-button/ui-button.html +11 -4
- package/components/ui-color-picker/ui-color-picker.html +1 -1
- package/components/ui-combobox/ui-combobox.html +1 -1
- package/components/ui-curve-editor/ui-curve-editor.html +1 -0
- package/components/ui-curve-editor/ui-curve-editor.js +15 -6
- package/components/ui-input/ui-input.html +4 -1
- package/components/ui-light-editor/ui-light-editor.html +143 -19
- package/components/ui-light-editor/ui-light-editor.js +26 -9
- package/components/ui-reel/ui-reel.html +2 -1
- package/components/ui-select/ui-select.html +10 -6
- package/components/ui-tabs/ui-tabs.html +2 -3
- package/components/ui-tabs/ui-tabs.js +0 -9
- package/components/ui-tooltip/ui-tooltip.html +2 -1
- package/dist/index.js +1 -1
- package/docs/README.md +36 -8
- package/docs/card.html +7 -6
- package/docs/card.js +1 -0
- package/docs/catalog-view.mjs +2 -1
- package/docs/catalog.json +46 -12
- package/docs/customizer-render.mjs +38 -18
- package/docs/customizer.js +128 -21
- package/docs/fonts.mjs +45 -0
- package/docs/index.d.ts +2 -2
- package/docs/palette.mjs +67 -0
- package/docs/site.mjs +5 -4
- package/docs/theme-session.mjs +28 -9
- package/docs/theme.mjs +69 -27
- package/docs/value-editors.mjs +6 -4
- package/package.json +2 -2
- package/styles/ui-button.css +11 -4
- package/styles/ui-color-picker.css +1 -1
- package/styles/ui-combobox.css +2 -2
- package/styles/ui-input.css +4 -1
- package/styles/ui-light-editor.css +111 -16
- package/styles/ui-select.css +10 -6
- package/styles/ui-tabs.css +2 -3
- package/styles/ui-tooltip.css +2 -1
- package/theme-system.css +14 -2
- package/tokens.css +11 -1
- package/vanilla/UiCurveEditor.d.ts +1 -0
- package/vanilla/UiCurveEditor.js +1 -1
- package/vanilla/UiLightEditor.d.ts +1 -0
- package/vanilla/UiLightEditor.js +2 -2
- package/vue/UiButton.vue +4 -2
- package/vue/UiColorPicker.vue +1 -1
- package/vue/UiCombobox.vue +2 -2
- package/vue/UiCurveEditor.d.ts +6 -0
- package/vue/UiCurveEditor.vue +2 -0
- package/vue/UiInput.vue +4 -1
- package/vue/UiLightEditor.d.ts +8 -0
- package/vue/UiLightEditor.vue +22 -3
- package/vue/UiReel.vue +1 -1
- package/vue/UiSelect.vue +1 -1
- package/vue/UiTabs.vue +2 -3
- package/vue/UiTooltip.vue +2 -1
- package/vue/chunks/UiButton.js +1 -1
- package/vue/chunks/UiColorPicker.js +1 -1
- package/vue/chunks/UiCombobox.js +1 -1
- package/vue/chunks/UiCurveEditor.js +5 -3
- package/vue/chunks/UiInput.js +1 -1
- package/vue/chunks/UiLightEditor.js +16 -6
- package/vue/chunks/UiReel.js +2 -2
- package/vue/chunks/UiSelect.js +1 -1
- package/vue/chunks/UiTabs.js +1 -1
- package/vue/chunks/UiTooltip.js +1 -1
- package/vue/components.css +47 -19
|
@@ -249,7 +249,9 @@
|
|
|
249
249
|
line-height: var(--ui-line-height-tight);
|
|
250
250
|
padding: var(--ui-control-inset-block-md, var(--ui-space-2)) var(--ui-action-inset-md, var(--ui-space-3)) !important;
|
|
251
251
|
background-image: var(--ui-surface-highlight, none);
|
|
252
|
-
box-shadow:
|
|
252
|
+
box-shadow:
|
|
253
|
+
var(--ui-button-shadow, var(--ui-action-shadow, var(--_ui-action-shadow))),
|
|
254
|
+
var(--_tone-glow);
|
|
253
255
|
cursor: pointer;
|
|
254
256
|
transition:
|
|
255
257
|
scale var(--ui-motion-press, var(--ui-motion-fast)) var(--ui-motion-ease),
|
|
@@ -382,7 +384,9 @@
|
|
|
382
384
|
background-color: var(--_tone);
|
|
383
385
|
color: var(--_on-tone);
|
|
384
386
|
background-image: var(--_tone-highlight);
|
|
385
|
-
box-shadow:
|
|
387
|
+
box-shadow:
|
|
388
|
+
var(--ui-button-solid-shadow, var(--ui-action-shadow, var(--_ui-action-shadow))),
|
|
389
|
+
var(--_tone-glow);
|
|
386
390
|
}
|
|
387
391
|
|
|
388
392
|
:is(:host[data-appearance="solid"], :host[data-appearance="danger"]):hover:not(:disabled, [aria-disabled="true"]) {
|
|
@@ -552,8 +556,11 @@
|
|
|
552
556
|
|
|
553
557
|
/* The size states outrank :host, so sm is named too, or a small button would stay small. */
|
|
554
558
|
@media (pointer: coarse) {
|
|
555
|
-
:host
|
|
556
|
-
min-block-size: var(--ui-control-min-block-size);
|
|
559
|
+
:host {
|
|
560
|
+
min-block-size: max(var(--ui-control-size-md), var(--ui-control-min-block-size));
|
|
561
|
+
}
|
|
562
|
+
:host([size="sm"]) {
|
|
563
|
+
min-block-size: max(var(--ui-control-size-sm), var(--ui-control-min-block-size));
|
|
557
564
|
}
|
|
558
565
|
}
|
|
559
566
|
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
--_size: max(var(--ui-control-size-md), var(--ui-control-min-block-size));
|
|
128
128
|
}
|
|
129
129
|
:host([size="sm"]) {
|
|
130
|
-
--_size: var(--ui-control-min-block-size);
|
|
130
|
+
--_size: max(var(--ui-control-size-sm), var(--ui-control-min-block-size));
|
|
131
131
|
--_font: var(--ui-font-size-md);
|
|
132
132
|
}
|
|
133
133
|
}
|
|
@@ -882,7 +882,7 @@
|
|
|
882
882
|
/* Under touch a small field is still a touch-sized target, as a small button is, and keeps
|
|
883
883
|
body-size text: below 16px, iOS zooms the page into the focused field. */
|
|
884
884
|
:host([size="sm"]) {
|
|
885
|
-
--_row: var(--ui-control-min-block-size);
|
|
885
|
+
--_row: max(var(--ui-control-size-sm), var(--ui-control-min-block-size));
|
|
886
886
|
}
|
|
887
887
|
:host([size="sm"]) input {
|
|
888
888
|
font-size: inherit;
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
<prop name="disabled" type="boolean" default="false">Prevents typing, stepping and pointer or keyboard changes while keeping the value visible.</prop>
|
|
14
14
|
<event name="input" type="object"><prop name="value" type="string" required></prop></event>
|
|
15
15
|
<event name="change" type="object"><prop name="value" type="string" required></prop></event>
|
|
16
|
+
<event name="cancel" type="object"><prop name="value" type="string" required></prop></event>
|
|
16
17
|
</defs>
|
|
17
18
|
<span>
|
|
18
19
|
<span class="plot-frame" from:aria-label="$label" role="group"><span class="plot" $ref="plot">
|
|
@@ -24,21 +24,30 @@ export function connect(host) {
|
|
|
24
24
|
if (!drag || event.pointerId !== drag.id || host.props.disabled.value) return;
|
|
25
25
|
const rect = plot.getBoundingClientRect();
|
|
26
26
|
if (!rect.width || !rect.height) return;
|
|
27
|
-
points[drag.index] = (event.clientX - rect.left) / rect.width;
|
|
28
|
-
points[drag.index + 1] = 2 - (event.clientY - rect.top) / rect.height * 3;
|
|
27
|
+
points[drag.index] = (event.clientX - rect.left - drag.offsetX) / rect.width;
|
|
28
|
+
points[drag.index + 1] = 2 - (event.clientY - rect.top - drag.offsetY) / rect.height * 3;
|
|
29
29
|
paint(); emit("input");
|
|
30
30
|
};
|
|
31
|
-
const finish = event => {
|
|
31
|
+
const finish = event => {
|
|
32
|
+
if (drag?.id !== event.pointerId) return;
|
|
33
|
+
const start = drag; drag = undefined;
|
|
34
|
+
if (start.handle.hasPointerCapture(start.id)) start.handle.releasePointerCapture(start.id);
|
|
35
|
+
if (event.type !== 'pointerup' || host.props.disabled.value) { points = start.points; paint(); emit('input'); emit('cancel'); }
|
|
36
|
+
else emit('change');
|
|
37
|
+
};
|
|
32
38
|
const cleanups = [];
|
|
33
39
|
for (const [index, handle] of [[0, first], [2, second]]) {
|
|
34
|
-
const down = event => { if (host.props.disabled.value || event.button !== 0) return; event.preventDefault(); handle.focus();
|
|
40
|
+
const down = event => { if (host.props.disabled.value || event.button !== 0) return; event.preventDefault(); handle.focus(); const rect = plot.getBoundingClientRect();
|
|
41
|
+
drag = { id: event.pointerId, index, handle, points: [...points], offsetX: event.clientX - rect.left - points[index] * rect.width, offsetY: event.clientY - rect.top - (2 - points[index + 1]) / 3 * rect.height };
|
|
42
|
+
handle.setPointerCapture(event.pointerId); };
|
|
35
43
|
const key = event => {
|
|
44
|
+
if (event.key === 'Escape' && drag) { event.preventDefault(); finish({ pointerId: drag.id, type: 'cancel' }); return; }
|
|
36
45
|
if (host.props.disabled.value || !["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(event.key)) return;
|
|
37
46
|
event.preventDefault(); const step = event.shiftKey ? .1 : .01;
|
|
38
47
|
points[index + (event.key === "ArrowUp" || event.key === "ArrowDown" ? 1 : 0)] += (event.key === "ArrowLeft" || event.key === "ArrowDown" ? -step : step);
|
|
39
48
|
paint(); emit("input"); emit("change");
|
|
40
49
|
};
|
|
41
|
-
for (const [type, callback] of [["pointerdown", down], ["pointermove", move], ["pointerup", finish], ["pointercancel", finish], ["keydown", key]]) {
|
|
50
|
+
for (const [type, callback] of [["pointerdown", down], ["pointermove", move], ["pointerup", finish], ["pointercancel", finish], ["lostpointercapture", finish], ["keydown", key]]) {
|
|
42
51
|
handle.addEventListener(type, callback); cleanups.push(() => handle.removeEventListener(type, callback));
|
|
43
52
|
}
|
|
44
53
|
}
|
|
@@ -47,5 +56,5 @@ export function connect(host) {
|
|
|
47
56
|
for (const type of ["input", "change"]) { field.addEventListener(type, edit); cleanups.push(() => field.removeEventListener(type, edit)); }
|
|
48
57
|
});
|
|
49
58
|
const stop = host.effect(() => { const value = host.props.value.value; if (value === external) return; external = value; try { points = curvePoints(value); } catch { /* Keep the last valid curve. */ } paint(); });
|
|
50
|
-
return () => { stop(); cleanups.forEach(cleanup => cleanup()); };
|
|
59
|
+
return () => { stop(); cleanups.forEach(cleanup => cleanup()); if (drag?.handle.hasPointerCapture(drag.id)) drag.handle.releasePointerCapture(drag.id); };
|
|
51
60
|
}
|
|
@@ -150,7 +150,10 @@
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
:host([size="sm"]) {
|
|
153
|
-
min-block-size: var(
|
|
153
|
+
min-block-size: var(
|
|
154
|
+
--ui-input-min-block-size,
|
|
155
|
+
var(--_ui-default-input-min-block-size, max(var(--ui-control-size-sm), var(--ui-control-min-block-size)))
|
|
156
|
+
);
|
|
154
157
|
}
|
|
155
158
|
}
|
|
156
159
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
<link rel="component" href="../ui-icon/ui-icon.html">
|
|
1
2
|
<link rel="component" href="../ui-number-input/ui-number-input.html">
|
|
2
3
|
<template component="ui-light-editor" module="./ui-light-editor.js">
|
|
3
4
|
<defs>
|
|
@@ -10,36 +11,159 @@
|
|
|
10
11
|
<prop name="disabled" type="boolean" default="false">Prevents pointer, keyboard and exact edits.</prop>
|
|
11
12
|
<event name="input" type="object"><prop name="x" type="number" required></prop><prop name="y" type="number" required></prop></event>
|
|
12
13
|
<event name="change" type="object"><prop name="x" type="number" required></prop><prop name="y" type="number" required></prop></event>
|
|
14
|
+
<event name="cancel" type="object"><prop name="x" type="number" required></prop><prop name="y" type="number" required></prop></event>
|
|
13
15
|
</defs>
|
|
14
16
|
<span>
|
|
15
17
|
<span class="stage" from:aria-label="$label" role="group">
|
|
16
18
|
<span class="plane" $ref="plane">
|
|
17
19
|
<svg viewBox="0 0 100 60" preserveAspectRatio="none" aria-hidden="true"><path d="M0 30 H100 M50 0 V60"></path></svg>
|
|
18
20
|
<span class="specimen"><slot></slot></span>
|
|
19
|
-
<button
|
|
21
|
+
<button
|
|
22
|
+
type="button"
|
|
23
|
+
class="source"
|
|
24
|
+
$ref="source"
|
|
25
|
+
from:disabled="$disabled"
|
|
26
|
+
from:aria-label="concat($label, '. Arrow keys move the source; Shift moves faster.')"
|
|
27
|
+
>
|
|
28
|
+
<span class="source-glyph"><ui-icon name="lightbulb"></ui-icon></span>
|
|
29
|
+
</button>
|
|
20
30
|
</span>
|
|
21
31
|
</span>
|
|
22
32
|
<span class="coordinates">
|
|
23
|
-
<ui-number-input
|
|
24
|
-
|
|
33
|
+
<ui-number-input
|
|
34
|
+
data-coordinate="x"
|
|
35
|
+
from:value="$horizontal"
|
|
36
|
+
min="0"
|
|
37
|
+
max="100"
|
|
38
|
+
step="1"
|
|
39
|
+
unit="%"
|
|
40
|
+
label="Light horizontal position"
|
|
41
|
+
from:size="$size"
|
|
42
|
+
from:disabled="$disabled"
|
|
43
|
+
></ui-number-input>
|
|
44
|
+
<ui-number-input
|
|
45
|
+
data-coordinate="y"
|
|
46
|
+
from:value="$vertical"
|
|
47
|
+
min="0"
|
|
48
|
+
max="100"
|
|
49
|
+
step="1"
|
|
50
|
+
unit="%"
|
|
51
|
+
label="Light vertical position"
|
|
52
|
+
from:size="$size"
|
|
53
|
+
from:disabled="$disabled"
|
|
54
|
+
></ui-number-input>
|
|
25
55
|
</span>
|
|
26
|
-
<span class="hint">Drag the light, use arrow keys, or enter its position. Top centre is 50%, 0%.</span>
|
|
56
|
+
<span class="hint">Drag the light, tap the background, use arrow keys, or enter its position. Escape cancels a drag. Top centre is 50%, 0%.</span>
|
|
27
57
|
</span>
|
|
28
58
|
<style>
|
|
29
|
-
:host {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
59
|
+
:host {
|
|
60
|
+
display: grid !important;
|
|
61
|
+
gap: var(--ui-space-3) !important;
|
|
62
|
+
min-inline-size: 0;
|
|
63
|
+
padding: 0 !important;
|
|
64
|
+
margin: 0 !important;
|
|
65
|
+
}
|
|
66
|
+
.stage {
|
|
67
|
+
box-sizing: border-box !important;
|
|
68
|
+
display: block !important;
|
|
69
|
+
block-size: 13rem;
|
|
70
|
+
padding: calc(var(--ui-control-min-block-size) / 2) !important;
|
|
71
|
+
border: var(--ui-border-width) solid var(--ui-control-border);
|
|
72
|
+
border-radius: var(--ui-field-radius, var(--ui-radius-md));
|
|
73
|
+
background: var(--ui-neutral-surface, var(--ui-surface));
|
|
74
|
+
}
|
|
75
|
+
.plane {
|
|
76
|
+
position: relative;
|
|
77
|
+
display: block !important;
|
|
78
|
+
inline-size: 100%;
|
|
79
|
+
block-size: 100%;
|
|
80
|
+
}
|
|
81
|
+
svg {
|
|
82
|
+
inline-size: 100%;
|
|
83
|
+
block-size: 100%;
|
|
84
|
+
overflow: visible;
|
|
85
|
+
}
|
|
86
|
+
path {
|
|
87
|
+
fill: none;
|
|
88
|
+
stroke: var(--ui-border);
|
|
89
|
+
stroke-width: 1;
|
|
90
|
+
vector-effect: non-scaling-stroke;
|
|
91
|
+
}
|
|
92
|
+
.specimen {
|
|
93
|
+
position: absolute;
|
|
94
|
+
inset: 0;
|
|
95
|
+
display: grid !important;
|
|
96
|
+
place-items: center !important;
|
|
97
|
+
}
|
|
98
|
+
.source {
|
|
99
|
+
position: absolute;
|
|
100
|
+
left: 50%;
|
|
101
|
+
top: 0;
|
|
102
|
+
translate: -50% -50%;
|
|
103
|
+
display: grid !important;
|
|
104
|
+
place-items: center !important;
|
|
105
|
+
inline-size: max(2rem, var(--ui-control-min-block-size));
|
|
106
|
+
block-size: max(2rem, var(--ui-control-min-block-size));
|
|
107
|
+
padding: 0 !important;
|
|
108
|
+
margin: 0 !important;
|
|
109
|
+
border: 0;
|
|
110
|
+
border-radius: 50%;
|
|
111
|
+
background: transparent;
|
|
112
|
+
cursor: grab;
|
|
113
|
+
touch-action: none;
|
|
114
|
+
}
|
|
115
|
+
.source-glyph {
|
|
116
|
+
position: relative;
|
|
117
|
+
z-index: 1;
|
|
118
|
+
display: inline-flex !important;
|
|
119
|
+
color: var(--ui-accent);
|
|
120
|
+
font-size: var(--ui-font-size-lg);
|
|
121
|
+
}
|
|
122
|
+
.source::after {
|
|
123
|
+
content: "";
|
|
124
|
+
position: absolute;
|
|
125
|
+
inline-size: 2rem;
|
|
126
|
+
block-size: 2rem;
|
|
127
|
+
border: var(--ui-border-width) solid var(--ui-accent);
|
|
128
|
+
border-radius: 50%;
|
|
129
|
+
background: var(--ui-surface-raised);
|
|
130
|
+
box-shadow: 0 0 0 var(--ui-space-2) color-mix(in srgb, var(--ui-accent) 12%, transparent), var(--ui-shadow-sm);
|
|
131
|
+
}
|
|
132
|
+
.source:active {
|
|
133
|
+
cursor: grabbing;
|
|
134
|
+
}
|
|
135
|
+
.source:focus-visible {
|
|
136
|
+
outline: var(--ui-focus-width) solid var(--ui-focus-ring);
|
|
137
|
+
outline-offset: var(--ui-space-1);
|
|
138
|
+
}
|
|
139
|
+
.source:disabled {
|
|
140
|
+
cursor: not-allowed;
|
|
141
|
+
}
|
|
142
|
+
.source:disabled::after {
|
|
143
|
+
background: var(--ui-disabled-surface);
|
|
144
|
+
border-color: var(--ui-disabled-border);
|
|
145
|
+
box-shadow: none;
|
|
146
|
+
}
|
|
147
|
+
.coordinates {
|
|
148
|
+
display: grid !important;
|
|
149
|
+
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
|
150
|
+
gap: var(--ui-space-2) !important;
|
|
151
|
+
}
|
|
152
|
+
.hint {
|
|
153
|
+
font-size: var(--ui-font-size-sm);
|
|
154
|
+
color: var(--ui-text-secondary);
|
|
155
|
+
}
|
|
156
|
+
@media (forced-colors: active) {
|
|
157
|
+
.source-glyph {
|
|
158
|
+
color: CanvasText;
|
|
159
|
+
}
|
|
160
|
+
.source::after {
|
|
161
|
+
background: Canvas;
|
|
162
|
+
border-color: CanvasText;
|
|
163
|
+
}
|
|
164
|
+
.source:focus-visible {
|
|
165
|
+
outline-color: Highlight;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
44
168
|
</style>
|
|
45
169
|
</template>
|
|
@@ -2,7 +2,7 @@ const coordinate = value => Number.isFinite(Number(value)) ? Math.round(Math.max
|
|
|
2
2
|
|
|
3
3
|
export function connect(host) {
|
|
4
4
|
const { plane, source } = host.refs;
|
|
5
|
-
let x = coordinate(host.props.x.value), y = coordinate(host.props.y.value), drag;
|
|
5
|
+
let x = coordinate(host.props.x.value), y = coordinate(host.props.y.value), drag, suppressClick = false;
|
|
6
6
|
const listeners = [];
|
|
7
7
|
const listen = (element, type, fn) => { element.addEventListener(type, fn); listeners.push(() => element.removeEventListener(type, fn)); };
|
|
8
8
|
const paint = () => {
|
|
@@ -14,21 +14,38 @@ export function connect(host) {
|
|
|
14
14
|
const emit = type => host.dispatch(type, { x, y });
|
|
15
15
|
listen(source, 'pointerdown', event => {
|
|
16
16
|
if (host.props.disabled.value || event.button !== 0) return;
|
|
17
|
-
event.preventDefault(); source.focus();
|
|
17
|
+
event.preventDefault(); source.focus();
|
|
18
|
+
const rect = plane.getBoundingClientRect();
|
|
19
|
+
drag = { id: event.pointerId, x, y, offsetX: event.clientX - rect.left - x * rect.width, offsetY: event.clientY - rect.top - y * rect.height };
|
|
20
|
+
source.setPointerCapture(drag.id);
|
|
18
21
|
});
|
|
19
22
|
listen(source, 'pointermove', event => {
|
|
20
|
-
if (drag !== event.pointerId || host.props.disabled.value) return;
|
|
23
|
+
if (drag?.id !== event.pointerId || host.props.disabled.value) return;
|
|
21
24
|
const rect = plane.getBoundingClientRect(); if (!rect.width || !rect.height) return;
|
|
22
|
-
x = (event.clientX - rect.left) / rect.width; y = (event.clientY - rect.top) / rect.height;
|
|
25
|
+
x = (event.clientX - rect.left - drag.offsetX) / rect.width; y = (event.clientY - rect.top - drag.offsetY) / rect.height;
|
|
23
26
|
paint(); emit('input');
|
|
24
27
|
});
|
|
25
28
|
const end = event => {
|
|
26
|
-
if (drag !== event.pointerId) return;
|
|
27
|
-
drag = undefined;
|
|
28
|
-
if (source.hasPointerCapture(
|
|
29
|
-
if (
|
|
29
|
+
if (drag?.id !== event.pointerId) return;
|
|
30
|
+
const start = drag; drag = undefined; suppressClick = true;
|
|
31
|
+
if (source.hasPointerCapture(start.id)) source.releasePointerCapture(start.id);
|
|
32
|
+
if (event.type !== 'pointerup' || host.props.disabled.value) {
|
|
33
|
+
x = start.x; y = start.y; paint(); emit('input'); emit('cancel');
|
|
34
|
+
} else if (!host.props.disabled.value) emit('change');
|
|
30
35
|
};
|
|
31
36
|
listen(source, 'pointerup', end); listen(source, 'pointercancel', end); listen(source, 'lostpointercapture', end);
|
|
37
|
+
listen(source, 'keydown', event => {
|
|
38
|
+
if (event.key === 'Escape' && drag) { event.preventDefault(); end({ pointerId: drag.id, type: 'cancel' }); }
|
|
39
|
+
});
|
|
40
|
+
// A background tap is deliberate placement; scrolling and specimen controls keep native gestures.
|
|
41
|
+
listen(plane, 'pointerdown', () => { suppressClick = false; });
|
|
42
|
+
listen(plane, 'click', event => {
|
|
43
|
+
if (suppressClick) { suppressClick = false; return; }
|
|
44
|
+
if (host.props.disabled.value || event.target.closest('button,input,select,textarea,a,[role="button"]')) return;
|
|
45
|
+
const rect = plane.getBoundingClientRect(); if (!rect.width || !rect.height) return;
|
|
46
|
+
x = (event.clientX - rect.left) / rect.width; y = (event.clientY - rect.top) / rect.height;
|
|
47
|
+
paint(); emit('input'); emit('change');
|
|
48
|
+
});
|
|
32
49
|
listen(source, 'keydown', event => {
|
|
33
50
|
if (host.props.disabled.value || !['ArrowLeft','ArrowRight','ArrowUp','ArrowDown','Home','End'].includes(event.key)) return;
|
|
34
51
|
event.preventDefault(); const step = event.shiftKey ? .1 : .01;
|
|
@@ -49,5 +66,5 @@ export function connect(host) {
|
|
|
49
66
|
});
|
|
50
67
|
}
|
|
51
68
|
const stop = host.effect(() => { x = coordinate(host.props.x.value); y = coordinate(host.props.y.value); paint(); });
|
|
52
|
-
return () => { stop(); listeners.forEach(cleanup => cleanup()); if (drag
|
|
69
|
+
return () => { stop(); listeners.forEach(cleanup => cleanup()); if (drag && source.hasPointerCapture(drag.id)) source.releasePointerCapture(drag.id); };
|
|
53
70
|
}
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
scrolls sideways once they overflow.</prop>
|
|
9
9
|
<prop name="snap" type="keyword" values="start, center"
|
|
10
10
|
>Snaps scrolling to items: start comes to rest with an item's start edge at the reel's start, and center with an item centred in the reel. Unset, the reel
|
|
11
|
-
scrolls freely
|
|
11
|
+
scrolls freely. Wrap a component item in a plain element when snapping: the wrapper receives snap alignment while the component keeps ownership of its
|
|
12
|
+
root styles.</prop>
|
|
12
13
|
</defs>
|
|
13
14
|
<div role="region" tabindex="0"><slot></slot></div>
|
|
14
15
|
<style>
|
|
@@ -84,10 +84,12 @@
|
|
|
84
84
|
--_ui-select-highlight: var(--ui-surface-highlight, linear-gradient(transparent, transparent));
|
|
85
85
|
appearance: none;
|
|
86
86
|
padding-inline-end: calc(var(--ui-space-3) + 1.5rem) !important;
|
|
87
|
-
background-image:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
background-image:
|
|
88
|
+
var(
|
|
89
|
+
--ui-select-chevron,
|
|
90
|
+
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235c5c58' stroke-width='2.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E")
|
|
91
|
+
),
|
|
92
|
+
var(--_ui-select-highlight);
|
|
91
93
|
background-repeat: no-repeat;
|
|
92
94
|
background-position: right var(--ui-space-3) center, center;
|
|
93
95
|
background-size: 1.125rem 1.125rem, 100% 100%;
|
|
@@ -134,7 +136,9 @@
|
|
|
134
136
|
padding: var(--ui-control-inset-block-sm, var(--ui-space-1)) var(--ui-space-2) !important;
|
|
135
137
|
padding-inline-end: calc(var(--ui-space-2) + var(--ui-icon-size-sm) + var(--ui-space-1)) !important;
|
|
136
138
|
background-position: right var(--ui-space-2) center, center;
|
|
137
|
-
background-size:
|
|
139
|
+
background-size:
|
|
140
|
+
var(--ui-icon-size-sm) var(--ui-icon-size-sm),
|
|
141
|
+
100% 100%;
|
|
138
142
|
font-size: var(--ui-font-size-sm);
|
|
139
143
|
}
|
|
140
144
|
|
|
@@ -154,7 +158,7 @@
|
|
|
154
158
|
}
|
|
155
159
|
|
|
156
160
|
:host([size="sm"]) {
|
|
157
|
-
min-block-size: var(--ui-control-min-block-size);
|
|
161
|
+
min-block-size: max(var(--ui-control-size-sm), var(--ui-control-min-block-size));
|
|
158
162
|
font-size: inherit;
|
|
159
163
|
}
|
|
160
164
|
}
|
|
@@ -98,10 +98,9 @@
|
|
|
98
98
|
max-inline-size: 100%;
|
|
99
99
|
/* Tabs that do not fit scroll sideways rather than wrapping or squeezing. */
|
|
100
100
|
overflow-x: auto;
|
|
101
|
-
/*
|
|
102
|
-
strip or the page vertically. */
|
|
101
|
+
/* Contain sideways overscroll without trapping vertical page scrolling. */
|
|
103
102
|
overflow-y: hidden;
|
|
104
|
-
overscroll-behavior: contain;
|
|
103
|
+
overscroll-behavior-inline: contain;
|
|
105
104
|
scrollbar-width: thin;
|
|
106
105
|
border-block-end: var(--ui-border-width) solid var(--ui-border-divider);
|
|
107
106
|
}
|
|
@@ -122,17 +122,9 @@ export function connect(host) {
|
|
|
122
122
|
if (list.contains(event.relatedTarget)) return;
|
|
123
123
|
for (const button of tabButtons()) button.tabIndex = button.value === host.state.internalValue ? 0 : -1;
|
|
124
124
|
};
|
|
125
|
-
// A vertical mouse wheel over an overflowing strip scrolls it sideways instead of the page.
|
|
126
|
-
// Trackpad gestures already carry a horizontal delta and are left to the browser.
|
|
127
|
-
const onWheel = (event) => {
|
|
128
|
-
if (list.scrollWidth <= list.clientWidth || event.deltaX !== 0 || event.ctrlKey) return;
|
|
129
|
-
event.preventDefault();
|
|
130
|
-
list.scrollLeft += event.deltaY;
|
|
131
|
-
};
|
|
132
125
|
list.addEventListener("click", onClick);
|
|
133
126
|
list.addEventListener("keydown", onKeydown);
|
|
134
127
|
list.addEventListener("focusout", onFocusout);
|
|
135
|
-
list.addEventListener("wheel", onWheel, { passive: false });
|
|
136
128
|
const observer = new MutationObserver(readPanels);
|
|
137
129
|
observer.observe(container, { childList: true });
|
|
138
130
|
const tabObserver = new MutationObserver(syncButtons);
|
|
@@ -153,6 +145,5 @@ export function connect(host) {
|
|
|
153
145
|
list.removeEventListener("click", onClick);
|
|
154
146
|
list.removeEventListener("keydown", onKeydown);
|
|
155
147
|
list.removeEventListener("focusout", onFocusout);
|
|
156
|
-
list.removeEventListener("wheel", onWheel);
|
|
157
148
|
};
|
|
158
149
|
}
|
|
@@ -238,7 +238,8 @@
|
|
|
238
238
|
border-inline-end-width: var(--ui-border-width);
|
|
239
239
|
border-block-end-width: var(--ui-border-width);
|
|
240
240
|
border-end-end-radius: var(--ui-border-width);
|
|
241
|
-
|
|
241
|
+
/* Stretch in screen axes after rotation so both arrow shoulders stay level. */
|
|
242
|
+
transform: scale(1.25, 0.85) rotate(45deg);
|
|
242
243
|
}
|
|
243
244
|
|
|
244
245
|
:host[data-ui-actual-placement="bottom"] .surface::after {
|