@threadlabs/looma 0.39.0 → 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.
@@ -7,7 +7,7 @@ export const themeEvent = 'looma-documentation-theme';
7
7
  export function createThemeSession(view, { base, baseline, systemCSS, historyLimit = 100 }) {
8
8
  const key = `looma:documentation-theme:v1:${base}`;
9
9
  let original = validateRecipe(baseline);
10
- let selected, draft, comparison;
10
+ let selected, draft;
11
11
  const past = [], future = [];
12
12
  let portableCSS = '';
13
13
  let storageAvailable = true;
@@ -17,7 +17,7 @@ export function createThemeSession(view, { base, baseline, systemCSS, historyLim
17
17
  const read = value => { try { return value ? validateRecipe(JSON.parse(value)) : undefined; } catch { return undefined; } };
18
18
  try { selected = read(view.localStorage.getItem(key)); } catch { storageAvailable = false; }
19
19
  const paint = () => {
20
- const displayed = comparison ?? draft ?? selected;
20
+ const displayed = draft ?? selected;
21
21
  portableCSS = displayed ? themeCSS(displayed, systemCSS) : '';
22
22
  view.document.documentElement.toggleAttribute('data-looma-documentation-theme', Boolean(displayed));
23
23
  style.textContent = displayed ? themeCSS(displayed, systemCSS, { scope: 'documentation' }) : '';
@@ -42,22 +42,21 @@ export function createThemeSession(view, { base, baseline, systemCSS, historyLim
42
42
  preview(recipe) { draft = validateRecipe(recipe); paint(); },
43
43
  commit() { if (draft) session.apply(draft); },
44
44
  cancel() { draft = undefined; paint(); },
45
- compare(recipe) { comparison = recipe === undefined ? undefined : validateRecipe(recipe); paint(); },
46
- undo() { if (!past.length) return; future.push(structuredClone(selected)); selected = past.pop(); draft = comparison = undefined; persist(); },
47
- redo() { if (!future.length) return; past.push(structuredClone(selected)); selected = future.pop(); draft = comparison = undefined; persist(); },
45
+ undo() { if (!past.length) return; future.push(structuredClone(selected)); selected = past.pop(); draft = undefined; persist(); },
46
+ redo() { if (!future.length) return; past.push(structuredClone(selected)); selected = future.pop(); draft = undefined; persist(); },
48
47
  apply(recipe) {
49
48
  const next = validateRecipe(recipe);
50
49
  if (JSON.stringify(next) !== JSON.stringify(selected)) remember();
51
- selected = next; draft = comparison = undefined; persist();
50
+ selected = next; draft = undefined; persist();
52
51
  },
53
- reset() { if (selected !== undefined) remember(); selected = draft = comparison = undefined; persist(); return structuredClone(original); },
52
+ reset() { if (selected !== undefined) remember(); selected = draft = undefined; persist(); return structuredClone(original); },
54
53
  baseline(recipe) { original = validateRecipe(recipe); },
55
54
  };
56
55
  view.addEventListener('storage', event => {
57
56
  if (event.key !== key && event.key !== null) return;
58
57
  const next = read(event.newValue);
59
58
  if (event.newValue !== null && !next) return;
60
- selected = next; draft = comparison = undefined; past.length = future.length = 0; paint();
59
+ selected = next; draft = undefined; past.length = future.length = 0; paint();
61
60
  });
62
61
  view.addEventListener('markupress-header-notice-dismiss', () => session.reset());
63
62
  paint();
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.0",
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
+ }
@@ -16,8 +16,20 @@
16
16
  syntax: "*";
17
17
  inherits: false;
18
18
  }
19
+ @property --_ui-skeleton-viewport-x {
20
+ syntax: "<length>";
21
+ inherits: false;
22
+ initial-value: 0px;
23
+ }
24
+ @property --_ui-skeleton-viewport-scale {
25
+ syntax: "<number>";
26
+ inherits: false;
27
+ initial-value: 1;
28
+ }
19
29
 
20
30
  :scope:where([data-component~="ui-skeleton"]) {
31
+ --_ui-skeleton-paint-x: var(--_ui-skeleton-viewport-x);
32
+ --_ui-skeleton-paint-scale: var(--_ui-skeleton-viewport-scale);
21
33
  display: block !important;
22
34
  position: relative;
23
35
  box-sizing: border-box !important;
@@ -59,23 +71,36 @@
59
71
  }
60
72
 
61
73
  :scope:where([data-component~="ui-skeleton"])[data-ui-skeleton-state~="animated"]::before {
62
- animation: ui-skeleton-pulse 1.6s ease-in-out infinite;
63
- }
64
- @keyframes ui-skeleton-pulse {
65
- 50% {
66
- opacity: 0.45;
74
+ background-image: linear-gradient(
75
+ 90deg,
76
+ transparent,
77
+ light-dark(rgb(255 255 255 / 0.6), rgb(255 255 255 / 0.14)) 50%,
78
+ transparent
79
+ );
80
+ background-size: calc(100vw / var(--_ui-skeleton-paint-scale)) 100%;
81
+ background-position: calc(0vw - var(--_ui-skeleton-paint-x)) 0;
82
+ background-repeat: repeat-x;
83
+ animation: ui-skeleton-shimmer 3s linear infinite;
84
+ }
85
+ @keyframes ui-skeleton-shimmer {
86
+ to {
87
+ background-position: calc(100vw / var(--_ui-skeleton-paint-scale) - var(--_ui-skeleton-paint-x)) 0;
67
88
  }
68
89
  }
69
90
  @media (prefers-reduced-motion: reduce) {
70
91
  :scope:where([data-component~="ui-skeleton"])[data-ui-skeleton-state~="animated"]::before {
71
92
  animation: none;
93
+ background-image: none;
72
94
  }
73
95
  }
74
96
  @media (forced-colors: active) {
75
97
  :scope:where([data-component~="ui-skeleton"])::before {
76
98
  background: CanvasText;
77
99
  opacity: 0.2;
100
+ }
101
+ :scope:where([data-component~="ui-skeleton"])[data-ui-skeleton-state~="animated"]::before {
78
102
  animation: none;
103
+ background-image: none;
79
104
  }
80
105
  }
81
106
  :scope:where([data-component~="ui-skeleton"])[hidden] {
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 };
@@ -1,9 +1,11 @@
1
1
  // Generated by HTML Next 1.0.0-alpha.69 for Vanilla DOM. Do not edit.
2
- import { attachGeneratedController, toText, writeAttribute, formatOf, keywordFormat, hostState, acceptProps, manageProps, generatedInputs, checkBoolean, checkFormat, checkString, defineComponent } from "@nextwebwg/html-next/generated-runtime";
2
+ import { attachGeneratedController, toText, writeAttribute, formatOf, keywordFormat, hostState, acceptProps, manageProps, generatedInputs, checkBoolean, checkFormat, checkString, defineComponent, activateModule } from "@nextwebwg/html-next/generated-runtime";
3
+ import * as controller from "../components/ui-skeleton/ui-skeleton.js";
3
4
  import "../styles/ui-skeleton.css";
4
5
 
5
- const S = { n: [], t: [], f: import.meta.url, g: "ui-skeleton", b: generatedInputs };
6
- const D = { g: "ui-skeleton", props: { "animated": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "height": { "required":false,"default":"", c: checkString, t: "string", j: 0, m: "s" }, "shape": { "required":false,"values":["text","circle","rectangle"],"default":"text", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat)] }, "width": { "required":false,"default":"", c: checkString, t: "string", j: 0, m: "s" } } };
6
+ const S = { n: [], t: [], f: import.meta.url, g: "ui-skeleton", a: (host, I) => C(host, I), b: generatedInputs };
7
+ const D = { g: "ui-skeleton", props: { "animated": { "required":false,"default":true, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "height": { "required":false,"default":"", c: checkString, t: "string", j: 0, m: "s" }, "shape": { "required":false,"values":["text","circle","rectangle"],"default":"text", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat)] }, "width": { "required":false,"default":"", c: checkString, t: "string", j: 0, m: "s" } } };
8
+ const C = (host) => activateModule(controller, host);
7
9
 
8
10
  export function createUiSkeleton(options = {}, html) {
9
11
  const { attributes = {}, children = [], slots = {}, ...componentProps } = options;