@threadlabs/looma 0.27.2 → 0.27.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@threadlabs/looma",
3
- "version": "0.27.2",
3
+ "version": "0.27.3",
4
4
  "type": "module",
5
5
  "description": "Looma components as HTML, Vue, and plain DOM, with design tokens.",
6
6
  "license": "MIT",
@@ -323,6 +323,33 @@
323
323
  --_on-hue: var(--ui-surface);
324
324
  }
325
325
 
326
+ /* A value picker is a field, with Select's quiet edge and resting lift. */
327
+ :scope[data-ui-button-state~="variant=field"] {
328
+ --_ui-field-shadow: var(--ui-shadow-sm);
329
+ border-color: var(--ui-button-border, var(--ui-control-border, var(--ui-border-strong)));
330
+ border-radius: var(--ui-button-radius, var(--ui-field-radius, var(--ui-radius-md)));
331
+ background-color: var(--ui-button-surface, var(--ui-control-surface, var(--ui-surface-elevated)));
332
+ color: var(--ui-button-text, var(--ui-text-primary));
333
+ font-weight: var(--ui-font-normal);
334
+ box-shadow: var(--ui-button-shadow, var(--_ui-field-shadow));
335
+ }
336
+
337
+ :scope[data-ui-button-state~="variant=field"]:hover:not(:disabled, [aria-disabled="true"]) {
338
+ border-color: var(--ui-button-hover-border, var(--ui-control-border-hover, var(--ui-text-secondary)));
339
+ background-color: var(--ui-button-hover-surface, var(--ui-control-surface, var(--ui-surface-elevated)));
340
+ }
341
+
342
+ :scope[data-ui-button-state~="variant=field"]:is(:active, [aria-expanded="true"]):not(:disabled, [aria-disabled="true"]) {
343
+ --_ui-field-shadow: var(--ui-pressed);
344
+ }
345
+
346
+ :scope[data-ui-button-state~="variant=field"]:focus-visible {
347
+ border-color: var(--ui-control-focus, var(--ui-accent-solid));
348
+ box-shadow:
349
+ var(--ui-button-shadow, var(--_ui-field-shadow)),
350
+ var(--ui-control-focus-halo-shadow);
351
+ }
352
+
326
353
  :scope[data-ui-button-state~="variant=ghost"] {
327
354
  border-color: transparent;
328
355
  background: transparent;
@@ -484,6 +511,8 @@
484
511
  /* Tone is the colour, variant is the volume. Every pairing works: a danger outline is a
485
512
  destructive secondary action, not a special case. */
486
513
 
514
+ /* A value picker is a field, with Select's quiet edge and resting lift. */
515
+
487
516
  /* Tints derive from the text colour, not a surface token, so hover and press show on any background. */
488
517
 
489
518
  /* Pressed, or holding its menu open, a ghost button sinks slightly: a light surface with a faint
@@ -85,6 +85,7 @@
85
85
  --_ui-combobox-popup-radius: var(--ui-combobox-popup-radius);
86
86
  --_ui-combobox-popup-shadow: var(--ui-combobox-popup-shadow);
87
87
 
88
+ --_row: var(--ui-control-size-md);
88
89
  display: grid;
89
90
  gap: var(--ui-space-1);
90
91
  min-inline-size: 0;
@@ -106,6 +107,8 @@
106
107
  }
107
108
 
108
109
  .field {
110
+ box-sizing: border-box;
111
+ min-block-size: var(--_row);
109
112
  flex: 1 1 auto;
110
113
  display: flex;
111
114
  min-inline-size: 0;
@@ -172,14 +175,14 @@
172
175
  appearance: none;
173
176
  width: 100%;
174
177
  min-inline-size: 0;
175
- min-block-size: calc(var(--ui-control-size-md) - 2px);
178
+ min-block-size: calc(var(--_row) - 2 * var(--ui-border-width));
176
179
  box-sizing: border-box;
177
180
  border: 0;
178
181
  border-radius: inherit;
179
182
  background: transparent;
180
183
  color: inherit;
181
184
  font: inherit;
182
- line-height: var(--ui-line-height-md);
185
+ line-height: var(--ui-line-height-tight);
183
186
  padding: var(--ui-space-2) var(--ui-space-3);
184
187
  margin: 0;
185
188
  outline: none;
@@ -188,12 +191,13 @@
188
191
  --_row: var(--ui-control-size-sm);
189
192
  }
190
193
  :scope[data-ui-combobox-state~="size=sm"] input {
191
- min-block-size: calc(var(--_row) - 2px);
192
194
  padding: var(--ui-space-1) var(--ui-space-2);
193
195
  font-size: var(--ui-font-size-sm);
194
196
  }
197
+ :scope[data-ui-combobox-state~="size=lg"] {
198
+ --_row: var(--ui-control-size-lg);
199
+ }
195
200
  :scope[data-ui-combobox-state~="size=lg"] input {
196
- min-block-size: calc(var(--ui-control-size-lg) - 2px);
197
201
  padding: var(--ui-space-3) var(--ui-space-4);
198
202
  font-size: var(--ui-font-size-md);
199
203
  }
@@ -299,9 +303,12 @@
299
303
  white-space: nowrap;
300
304
  }
301
305
  @media (pointer: coarse) {
306
+ :scope {
307
+ --_row: max(var(--ui-control-size-md), var(--ui-control-min-block-size));
308
+ }
302
309
  button {
303
310
  min-inline-size: 2.75rem;
304
- min-block-size: 2.75rem;
311
+ min-block-size: calc(var(--_row) - 2 * var(--ui-border-width));
305
312
  }
306
313
  /* Under touch a small field is still a touch-sized target, as a small button is, and keeps
307
314
  body-size text: below 16px, iOS zooms the page into the focused field. */
@@ -325,7 +332,7 @@
325
332
  while the clear and disclosure controls remain fixed at the end of the field. */
326
333
  :scope[data-ui-combobox-state~="multiple"] .field {
327
334
  align-items: center;
328
- min-block-size: var(--_ui-combobox-item-min-block-size, 2.5rem);
335
+ min-block-size: var(--_ui-combobox-item-min-block-size, var(--_row));
329
336
  padding: var(--ui-space-1);
330
337
  }
331
338
  :scope[data-ui-combobox-state~="multiple"] .entry {
@@ -80,6 +80,10 @@
80
80
  }
81
81
 
82
82
  @media (pointer: coarse) {
83
+ :scope {
84
+ min-block-size: max(var(--ui-control-size-md), var(--ui-control-min-block-size));
85
+ }
86
+
83
87
  :scope:has(.field [data-ui-input-state~="size=sm"]) {
84
88
  min-block-size: var(--ui-control-min-block-size);
85
89
  }
@@ -71,7 +71,7 @@
71
71
  background: var(--ui-input-surface, var(--_ui-default-input-surface, var(--ui-control-surface, var(--ui-surface-elevated))));
72
72
  color: var(--ui-text-primary);
73
73
  font: inherit;
74
- line-height: var(--ui-line-height-md);
74
+ line-height: var(--ui-line-height-tight);
75
75
  box-shadow: var(--ui-input-shadow, var(--_ui-default-input-shadow, var(--ui-control-inset)));
76
76
  transition:
77
77
  border-color var(--ui-motion-fast) var(--ui-motion-ease),
@@ -95,6 +95,13 @@
95
95
  /* Under touch a small field is still a touch-sized target, as a small button is, and keeps
96
96
  body-size text: below 16px, iOS zooms the page into the focused field. */
97
97
  @media (pointer: coarse) {
98
+ :scope {
99
+ min-block-size: var(
100
+ --ui-input-min-block-size,
101
+ var(--_ui-default-input-min-block-size, max(var(--ui-control-size-md), var(--ui-control-min-block-size)))
102
+ );
103
+ }
104
+
98
105
  :scope,
99
106
  :scope[data-ui-input-state~="size=sm"],
100
107
  :scope[data-ui-input-state~="size=lg"] {
@@ -56,8 +56,8 @@
56
56
  color: var(--ui-text-primary);
57
57
  color-scheme: light dark;
58
58
  font: inherit;
59
- line-height: var(--ui-line-height-md);
60
- box-shadow: var(--ui-select-shadow, var(--ui-control-inset));
59
+ line-height: var(--ui-line-height-tight);
60
+ box-shadow: var(--ui-select-shadow, var(--ui-shadow-sm));
61
61
  transition:
62
62
  border-color var(--ui-motion-fast) var(--ui-motion-ease),
63
63
  box-shadow var(--ui-motion-fast) var(--ui-motion-ease),
@@ -85,7 +85,7 @@
85
85
  :scope:focus-visible {
86
86
  border-color: var(--ui-select-focus-border, var(--ui-control-focus, var(--ui-accent-solid)));
87
87
  outline: none;
88
- box-shadow: var(--ui-select-focus-shadow, var(--ui-control-inset), var(--ui-control-focus-halo-shadow));
88
+ box-shadow: var(--ui-select-focus-shadow, var(--ui-shadow-sm), var(--ui-control-focus-halo-shadow));
89
89
  }
90
90
 
91
91
  /* The app's invalid flag, or the browser's own verdict once the user has left an invalid field. */
@@ -97,7 +97,7 @@
97
97
  :scope:is([aria-invalid="true"], :is(:user-invalid, [data-user-invalid])):focus-visible {
98
98
  box-shadow: var(
99
99
  --ui-select-invalid-focus-shadow,
100
- var(--ui-control-inset),
100
+ var(--ui-shadow-sm),
101
101
  0 0 0 calc(var(--ui-focus-width) * 1.5) color-mix(
102
102
  in srgb,
103
103
  var(--ui-select-invalid-border, var(--ui-field-danger, var(--ui-danger-solid))) 22%,
@@ -133,6 +133,10 @@
133
133
  /* Under touch a small field is still a touch-sized target, as a small button is, and keeps
134
134
  body-size text: below 16px, iOS zooms the page into the focused field. */
135
135
  @media (pointer: coarse) {
136
+ :scope {
137
+ min-block-size: max(var(--ui-control-size-md), var(--ui-control-min-block-size));
138
+ }
139
+
136
140
  :scope[data-ui-select-state~="size=sm"] {
137
141
  min-block-size: var(--ui-control-min-block-size);
138
142
  font-size: inherit;
@@ -19,7 +19,7 @@ export interface UiButtonProps {
19
19
  target?: string | null;
20
20
  tone?: "accent" | "neutral" | "danger" | "success" | "warning" | "info" | null;
21
21
  type?: "button" | "submit" | "reset" | null;
22
- variant?: "outline" | "solid" | "danger" | "ghost" | "link" | "card" | null;
22
+ variant?: "outline" | "solid" | "danger" | "ghost" | "link" | "card" | "field" | null;
23
23
  attributes?: Readonly<Record<string, string | number | boolean | null | undefined>>;
24
24
  children?: readonly (string | Node)[];
25
25
  slots?: Readonly<Record<string, readonly (string | Node)[]>>;
@@ -27,7 +27,7 @@ let P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P
27
27
  const S = { n: [], t: [], f: import.meta.url, g: "ui-button" };
28
28
  const A = [["a",{},[],"",[],["data-ui-button-state","data-align","data-as","data-disabled","data-href","data-loading","data-pending","data-rel","data-shape","data-size","data-stretch","data-target","data-tone","data-type","data-variant","href","target","rel","aria-disabled","aria-busy","tabindex"]],["button",{},[],"",[],["data-ui-button-state","data-align","data-as","data-disabled","data-href","data-loading","data-pending","data-rel","data-shape","data-size","data-stretch","data-target","data-tone","data-type","data-variant","type","disabled","aria-disabled","aria-busy"]],["a",{},[],"",[],["data-ui-button-state","data-align","data-as","data-disabled","data-href","data-loading","data-pending","data-rel","data-shape","data-size","data-stretch","data-target","data-tone","data-type","data-variant","href","target","rel","aria-disabled","aria-busy","tabindex"]],["button",{},[],"",[],["data-ui-button-state","data-align","data-as","data-disabled","data-href","data-loading","data-pending","data-rel","data-shape","data-size","data-stretch","data-target","data-tone","data-type","data-variant","type","disabled","aria-disabled","aria-busy"]]];
29
29
  const Q = [[],[],[],[]];
30
- const D = { props: { "align": { "required":false,"values":["center","start"],"default":"center", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "as": { "required":false,"values":["button","a"],"default":"button", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "disabled": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "href": { "required":false, c: checkString, t: "string", j: 0, m: "s" }, "loading": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "pending": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "rel": { "required":false, c: checkString, t: "string", j: 0, m: "s" }, "shape": { "required":false,"values":["rounded","pill"],"default":"rounded", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "size": { "required":false,"values":["xs","sm","md","lg"],"default":"md", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "stretch": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "target": { "required":false, c: checkString, t: "string", j: 0, m: "s" }, "tone": { "required":false,"values":["accent","neutral","danger","success","warning","info"],"default":"accent", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "type": { "required":false,"values":["button","submit","reset"],"default":"button", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "variant": { "required":false,"values":["outline","solid","danger","ghost","link","card"],"default":"outline", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] } } };
30
+ const D = { props: { "align": { "required":false,"values":["center","start"],"default":"center", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "as": { "required":false,"values":["button","a"],"default":"button", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "disabled": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "href": { "required":false, c: checkString, t: "string", j: 0, m: "s" }, "loading": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "pending": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "rel": { "required":false, c: checkString, t: "string", j: 0, m: "s" }, "shape": { "required":false,"values":["rounded","pill"],"default":"rounded", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "size": { "required":false,"values":["xs","sm","md","lg"],"default":"md", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "stretch": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "target": { "required":false, c: checkString, t: "string", j: 0, m: "s" }, "tone": { "required":false,"values":["accent","neutral","danger","success","warning","info"],"default":"accent", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "type": { "required":false,"values":["button","submit","reset"],"default":"button", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "variant": { "required":false,"values":["outline","solid","danger","ghost","link","card","field"],"default":"outline", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] } } };
31
31
  const C = (host) => controller.default(host);
32
32
 
33
33
  export function createUiButton(options = {}, html) {
package/vue/UiButton.d.ts CHANGED
@@ -75,7 +75,7 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
75
75
  default: string;
76
76
  };
77
77
  variant: {
78
- type: PropType<"outline" | "solid" | "danger" | "ghost" | "link" | "card" | null>;
78
+ type: PropType<"outline" | "solid" | "danger" | "ghost" | "link" | "card" | "field" | null>;
79
79
  default: string;
80
80
  };
81
81
  }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
@@ -132,7 +132,7 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
132
132
  default: string;
133
133
  };
134
134
  variant: {
135
- type: PropType<"outline" | "solid" | "danger" | "ghost" | "link" | "card" | null>;
135
+ type: PropType<"outline" | "solid" | "danger" | "ghost" | "link" | "card" | "field" | null>;
136
136
  default: string;
137
137
  };
138
138
  }>> & Readonly<{}>, {
@@ -140,7 +140,7 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
140
140
  shape: "pill" | "rounded" | null;
141
141
  size: "xs" | "sm" | "md" | "lg" | null;
142
142
  tone: "info" | "neutral" | "accent" | "success" | "warning" | "danger" | null;
143
- variant: "link" | "danger" | "solid" | "outline" | "ghost" | "card" | null;
143
+ variant: "link" | "field" | "danger" | "solid" | "outline" | "ghost" | "card" | null;
144
144
  align: "center" | "start" | null;
145
145
  as: "a" | "button" | null;
146
146
  disabled: boolean | null;
package/vue/UiButton.vue CHANGED
@@ -102,7 +102,7 @@ const propValidityContract = {
102
102
  required: false,
103
103
  target: { attribute: 'tone' },
104
104
  description:
105
- 'Which colour the button speaks in. It applies to every variant, so an outline, a ghost, and a solid in the same tone are the same action at three volumes:\n tone="danger" variant="outline" is a destructive secondary action. neutral is the quiet one, for a button that should not claim a colour.',
105
+ 'Which colour the button speaks in. It applies to every variant, so an outline, a ghost, and a solid in the same tone are the same action at three volumes:\n tone="danger" variant="outline" is a destructive secondary action. neutral is the quiet one, for a button that should not claim a colour. The field variant always uses neutral field colors.',
106
106
  values: ['accent', 'neutral', 'danger', 'success', 'warning', 'info'],
107
107
  default: 'accent',
108
108
  },
@@ -120,8 +120,8 @@ const propValidityContract = {
120
120
  required: false,
121
121
  target: { attribute: 'variant' },
122
122
  description:
123
- 'Emphasis: how loudly the button asks. solid fills with its tone, outline draws its tone at the edge over a wash of it, ghost carries only text until it is\n hovered, and link is an inline text action that reads as a link. danger is solid in the danger tone, kept for the call sites that name it. card uses the\n same tone on a full-width action with an icon slot, wrapping content, and a centered action slot.',
124
- values: ['outline', 'solid', 'danger', 'ghost', 'link', 'card'],
123
+ 'Emphasis: how loudly the button asks. solid fills with its tone, outline draws its tone at the edge over a wash of it, ghost carries only text until it is\n hovered, and link is an inline text action that reads as a link. danger is solid in the danger tone, kept for the call sites that name it. card uses the\n same tone on a full-width action with an icon slot, wrapping content, and a centered action slot. field opens a custom value picker: it shares Select\'s\n quiet outline, neutral text and slightly raised surface, independently of action tone.',
124
+ values: ['outline', 'solid', 'danger', 'ghost', 'link', 'card', 'field'],
125
125
  default: 'outline',
126
126
  },
127
127
  },
@@ -150,7 +150,7 @@ const props = defineProps({
150
150
  },
151
151
  variant: {
152
152
  type: null as unknown as PropType<
153
- 'outline' | 'solid' | 'danger' | 'ghost' | 'link' | 'card' | null
153
+ 'outline' | 'solid' | 'danger' | 'ghost' | 'link' | 'card' | 'field' | null
154
154
  >,
155
155
  default: 'outline',
156
156
  },
@@ -263,13 +263,13 @@ const checkedProps = computed(() => ({
263
263
  'type',
264
264
  acceptedProps,
265
265
  ) as 'button' | 'submit' | 'reset',
266
- variant: checkedProp<'outline' | 'solid' | 'danger' | 'ghost' | 'link' | 'card'>(
266
+ variant: checkedProp<'outline' | 'solid' | 'danger' | 'ghost' | 'link' | 'card' | 'field'>(
267
267
  props['variant'],
268
268
  { kind: 'terminal', name: 'keyword' },
269
269
  false,
270
270
  'variant',
271
271
  acceptedProps,
272
- ) as 'outline' | 'solid' | 'danger' | 'ghost' | 'link' | 'card',
272
+ ) as 'outline' | 'solid' | 'danger' | 'ghost' | 'link' | 'card' | 'field',
273
273
  }))
274
274
  void checkedProps.value
275
275
  watchSyncEffect(() => {
@@ -1175,6 +1175,39 @@ function concat<T extends unknown[]>(...values: T): ConcatResult<T> {
1175
1175
  --_on-hue: var(--ui-surface);
1176
1176
  }
1177
1177
 
1178
+ /* A value picker is a field, with Select's quiet edge and resting lift. */
1179
+ [data-component~="ui-button"][data-ui-button-state~="variant=field"] {
1180
+ --_ui-field-shadow: var(--ui-shadow-sm);
1181
+ border-color: var(--ui-button-border, var(--ui-control-border, var(--ui-border-strong)));
1182
+ border-radius: var(--ui-button-radius, var(--ui-field-radius, var(--ui-radius-md)));
1183
+ background-color: var(--ui-button-surface, var(--ui-control-surface, var(--ui-surface-elevated)));
1184
+ color: var(--ui-button-text, var(--ui-text-primary));
1185
+ font-weight: var(--ui-font-normal);
1186
+ box-shadow: var(--ui-button-shadow, var(--_ui-field-shadow));
1187
+ }
1188
+
1189
+ [data-component~="ui-button"][data-ui-button-state~="variant=field"]:hover:not(:disabled, [aria-disabled="true"]) {
1190
+ border-color: var(
1191
+ --ui-button-hover-border,
1192
+ var(--ui-control-border-hover, var(--ui-text-secondary))
1193
+ );
1194
+ background-color: var(
1195
+ --ui-button-hover-surface,
1196
+ var(--ui-control-surface, var(--ui-surface-elevated))
1197
+ );
1198
+ }
1199
+
1200
+ [data-component~="ui-button"][data-ui-button-state~="variant=field"]:is(:active, [aria-expanded="true"]):not(:disabled, [aria-disabled="true"]) {
1201
+ --_ui-field-shadow: var(--ui-pressed);
1202
+ }
1203
+
1204
+ [data-component~="ui-button"][data-ui-button-state~="variant=field"]:focus-visible {
1205
+ border-color: var(--ui-control-focus, var(--ui-accent-solid));
1206
+ box-shadow:
1207
+ var(--ui-button-shadow, var(--_ui-field-shadow)),
1208
+ var(--ui-control-focus-halo-shadow);
1209
+ }
1210
+
1178
1211
  [data-component~="ui-button"][data-ui-button-state~="variant=ghost"] {
1179
1212
  border-color: transparent;
1180
1213
  background: transparent;
@@ -62,7 +62,7 @@ const propValidityContract = {
62
62
  required: false,
63
63
  target: { attribute: 'clearqueryonblur' },
64
64
  description:
65
- 'In multiple mode, clears unfinished search text when focus leaves the whole combobox, preserving selected items. A controlled query reports query-change\n with an empty query; the consumer must reflect it back. Does not choose or create an option. The default preserves unfinished text. Single mode keeps\n its existing selection and free-text resolution.',
65
+ 'In multiple mode, clears unfinished search text when focus leaves the whole combobox, preserving selected items. A controlled query reports query-change\n with an empty query; the consumer must reflect it back. Does not choose or create an option. The default preserves unfinished text. Single mode keeps its\n existing selection and free-text resolution.',
66
66
  default: false,
67
67
  },
68
68
  clearable: {
@@ -2256,6 +2256,7 @@ function uniqueKeys<T>(
2256
2256
  --_ui-combobox-popup-radius: var(--ui-combobox-popup-radius);
2257
2257
  --_ui-combobox-popup-shadow: var(--ui-combobox-popup-shadow);
2258
2258
 
2259
+ --_row: var(--ui-control-size-md);
2259
2260
  display: grid;
2260
2261
  gap: var(--ui-space-1);
2261
2262
  min-inline-size: 0;
@@ -2277,6 +2278,8 @@ label {
2277
2278
  }
2278
2279
 
2279
2280
  .field {
2281
+ box-sizing: border-box;
2282
+ min-block-size: var(--_row);
2280
2283
  flex: 1 1 auto;
2281
2284
  display: flex;
2282
2285
  min-inline-size: 0;
@@ -2347,14 +2350,14 @@ input {
2347
2350
  appearance: none;
2348
2351
  width: 100%;
2349
2352
  min-inline-size: 0;
2350
- min-block-size: calc(var(--ui-control-size-md) - 2px);
2353
+ min-block-size: calc(var(--_row) - 2 * var(--ui-border-width));
2351
2354
  box-sizing: border-box;
2352
2355
  border: 0;
2353
2356
  border-radius: inherit;
2354
2357
  background: transparent;
2355
2358
  color: inherit;
2356
2359
  font: inherit;
2357
- line-height: var(--ui-line-height-md);
2360
+ line-height: var(--ui-line-height-tight);
2358
2361
  padding: var(--ui-space-2) var(--ui-space-3);
2359
2362
  margin: 0;
2360
2363
  outline: none;
@@ -2363,12 +2366,13 @@ input {
2363
2366
  --_row: var(--ui-control-size-sm);
2364
2367
  }
2365
2368
  [data-component~="ui-combobox"][data-ui-combobox-state~="size=sm"] input {
2366
- min-block-size: calc(var(--_row) - 2px);
2367
2369
  padding: var(--ui-space-1) var(--ui-space-2);
2368
2370
  font-size: var(--ui-font-size-sm);
2369
2371
  }
2372
+ [data-component~="ui-combobox"][data-ui-combobox-state~="size=lg"] {
2373
+ --_row: var(--ui-control-size-lg);
2374
+ }
2370
2375
  [data-component~="ui-combobox"][data-ui-combobox-state~="size=lg"] input {
2371
- min-block-size: calc(var(--ui-control-size-lg) - 2px);
2372
2376
  padding: var(--ui-space-3) var(--ui-space-4);
2373
2377
  font-size: var(--ui-font-size-md);
2374
2378
  }
@@ -2485,9 +2489,12 @@ input:disabled, button:disabled {
2485
2489
  white-space: nowrap;
2486
2490
  }
2487
2491
  @media (pointer: coarse) {
2492
+ [data-component~="ui-combobox"] {
2493
+ --_row: max(var(--ui-control-size-md), var(--ui-control-min-block-size));
2494
+ }
2488
2495
  button {
2489
2496
  min-inline-size: 2.75rem;
2490
- min-block-size: 2.75rem;
2497
+ min-block-size: calc(var(--_row) - 2 * var(--ui-border-width));
2491
2498
  }
2492
2499
  /* Under touch a small field is still a touch-sized target, as a small button is, and keeps
2493
2500
  body-size text: below 16px, iOS zooms the page into the focused field. */
@@ -2511,7 +2518,7 @@ input:disabled, button:disabled {
2511
2518
  while the clear and disclosure controls remain fixed at the end of the field. */
2512
2519
  [data-component~="ui-combobox"][data-ui-combobox-state~="multiple"] .field {
2513
2520
  align-items: center;
2514
- min-block-size: var(--_ui-combobox-item-min-block-size, 2.5rem);
2521
+ min-block-size: var(--_ui-combobox-item-min-block-size, var(--_row));
2515
2522
  padding: var(--ui-space-1);
2516
2523
  }
2517
2524
  [data-component~="ui-combobox"][data-ui-combobox-state~="multiple"] .entry {
package/vue/UiInput.vue CHANGED
@@ -584,7 +584,7 @@ useComponentHost(() => import('../components/ui-input/ui-input.js'), {
584
584
  );
585
585
  color: var(--ui-text-primary);
586
586
  font: inherit;
587
- line-height: var(--ui-line-height-md);
587
+ line-height: var(--ui-line-height-tight);
588
588
  box-shadow: var(--ui-input-shadow, var(--_ui-default-input-shadow, var(--ui-control-inset)));
589
589
  transition:
590
590
  border-color var(--ui-motion-fast) var(--ui-motion-ease),
@@ -614,6 +614,16 @@ useComponentHost(() => import('../components/ui-input/ui-input.js'), {
614
614
  /* Under touch a small field is still a touch-sized target, as a small button is, and keeps
615
615
  body-size text: below 16px, iOS zooms the page into the focused field. */
616
616
  @media (pointer: coarse) {
617
+ [data-component~="ui-input"] {
618
+ min-block-size: var(
619
+ --ui-input-min-block-size,
620
+ var(
621
+ --_ui-default-input-min-block-size,
622
+ max(var(--ui-control-size-md), var(--ui-control-min-block-size))
623
+ )
624
+ );
625
+ }
626
+
617
627
  [data-component~="ui-input"],
618
628
  [data-component~="ui-input"][data-ui-input-state~="size=sm"],
619
629
  [data-component~="ui-input"][data-ui-input-state~="size=lg"] {
@@ -306,6 +306,10 @@ useComponentHost(() => import('../components/ui-input-group/ui-input-group.js'),
306
306
  }
307
307
 
308
308
  @media (pointer: coarse) {
309
+ [data-component~="ui-input-group"] {
310
+ min-block-size: max(var(--ui-control-size-md), var(--ui-control-min-block-size));
311
+ }
312
+
309
313
  [data-component~="ui-input-group"]:has(.field [data-ui-input-state~="size=sm"]) {
310
314
  min-block-size: var(--ui-control-min-block-size);
311
315
  }
package/vue/UiSelect.vue CHANGED
@@ -286,8 +286,8 @@ useComponentHost(() => import('../components/ui-select/ui-select.js'), {
286
286
  color: var(--ui-text-primary);
287
287
  color-scheme: light dark;
288
288
  font: inherit;
289
- line-height: var(--ui-line-height-md);
290
- box-shadow: var(--ui-select-shadow, var(--ui-control-inset));
289
+ line-height: var(--ui-line-height-tight);
290
+ box-shadow: var(--ui-select-shadow, var(--ui-shadow-sm));
291
291
  transition:
292
292
  border-color var(--ui-motion-fast) var(--ui-motion-ease),
293
293
  box-shadow var(--ui-motion-fast) var(--ui-motion-ease),
@@ -320,7 +320,7 @@ useComponentHost(() => import('../components/ui-select/ui-select.js'), {
320
320
  outline: none;
321
321
  box-shadow: var(
322
322
  --ui-select-focus-shadow,
323
- var(--ui-control-inset),
323
+ var(--ui-shadow-sm),
324
324
  var(--ui-control-focus-halo-shadow)
325
325
  );
326
326
  }
@@ -334,7 +334,7 @@ useComponentHost(() => import('../components/ui-select/ui-select.js'), {
334
334
  [data-component~="ui-select"]:is([aria-invalid="true"], :is(:user-invalid, [data-user-invalid])):focus-visible {
335
335
  box-shadow: var(
336
336
  --ui-select-invalid-focus-shadow,
337
- var(--ui-control-inset),
337
+ var(--ui-shadow-sm),
338
338
  0 0 0 calc(var(--ui-focus-width) * 1.5) color-mix(
339
339
  in srgb,
340
340
  var(--ui-select-invalid-border, var(--ui-field-danger, var(--ui-danger-solid))) 22%,
@@ -370,6 +370,10 @@ useComponentHost(() => import('../components/ui-select/ui-select.js'), {
370
370
  /* Under touch a small field is still a touch-sized target, as a small button is, and keeps
371
371
  body-size text: below 16px, iOS zooms the page into the focused field. */
372
372
  @media (pointer: coarse) {
373
+ [data-component~="ui-select"] {
374
+ min-block-size: max(var(--ui-control-size-md), var(--ui-control-min-block-size));
375
+ }
376
+
373
377
  [data-component~="ui-select"][data-ui-select-state~="size=sm"] {
374
378
  min-block-size: var(--ui-control-min-block-size);
375
379
  font-size: inherit;
@@ -297,7 +297,7 @@ var UiButton_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PUR
297
297
  },
298
298
  required: false,
299
299
  target: { attribute: "tone" },
300
- description: "Which colour the button speaks in. It applies to every variant, so an outline, a ghost, and a solid in the same tone are the same action at three volumes:\n tone=\"danger\" variant=\"outline\" is a destructive secondary action. neutral is the quiet one, for a button that should not claim a colour.",
300
+ description: "Which colour the button speaks in. It applies to every variant, so an outline, a ghost, and a solid in the same tone are the same action at three volumes:\n tone=\"danger\" variant=\"outline\" is a destructive secondary action. neutral is the quiet one, for a button that should not claim a colour. The field variant always uses neutral field colors.",
301
301
  values: [
302
302
  "accent",
303
303
  "neutral",
@@ -330,14 +330,15 @@ var UiButton_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PUR
330
330
  },
331
331
  required: false,
332
332
  target: { attribute: "variant" },
333
- description: "Emphasis: how loudly the button asks. solid fills with its tone, outline draws its tone at the edge over a wash of it, ghost carries only text until it is\n hovered, and link is an inline text action that reads as a link. danger is solid in the danger tone, kept for the call sites that name it. card uses the\n same tone on a full-width action with an icon slot, wrapping content, and a centered action slot.",
333
+ description: "Emphasis: how loudly the button asks. solid fills with its tone, outline draws its tone at the edge over a wash of it, ghost carries only text until it is\n hovered, and link is an inline text action that reads as a link. danger is solid in the danger tone, kept for the call sites that name it. card uses the\n same tone on a full-width action with an icon slot, wrapping content, and a centered action slot. field opens a custom value picker: it shares Select's\n quiet outline, neutral text and slightly raised surface, independently of action tone.",
334
334
  values: [
335
335
  "outline",
336
336
  "solid",
337
337
  "danger",
338
338
  "ghost",
339
339
  "link",
340
- "card"
340
+ "card",
341
+ "field"
341
342
  ],
342
343
  default: "outline"
343
344
  }
@@ -765,6 +766,6 @@ var UiButton_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PUR
765
766
  }]]);
766
767
  };
767
768
  }
768
- }), [["__scopeId", "data-v-e56c4e01"]]);
769
+ }), [["__scopeId", "data-v-8789629f"]]);
769
770
  //#endregion
770
771
  export { UiButton_default as t };
@@ -294,7 +294,7 @@ var UiCombobox_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__P
294
294
  },
295
295
  required: false,
296
296
  target: { attribute: "clearqueryonblur" },
297
- description: "In multiple mode, clears unfinished search text when focus leaves the whole combobox, preserving selected items. A controlled query reports query-change\n with an empty query; the consumer must reflect it back. Does not choose or create an option. The default preserves unfinished text. Single mode keeps\n its existing selection and free-text resolution.",
297
+ description: "In multiple mode, clears unfinished search text when focus leaves the whole combobox, preserving selected items. A controlled query reports query-change\n with an empty query; the consumer must reflect it back. Does not choose or create an option. The default preserves unfinished text. Single mode keeps its\n existing selection and free-text resolution.",
298
298
  default: false
299
299
  },
300
300
  clearable: {
@@ -2090,6 +2090,6 @@ var UiCombobox_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__P
2090
2090
  }]]);
2091
2091
  };
2092
2092
  }
2093
- }), [["__scopeId", "data-v-33359776"]]);
2093
+ }), [["__scopeId", "data-v-7a56e205"]]);
2094
2094
  //#endregion
2095
2095
  export { UiCombobox_default as t };
@@ -720,6 +720,6 @@ var UiInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE
720
720
  }]]);
721
721
  };
722
722
  }
723
- }), [["__scopeId", "data-v-1e464f91"]]);
723
+ }), [["__scopeId", "data-v-3c4d919b"]]);
724
724
  //#endregion
725
725
  export { UiInput_default as t };
@@ -212,6 +212,6 @@ var UiInputGroup_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @_
212
212
  }]]);
213
213
  };
214
214
  }
215
- }), [["__scopeId", "data-v-ac4d8095"]]);
215
+ }), [["__scopeId", "data-v-9e6e0392"]]);
216
216
  //#endregion
217
217
  export { UiInputGroup_default as t };
@@ -228,6 +228,6 @@ var UiSelect_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PUR
228
228
  }]]);
229
229
  };
230
230
  }
231
- }), [["__scopeId", "data-v-0c317a58"]]);
231
+ }), [["__scopeId", "data-v-395cd822"]]);
232
232
  //#endregion
233
233
  export { UiSelect_default as t };