@threadlabs/looma 0.13.6 → 0.14.1

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 (80) hide show
  1. package/components/ui-button/ui-button.html +2 -1
  2. package/components/ui-checkbox/ui-checkbox.html +35 -0
  3. package/components/ui-combobox/ui-combobox.html +38 -3
  4. package/components/ui-description-list/ui-description-list.html +54 -9
  5. package/components/ui-input/ui-input.html +27 -0
  6. package/components/ui-input-group/ui-input-group.html +15 -0
  7. package/components/ui-radio/ui-radio.html +35 -0
  8. package/components/ui-select/ui-select.html +29 -3
  9. package/components/ui-switch/ui-switch.html +36 -0
  10. package/components/ui-table/ui-table.html +131 -0
  11. package/components/ui-table/ui-table.js +55 -0
  12. package/components/ui-textarea/ui-textarea.html +20 -0
  13. package/dist/index.js +8 -7
  14. package/package.json +1 -1
  15. package/styles/ui-button.css +3 -1
  16. package/styles/ui-checkbox.css +38 -0
  17. package/styles/ui-combobox.css +38 -1
  18. package/styles/ui-description-list.css +52 -7
  19. package/styles/ui-input-group.css +20 -0
  20. package/styles/ui-input.css +29 -0
  21. package/styles/ui-radio.css +38 -0
  22. package/styles/ui-select.css +29 -3
  23. package/styles/ui-switch.css +41 -0
  24. package/styles/ui-table.css +134 -0
  25. package/styles/ui-textarea.css +21 -0
  26. package/vanilla/UiCheckbox.d.ts +1 -0
  27. package/vanilla/UiCheckbox.js +1 -1
  28. package/vanilla/UiCombobox.d.ts +1 -1
  29. package/vanilla/UiCombobox.js +1 -1
  30. package/vanilla/UiDescriptionList.d.ts +3 -1
  31. package/vanilla/UiDescriptionList.js +6 -2
  32. package/vanilla/UiInput.d.ts +1 -0
  33. package/vanilla/UiInput.js +1 -1
  34. package/vanilla/UiRadio.d.ts +1 -0
  35. package/vanilla/UiRadio.js +1 -1
  36. package/vanilla/UiSelect.d.ts +1 -0
  37. package/vanilla/UiSelect.js +1 -1
  38. package/vanilla/UiSwitch.d.ts +1 -0
  39. package/vanilla/UiSwitch.js +1 -1
  40. package/vanilla/UiTable.d.ts +15 -0
  41. package/vanilla/UiTable.js +30 -0
  42. package/vanilla/UiTextarea.d.ts +1 -0
  43. package/vanilla/UiTextarea.js +1 -1
  44. package/vanilla/index.js +1 -0
  45. package/vue/UiButton.vue +2 -1
  46. package/vue/UiCheckbox.d.ts +2 -0
  47. package/vue/UiCheckbox.vue +34 -0
  48. package/vue/UiCombobox.d.ts +2 -2
  49. package/vue/UiCombobox.vue +39 -2
  50. package/vue/UiDescriptionList.d.ts +5 -2
  51. package/vue/UiDescriptionList.vue +51 -7
  52. package/vue/UiInput.d.ts +2 -0
  53. package/vue/UiInput.vue +41 -0
  54. package/vue/UiInputGroup.vue +15 -0
  55. package/vue/UiList.d.ts +1 -1
  56. package/vue/UiRadio.d.ts +2 -0
  57. package/vue/UiRadio.vue +34 -0
  58. package/vue/UiSelect.d.ts +2 -0
  59. package/vue/UiSelect.vue +34 -3
  60. package/vue/UiSwitch.d.ts +2 -0
  61. package/vue/UiSwitch.vue +35 -0
  62. package/vue/UiTable.d.ts +20 -0
  63. package/vue/UiTable.js +2 -0
  64. package/vue/UiTable.vue +172 -0
  65. package/vue/UiTextarea.d.ts +2 -0
  66. package/vue/UiTextarea.vue +27 -0
  67. package/vue/chunks/UiButton.js +1 -1
  68. package/vue/chunks/UiCheckbox.js +3 -2
  69. package/vue/chunks/UiCombobox.js +1 -1
  70. package/vue/chunks/UiDescriptionList.js +11 -3
  71. package/vue/chunks/UiInput.js +7 -2
  72. package/vue/chunks/UiInputGroup.js +1 -1
  73. package/vue/chunks/UiRadio.js +3 -2
  74. package/vue/chunks/UiSelect.js +7 -2
  75. package/vue/chunks/UiSwitch.js +3 -2
  76. package/vue/chunks/UiTable.js +40 -0
  77. package/vue/chunks/UiTextarea.js +7 -2
  78. package/vue/components.css +522 -174
  79. package/vue/index.d.ts +1 -0
  80. package/vue/index.js +2 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@threadlabs/looma",
3
- "version": "0.13.6",
3
+ "version": "0.14.1",
4
4
  "type": "module",
5
5
  "description": "Looma components as HTML, Vue, and plain DOM, with design tokens.",
6
6
  "license": "MIT",
@@ -274,8 +274,9 @@
274
274
  border-color: transparent;
275
275
  }
276
276
 
277
+ /* The size states outrank :host, so sm is named too, or a small button would stay small. */
277
278
  @media (pointer: coarse) {
278
- :scope {
279
+ :scope, :scope[data-ui-button-state~="size=sm"] {
279
280
  min-block-size: var(--ui-control-min-block-size);
280
281
  }
281
282
  }
@@ -374,6 +375,7 @@
374
375
  the way hover washes the live one, so it stays light in every tone. An opaque mix toward the
375
376
  hue came out a mid-grey slab for neutral, whose hue is the ink: louder than the enabled button. */
376
377
 
378
+ /* The size states outrank :host, so sm is named too, or a small button would stay small. */
377
379
  @media (pointer: coarse) {
378
380
  }
379
381
 
@@ -120,6 +120,37 @@
120
120
  cursor: not-allowed;
121
121
  }
122
122
 
123
+ /* A sized choice keeps its box and aligns instead: the label takes that size's field type size,
124
+ and the first line centres in that control height, so the label shares a row's baseline.
125
+ Multi-line labels and descriptions still grow downward from that first line. */
126
+ :scope[data-ui-checkbox-state~="size=sm"] {
127
+ --_font: var(--ui-font-size-sm);
128
+ --_row: var(--ui-control-size-sm);
129
+ }
130
+
131
+ :scope[data-ui-checkbox-state~="size=lg"] {
132
+ --_font: var(--ui-font-size-md);
133
+ --_row: var(--ui-control-size-lg);
134
+ }
135
+
136
+ :scope[data-ui-checkbox-state~="size=sm"],
137
+ :scope[data-ui-checkbox-state~="size=lg"] {
138
+ --_line: calc(var(--_font) * var(--ui-line-height-md));
139
+ font-size: var(--_font);
140
+ padding-block: calc((var(--_row) - var(--_line)) / 2);
141
+ }
142
+
143
+ :scope[data-ui-checkbox-state~="size=sm"] input,
144
+ :scope[data-ui-checkbox-state~="size=lg"] input {
145
+ margin-top: calc((var(--_line) - var(--_ui-checkbox-size, 1.125rem)) / 2);
146
+ }
147
+
148
+ @media (pointer: coarse) {
149
+ :scope[data-ui-checkbox-state~="size=sm"] {
150
+ --_row: var(--ui-control-min-block-size);
151
+ }
152
+ }
153
+
123
154
  /* A component that sets its display still honours the hidden attribute on its root. */
124
155
  :scope[hidden] {
125
156
  display: none;
@@ -132,5 +163,12 @@
132
163
 
133
164
  /* Centre the tick's box, then lift it: a rotated L's ink sits ~0.11rem below its box centre. */
134
165
 
166
+ /* A sized choice keeps its box and aligns instead: the label takes that size's field type size,
167
+ and the first line centres in that control height, so the label shares a row's baseline.
168
+ Multi-line labels and descriptions still grow downward from that first line. */
169
+
170
+ @media (pointer: coarse) {
171
+ }
172
+
135
173
  /* A component that sets its display still honours the hidden attribute on its root. */
136
174
  }
@@ -149,9 +149,18 @@
149
149
  margin: 0;
150
150
  outline: none;
151
151
  }
152
+ :scope[data-ui-combobox-state~="size=sm"] {
153
+ --_row: var(--ui-control-size-sm);
154
+ }
152
155
  :scope[data-ui-combobox-state~="size=sm"] input {
153
- min-block-size: calc(var(--ui-control-size-sm) - 2px);
156
+ min-block-size: calc(var(--_row) - 2px);
154
157
  padding: var(--ui-space-1) var(--ui-space-2);
158
+ font-size: var(--ui-font-size-sm);
159
+ }
160
+ :scope[data-ui-combobox-state~="size=lg"] input {
161
+ min-block-size: calc(var(--ui-control-size-lg) - 2px);
162
+ padding: var(--ui-space-3) var(--ui-space-4);
163
+ font-size: var(--ui-font-size-md);
155
164
  }
156
165
  button {
157
166
  appearance: none;
@@ -252,6 +261,14 @@
252
261
  min-inline-size: 2.75rem;
253
262
  min-block-size: 2.75rem;
254
263
  }
264
+ /* Under touch a small field is still a touch-sized target, as a small button is, and keeps
265
+ body-size text: below 16px, iOS zooms the page into the focused field. */
266
+ :scope[data-ui-combobox-state~="size=sm"] {
267
+ --_row: var(--ui-control-min-block-size);
268
+ }
269
+ :scope[data-ui-combobox-state~="size=sm"] input {
270
+ font-size: inherit;
271
+ }
255
272
  }
256
273
  @media (forced-colors: active) {
257
274
  .field, .popup {
@@ -309,6 +326,22 @@
309
326
  min-block-size: 1.75rem;
310
327
  padding: var(--ui-space-0-5) var(--ui-space-1);
311
328
  }
329
+ /* A small multiple field starts at the small control height; chosen items still wrap it taller. */
330
+ :scope[data-ui-combobox-state~="multiple"]:scope[data-ui-combobox-state~="size=sm"] .field {
331
+ min-block-size: 0;
332
+ padding: var(--ui-space-0-5);
333
+ }
334
+ :scope[data-ui-combobox-state~="multiple"]:scope[data-ui-combobox-state~="size=sm"] input {
335
+ min-block-size: calc(var(--_row) - 2px - 2 * var(--ui-space-0-5));
336
+ padding: 0 var(--ui-space-1);
337
+ }
338
+ /* A large multiple field starts at the large control height, as sm starts at the small one. */
339
+ :scope[data-ui-combobox-state~="multiple"]:scope[data-ui-combobox-state~="size=lg"] .field {
340
+ min-block-size: 0;
341
+ }
342
+ :scope[data-ui-combobox-state~="multiple"]:scope[data-ui-combobox-state~="size=lg"] input {
343
+ min-block-size: calc(var(--ui-control-size-lg) - 2px - 2 * var(--ui-space-1));
344
+ }
312
345
  .item {
313
346
  appearance: none;
314
347
  display: inline-flex;
@@ -342,6 +375,8 @@
342
375
  not inside it, because a control's box holds its value. */
343
376
  /* A ghost icon button: the affordance is the mark, not a box around it. */
344
377
  @media (pointer: coarse) {
378
+ /* Under touch a small field is still a touch-sized target, as a small button is, and keeps
379
+ body-size text: below 16px, iOS zooms the page into the focused field. */
345
380
  }
346
381
  @media (forced-colors: active) {
347
382
  }
@@ -351,6 +386,8 @@
351
386
  Longhands, not shorthands: lowering drops a shorthand that omits a component. */
352
387
 
353
388
  /* Multiple mode: chips wrap before the cursor on one flexible field line. */
389
+ /* A small multiple field starts at the small control height; chosen items still wrap it taller. */
390
+ /* A large multiple field starts at the large control height, as sm starts at the small one. */
354
391
 
355
392
  /* A component that sets its display still honours the hidden attribute on its root. */
356
393
  }
@@ -3,24 +3,63 @@
3
3
  /* Named values: a term and what it is, for each item (ui-description-item). */
4
4
  /* Rows line every term up in one column and every value in the next, through subgrid. */
5
5
  :scope {
6
+ --_gap: var(--ui-space-2);
7
+ --_gap-compact: var(--ui-space-1);
8
+ --_column-gap: var(--ui-space-6);
9
+ --_min: 10rem;
10
+ --_track: var(--_min);
11
+ --_tile-padding: var(--ui-space-3);
6
12
  display: grid;
7
13
  grid-template-columns: minmax(6rem, max-content) minmax(0, 1fr);
8
- gap: var(--ui-space-2) var(--ui-space-6);
14
+ gap: var(--_gap) var(--_column-gap);
9
15
  min-inline-size: 0;
10
16
  margin: 0;
11
17
  }
12
18
 
13
- /* Tiles pass their shape to the items through private properties, which cross into each item. */
19
+ /* Stacked, grid, and tiles set each term above its value, passing the shape to the items through
20
+ private properties, which cross into each item. */
21
+ :scope[data-ui-description-list-state~="layout=stacked"],
22
+ :scope[data-ui-description-list-state~="layout=grid"],
14
23
  :scope[data-ui-description-list-state~="layout=tiles"] {
15
24
  --_ui-description-display: flex;
16
25
  --_ui-description-column: auto;
26
+ --_ui-description-align: stretch;
27
+ --_gap: var(--ui-space-4);
28
+ --_gap-compact: var(--ui-space-2);
29
+ grid-template-columns: minmax(0, 1fr);
30
+ }
31
+
32
+ /* A column is at least --_min wide; with columns set, at least its share of the width, so no more
33
+ than that many fit. */
34
+ :scope[data-ui-description-list-state~="layout=grid"],
35
+ :scope[data-ui-description-list-state~="layout=tiles"] {
36
+ grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--_track)), 1fr));
37
+ }
38
+
39
+ :scope[data-ui-description-list-state~="layout=tiles"] {
17
40
  --_ui-description-align: flex-start;
18
41
  --_ui-description-value-size: var(--ui-font-size-lg);
19
- --_ui-description-padding: var(--ui-space-3);
42
+ --_ui-description-padding: var(--_tile-padding);
20
43
  --_ui-description-border-width: 1px;
21
- display: grid;
22
- grid-template-columns: repeat(auto-fill, minmax(min(100%, 10rem), 1fr));
23
- gap: var(--ui-space-3);
44
+ --_gap: var(--ui-space-3);
45
+ --_gap-compact: var(--ui-space-2);
46
+ --_column-gap: var(--_gap);
47
+ }
48
+
49
+ :scope[data-ui-description-list-state~="columns=2"] {
50
+ --_track: max(var(--_min), (100% - var(--_column-gap)) / 2);
51
+ }
52
+ :scope[data-ui-description-list-state~="columns=3"] {
53
+ --_track: max(var(--_min), (100% - 2 * var(--_column-gap)) / 3);
54
+ }
55
+ :scope[data-ui-description-list-state~="columns=4"] {
56
+ --_track: max(var(--_min), (100% - 3 * var(--_column-gap)) / 4);
57
+ }
58
+
59
+ /* Compact takes each layout's closer spacing. */
60
+ :scope[data-ui-description-list-state~="density=compact"] {
61
+ --_gap: var(--_gap-compact);
62
+ --_tile-padding: var(--ui-space-2);
24
63
  }
25
64
 
26
65
  /* A component that sets its display still honours the hidden attribute on its root. */
@@ -32,7 +71,13 @@
32
71
  /* Named values: a term and what it is, for each item (ui-description-item). */
33
72
  /* Rows line every term up in one column and every value in the next, through subgrid. */
34
73
 
35
- /* Tiles pass their shape to the items through private properties, which cross into each item. */
74
+ /* Stacked, grid, and tiles set each term above its value, passing the shape to the items through
75
+ private properties, which cross into each item. */
76
+
77
+ /* A column is at least --_min wide; with columns set, at least its share of the width, so no more
78
+ than that many fit. */
79
+
80
+ /* Compact takes each layout's closer spacing. */
36
81
 
37
82
  /* A component that sets its display still honours the hidden attribute on its root. */
38
83
  }
@@ -41,6 +41,21 @@
41
41
  transition: border-color var(--ui-motion-fast) var(--ui-motion-ease), box-shadow var(--ui-motion-fast) var(--ui-motion-ease);
42
42
  }
43
43
 
44
+ /* The frame takes its input's size, so a small group lines up with small buttons and fields. */
45
+ :scope:has(.field [data-ui-input-state~="size=sm"]) {
46
+ min-block-size: var(--ui-control-size-sm);
47
+ }
48
+
49
+ :scope:has(.field [data-ui-input-state~="size=lg"]) {
50
+ min-block-size: var(--ui-control-size-lg);
51
+ }
52
+
53
+ @media (pointer: coarse) {
54
+ :scope:has(.field [data-ui-input-state~="size=sm"]) {
55
+ min-block-size: var(--ui-control-min-block-size);
56
+ }
57
+ }
58
+
44
59
  :scope:hover:not(:has(input:focus, input:disabled)) {
45
60
  border-color: var(--ui-control-border-hover, var(--ui-text-secondary));
46
61
  }
@@ -125,6 +140,11 @@
125
140
  thing the field is for ("Continue" after a site's address), so it reads as the field's action
126
141
  and not the form's. */
127
142
 
143
+ /* The frame takes its input's size, so a small group lines up with small buttons and fields. */
144
+
145
+ @media (pointer: coarse) {
146
+ }
147
+
128
148
  /* The ring shows when the field's input would show its own: when it is focus-visible, which a
129
149
  text input is for a pointer as well as a keyboard. A focused action button draws its own. */
130
150
 
@@ -74,6 +74,28 @@
74
74
  background-color var(--ui-motion-fast) var(--ui-motion-ease);
75
75
  }
76
76
 
77
+ /* Each size reads the hook and then an Input Group's default first, so a group, which draws the frame, can zero it. */
78
+ :scope[data-ui-input-state~="size=sm"] {
79
+ min-block-size: var(--ui-input-min-block-size, var(--_ui-default-input-min-block-size, var(--ui-control-size-sm)));
80
+ padding: var(--ui-space-1) var(--ui-space-2);
81
+ font-size: var(--ui-font-size-sm);
82
+ }
83
+
84
+ :scope[data-ui-input-state~="size=lg"] {
85
+ min-block-size: var(--ui-input-min-block-size, var(--_ui-default-input-min-block-size, var(--ui-control-size-lg)));
86
+ padding: var(--ui-space-3) var(--ui-space-4);
87
+ font-size: var(--ui-font-size-md);
88
+ }
89
+
90
+ /* Under touch a small field is still a touch-sized target, as a small button is, and keeps
91
+ body-size text: below 16px, iOS zooms the page into the focused field. */
92
+ @media (pointer: coarse) {
93
+ :scope[data-ui-input-state~="size=sm"] {
94
+ min-block-size: var(--ui-input-min-block-size, var(--_ui-default-input-min-block-size, var(--ui-control-min-block-size)));
95
+ font-size: inherit;
96
+ }
97
+ }
98
+
77
99
  /* Tabbing into a field selects its text; tint that selection softly instead of the page highlight. */
78
100
  :scope::selection {
79
101
  background: color-mix(in srgb, var(--ui-accent-solid) 20%, transparent);
@@ -139,6 +161,13 @@
139
161
  /* This component's hooks style the element they are set on, and never a nested one. An Input
140
162
  Group sets --_ui-default-input-* for the input inside it, below the input's own hooks. */
141
163
 
164
+ /* Each size reads the hook and then an Input Group's default first, so a group, which draws the frame, can zero it. */
165
+
166
+ /* Under touch a small field is still a touch-sized target, as a small button is, and keeps
167
+ body-size text: below 16px, iOS zooms the page into the focused field. */
168
+ @media (pointer: coarse) {
169
+ }
170
+
142
171
  /* Tabbing into a field selects its text; tint that selection softly instead of the page highlight. */
143
172
 
144
173
  /* The app's invalid flag, or the browser's own verdict once the user has left an invalid field. */
@@ -67,6 +67,37 @@
67
67
  cursor: not-allowed;
68
68
  }
69
69
 
70
+ /* A sized choice keeps its box and aligns instead: the label takes that size's field type size,
71
+ and the first line centres in that control height, so the label shares a row's baseline.
72
+ Multi-line labels and descriptions still grow downward from that first line. */
73
+ :scope[data-ui-radio-state~="size=sm"] {
74
+ --_font: var(--ui-font-size-sm);
75
+ --_row: var(--ui-control-size-sm);
76
+ }
77
+
78
+ :scope[data-ui-radio-state~="size=lg"] {
79
+ --_font: var(--ui-font-size-md);
80
+ --_row: var(--ui-control-size-lg);
81
+ }
82
+
83
+ :scope[data-ui-radio-state~="size=sm"],
84
+ :scope[data-ui-radio-state~="size=lg"] {
85
+ --_line: calc(var(--_font) * var(--ui-line-height-md));
86
+ font-size: var(--_font);
87
+ padding-block: calc((var(--_row) - var(--_line)) / 2);
88
+ }
89
+
90
+ :scope[data-ui-radio-state~="size=sm"] input,
91
+ :scope[data-ui-radio-state~="size=lg"] input {
92
+ margin-top: calc((var(--_line) - var(--_ui-radio-size, 1.125rem)) / 2);
93
+ }
94
+
95
+ @media (pointer: coarse) {
96
+ :scope[data-ui-radio-state~="size=sm"] {
97
+ --_row: var(--ui-control-min-block-size);
98
+ }
99
+ }
100
+
70
101
  /* A component that sets its display still honours the hidden attribute on its root. */
71
102
  :scope[hidden] {
72
103
  display: none;
@@ -77,5 +108,12 @@
77
108
 
78
109
  /* The label, and under it an optional line saying what the choice means. */
79
110
 
111
+ /* A sized choice keeps its box and aligns instead: the label takes that size's field type size,
112
+ and the first line centres in that control height, so the label shares a row's baseline.
113
+ Multi-line labels and descriptions still grow downward from that first line. */
114
+
115
+ @media (pointer: coarse) {
116
+ }
117
+
80
118
  /* A component that sets its display still honours the hidden attribute on its root. */
81
119
  }
@@ -79,9 +79,30 @@
79
79
  cursor: not-allowed;
80
80
  }
81
81
 
82
- :scope[size]:not([size="1"]) {
83
- min-block-size: 7rem;
84
- padding: var(--ui-space-2);
82
+ :scope[data-ui-select-state~="size=sm"] {
83
+ min-block-size: var(--ui-control-size-sm);
84
+ padding: var(--ui-space-1) var(--ui-space-2);
85
+ padding-inline-end: calc(var(--ui-space-2) + var(--ui-icon-size-sm) + var(--ui-space-1));
86
+ background-position: right var(--ui-space-2) center;
87
+ background-size: var(--ui-icon-size-sm) var(--ui-icon-size-sm);
88
+ font-size: var(--ui-font-size-sm);
89
+ }
90
+
91
+ :scope[data-ui-select-state~="size=lg"] {
92
+ min-block-size: var(--ui-control-size-lg);
93
+ padding: var(--ui-space-3) var(--ui-space-4);
94
+ padding-inline-end: calc(var(--ui-space-4) + 1.5rem);
95
+ background-position: right var(--ui-space-4) center;
96
+ font-size: var(--ui-font-size-md);
97
+ }
98
+
99
+ /* Under touch a small field is still a touch-sized target, as a small button is, and keeps
100
+ body-size text: below 16px, iOS zooms the page into the focused field. */
101
+ @media (pointer: coarse) {
102
+ :scope[data-ui-select-state~="size=sm"] {
103
+ min-block-size: var(--ui-control-min-block-size);
104
+ font-size: inherit;
105
+ }
85
106
  }
86
107
 
87
108
  /* A component that sets its display still honours the hidden attribute on its root. */
@@ -97,5 +118,10 @@
97
118
 
98
119
  /* The app's invalid flag, or the browser's own verdict once the user has left an invalid field. */
99
120
 
121
+ /* Under touch a small field is still a touch-sized target, as a small button is, and keeps
122
+ body-size text: below 16px, iOS zooms the page into the focused field. */
123
+ @media (pointer: coarse) {
124
+ }
125
+
100
126
  /* A component that sets its display still honours the hidden attribute on its root. */
101
127
  }
@@ -111,6 +111,38 @@
111
111
  }
112
112
  }
113
113
 
114
+ /* A sized switch keeps its track and aligns instead: the label takes that size's field type size,
115
+ and the first line centres in that control height, so the label shares a row's baseline.
116
+ Multi-line labels and descriptions still grow downward from that first line. */
117
+ :scope[data-ui-switch-state~="size=sm"] {
118
+ --_font: var(--ui-font-size-sm);
119
+ --_row: var(--ui-control-size-sm);
120
+ }
121
+
122
+ :scope[data-ui-switch-state~="size=lg"] {
123
+ --_font: var(--ui-font-size-md);
124
+ --_row: var(--ui-control-size-lg);
125
+ }
126
+
127
+ :scope[data-ui-switch-state~="size=sm"],
128
+ :scope[data-ui-switch-state~="size=lg"] {
129
+ --_line: calc(var(--_font) * var(--ui-line-height-md));
130
+ font-size: var(--_font);
131
+ padding-block: calc((var(--_row) - var(--ui-switch-track-height, 1.5rem)) / 2);
132
+ }
133
+
134
+ /* The label centres on the track. */
135
+ :scope[data-ui-switch-state~="size=sm"] .text,
136
+ :scope[data-ui-switch-state~="size=lg"] .text {
137
+ margin-top: calc((var(--_ui-switch-track-height, 1.5rem) - var(--_line)) / 2);
138
+ }
139
+
140
+ @media (pointer: coarse) {
141
+ :scope[data-ui-switch-state~="size=sm"] {
142
+ --_row: var(--ui-control-min-block-size);
143
+ }
144
+ }
145
+
114
146
  /* A component that sets its display still honours the hidden attribute on its root. */
115
147
  :scope[hidden] {
116
148
  display: none;
@@ -124,5 +156,14 @@
124
156
  @media (prefers-reduced-motion: reduce) {
125
157
  }
126
158
 
159
+ /* A sized switch keeps its track and aligns instead: the label takes that size's field type size,
160
+ and the first line centres in that control height, so the label shares a row's baseline.
161
+ Multi-line labels and descriptions still grow downward from that first line. */
162
+
163
+ /* The label centres on the track. */
164
+
165
+ @media (pointer: coarse) {
166
+ }
167
+
127
168
  /* A component that sets its display still honours the hidden attribute on its root. */
128
169
  }
@@ -0,0 +1,134 @@
1
+ /* Generated by HTML Next 1.0.0-alpha.7. Do not edit. */
2
+ @scope ([data-component~="ui-table"]) to ([data-component], [data-slotted]) {
3
+ /* Styles an authored <table> (caption, thead, tbody, th scope) without replacing it: the table is
4
+ meaningful before JavaScript, and a screen reader reads it as a table. The root scrolls a table
5
+ wider than it, or taller than a bounded height; while it does, the controller makes the root a named, focusable region so a
6
+ keyboard can scroll it. A cell's data-ui-align (start, center, or end; start by default) sets its
7
+ content along the row, such as end for a column of figures. */
8
+ :scope {
9
+ --_pad-block: var(--ui-space-1);
10
+ --_pad-inline: var(--ui-space-3);
11
+ --_row-size: var(--ui-control-size);
12
+ display: block;
13
+ box-sizing: border-box;
14
+ max-inline-size: 100%;
15
+ min-inline-size: 0;
16
+ min-block-size: 0;
17
+ overflow: auto;
18
+ overscroll-behavior-inline: contain;
19
+ /* The sideways scroll fade every Looma scroller uses (see ui-scroll-area). Only the inline edges
20
+ fade, so a sticky header never fades. */
21
+ --_ui-scroll-fade-direction: to right;
22
+ --_ui-scroll-fade-size: var(--ui-scroll-fade-size, 24px);
23
+ mask-image: linear-gradient(
24
+ var(--_ui-scroll-fade-direction),
25
+ transparent,
26
+ #000 var(--_ui-scroll-fade-start),
27
+ #000 calc(100% - var(--_ui-scroll-fade-end)),
28
+ transparent
29
+ );
30
+ animation: ui-scroll-fade-start linear, ui-scroll-fade-start-hold linear, ui-scroll-fade-end-hold linear, ui-scroll-fade-end linear;
31
+ animation-timeline: scroll(self inline);
32
+ animation-range:
33
+ 0 var(--_ui-scroll-fade-size),
34
+ var(--_ui-scroll-fade-size) calc(100% + 1px),
35
+ -1px calc(100% - var(--_ui-scroll-fade-size)),
36
+ calc(100% - var(--_ui-scroll-fade-size)) 100%;
37
+ }
38
+
39
+ :scope:dir(rtl) {
40
+ --_ui-scroll-fade-direction: to left;
41
+ }
42
+
43
+ :scope:focus-visible {
44
+ outline: 2px solid var(--ui-focus-ring);
45
+ outline-offset: -2px;
46
+ }
47
+
48
+ :scope[data-ui-table-state~="density=compact"] {
49
+ --_pad-block: var(--ui-space-0-5);
50
+ --_pad-inline: var(--ui-space-2);
51
+ --_row-size: var(--ui-control-size-sm);
52
+ }
53
+
54
+ /* Separate borders stay on their cells when a sticky header scrolls; collapsed ones stay behind. */
55
+
56
+ /* Every row is at least one control tall, so a row holding a checkbox, select, or input keeps the
57
+ rhythm of the rows around it. A cell's height is its minimum. */
58
+
59
+ /* A component that sets its display still honours the hidden attribute on its root. */
60
+ :scope[hidden] {
61
+ display: none;
62
+ }
63
+ }
64
+ @scope ([data-component~="ui-table"]) to ([data-component]) {
65
+ /* Styles an authored <table> (caption, thead, tbody, th scope) without replacing it: the table is
66
+ meaningful before JavaScript, and a screen reader reads it as a table. The root scrolls a table
67
+ wider than it, or taller than a bounded height; while it does, the controller makes the root a named, focusable region so a
68
+ keyboard can scroll it. A cell's data-ui-align (start, center, or end; start by default) sets its
69
+ content along the row, such as end for a column of figures. */
70
+
71
+ /* Separate borders stay on their cells when a sticky header scrolls; collapsed ones stay behind. */
72
+ :where([data-slotted], [data-slotted] *):is(table) {
73
+ inline-size: 100%;
74
+ border-collapse: separate;
75
+ border-spacing: 0;
76
+ color: var(--ui-text-primary);
77
+ font-size: var(--ui-font-size-sm);
78
+ line-height: var(--ui-line-height);
79
+ font-variant-numeric: tabular-nums;
80
+ }
81
+
82
+ :where([data-slotted], [data-slotted] *):is(caption) {
83
+ padding-block-end: var(--ui-space-2);
84
+ color: var(--ui-text-secondary);
85
+ font-weight: var(--ui-font-medium);
86
+ text-align: start;
87
+ }
88
+
89
+ /* Every row is at least one control tall, so a row holding a checkbox, select, or input keeps the
90
+ rhythm of the rows around it. A cell's height is its minimum. */
91
+ :where([data-slotted], [data-slotted] *):is(th),
92
+ :where([data-slotted], [data-slotted] *):is(td) {
93
+ block-size: var(--_row-size);
94
+ padding: var(--_pad-block) var(--_pad-inline);
95
+ border-block-end: 1px solid var(--ui-border-subtle);
96
+ text-align: start;
97
+ vertical-align: middle;
98
+ }
99
+
100
+ :where([data-slotted], [data-slotted] *):is(thead th) {
101
+ block-size: auto;
102
+ padding-block: var(--ui-space-2);
103
+ border-block-end-color: var(--ui-border-default);
104
+ background: var(--ui-surface);
105
+ color: var(--ui-text-muted);
106
+ font-weight: var(--ui-font-medium);
107
+ white-space: nowrap;
108
+ vertical-align: bottom;
109
+ }
110
+
111
+ :where([data-slotted], [data-slotted] *):is(tbody th) {
112
+ font-weight: var(--ui-font-medium);
113
+ }
114
+
115
+ :where([data-slotted], [data-slotted] *):is(tbody tr:last-child > *) {
116
+ border-block-end-width: 0;
117
+ }
118
+
119
+ :where([data-slotted], [data-slotted] *):is([data-ui-align="center"]) {
120
+ text-align: center;
121
+ }
122
+
123
+ :where([data-slotted], [data-slotted] *):is([data-ui-align="end"]) {
124
+ text-align: end;
125
+ }
126
+
127
+ :scope[data-ui-table-state~="stickyHeader"] :where([data-slotted], [data-slotted] *):is(thead th) {
128
+ position: sticky;
129
+ inset-block-start: 0;
130
+ z-index: 1;
131
+ }
132
+
133
+ /* A component that sets its display still honours the hidden attribute on its root. */
134
+ }
@@ -77,6 +77,23 @@
77
77
  cursor: not-allowed;
78
78
  }
79
79
 
80
+ :scope[data-ui-textarea-state~="size=sm"] {
81
+ padding: var(--ui-space-1) var(--ui-space-2);
82
+ font-size: var(--ui-font-size-sm);
83
+ }
84
+
85
+ :scope[data-ui-textarea-state~="size=lg"] {
86
+ padding: var(--ui-space-3) var(--ui-space-4);
87
+ font-size: var(--ui-font-size-md);
88
+ }
89
+
90
+ /* Below 16px, iOS zooms the page into the focused field, so touch keeps body-size text. */
91
+ @media (pointer: coarse) {
92
+ :scope[data-ui-textarea-state~="size=sm"] {
93
+ font-size: inherit;
94
+ }
95
+ }
96
+
80
97
  /* A component that sets its display still honours the hidden attribute on its root. */
81
98
  :scope[hidden] {
82
99
  display: none;
@@ -89,5 +106,9 @@
89
106
 
90
107
  /* The app's invalid flag, or the browser's own verdict once the user has left an invalid field. */
91
108
 
109
+ /* Below 16px, iOS zooms the page into the focused field, so touch keeps body-size text. */
110
+ @media (pointer: coarse) {
111
+ }
112
+
92
113
  /* A component that sets its display still honours the hidden attribute on its root. */
93
114
  }
@@ -12,6 +12,7 @@ export interface UiCheckboxProps {
12
12
  indeterminate?: boolean | null;
13
13
  name?: string | null;
14
14
  required?: boolean | null;
15
+ size?: "sm" | "md" | "lg" | null;
15
16
  value?: string | null;
16
17
  attributes?: Readonly<Record<string, string | number | boolean | null | undefined>>;
17
18
  children?: readonly (string | Node)[];