@threadlabs/looma 0.35.1 → 0.37.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 (60) hide show
  1. package/components/ui-avatar/ui-avatar.html +1 -1
  2. package/components/ui-avatar-group/ui-avatar-group.html +1 -1
  3. package/components/ui-button/ui-button.html +11 -4
  4. package/components/ui-color-picker/ui-color-picker.html +1 -1
  5. package/components/ui-combobox/ui-combobox.html +1 -1
  6. package/components/ui-curve-editor/ui-curve-editor.html +1 -0
  7. package/components/ui-curve-editor/ui-curve-editor.js +15 -6
  8. package/components/ui-input/ui-input.html +4 -1
  9. package/components/ui-light-editor/ui-light-editor.html +143 -19
  10. package/components/ui-light-editor/ui-light-editor.js +26 -9
  11. package/components/ui-select/ui-select.html +10 -6
  12. package/dist/index.js +1 -1
  13. package/docs/README.md +33 -7
  14. package/docs/card.js +1 -0
  15. package/docs/catalog.json +39 -5
  16. package/docs/customizer-render.mjs +27 -9
  17. package/docs/customizer.js +146 -18
  18. package/docs/fonts.mjs +45 -0
  19. package/docs/index.d.ts +2 -2
  20. package/docs/palette.mjs +67 -0
  21. package/docs/site.mjs +1 -1
  22. package/docs/theme-session.mjs +28 -9
  23. package/docs/theme.mjs +67 -26
  24. package/docs/value-editors.mjs +6 -3
  25. package/package.json +2 -2
  26. package/styles/ui-avatar-group.css +1 -1
  27. package/styles/ui-avatar.css +1 -1
  28. package/styles/ui-button.css +11 -4
  29. package/styles/ui-color-picker.css +1 -1
  30. package/styles/ui-combobox.css +2 -2
  31. package/styles/ui-input.css +4 -1
  32. package/styles/ui-light-editor.css +111 -16
  33. package/styles/ui-select.css +10 -6
  34. package/theme-system.css +17 -4
  35. package/tokens.css +28 -15
  36. package/vanilla/UiCurveEditor.d.ts +1 -0
  37. package/vanilla/UiCurveEditor.js +1 -1
  38. package/vanilla/UiLightEditor.d.ts +1 -0
  39. package/vanilla/UiLightEditor.js +2 -2
  40. package/vue/UiAvatar.vue +1 -1
  41. package/vue/UiAvatarGroup.vue +1 -1
  42. package/vue/UiButton.vue +4 -2
  43. package/vue/UiColorPicker.vue +1 -1
  44. package/vue/UiCombobox.vue +2 -2
  45. package/vue/UiCurveEditor.d.ts +6 -0
  46. package/vue/UiCurveEditor.vue +2 -0
  47. package/vue/UiInput.vue +4 -1
  48. package/vue/UiLightEditor.d.ts +8 -0
  49. package/vue/UiLightEditor.vue +22 -3
  50. package/vue/UiSelect.vue +1 -1
  51. package/vue/chunks/UiAvatar.js +1 -1
  52. package/vue/chunks/UiAvatarGroup.js +1 -1
  53. package/vue/chunks/UiButton.js +1 -1
  54. package/vue/chunks/UiColorPicker.js +1 -1
  55. package/vue/chunks/UiCombobox.js +1 -1
  56. package/vue/chunks/UiCurveEditor.js +5 -3
  57. package/vue/chunks/UiInput.js +1 -1
  58. package/vue/chunks/UiLightEditor.js +16 -6
  59. package/vue/chunks/UiSelect.js +1 -1
  60. package/vue/components.css +43 -16
@@ -89,7 +89,7 @@
89
89
  inline-size: 1.25rem;
90
90
  block-size: 1.25rem;
91
91
  /* Below the type scale on purpose: two initials need room inside a 1.25rem circle. */
92
- font-size: calc(var(--ui-font-size) * 0.5625);
92
+ font-size: calc(var(--ui-font-size-md) * 0.5625);
93
93
  letter-spacing: 0.02em;
94
94
  }
95
95
 
@@ -92,7 +92,7 @@
92
92
  inline-size: 1.25rem;
93
93
  block-size: 1.25rem;
94
94
  /* Matches the xs Avatar's initials. */
95
- font-size: calc(var(--ui-font-size) * 0.5625);
95
+ font-size: calc(var(--ui-font-size-md) * 0.5625);
96
96
  line-height: 1;
97
97
  }
98
98
 
@@ -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: var(--ui-button-shadow, var(--ui-action-shadow, var(--_ui-action-shadow))), var(--_tone-glow);
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: var(--ui-button-solid-shadow, var(--ui-action-shadow, var(--_ui-action-shadow))), var(--_tone-glow);
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, :host([size="sm"]) {
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 => { if (drag?.id !== event.pointerId) return; drag = undefined; if (!host.props.disabled.value) emit("change"); };
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(); drag = { id: event.pointerId, index }; handle.setPointerCapture(event.pointerId); };
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(--ui-input-min-block-size, var(--_ui-default-input-min-block-size, var(--ui-control-min-block-size)));
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 type="button" class="source" $ref="source" from:disabled="$disabled" from:aria-label="concat($label, '. Arrow keys move the source; Shift moves faster.')"></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 data-coordinate="x" from:value="$horizontal" min="0" max="100" step="1" unit="%" label="Light horizontal position" from:size="$size" from:disabled="$disabled"></ui-number-input>
24
- <ui-number-input data-coordinate="y" from:value="$vertical" min="0" max="100" step="1" unit="%" label="Light vertical position" from:size="$size" from:disabled="$disabled"></ui-number-input>
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 { display: grid !important; gap: var(--ui-space-3) !important; min-inline-size: 0; padding: 0 !important; margin: 0 !important; }
30
- .stage { box-sizing: border-box !important; display: block !important; block-size: 13rem; padding: calc(var(--ui-control-min-block-size) / 2) !important; border: var(--ui-border-width) solid var(--ui-control-border); border-radius: var(--ui-field-radius, var(--ui-radius-md)); background: var(--ui-neutral-surface, var(--ui-surface)); }
31
- .plane { position: relative; display: block !important; inline-size: 100%; block-size: 100%; }
32
- svg { inline-size: 100%; block-size: 100%; overflow: visible; }
33
- path { fill: none; stroke: var(--ui-border); stroke-width: 1; vector-effect: non-scaling-stroke; }
34
- .specimen { position: absolute; inset: 0; display: grid !important; place-items: center !important; }
35
- .source { position: absolute; left: 50%; top: 0; translate: -50% -50%; display: grid !important; place-items: center !important; inline-size: max(2rem, var(--ui-control-min-block-size)); block-size: max(2rem, var(--ui-control-min-block-size)); padding: 0 !important; margin: 0 !important; border: 0; border-radius: 50%; background: transparent; cursor: grab; touch-action: none; }
36
- .source::after { content: ""; inline-size: 1rem; block-size: 1rem; border: var(--ui-border-width) solid var(--ui-accent); border-radius: 50%; background: var(--ui-surface-raised); box-shadow: 0 0 0 var(--ui-space-2) color-mix(in srgb, var(--ui-accent) 12%, transparent), var(--ui-shadow-sm); }
37
- .source:active { cursor: grabbing; }
38
- .source:focus-visible { outline: var(--ui-focus-width) solid var(--ui-focus-ring); outline-offset: var(--ui-space-1); }
39
- .source:disabled { cursor: not-allowed; }
40
- .source:disabled::after { background: var(--ui-disabled-surface); border-color: var(--ui-disabled-border); box-shadow: none; }
41
- .coordinates { display: grid !important; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--ui-space-2) !important; }
42
- .hint { font-size: var(--ui-font-size-sm); color: var(--ui-text-secondary); }
43
- @media (forced-colors: active) { .source::after { background: Canvas; border-color: CanvasText; } .source:focus-visible { outline-color: Highlight; } }
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(); drag = event.pointerId; source.setPointerCapture(drag);
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(event.pointerId)) source.releasePointerCapture(event.pointerId);
29
- if (!host.props.disabled.value) emit('change');
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 !== undefined && source.hasPointerCapture(drag)) source.releasePointerCapture(drag); };
69
+ return () => { stop(); listeners.forEach(cleanup => cleanup()); if (drag && source.hasPointerCapture(drag.id)) source.releasePointerCapture(drag.id); };
53
70
  }
@@ -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: var(
88
- --ui-select-chevron,
89
- 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")
90
- ), var(--_ui-select-highlight);
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: var(--ui-icon-size-sm) var(--ui-icon-size-sm), 100% 100%;
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
  }