@threadlabs/looma 0.39.1 → 0.39.2

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/docs/theme.mjs CHANGED
@@ -49,9 +49,9 @@ const starting = values => ({ schemaVersion: 1, values: { ...defaultRecipe().val
49
49
  export const startingPoints = {
50
50
  clear: defaultRecipe(),
51
51
  bookish: starting({ rhythm: '1.05', action: '#2e1228', canvas: '#f7f7f5', surface: '#fffefd', text: '#0d0c0c', 'body-font': 'Arial, sans-serif', 'display-font': 'Georgia, serif', 'text-weight': '400', 'control-roominess': '2.625', 'scale-contrast': '1.18', 'outer-depth': '1', 'inner-depth': '1', glassiness: '0.8', 'surface-separation': '0.65', translucency: '0.18', 'edge-light': '1', finish: '0.85', glow: '0.3', 'light-spread': '0.3', 'light-strength': '0.9', 'response-time': '150ms', 'response-curve': 'ease' }),
52
- wiki: starting({ rhythm: '0.8', 'text-weight': '400', 'space-curve': '0.8', action: '#4f46e5', canvas: '#ffffff', surface: '#ffffff', text: '#1a1a1a', 'control-roominess': '2.125', spacing: '0.85', 'scale-contrast': '1.15', 'corner-roundness': '6px', 'outer-depth': '0.35', 'inner-depth': '0', 'response-time': '110ms' }),
52
+ wiki: starting({ rhythm: '0.95', 'text-weight': '400', 'space-curve': '0.8', action: '#4f46e5', canvas: '#ffffff', surface: '#ffffff', text: '#1a1a1a', 'control-roominess': '2.125', spacing: '0.95', 'scale-contrast': '1.15', 'corner-roundness': '6px', 'outer-depth': '0.35', 'inner-depth': '0', 'response-time': '110ms' }),
53
53
  editorial: starting({ rhythm: '1.3', 'text-size': '17px', spacing: '1.12', 'space-curve': '1.4', 'scale-contrast': '1.3', action: '#182c24', canvas: '#eee9dc', surface: '#fffdf5', text: '#182c24', 'corner-roundness': '0px', 'outer-depth': '0', 'inner-depth': '0', 'body-font': 'Georgia, serif', 'display-font': 'Georgia, serif', 'text-weight': '400', 'line-weight': '2px', 'response-time': '90ms', 'response-curve': 'linear' }),
54
- night: starting({ rhythm: '0.9', 'display-font': 'ui-monospace, monospace', 'text-weight': '600', 'control-roominess': '2.375', spacing: '0.9', 'scale-contrast': '1.25', 'space-curve': '1.2', finish: '0.6', glow: '0.45', glassiness: '0.45', 'surface-separation': '0.25', 'light-x': '0.25', 'light-spread': '0.65', action: '#a7b8ff', canvas: '#11141c', surface: '#202634', text: '#edf1fb', 'corner-roundness': '12px', 'outer-depth': '1.4', 'inner-depth': '0.45', translucency: '0.2', 'edge-light': '0.65', 'response-time': '210ms', 'response-curve': 'cubic-bezier(0.2,0.8,0.2,1)' }),
54
+ night: starting({ rhythm: '1.05', 'display-font': 'ui-monospace, monospace', 'text-weight': '500', 'control-roominess': '2.375', spacing: '1.05', 'scale-contrast': '1.25', 'space-curve': '1', finish: '0.6', glow: '0.45', glassiness: '0.45', 'surface-separation': '0.25', 'light-x': '0.25', 'light-spread': '0.65', action: '#a7b8ff', canvas: '#11141c', surface: '#202634', text: '#edf1fb', 'corner-roundness': '12px', 'outer-depth': '1.4', 'inner-depth': '0.45', translucency: '0.2', 'edge-light': '0.65', 'response-time': '210ms', 'response-curve': 'cubic-bezier(0.2,0.8,0.2,1)' }),
55
55
  garden: starting({ rhythm: '1.2', 'body-font': 'ui-rounded, system-ui, sans-serif', 'display-font': 'Georgia, serif', 'text-weight': '400', 'space-curve': '1.15', finish: '0.15', 'light-spread': '1', action: '#256342', canvas: '#f4edda', surface: '#fff8e7', text: '#173121', 'corner-roundness': '22px', 'outer-depth': '0.35', 'inner-depth': '0', 'edge-light': '0.2', 'control-roominess': '2.875', 'scale-contrast': '1.24', spacing: '1.05', 'response-time': '180ms', 'response-curve': 'cubic-bezier(0.16,1,0.3,1)' }),
56
56
  };
57
57
  export const startingPointLabels = { clear: 'Clear', bookish: 'Bookish', wiki: 'Wiki', editorial: 'Editorial', night: 'Night', garden: 'Garden' };
@@ -95,24 +95,6 @@ export function paletteFor(input) {
95
95
  return Object.entries(themePalettes).find(([, palette]) => paletteKeys.every(key => values[key].toLowerCase() === palette.values[key].toLowerCase()))?.[0] ?? 'custom';
96
96
  }
97
97
 
98
- export const themeVariations = { warmer: 'Warmer', quieter: 'Quieter', expressive: 'More expressive', compact: 'More compact' };
99
- /** Nearby choices reuse the same bounded semantics; locks protect whole decision families. */
100
- export function nearbyVariation(input, kind, locks = []) {
101
- const recipe = validateRecipe(input);
102
- if (!Object.hasOwn(themeVariations, kind)) throw new Error('Unknown theme variation.');
103
- const deltas = {
104
- warmer: { warmth: .15 }, quieter: { 'color-intensity': -.12, glow: -.1, 'outer-depth': -.15 },
105
- expressive: { finish: .15, 'edge-light': .15, glow: .1 }, compact: { 'control-roominess': -.15, spacing: -.1 },
106
- }[kind];
107
- const values = { ...recipe.values };
108
- for (const [id, delta] of Object.entries(deltas)) {
109
- const field = themeFields.find(item => item.id === id);
110
- const family = field.family === 'Colour' ? 'palette' : field.family === 'Material' ? 'material' : 'type';
111
- if (!locks.includes(family)) values[id] = String(Number(Math.max(field.min, Math.min(field.max, Number(values[id]) + delta)).toFixed(3)));
112
- }
113
- return validateRecipe({ ...recipe, values });
114
- }
115
-
116
98
  const scalar = /^[+]?(?:\d+(?:\.\d*)?|\.\d+)$/;
117
99
  const length = /^(?:0|[+]?(?:\d+(?:\.\d*)?|\.\d+)(?:px|rem|em|ex|ch|cap|ic|lh|rlh|vw|vh|vi|vb|vmin|vmax|svw|svh|lvw|lvh|dvw|dvh|cm|mm|Q|in|pt|pc))$/;
118
100
  const font = /^(?:"[^"\\;{}<>@\x00-\x1f]+"|'[^'\\;{}<>@\x00-\x1f]+'|[\p{L}_-][\p{L}\p{N}_ -]*)(?:\s*,\s*(?:"[^"\\;{}<>@\x00-\x1f]+"|'[^'\\;{}<>@\x00-\x1f]+'|[\p{L}_-][\p{L}\p{N}_ -]*))*$/u;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@threadlabs/looma",
3
- "version": "0.39.1",
3
+ "version": "0.39.2",
4
4
  "type": "module",
5
5
  "description": "Looma components as HTML, Vue, and plain DOM, with design tokens.",
6
6
  "license": "MIT",
@@ -130,6 +130,7 @@
130
130
 
131
131
  :scope:where([data-component~="ui-button"]) {
132
132
  --_button-square-size: var(--ui-button-min-block-size, var(--ui-control-size-md));
133
+ box-sizing: border-box !important;
133
134
  appearance: none;
134
135
  min-block-size: var(--ui-button-min-block-size, var(--ui-control-size-md));
135
136
  border-style: solid;
@@ -176,6 +177,21 @@
176
177
  padding-block: var(--ui-space-0-5) !important;
177
178
  }
178
179
 
180
+ /* Trim the text line itself: an anonymous flex item retains font-metric space in some fonts. */
181
+ .label {
182
+ display: block !important;
183
+ align-content: center !important;
184
+ min-inline-size: 0 !important;
185
+ min-block-size: 1lh !important;
186
+ }
187
+
188
+ .label:has(svg) {
189
+ display: inline-flex !important;
190
+ align-items: center !important;
191
+ gap: var(--ui-space-2) !important;
192
+ text-box-trim: none;
193
+ }
194
+
179
195
  .badge {
180
196
  display: inline-flex !important;
181
197
  align-items: center !important;
@@ -187,7 +203,7 @@
187
203
  }
188
204
 
189
205
  @supports (text-box-trim: trim-both) {
190
- :scope:where([data-component~="ui-button"]) {
206
+ .label:not(:has(svg)) {
191
207
  text-box-trim: trim-both;
192
208
  text-box-edge: cap alphabetic;
193
209
  }
@@ -515,6 +531,11 @@
515
531
  transition: color var(--ui-motion-fast) var(--ui-motion-ease), text-decoration-color var(--ui-motion-fast) var(--ui-motion-ease);
516
532
  }
517
533
 
534
+ :scope:where([data-component~="ui-button"])[data-appearance="link"] .label {
535
+ display: inline !important;
536
+ text-box-trim: none;
537
+ }
538
+
518
539
  :scope:where([data-component~="ui-button"])[data-appearance="link"]:hover:not(:disabled, [aria-disabled="true"]) {
519
540
  color: var(--ui-button-link-hover-text, var(--ui-text-primary));
520
541
  text-decoration-color: currentColor;
@@ -552,6 +573,8 @@
552
573
  /* A count is an adornment, not another line of label text. Keep the standard control height
553
574
  with enough inset for a 24px mark; large content can still grow the button naturally. */
554
575
 
576
+ /* Trim the text line itself: an anonymous flex item retains font-metric space in some fonts. */
577
+
555
578
  @supports (text-box-trim: trim-both) {
556
579
  }
557
580
 
@@ -103,7 +103,8 @@
103
103
 
104
104
  input:where(:not([data-component], [data-slotted]))::after {
105
105
  content: "";
106
- position: absolute;
106
+ position: absolute !important;
107
+ transform: none !important;
107
108
  opacity: 0;
108
109
  transition: opacity var(--ui-motion-fast) var(--ui-motion-ease);
109
110
  }
@@ -116,43 +117,44 @@
116
117
  box-shadow: var(--ui-control-inset);
117
118
  }
118
119
 
119
- /* Centre the tick's box and lift it slightly for the rotated check shape. */
120
+ /* The SVG's painted bounds are centred in its viewBox, including its round stroke.
121
+ Centre that box directly; rotated border boxes do not centre the visible check. */
120
122
  input:checked:where(:not([data-component], [data-slotted]))::after {
121
- inset: 0;
122
- margin: auto !important;
123
- inline-size: calc(var(--_ui-checkbox-size) * 0.28 / 1.125);
124
- block-size: calc(var(--_ui-checkbox-size) * 0.5 / 1.125);
125
- /* Longhands, not `border: solid var(...)`: lowering drops a shorthand that omits a
126
- component, which left the tick with no style and no colour. */
127
- border-style: solid;
128
- border-color: var(--ui-text-on-accent);
129
- border-width: 0 calc(var(--_ui-checkbox-size) * 0.125 / 1.125) calc(var(--_ui-checkbox-size) * 0.125 / 1.125) 0;
123
+ inset: 50% auto auto 50% !important;
124
+ inline-size: calc(var(--_ui-checkbox-size) * 0.7) !important;
125
+ block-size: calc(var(--_ui-checkbox-size) * 0.7) !important;
126
+ background: var(--ui-text-on-accent);
127
+ mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m5 11.5 5 5 9-9'/%3E%3C/svg%3E")
128
+ center / contain no-repeat;
130
129
  opacity: 1;
131
- transform: translateY(calc(var(--_ui-checkbox-size) * -0.09 / 1.125)) rotate(45deg);
130
+ translate: -50% -50% !important;
132
131
  animation: ui-checkbox-check var(--ui-motion-base) var(--ui-motion-ease-out, cubic-bezier(0.16, 1, 0.3, 1)) both;
133
132
  }
134
133
 
135
134
  input:indeterminate:where(:not([data-component], [data-slotted]))::after {
136
- inset: 50% calc(var(--_ui-checkbox-size) * 0.23 / 1.125) auto;
137
- block-size: calc(var(--_ui-checkbox-size) * 0.125 / 1.125);
135
+ mask: none;
136
+ animation: none;
137
+ inset: 50% calc(var(--_ui-checkbox-size) * 0.23 / 1.125) auto !important;
138
+ inline-size: auto !important;
139
+ block-size: calc(var(--_ui-checkbox-size) * 0.125 / 1.125) !important;
138
140
  border-radius: var(--ui-radius-round);
139
141
  background: var(--ui-text-on-accent);
140
142
  opacity: 1;
141
- transform: translateY(-50%);
143
+ translate: 0 -50% !important;
142
144
  }
143
145
 
144
146
  @keyframes ui-checkbox-check {
145
147
  from {
146
148
  opacity: 0;
147
- transform: translateY(calc(var(--_ui-checkbox-size) * -0.09 / 1.125)) rotate(45deg) scale(0.55);
149
+ scale: 0.55;
148
150
  }
149
151
  65% {
150
152
  opacity: 1;
151
- transform: translateY(calc(var(--_ui-checkbox-size) * -0.09 / 1.125)) rotate(45deg) scale(1.12);
153
+ scale: 1.12;
152
154
  }
153
155
  to {
154
156
  opacity: 1;
155
- transform: translateY(calc(var(--_ui-checkbox-size) * -0.09 / 1.125)) rotate(45deg) scale(1);
157
+ scale: 1;
156
158
  }
157
159
  }
158
160
 
@@ -163,6 +165,20 @@
163
165
  }
164
166
  }
165
167
 
168
+ @media (forced-colors: active) {
169
+ input:checked,
170
+ input:indeterminate {
171
+ forced-color-adjust: none;
172
+ background: Highlight;
173
+ border-color: Highlight;
174
+ }
175
+ input:checked:where(:not([data-component], [data-slotted]))::after,
176
+ input:indeterminate:where(:not([data-component], [data-slotted]))::after {
177
+ forced-color-adjust: none;
178
+ background: HighlightText;
179
+ }
180
+ }
181
+
166
182
  input:hover:enabled {
167
183
  border-color: var(--ui-accent-solid);
168
184
  }
@@ -220,11 +236,15 @@
220
236
 
221
237
  /* The label, and under it an optional line saying what the choice means. */
222
238
 
223
- /* Centre the tick's box and lift it slightly for the rotated check shape. */
239
+ /* The SVG's painted bounds are centred in its viewBox, including its round stroke.
240
+ Centre that box directly; rotated border boxes do not centre the visible check. */
224
241
 
225
242
  @media (prefers-reduced-motion: reduce) {
226
243
  }
227
244
 
245
+ @media (forced-colors: active) {
246
+ }
247
+
228
248
  /* A sized choice keeps its box and aligns instead: the label takes that size's field type size,
229
249
  and the first line centres in that control height, so the label shares a row's baseline.
230
250
  Multi-line labels and descriptions still grow downward from that first line. */
@@ -121,3 +121,114 @@
121
121
  @media (forced-colors: active) {
122
122
  }
123
123
  }
124
+ @scope ([data-slotted~="ui-light-editor"]:not([data-component])) to ([data-component], [data-slotted]) {
125
+ :where([data-component~="ui-light-editor"]):where(:scope, :not([data-component], [data-slotted]:not([data-slotted~="ui-light-editor"]))) {
126
+ display: grid !important;
127
+ gap: var(--ui-space-3) !important;
128
+ min-inline-size: 0;
129
+ padding: 0 !important;
130
+ margin: 0 !important;
131
+ }
132
+ .stage:where(:scope, :scope *) {
133
+ box-sizing: border-box !important;
134
+ display: block !important;
135
+ block-size: 13rem;
136
+ padding: calc(var(--ui-control-min-block-size) / 2) !important;
137
+ border: var(--ui-border-width) solid var(--ui-control-border);
138
+ border-radius: var(--ui-field-radius, var(--ui-radius-md));
139
+ background: var(--ui-neutral-surface, var(--ui-surface));
140
+ }
141
+ .plane:where(:scope, :scope *) {
142
+ position: relative;
143
+ display: block !important;
144
+ inline-size: 100%;
145
+ block-size: 100%;
146
+ }
147
+ svg:where(:scope, :scope *) {
148
+ inline-size: 100%;
149
+ block-size: 100%;
150
+ overflow: visible;
151
+ }
152
+ path:where(:scope, :scope *) {
153
+ fill: none;
154
+ stroke: var(--ui-border);
155
+ stroke-width: 1;
156
+ vector-effect: non-scaling-stroke;
157
+ }
158
+ .specimen:where(:scope, :scope *) {
159
+ position: absolute;
160
+ inset: 0;
161
+ display: grid !important;
162
+ place-items: center !important;
163
+ }
164
+ .source:where(:scope, :scope *) {
165
+ position: absolute;
166
+ left: 50%;
167
+ top: 0;
168
+ translate: -50% -50%;
169
+ display: grid !important;
170
+ place-items: center !important;
171
+ inline-size: max(2rem, var(--ui-control-min-block-size));
172
+ block-size: max(2rem, var(--ui-control-min-block-size));
173
+ padding: 0 !important;
174
+ margin: 0 !important;
175
+ border: 0;
176
+ border-radius: 50%;
177
+ background: transparent;
178
+ cursor: grab;
179
+ touch-action: none;
180
+ }
181
+ .source-glyph:where(:scope, :scope *) {
182
+ position: relative;
183
+ z-index: 1;
184
+ display: inline-flex !important;
185
+ color: var(--ui-accent);
186
+ font-size: var(--ui-font-size-lg);
187
+ }
188
+ .source:where(:not([data-component], [data-slotted]:not([data-slotted~="ui-light-editor"]))):where(:scope, :scope *)::after {
189
+ content: "";
190
+ position: absolute;
191
+ inline-size: 2rem;
192
+ block-size: 2rem;
193
+ border: var(--ui-border-width) solid var(--ui-accent);
194
+ border-radius: 50%;
195
+ background: var(--ui-surface-raised);
196
+ box-shadow: 0 0 0 var(--ui-space-2) color-mix(in srgb, var(--ui-accent) 12%, transparent), var(--ui-shadow-sm);
197
+ }
198
+ .source:active:where(:scope, :scope *) {
199
+ cursor: grabbing;
200
+ }
201
+ .source:focus-visible:where(:scope, :scope *) {
202
+ outline: var(--ui-focus-width) solid var(--ui-focus-ring);
203
+ outline-offset: var(--ui-space-1);
204
+ }
205
+ .source:disabled:where(:scope, :scope *) {
206
+ cursor: not-allowed;
207
+ }
208
+ .source:disabled:where(:not([data-component], [data-slotted]:not([data-slotted~="ui-light-editor"]))):where(:scope, :scope *)::after {
209
+ background: var(--ui-disabled-surface);
210
+ border-color: var(--ui-disabled-border);
211
+ box-shadow: none;
212
+ }
213
+ .coordinates:where(:scope, :scope *) {
214
+ display: grid !important;
215
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
216
+ gap: var(--ui-space-2) !important;
217
+ }
218
+ .hint:where(:scope, :scope *) {
219
+ font-size: var(--ui-font-size-sm);
220
+ color: var(--ui-text-secondary);
221
+ }
222
+ @media (forced-colors: active) {
223
+ .source-glyph:where(:scope, :scope *) {
224
+ color: CanvasText;
225
+ }
226
+ .source:where(:not([data-component], [data-slotted]:not([data-slotted~="ui-light-editor"]))):where(:scope, :scope *)::after {
227
+ background: Canvas;
228
+ border-color: CanvasText;
229
+ }
230
+ .source:focus-visible:where(:scope, :scope *) {
231
+ outline-color: Highlight;
232
+ }
233
+ }
234
+ }
package/tokens.css CHANGED
@@ -353,9 +353,12 @@
353
353
  }
354
354
 
355
355
  [data-ui-choice-option]:is(:hover, .active) {
356
- background: var(--ui-surface-muted);
356
+ background: var(--ui-accent-subtle);
357
+ }
358
+
359
+ [data-ui-choice-option]:focus-visible {
357
360
  outline: var(--ui-focus-width) solid var(--ui-focus-ring);
358
- outline-offset: calc(-1 * var(--ui-border-width));
361
+ outline-offset: calc(-1 * var(--ui-focus-width));
359
362
  }
360
363
 
361
364
  [data-ui-choice-option][aria-disabled="true"],
@@ -403,6 +406,10 @@
403
406
  }
404
407
 
405
408
  @media (forced-colors: active) {
409
+ [data-ui-choice-option].active {
410
+ outline: var(--ui-focus-width) solid Highlight;
411
+ outline-offset: calc(-1 * var(--ui-focus-width));
412
+ }
406
413
  [data-ui-choice-option][aria-selected="true"]:not([aria-disabled="true"]):not(.active) {
407
414
  outline: var(--ui-focus-width) solid Highlight;
408
415
  outline-offset: calc(-1 * var(--ui-border-width));
@@ -15,11 +15,11 @@ const T8 = ["",[],4];
15
15
  const T9 = ["",[]];
16
16
  const T10 = ["",[]];
17
17
  const T11 = ["",[],["ui-icon",["name","chevron-right","aria-hidden","true"]]];
18
- const T12 = ["",[],"\n ",1,"\n ",4,"\n ",["span",["class","badge"],4],"\n "];
18
+ const T12 = ["",[],"\n ",1,"\n ",["span",["class","label"],4],"\n ",["span",["class","badge"],4],"\n "];
19
19
  const T13 = ["span",["class","spinner-wrap"],["ui-spinner",["size","sm"]]];
20
20
  const T14 = ["",[]];
21
21
  const T15 = ["",[]];
22
- const T16 = ["",[],"\n ",1,"\n ",4,"\n ",["span",["class","badge"],4],"\n "];
22
+ const T16 = ["",[],"\n ",1,"\n ",["span",["class","label"],4],"\n ",["span",["class","badge"],4],"\n "];
23
23
  const T17 = ["span",["class","spinner-wrap"],["ui-spinner",["size","sm"]]];
24
24
  const T18 = ["",[]];
25
25
  const T19 = ["",[]];
@@ -373,8 +373,9 @@ export function createUiButton(options = {}, html) {
373
373
  n.append((P12 ??= buildTemplate(T12)).cloneNode(true));
374
374
  const t0 = n.firstChild.nextSibling;
375
375
  const t1 = t0.nextSibling.nextSibling.nextSibling;
376
- const t2 = t1.nextSibling.nextSibling.nextSibling.firstChild;
377
- const r = { n, v0: undefined, v1: undefined, v2: undefined, v3: undefined, v4: undefined, v5: undefined, v6: undefined, v7: undefined, v8: undefined, a1: t0, e0: t0.nextSibling, b0: undefined, a2: t1, e1: t1.nextSibling, b1: undefined, f1: undefined, a3: t2, e2: t2.nextSibling, b2: undefined, f2: undefined };
376
+ const t2 = t1.firstChild;
377
+ const t3 = t1.nextSibling.nextSibling.firstChild;
378
+ const r = { n, v0: undefined, v1: undefined, v2: undefined, v3: undefined, v4: undefined, v5: undefined, v6: undefined, v7: undefined, v8: undefined, a1: t0, e0: t0.nextSibling, b0: undefined, a2: t2, e1: t2.nextSibling, b1: undefined, f1: undefined, a3: t3, e2: t3.nextSibling, b2: undefined, f2: undefined };
378
379
  p12(r, -1, d);
379
380
  return r;
380
381
  };
@@ -436,19 +437,20 @@ export function createUiButton(options = {}, html) {
436
437
  if (r.b0) { r.b0 = undefined; clearRegion(r.a1, r.e0); }
437
438
  else { r.b0 = m13(d); r.e0.before(r.b0.n); }
438
439
  } else if (r.b0 !== undefined) p13(r.b0, c, d);
439
- if (r.f2 === undefined) { r.f2 = 1; const at = fillSlot(r.a3, r.e2, "badge", J, false, undefined, d);
440
- if (Array.isArray(at)) (r.z ??= []).push(() => { at[0].l.delete(at[1]); dispose(at[1]); }); else if (at !== undefined) { r.b2 = m15(d); at.before(r.b2.n); } }
441
- else if (r.b2 !== undefined) p15(r.b2, c, d);
442
440
  if (r.f1 === undefined) { r.f1 = 1; const at = fillSlot(r.a2, r.e1, "", J, false, undefined, d);
443
441
  if (Array.isArray(at)) (r.z ??= []).push(() => { at[0].l.delete(at[1]); dispose(at[1]); }); else if (at !== undefined) { r.b1 = m14(d); at.before(r.b1.n); } }
444
442
  else if (r.b1 !== undefined) p14(r.b1, c, d);
443
+ if (r.f2 === undefined) { r.f2 = 1; const at = fillSlot(r.a3, r.e2, "badge", J, false, undefined, d);
444
+ if (Array.isArray(at)) (r.z ??= []).push(() => { at[0].l.delete(at[1]); dispose(at[1]); }); else if (at !== undefined) { r.b2 = m15(d); at.before(r.b2.n); } }
445
+ else if (r.b2 !== undefined) p15(r.b2, c, d);
445
446
  };
446
447
  const m16 = (n, d) => {
447
448
  n.append((P16 ??= buildTemplate(T16)).cloneNode(true));
448
449
  const t0 = n.firstChild.nextSibling;
449
450
  const t1 = t0.nextSibling.nextSibling.nextSibling;
450
- const t2 = t1.nextSibling.nextSibling.nextSibling.firstChild;
451
- const r = { n, v0: undefined, v1: undefined, v2: undefined, v3: undefined, v4: undefined, v5: undefined, v6: undefined, a1: t0, e0: t0.nextSibling, b0: undefined, a2: t1, e1: t1.nextSibling, b1: undefined, f1: undefined, a3: t2, e2: t2.nextSibling, b2: undefined, f2: undefined };
451
+ const t2 = t1.firstChild;
452
+ const t3 = t1.nextSibling.nextSibling.firstChild;
453
+ const r = { n, v0: undefined, v1: undefined, v2: undefined, v3: undefined, v4: undefined, v5: undefined, v6: undefined, a1: t0, e0: t0.nextSibling, b0: undefined, a2: t2, e1: t2.nextSibling, b1: undefined, f1: undefined, a3: t3, e2: t3.nextSibling, b2: undefined, f2: undefined };
452
454
  p16(r, -1, d);
453
455
  return r;
454
456
  };
@@ -496,12 +498,12 @@ export function createUiButton(options = {}, html) {
496
498
  if (r.b0) { r.b0 = undefined; clearRegion(r.a1, r.e0); }
497
499
  else { r.b0 = m17(d); r.e0.before(r.b0.n); }
498
500
  } else if (r.b0 !== undefined) p17(r.b0, c, d);
499
- if (r.f2 === undefined) { r.f2 = 1; const at = fillSlot(r.a3, r.e2, "badge", J, false, undefined, d);
500
- if (Array.isArray(at)) (r.z ??= []).push(() => { at[0].l.delete(at[1]); dispose(at[1]); }); else if (at !== undefined) { r.b2 = m19(d); at.before(r.b2.n); } }
501
- else if (r.b2 !== undefined) p19(r.b2, c, d);
502
501
  if (r.f1 === undefined) { r.f1 = 1; const at = fillSlot(r.a2, r.e1, "", J, false, undefined, d);
503
502
  if (Array.isArray(at)) (r.z ??= []).push(() => { at[0].l.delete(at[1]); dispose(at[1]); }); else if (at !== undefined) { r.b1 = m18(d); at.before(r.b1.n); } }
504
503
  else if (r.b1 !== undefined) p18(r.b1, c, d);
504
+ if (r.f2 === undefined) { r.f2 = 1; const at = fillSlot(r.a3, r.e2, "badge", J, false, undefined, d);
505
+ if (Array.isArray(at)) (r.z ??= []).push(() => { at[0].l.delete(at[1]); dispose(at[1]); }); else if (at !== undefined) { r.b2 = m19(d); at.before(r.b2.n); } }
506
+ else if (r.b2 !== undefined) p19(r.b2, c, d);
505
507
  };
506
508
  const M = [m0, m6, m12, m16], N = [p0, p6, p12, p16];
507
509
  let R;
@@ -3,7 +3,7 @@ import { attachGeneratedController, buildTemplate, NONCONFORMING, textCall, toAt
3
3
  import * as controller from "../components/ui-light-editor/ui-light-editor.js";
4
4
  import "../styles/ui-light-editor.css";
5
5
 
6
- const T0 = ["",[],"\n ",["span",["class","stage","role","group"],"\n ",["span",["class","plane"],"\n ",["svg",["viewBox","0 0 100 60","preserveAspectRatio","none","aria-hidden","true"],["path",["d","M0 30 H100 M50 0 V60"]]],"\n ",["span",["class","specimen"],4],"\n ",["button",["type","button","class","source"],"\n ",["span",["class","source-glyph"],["ui-icon",["name","lightbulb"]]],"\n "],"\n "],"\n "],"\n ",["span",["class","coordinates"],"\n ",["ui-number-input",["data-coordinate","x","min","0","max","100","step","1","unit","%","label","Light horizontal position"]],"\n ",["ui-number-input",["data-coordinate","y","min","0","max","100","step","1","unit","%","label","Light vertical position"]],"\n "],"\n ",["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%."],"\n "];
6
+ const T0 = ["",[],"\n ",["span",["class","stage","role","group"],"\n ",["span",["class","plane"],"\n ",["svg",["viewBox","0 0 100 60","preserveAspectRatio","none","aria-hidden","true"],["path",["d","M0 30 H100 M50 0 V60"]]],"\n ",["span",["class","specimen"],4],"\n ",["button",["type","button","class","source"],"\n ",["span",["class","source-glyph"],["ui-icon",["name","lightbulb"]]],"\n "],"\n "],"\n "],"\n ",["span",["class","hint"],"Drag or tap to move the light. Arrow keys adjust; Escape cancels."],"\n ",["ui-disclosure",["summary","Exact position"],["span",["class","coordinates","data-slotted","ui-light-editor"],"\n ",["ui-number-input",["data-coordinate","x","min","0","max","100","step","1","unit","%","label","Light horizontal position"]],"\n ",["ui-number-input",["data-coordinate","y","min","0","max","100","step","1","unit","%","label","Light vertical position"]],"\n "]],"\n "];
7
7
  const T1 = ["",[]];
8
8
  let P0, P1;
9
9
  const S = { n: ["horizontal","vertical"], t: ["s","s"], f: import.meta.url, g: "ui-light-editor", d: { "input": [detailCheck(checkObject([["x", checkNumber, false], ["y", checkNumber, false]], false)), true, true, false], "change": [detailCheck(checkObject([["x", checkNumber, false], ["y", checkNumber, false]], false)), true, true, false], "cancel": [detailCheck(checkObject([["x", checkNumber, false], ["y", checkNumber, false]], false)), true, true, false] }, x: dispatchDeclared, a: (host, I) => C(host, I), b: generatedInputs, i: ["50", "0"] };
@@ -86,7 +86,7 @@ export function createUiLightEditor(options = {}, html) {
86
86
  const t2 = t1.firstChild.nextSibling.nextSibling.nextSibling;
87
87
  const t3 = t2.firstChild;
88
88
  const t4 = t2.nextSibling.nextSibling;
89
- const t5 = t0.nextSibling.nextSibling;
89
+ const t5 = t0.nextSibling.nextSibling.nextSibling.nextSibling.firstChild;
90
90
  const t6 = t5.firstChild.nextSibling;
91
91
  const t7 = t6.nextSibling.nextSibling;
92
92
  const R = { a0: t0, a3: t4, a4: t6, a5: t7, v0: null, v1: null, v2: null, v3: null, v4: null, v5: null, v6: null, v7: null, v8: null, a2: t3, e0: t3.nextSibling, b0: undefined, f0: undefined };
package/vue/UiButton.vue CHANGED
@@ -904,10 +904,10 @@ useComponentHost(() => import('../components/ui-button/ui-button.js'), {
904
904
  v-if="checkedProps.loading || checkedProps.pending"
905
905
  class="spinner-wrap"
906
906
  data-hn-ui-button
907
- ><UiSpinner class="ui-spinner" size="sm" /></span>{{ ' ' }}<slot />{{ ' ' }}<span
908
- class="badge"
907
+ ><UiSpinner class="ui-spinner" size="sm" /></span>{{ ' ' }}<span
908
+ class="label"
909
909
  data-hn-ui-button
910
- ><slot name="badge" /></span></a>
910
+ ><slot /></span>{{ ' ' }}<span class="badge" data-hn-ui-button><slot name="badge" /></span></a>
911
911
  <button
912
912
  v-else
913
913
  class="ui-button"
@@ -944,10 +944,10 @@ useComponentHost(() => import('../components/ui-button/ui-button.js'), {
944
944
  v-if="checkedProps.loading || checkedProps.pending"
945
945
  class="spinner-wrap"
946
946
  data-hn-ui-button
947
- ><UiSpinner class="ui-spinner" size="sm" /></span>{{ ' ' }}<slot />{{ ' ' }}<span
948
- class="badge"
947
+ ><UiSpinner class="ui-spinner" size="sm" /></span>{{ ' ' }}<span
948
+ class="label"
949
949
  data-hn-ui-button
950
- ><slot name="badge" /></span>
950
+ ><slot /></span>{{ ' ' }}<span class="badge" data-hn-ui-button><slot name="badge" /></span>
951
951
  </button>
952
952
  </template>
953
953
 
@@ -1031,6 +1031,8 @@ useComponentHost(() => import('../components/ui-button/ui-button.js'), {
1031
1031
  /* A count is an adornment, not another line of label text. Keep the standard control height
1032
1032
  with enough inset for a 24px mark; large content can still grow the button naturally. */
1033
1033
 
1034
+ /* Trim the text line itself: an anonymous flex item retains font-metric space in some fonts. */
1035
+
1034
1036
  /* Short numeric and icon actions share a minimum box; large values remain readable. */
1035
1037
 
1036
1038
  /* Card actions inherit Button's palette and behavior; only their shared layout changes.
@@ -1125,6 +1127,7 @@ useComponentHost(() => import('../components/ui-button/ui-button.js'), {
1125
1127
 
1126
1128
  :global(:scope:where([data-hn-ui-button])) {
1127
1129
  --_button-square-size: var(--ui-button-min-block-size, var(--ui-control-size-md));
1130
+ box-sizing: border-box !important;
1128
1131
  appearance: none;
1129
1132
  min-block-size: var(--ui-button-min-block-size, var(--ui-control-size-md));
1130
1133
  border-style: solid;
@@ -1166,6 +1169,19 @@ useComponentHost(() => import('../components/ui-button/ui-button.js'), {
1166
1169
  :global(:scope:not([data-appearance="link"]):has(.badge:not(:empty)):where([data-hn-ui-button])) {
1167
1170
  padding-block: var(--ui-space-0-5) !important;
1168
1171
  }
1172
+ :global(.label:where([data-hn-ui-button])) {
1173
+ display: block !important;
1174
+ align-content: center !important;
1175
+ min-inline-size: 0 !important;
1176
+ min-block-size: 1lh !important;
1177
+ }
1178
+
1179
+ :global(.label:has(svg):where([data-hn-ui-button])) {
1180
+ display: inline-flex !important;
1181
+ align-items: center !important;
1182
+ gap: var(--ui-space-2) !important;
1183
+ text-box-trim: none;
1184
+ }
1169
1185
 
1170
1186
  :global(.badge:where([data-hn-ui-button])) {
1171
1187
  display: inline-flex !important;
@@ -1178,7 +1194,7 @@ useComponentHost(() => import('../components/ui-button/ui-button.js'), {
1178
1194
  }
1179
1195
 
1180
1196
  @supports (text-box-trim: trim-both) {
1181
- :global(:scope:where([data-hn-ui-button])) {
1197
+ :global(.label:not(:has(svg)):where([data-hn-ui-button])) {
1182
1198
  text-box-trim: trim-both;
1183
1199
  text-box-edge: cap alphabetic;
1184
1200
  }
@@ -1499,6 +1515,11 @@ useComponentHost(() => import('../components/ui-button/ui-button.js'), {
1499
1515
  text-decoration-color var(--ui-motion-fast) var(--ui-motion-ease);
1500
1516
  }
1501
1517
 
1518
+ :global(:scope[data-appearance="link"] .label:where([data-hn-ui-button])) {
1519
+ display: inline !important;
1520
+ text-box-trim: none;
1521
+ }
1522
+
1502
1523
  :global(:scope[data-appearance="link"]:hover:not(:disabled, [aria-disabled="true"]):where([data-hn-ui-button])) {
1503
1524
  color: var(--ui-button-link-hover-text, var(--ui-text-primary));
1504
1525
  text-decoration-color: currentColor;
@@ -377,20 +377,21 @@ useComponentHost(() => import('../components/ui-checkbox/ui-checkbox.js'), {
377
377
 
378
378
  /* The label, and under it an optional line saying what the choice means. */
379
379
 
380
- /* Centre the tick's box and lift it slightly for the rotated check shape. */
380
+ /* The SVG's painted bounds are centred in its viewBox, including its round stroke.
381
+ Centre that box directly; rotated border boxes do not centre the visible check. */
381
382
 
382
383
  @keyframes ui-checkbox-check {
383
384
  from {
384
385
  opacity: 0;
385
- transform: translateY(calc(var(--_ui-checkbox-size) * -0.09 / 1.125)) rotate(45deg) scale(0.55);
386
+ scale: 0.55;
386
387
  }
387
388
  65% {
388
389
  opacity: 1;
389
- transform: translateY(calc(var(--_ui-checkbox-size) * -0.09 / 1.125)) rotate(45deg) scale(1.12);
390
+ scale: 1.12;
390
391
  }
391
392
  to {
392
393
  opacity: 1;
393
- transform: translateY(calc(var(--_ui-checkbox-size) * -0.09 / 1.125)) rotate(45deg) scale(1);
394
+ scale: 1;
394
395
  }
395
396
  }
396
397
 
@@ -484,7 +485,8 @@ useComponentHost(() => import('../components/ui-checkbox/ui-checkbox.js'), {
484
485
 
485
486
  :global(input:where([data-hn-ui-checkbox])::after) {
486
487
  content: "";
487
- position: absolute;
488
+ position: absolute !important;
489
+ transform: none !important;
488
490
  opacity: 0;
489
491
  transition: opacity var(--ui-motion-fast) var(--ui-motion-ease);
490
492
  }
@@ -497,29 +499,28 @@ useComponentHost(() => import('../components/ui-checkbox/ui-checkbox.js'), {
497
499
  box-shadow: var(--ui-control-inset);
498
500
  }
499
501
  :global(input:checked:where([data-hn-ui-checkbox])::after) {
500
- inset: 0;
501
- margin: auto !important;
502
- inline-size: calc(var(--_ui-checkbox-size) * 0.28 / 1.125);
503
- block-size: calc(var(--_ui-checkbox-size) * 0.5 / 1.125);
504
- /* Longhands, not `border: solid var(...)`: lowering drops a shorthand that omits a
505
- component, which left the tick with no style and no colour. */
506
- border-style: solid;
507
- border-color: var(--ui-text-on-accent);
508
- border-width: 0 calc(var(--_ui-checkbox-size) * 0.125 / 1.125)
509
- calc(var(--_ui-checkbox-size) * 0.125 / 1.125) 0;
502
+ inset: 50% auto auto 50% !important;
503
+ inline-size: calc(var(--_ui-checkbox-size) * 0.7) !important;
504
+ block-size: calc(var(--_ui-checkbox-size) * 0.7) !important;
505
+ background: var(--ui-text-on-accent);
506
+ mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m5 11.5 5 5 9-9'/%3E%3C/svg%3E")
507
+ center / contain no-repeat;
510
508
  opacity: 1;
511
- transform: translateY(calc(var(--_ui-checkbox-size) * -0.09 / 1.125)) rotate(45deg);
509
+ translate: -50% -50% !important;
512
510
  animation: ui-checkbox-check var(--ui-motion-base)
513
511
  var(--ui-motion-ease-out, cubic-bezier(0.16, 1, 0.3, 1)) both;
514
512
  }
515
513
 
516
514
  :global(input:indeterminate:where([data-hn-ui-checkbox])::after) {
517
- inset: 50% calc(var(--_ui-checkbox-size) * 0.23 / 1.125) auto;
518
- block-size: calc(var(--_ui-checkbox-size) * 0.125 / 1.125);
515
+ mask: none;
516
+ animation: none;
517
+ inset: 50% calc(var(--_ui-checkbox-size) * 0.23 / 1.125) auto !important;
518
+ inline-size: auto !important;
519
+ block-size: calc(var(--_ui-checkbox-size) * 0.125 / 1.125) !important;
519
520
  border-radius: var(--ui-radius-round);
520
521
  background: var(--ui-text-on-accent);
521
522
  opacity: 1;
522
- transform: translateY(-50%);
523
+ translate: 0 -50% !important;
523
524
  }
524
525
 
525
526
  @media (prefers-reduced-motion: reduce) {
@@ -529,6 +530,20 @@ useComponentHost(() => import('../components/ui-checkbox/ui-checkbox.js'), {
529
530
  }
530
531
  }
531
532
 
533
+ @media (forced-colors: active) {
534
+ :global(input:checked:where([data-hn-ui-checkbox])),
535
+ :global(input:indeterminate:where([data-hn-ui-checkbox])) {
536
+ forced-color-adjust: none;
537
+ background: Highlight;
538
+ border-color: Highlight;
539
+ }
540
+ :global(input:checked:where([data-hn-ui-checkbox])::after),
541
+ :global(input:indeterminate:where([data-hn-ui-checkbox])::after) {
542
+ forced-color-adjust: none;
543
+ background: HighlightText;
544
+ }
545
+ }
546
+
532
547
  :global(input:hover:enabled:where([data-hn-ui-checkbox])) {
533
548
  border-color: var(--ui-accent-solid);
534
549
  }