@threadlabs/looma 0.26.1 → 0.26.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,12 +7,13 @@
7
7
  >Which colour the badge speaks in. neutral is a quiet grey for plain labels; accent draws the eye in the accent colour; info, success, warning, and danger
8
8
  mark a status, so match the tone to what the badge says. For a category rather than a status, set the --ui-badge-color hook on the badge instead: any CSS
9
9
  colour, which overrides the tone.</prop>
10
- <prop name="shape" type="keyword" values="pill, tag, dot, square" default="pill"
10
+ <prop name="shape" type="keyword" values="pill, tag, dot, square, circle" default="pill"
11
11
  >Silhouette. pill is rounded at both ends; tag is a pointed label. dot is a compact circular signal with visually hidden text: include a state label, or set
12
12
  aria-hidden when the surrounding control already names that state. square is a fixed rounded square for an icon or single glyph beside a heading; hide a
13
- decorative icon when adjacent words name it.</prop>
13
+ decorative icon when adjacent words name it. circle frames a visible single glyph or icon in a fixed circle, centered in both directions.</prop>
14
14
  <prop name="size" type="keyword" values="xs, md" default="md"
15
- >Scale. xs is a compact count beside small controls; md is the usual text badge. A square is 24px at xs and 32px at md with default spacing.</prop>
15
+ >Scale. xs is a compact count beside small controls; md is the usual text badge. A square is 24px at xs and 32px at md with default spacing. A circle is
16
+ 16px at xs and 24px at md.</prop>
16
17
  </defs>
17
18
  <span>
18
19
  <span class="icon" aria-hidden="true"><slot name="icon"></slot></span>
@@ -28,6 +29,10 @@
28
29
  syntax: "*";
29
30
  inherits: false;
30
31
  }
32
+ @property --ui-badge-circle-size {
33
+ syntax: "*";
34
+ inherits: false;
35
+ }
31
36
  @property --ui-badge-dot-size {
32
37
  syntax: "*";
33
38
  inherits: false;
@@ -318,6 +323,40 @@
318
323
  display: none;
319
324
  }
320
325
 
326
+ /* A visible glyph needs fixed geometry, unlike a content-sized pill or a hidden-label dot. */
327
+ :host([shape="circle"]) {
328
+ --_badge-circle-size: var(--ui-badge-circle-size, calc(var(--ui-space-3) * 2));
329
+ display: inline-flex;
330
+ flex: none;
331
+ inline-size: var(--_badge-circle-size);
332
+ block-size: var(--_badge-circle-size);
333
+ min-inline-size: var(--_badge-circle-size);
334
+ min-block-size: var(--_badge-circle-size);
335
+ padding: 0;
336
+ border-radius: var(--ui-radius-round);
337
+ text-box-trim: none;
338
+ }
339
+
340
+ :host([shape="circle"][size="xs"]) {
341
+ --_badge-circle-size: var(--ui-badge-circle-size, var(--ui-space-4));
342
+ }
343
+
344
+ :host([shape="circle"]) .label {
345
+ display: inline-flex;
346
+ align-items: center;
347
+ justify-content: center;
348
+ line-height: 1;
349
+ text-box-trim: none;
350
+ }
351
+
352
+ :host([shape="circle"]) .label :slotted(svg) {
353
+ margin-inline-end: 0;
354
+ }
355
+
356
+ :host([shape="circle"]) .label:empty {
357
+ display: none;
358
+ }
359
+
321
360
  /* A dot uses the badge's tone recipe; only its silhouette and visible label change. */
322
361
  :host([shape="dot"]) {
323
362
  position: relative;
@@ -1,5 +1,8 @@
1
1
  <template component="ui-container">
2
2
  <defs>
3
+ <prop name="fill" type="boolean" default="false"
4
+ >Keeps the readable width while filling available layout space. Uses a flex column so scrolling children can shrink inside a bounded flex or grid parent.
5
+ Omit it to keep the usual block layout.</prop>
3
6
  <prop name="measure" type="keyword" values="narrow, wide"
4
7
  >The container's maximum width, in characters of the current font: narrow is 45ch, wide is 80ch, and unset is 65ch, a comfortable reading line. The
5
8
  container centres itself when its parent is wider. The --ui-container-measure custom property, set on the container itself, overrides it; set on an
@@ -32,6 +35,14 @@
32
35
  :host([measure="narrow"]) {
33
36
  --_container-measure: 45ch;
34
37
  }
38
+ :host([fill]) {
39
+ box-sizing: border-box;
40
+ display: flex;
41
+ flex-direction: column;
42
+ flex: 1;
43
+ min-inline-size: 0;
44
+ min-block-size: 0;
45
+ }
35
46
  :host([measure="wide"]) {
36
47
  --_container-measure: 80ch;
37
48
  }
@@ -17,6 +17,9 @@
17
17
  <prop name="variant" type="keyword" values="ghost, outline, solid" default="ghost"
18
18
  >Emphasis. ghost shows only the icon until it is hovered, for toolbars and dense rows; outline adds a border on the default surface; solid fills with the
19
19
  accent colour, for the one primary action in a group.</prop>
20
+ <prop name="tone" type="keyword" values="accent, neutral"
21
+ >Colour for every variant. When omitted, ghost and outline keep their neutral appearance and solid keeps its accent fill. Set accent for a coloured action
22
+ or neutral for a quiet one. Native aria-pressed="true" keeps an outline toggle visibly pressed; the consumer owns that state.</prop>
20
23
  <prop name="anticipatory" type="boolean" default="false"
21
24
  >Keeps the button out of sight until it is wanted: at rest a fine pointer sees only a small guide dot, and the button appears on hover, on focus, or when
22
25
  the pointer comes near inside a ui-affordance-scope. Touch devices and the solid variant always show it.</prop>
@@ -121,6 +124,21 @@
121
124
  color: var(--ui-text-secondary);
122
125
  }
123
126
 
127
+ :host([tone="accent"]) {
128
+ --_icon-button-border: var(--ui-accent-solid);
129
+ --_icon-button-hover-border: var(--ui-accent-hover);
130
+ --_icon-button-text: var(--ui-accent-subtle-text, var(--ui-accent-solid));
131
+ --_icon-button-outline-surface: color-mix(in srgb, var(--ui-accent-solid) 5%, var(--ui-surface-default));
132
+ color: var(--_icon-button-text);
133
+ }
134
+
135
+ /* Omitted tone retains the existing accent solid; explicit neutral is additive. */
136
+ :host([tone="neutral"]) {
137
+ --_icon-button-solid-surface: var(--ui-text-primary);
138
+ --_icon-button-solid-hover-surface: var(--ui-text-primary);
139
+ --_icon-button-solid-text: var(--ui-surface-default);
140
+ }
141
+
124
142
  :host([anticipatory]) {
125
143
  position: relative;
126
144
  }
@@ -153,13 +171,13 @@
153
171
  }
154
172
 
155
173
  @media (hover: hover) and (pointer: fine) {
156
- :host([anticipatory]):not(:host([variant="solid"]), :host([loading]), :host([pending]), :disabled, [data-ui-proximity="near"], :hover, :focus-visible) {
174
+ :host([anticipatory]):not(:host([variant="solid"]), :host([loading]), :host([pending]), :disabled, [aria-pressed="true"], [data-ui-proximity="near"], :hover, :focus-visible) {
157
175
  border-color: transparent;
158
176
  background-color: transparent;
159
177
  box-shadow: none;
160
178
  }
161
179
 
162
- :host([anticipatory]):not(:host([variant="solid"]), :host([loading]), :host([pending]), :disabled, [data-ui-proximity="near"], :hover, :focus-visible)
180
+ :host([anticipatory]):not(:host([variant="solid"]), :host([loading]), :host([pending]), :disabled, [aria-pressed="true"], [data-ui-proximity="near"], :hover, :focus-visible)
163
181
  .content {
164
182
  opacity: 0;
165
183
  transform: scale(0.65);
@@ -167,7 +185,7 @@
167
185
  }
168
186
  }
169
187
 
170
- :host([anticipatory]):is(:host([variant="solid"]), [data-ui-proximity="near"], :hover, :focus-visible)::before {
188
+ :host([anticipatory]):is(:host([variant="solid"]), [aria-pressed="true"], [data-ui-proximity="near"], :hover, :focus-visible)::before {
171
189
  opacity: 0;
172
190
  }
173
191
 
@@ -277,17 +295,17 @@
277
295
  }
278
296
 
279
297
  :host([variant="outline"]) {
280
- border-color: var(--ui-icon-button-border, var(--ui-control-border, var(--ui-border-strong)));
281
- background-color: var(--ui-icon-button-outline-surface, var(--ui-surface-default));
298
+ border-color: var(--ui-icon-button-border, var(--_icon-button-border, var(--ui-control-border, var(--ui-border-strong))));
299
+ background-color: var(--ui-icon-button-outline-surface, var(--_icon-button-outline-surface, var(--ui-surface-default)));
282
300
  background-image: var(--ui-raised-highlight);
283
- color: var(--ui-icon-button-outline-text, var(--ui-text-primary));
301
+ color: var(--ui-icon-button-outline-text, var(--_icon-button-text, var(--ui-text-primary)));
284
302
  box-shadow: var(--ui-raised);
285
303
  }
286
304
 
287
305
  :host([variant="solid"]) {
288
- background-color: var(--ui-icon-button-solid-surface, var(--ui-accent-solid));
306
+ background-color: var(--ui-icon-button-solid-surface, var(--_icon-button-solid-surface, var(--ui-accent-solid)));
289
307
  background-image: var(--ui-raised-highlight);
290
- color: var(--ui-icon-button-solid-text, var(--ui-text-on-accent));
308
+ color: var(--ui-icon-button-solid-text, var(--_icon-button-solid-text, var(--ui-text-on-accent)));
291
309
  box-shadow: var(
292
310
  --ui-icon-button-solid-shadow,
293
311
  0 1px 2px color-mix(in srgb, var(--ui-accent-solid) 18%, transparent)
@@ -297,20 +315,32 @@
297
315
  :host:hover:enabled:not([aria-disabled="true"]) {
298
316
  /* Derived from the text colour so the ghost hover shows on any background. */
299
317
  background-color: var(--ui-icon-button-hover-surface, color-mix(in srgb, currentColor 8%, transparent));
300
- color: var(--ui-icon-button-hover-text, var(--ui-text-primary));
318
+ color: var(--ui-icon-button-hover-text, var(--_icon-button-text, var(--ui-text-primary)));
301
319
  }
302
320
 
303
321
  :host([variant="outline"]):hover:enabled:not([aria-disabled="true"]) {
304
- border-color: var(--ui-icon-button-hover-border, var(--ui-control-border-hover, var(--ui-control-border)));
322
+ border-color: var(--ui-icon-button-hover-border, var(--_icon-button-hover-border, var(--ui-control-border-hover, var(--ui-control-border))));
305
323
  }
306
324
 
307
325
  :host([variant="solid"]):hover:enabled:not([aria-disabled="true"]) {
308
- background-color: var(--ui-icon-button-solid-hover-surface, var(--ui-accent-hover));
326
+ background-color: var(--ui-icon-button-solid-hover-surface, var(--_icon-button-solid-hover-surface, var(--ui-accent-hover)));
327
+ }
328
+
329
+ :host([variant="solid"][tone="accent"]):hover:enabled:not([aria-disabled="true"]),
330
+ :host([variant="solid"][tone="neutral"]):hover:enabled:not([aria-disabled="true"]) {
331
+ color: var(--ui-icon-button-solid-text, var(--_icon-button-solid-text, var(--ui-text-on-accent)));
309
332
  }
310
333
 
311
334
  :host([variant="ghost"])[aria-expanded="true"]:enabled:not([aria-disabled="true"]) {
312
335
  background-color: var(--ui-icon-button-active-surface, color-mix(in srgb, currentColor 16%, transparent));
313
- color: var(--ui-icon-button-hover-text, var(--ui-text-primary));
336
+ color: var(--ui-icon-button-hover-text, var(--_icon-button-text, var(--ui-text-primary)));
337
+ box-shadow: var(--ui-pressed);
338
+ }
339
+
340
+ /* Native toggle state persists after pointer release, including during loading. */
341
+ :host([variant="outline"])[aria-pressed="true"]:enabled {
342
+ background-color: var(--ui-icon-button-active-surface, color-mix(in srgb, currentColor 12%, var(--ui-surface-default)));
343
+ background-image: none;
314
344
  box-shadow: var(--ui-pressed);
315
345
  }
316
346
 
@@ -343,6 +373,22 @@
343
373
  box-shadow: var(--ui-focus-halo);
344
374
  }
345
375
 
376
+ :host([variant="outline"])[aria-pressed="true"]:enabled:focus-visible {
377
+ box-shadow:
378
+ var(--ui-pressed),
379
+ var(--ui-focus-halo);
380
+ }
381
+
382
+ @media (forced-colors: active) {
383
+ :host([variant="outline"])[aria-pressed="true"]:enabled {
384
+ outline: var(--ui-focus-width) solid Highlight;
385
+ outline-offset: calc(-2 * var(--ui-focus-width));
386
+ }
387
+ :host([variant="outline"])[aria-pressed="true"]:enabled:focus-visible {
388
+ outline-offset: var(--ui-focus-width);
389
+ }
390
+ }
391
+
346
392
  /* Disabled is a flat neutral control, independent of the active variant's tone. */
347
393
  :host:disabled {
348
394
  border-color: var(--ui-disabled-border);