@threadlabs/looma 0.15.0 → 0.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@threadlabs/looma",
3
- "version": "0.15.0",
3
+ "version": "0.15.2",
4
4
  "type": "module",
5
5
  "description": "Looma components as HTML, Vue, and plain DOM, with design tokens.",
6
6
  "license": "MIT",
@@ -50,6 +50,9 @@
50
50
  :scope[data-ui-cluster-state~="justify=between"] {
51
51
  justify-content: space-between;
52
52
  }
53
+ :scope[data-ui-cluster-state~="wrap=nowrap"] {
54
+ flex-wrap: nowrap;
55
+ }
53
56
 
54
57
  /* A component that sets its display still honours the hidden attribute on its root. */
55
58
  :scope[hidden] {
@@ -35,6 +35,25 @@
35
35
  font-size: var(--ui-font-size-sm);
36
36
  }
37
37
 
38
+ /* A label action, such as a small help button, sits after the label text in a box one line
39
+ tall, centred on the first line and overflowing it. A button taller than a line of label
40
+ text then never makes this field's label row taller than its neighbours' in a grid row. */
41
+ .label:has(> .label-action > *) {
42
+ display: flex;
43
+ align-items: start;
44
+ gap: var(--ui-space-1);
45
+ }
46
+
47
+ .label-action {
48
+ display: flex;
49
+ align-items: unsafe center;
50
+ block-size: 1lh;
51
+ }
52
+
53
+ .label-action:not(:has(> *)) {
54
+ display: none;
55
+ }
56
+
38
57
  .help {
39
58
  color: var(--ui-text-muted);
40
59
  font-size: var(--ui-font-size-sm);
@@ -62,6 +81,10 @@
62
81
  /* Each named region styles whatever fills it, however it was slotted (slot="…" in HTML, #label
63
82
  in Vue), and generates no box of its own, so an empty region adds no gap. */
64
83
 
84
+ /* A label action, such as a small help button, sits after the label text in a box one line
85
+ tall, centred on the first line and overflowing it. A button taller than a line of label
86
+ text then never makes this field's label row taller than its neighbours' in a grid row. */
87
+
65
88
  .help > :where([data-slotted], [data-slotted] *):is(*) {
66
89
  min-block-size: var(--_ui-form-field-help-min-block-size, 0);
67
90
  }
package/tokens.css CHANGED
@@ -271,3 +271,15 @@
271
271
  from { --_ui-scroll-fade-end: var(--_ui-scroll-fade-size, 24px); }
272
272
  to { --_ui-scroll-fade-end: 0px; }
273
273
  }
274
+
275
+ /* Focus moved by script to a non-interactive target, such as a page's <h1 tabindex="-1"> after
276
+ navigation so a screen reader announces the new page, draws no focus ring: tabindex="-1" keeps
277
+ the target out of the Tab order, so there is no keyboard focus position to show, and the ring
278
+ around a wrapped heading is a jagged, notched box. Controls, links, editable regions, and any
279
+ element with a role (menu items, options, tabs, and other roving-focus widgets) keep their ring.
280
+ Layered, so any page or component focus style still wins. */
281
+ @layer base {
282
+ [tabindex="-1"]:not(a[href], area[href], audio, button, iframe, input, select, summary, textarea, video, [contenteditable], [role]):focus-visible {
283
+ outline: none;
284
+ }
285
+ }
@@ -9,6 +9,7 @@ export interface UiClusterProps {
9
9
  align?: "start" | "center" | "end" | "stretch" | null;
10
10
  gap?: "xs" | "s" | "m" | "l" | "xl" | null;
11
11
  justify?: "start" | "center" | "end" | "between" | null;
12
+ wrap?: "wrap" | "nowrap" | null;
12
13
  attributes?: Readonly<Record<string, string | number | boolean | null | undefined>>;
13
14
  children?: readonly (string | Node)[];
14
15
  slots?: Readonly<Record<string, readonly (string | Node)[]>>;
@@ -8,6 +8,7 @@ export function createUiCluster(options = {}) {
8
8
  const prop0 = componentProps["align"] === undefined ? undefined : componentProps["align"];
9
9
  const prop1 = componentProps["gap"] === undefined ? undefined : componentProps["gap"];
10
10
  const prop2 = componentProps["justify"] === undefined ? undefined : componentProps["justify"];
11
+ const prop3 = componentProps["wrap"] === undefined ? undefined : componentProps["wrap"];
11
12
  const element = document.createElement("div");
12
13
  for (const [name, value] of Object.entries(attributes)) {
13
14
  if (value === null || value === undefined || value === false) continue;
@@ -26,6 +27,7 @@ export function createUiCluster(options = {}) {
26
27
  { name: "align", attribute: "data-align", value: componentProps["align"], type: ["start","center","end","stretch"], required: false },
27
28
  { name: "gap", attribute: "data-gap", value: componentProps["gap"], type: ["xs","s","m","l","xl"], required: false },
28
29
  { name: "justify", attribute: "data-justify", value: componentProps["justify"], type: ["start","center","end","between"], required: false },
30
+ { name: "wrap", attribute: "data-wrap", value: componentProps["wrap"], type: ["wrap","nowrap"], required: false },
29
31
  ]);
30
32
  return element;
31
33
  }
@@ -3,7 +3,7 @@ import { manageComponentLifecycle } from "@nextwebwg/html-next/runtime";
3
3
  import * as controller from "../components/ui-form-field/ui-form-field.js";
4
4
  import "../styles/ui-form-field.css";
5
5
 
6
- const definition = {...{"contract":{"tag":"ui-form-field","props":{"disabled":{"type":"boolean","required":false,"target":{"attribute":"disabled"},"default":false},"invalid":{"type":"boolean","required":false,"target":{"attribute":"invalid"},"default":false},"required":{"type":"boolean","required":false,"target":{"attribute":"required"},"default":false}}},"template":{"kind":"element","name":"div","attributes":[],"children":[{"kind":"element","name":"div","attributes":[{"kind":"literal","name":"class","value":"label"}],"children":[{"kind":"slot","fallback":[],"name":"label"}]},{"kind":"slot"},{"kind":"element","name":"div","attributes":[{"kind":"literal","name":"class","value":"help"}],"children":[{"kind":"slot","fallback":[],"name":"help"}]},{"kind":"element","name":"div","attributes":[{"kind":"literal","name":"class","value":"error"}],"children":[{"kind":"slot","fallback":[],"name":"error"}]}]},"declarations":[],"root":{"kind":"native","element":"div","choices":["div"]}},source:{file:import.meta.url},css:""};
6
+ const definition = {...{"contract":{"tag":"ui-form-field","props":{"disabled":{"type":"boolean","required":false,"target":{"attribute":"disabled"},"default":false},"invalid":{"type":"boolean","required":false,"target":{"attribute":"invalid"},"default":false},"required":{"type":"boolean","required":false,"target":{"attribute":"required"},"default":false}}},"template":{"kind":"element","name":"div","attributes":[],"children":[{"kind":"element","name":"div","attributes":[{"kind":"literal","name":"class","value":"label"}],"children":[{"kind":"slot","fallback":[],"name":"label"},{"kind":"element","name":"span","attributes":[{"kind":"literal","name":"class","value":"label-action"}],"children":[{"kind":"slot","fallback":[],"name":"label-action"}]}]},{"kind":"slot"},{"kind":"element","name":"div","attributes":[{"kind":"literal","name":"class","value":"help"}],"children":[{"kind":"slot","fallback":[],"name":"help"}]},{"kind":"element","name":"div","attributes":[{"kind":"literal","name":"class","value":"error"}],"children":[{"kind":"slot","fallback":[],"name":"error"}]}]},"declarations":[],"root":{"kind":"native","element":"div","choices":["div"]}},source:{file:import.meta.url},css:""};
7
7
 
8
8
  export function createUiFormField(options = {}) {
9
9
  const { attributes = {}, children = [], slots = {}, ...componentProps } = options;
@@ -24,6 +24,17 @@ export function createUiFormField(options = {}) {
24
24
  }
25
25
  } else {
26
26
  }
27
+ const element1 = document.createElement("span");
28
+ element1.setAttribute("class", "label-action");
29
+ if (slots["label-action"] ?? [].length > 0) {
30
+ for (const child of slots["label-action"] ?? []) {
31
+ const node = typeof child === "string" ? document.createTextNode(child) : child;
32
+ projected.push([node, "label-action"]);
33
+ element1.append(node);
34
+ }
35
+ } else {
36
+ }
37
+ element0.append(element1);
27
38
  element.append(element0);
28
39
  if (children.length > 0) {
29
40
  for (const child of children) {
@@ -33,28 +44,28 @@ export function createUiFormField(options = {}) {
33
44
  }
34
45
  } else {
35
46
  }
36
- const element1 = document.createElement("div");
37
- element1.setAttribute("class", "help");
47
+ const element2 = document.createElement("div");
48
+ element2.setAttribute("class", "help");
38
49
  if (slots["help"] ?? [].length > 0) {
39
50
  for (const child of slots["help"] ?? []) {
40
51
  const node = typeof child === "string" ? document.createTextNode(child) : child;
41
52
  projected.push([node, "help"]);
42
- element1.append(node);
53
+ element2.append(node);
43
54
  }
44
55
  } else {
45
56
  }
46
- element.append(element1);
47
- const element2 = document.createElement("div");
48
- element2.setAttribute("class", "error");
57
+ element.append(element2);
58
+ const element3 = document.createElement("div");
59
+ element3.setAttribute("class", "error");
49
60
  if (slots["error"] ?? [].length > 0) {
50
61
  for (const child of slots["error"] ?? []) {
51
62
  const node = typeof child === "string" ? document.createTextNode(child) : child;
52
63
  projected.push([node, "error"]);
53
- element2.append(node);
64
+ element3.append(node);
54
65
  }
55
66
  } else {
56
67
  }
57
- element.append(element2);
68
+ element.append(element3);
58
69
  manageComponentLifecycle(element, definition, { props: componentProps, projected,
59
70
  controller,
60
71
  });
@@ -2,6 +2,7 @@ type __VLS_Props = {
2
2
  align?: 'start' | 'center' | 'end' | 'stretch';
3
3
  gap?: 'xs' | 's' | 'm' | 'l' | 'xl';
4
4
  justify?: 'start' | 'center' | 'end' | 'between';
5
+ wrap?: 'wrap' | 'nowrap';
5
6
  };
6
7
  declare var __VLS_1: {};
7
8
  type __VLS_Slots = {} & {
package/vue/UiCluster.vue CHANGED
@@ -8,6 +8,7 @@ const props = defineProps<{
8
8
  align?: 'start' | 'center' | 'end' | 'stretch'
9
9
  gap?: 'xs' | 's' | 'm' | 'l' | 'xl'
10
10
  justify?: 'start' | 'center' | 'end' | 'between'
11
+ wrap?: 'wrap' | 'nowrap'
11
12
  }>()
12
13
 
13
14
  /** The values the styles' :host-state() rules test. */
@@ -16,6 +17,7 @@ const hostState = computed(() =>
16
17
  props.gap && `gap gap=${props.gap}`,
17
18
  props.align && `align align=${props.align}`,
18
19
  props.justify && `justify justify=${props.justify}`,
20
+ props.wrap && `wrap wrap=${props.wrap}`,
19
21
  ].filter(Boolean).join(' ')
20
22
  )
21
23
  </script>
@@ -77,6 +79,9 @@ const hostState = computed(() =>
77
79
  [data-component~="ui-cluster"][data-ui-cluster-state~="justify=between"] {
78
80
  justify-content: space-between;
79
81
  }
82
+ [data-component~="ui-cluster"][data-ui-cluster-state~="wrap=nowrap"] {
83
+ flex-wrap: nowrap;
84
+ }
80
85
 
81
86
  /* A component that sets its display still honours the hidden attribute on its root. */
82
87
  [data-component~="ui-cluster"][hidden] {
@@ -3,15 +3,17 @@ type __VLS_Props = {
3
3
  invalid?: boolean;
4
4
  required?: boolean;
5
5
  };
6
- declare var __VLS_1: {}, __VLS_3: {}, __VLS_5: {}, __VLS_7: {};
6
+ declare var __VLS_1: {}, __VLS_3: {}, __VLS_5: {}, __VLS_7: {}, __VLS_9: {};
7
7
  type __VLS_Slots = {} & {
8
8
  label?: (props: typeof __VLS_1) => any;
9
9
  } & {
10
- default?: (props: typeof __VLS_3) => any;
10
+ 'label-action'?: (props: typeof __VLS_3) => any;
11
11
  } & {
12
- help?: (props: typeof __VLS_5) => any;
12
+ default?: (props: typeof __VLS_5) => any;
13
13
  } & {
14
- error?: (props: typeof __VLS_7) => any;
14
+ help?: (props: typeof __VLS_7) => any;
15
+ } & {
16
+ error?: (props: typeof __VLS_9) => any;
15
17
  };
16
18
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
17
19
  invalid: boolean;
@@ -44,7 +44,10 @@ useComponentHost(controllerModule.default, {
44
44
  :data-ui-form-field-state="hostState || undefined"
45
45
  ref="root"
46
46
  >
47
- <div class="label"><slot name="label" /></div>
47
+ <div class="label">
48
+ <slot name="label" />
49
+ <span class="label-action"><slot name="label-action" /></span>
50
+ </div>
48
51
  <slot />
49
52
  <div class="help"><slot name="help" /></div>
50
53
  <div class="error"><slot name="error" /></div>
@@ -87,6 +90,25 @@ useComponentHost(controllerModule.default, {
87
90
  font-size: var(--ui-font-size-sm);
88
91
  }
89
92
 
93
+ /* A label action, such as a small help button, sits after the label text in a box one line
94
+ tall, centred on the first line and overflowing it. A button taller than a line of label
95
+ text then never makes this field's label row taller than its neighbours' in a grid row. */
96
+ .label:has(> .label-action > *) {
97
+ display: flex;
98
+ align-items: start;
99
+ gap: var(--ui-space-1);
100
+ }
101
+
102
+ .label-action {
103
+ display: flex;
104
+ align-items: unsafe center;
105
+ block-size: 1lh;
106
+ }
107
+
108
+ .label-action:not(:has(> *)) {
109
+ display: none;
110
+ }
111
+
90
112
  .help {
91
113
  color: var(--ui-text-muted);
92
114
  font-size: var(--ui-font-size-sm);
@@ -10,7 +10,8 @@ var UiCluster_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
10
10
  props: {
11
11
  align: {},
12
12
  gap: {},
13
- justify: {}
13
+ justify: {},
14
+ wrap: {}
14
15
  },
15
16
  setup(__props) {
16
17
  const props = __props;
@@ -18,12 +19,13 @@ var UiCluster_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
18
19
  const hostState = computed(() => [
19
20
  props.gap && `gap gap=${props.gap}`,
20
21
  props.align && `align align=${props.align}`,
21
- props.justify && `justify justify=${props.justify}`
22
+ props.justify && `justify justify=${props.justify}`,
23
+ props.wrap && `wrap wrap=${props.wrap}`
22
24
  ].filter(Boolean).join(" "));
23
25
  return (_ctx, _cache) => {
24
26
  return openBlock(), createElementBlock("div", mergeProps({ "data-component": "ui-cluster" }, _ctx.$attrs, { "data-ui-cluster-state": hostState.value || void 0 }), [renderSlot(_ctx.$slots, "default", {}, void 0, true)], 16, _hoisted_1);
25
27
  };
26
28
  }
27
- }), [["__scopeId", "data-v-81b18ffc"]]);
29
+ }), [["__scopeId", "data-v-b98d575c"]]);
28
30
  //#endregion
29
31
  export { UiCluster_default as t };
@@ -5,8 +5,9 @@ import * as controllerModule from "@threadlabs/looma/components/ui-form-field/ui
5
5
  //#region .build/vue/UiFormField.vue?vue&type=script&setup=true&lang.ts
6
6
  var _hoisted_1 = ["data-ui-form-field-state"];
7
7
  var _hoisted_2 = { class: "label" };
8
- var _hoisted_3 = { class: "help" };
9
- var _hoisted_4 = { class: "error" };
8
+ var _hoisted_3 = { class: "label-action" };
9
+ var _hoisted_4 = { class: "help" };
10
+ var _hoisted_5 = { class: "error" };
10
11
  //#endregion
11
12
  //#region .build/vue/UiFormField.vue
12
13
  var UiFormField_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
@@ -43,13 +44,13 @@ var UiFormField_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__
43
44
  ref_key: "root",
44
45
  ref: root
45
46
  }), [
46
- createElementVNode("div", _hoisted_2, [renderSlot(_ctx.$slots, "label")]),
47
+ createElementVNode("div", _hoisted_2, [renderSlot(_ctx.$slots, "label"), createElementVNode("span", _hoisted_3, [renderSlot(_ctx.$slots, "label-action")])]),
47
48
  renderSlot(_ctx.$slots, "default"),
48
- createElementVNode("div", _hoisted_3, [renderSlot(_ctx.$slots, "help")]),
49
- createElementVNode("div", _hoisted_4, [renderSlot(_ctx.$slots, "error")])
49
+ createElementVNode("div", _hoisted_4, [renderSlot(_ctx.$slots, "help")]),
50
+ createElementVNode("div", _hoisted_5, [renderSlot(_ctx.$slots, "error")])
50
51
  ], 16, _hoisted_1);
51
52
  };
52
53
  }
53
- }), [["__scopeId", "data-v-27bff7f1"]]);
54
+ }), [["__scopeId", "data-v-453329c8"]]);
54
55
  //#endregion
55
56
  export { UiFormField_default as t };
@@ -1240,7 +1240,7 @@ input[data-v-5e151d99]:focus-visible {
1240
1240
  syntax: "*";
1241
1241
  inherits: false;
1242
1242
  }
1243
- [data-component~="ui-cluster"][data-v-81b18ffc] {
1243
+ [data-component~="ui-cluster"][data-v-b98d575c] {
1244
1244
  --_layout-gap: var(--ui-space-3);
1245
1245
  display: flex;
1246
1246
  flex-wrap: wrap;
@@ -1250,42 +1250,45 @@ input[data-v-5e151d99]:focus-visible {
1250
1250
  max-inline-size: 100%;
1251
1251
  margin: 0;
1252
1252
  }
1253
- [data-component~="ui-cluster"][data-ui-cluster-state~="gap=xs"][data-v-81b18ffc] {
1253
+ [data-component~="ui-cluster"][data-ui-cluster-state~="gap=xs"][data-v-b98d575c] {
1254
1254
  --_layout-gap: var(--ui-space-2);
1255
1255
  }
1256
- [data-component~="ui-cluster"][data-ui-cluster-state~="gap=s"][data-v-81b18ffc] {
1256
+ [data-component~="ui-cluster"][data-ui-cluster-state~="gap=s"][data-v-b98d575c] {
1257
1257
  --_layout-gap: var(--ui-space-3);
1258
1258
  }
1259
- [data-component~="ui-cluster"][data-ui-cluster-state~="gap=m"][data-v-81b18ffc] {
1259
+ [data-component~="ui-cluster"][data-ui-cluster-state~="gap=m"][data-v-b98d575c] {
1260
1260
  --_layout-gap: var(--ui-space-4);
1261
1261
  }
1262
- [data-component~="ui-cluster"][data-ui-cluster-state~="gap=l"][data-v-81b18ffc] {
1262
+ [data-component~="ui-cluster"][data-ui-cluster-state~="gap=l"][data-v-b98d575c] {
1263
1263
  --_layout-gap: var(--ui-space-5);
1264
1264
  }
1265
- [data-component~="ui-cluster"][data-ui-cluster-state~="gap=xl"][data-v-81b18ffc] {
1265
+ [data-component~="ui-cluster"][data-ui-cluster-state~="gap=xl"][data-v-b98d575c] {
1266
1266
  --_layout-gap: var(--ui-space-6);
1267
1267
  }
1268
- [data-component~="ui-cluster"][data-ui-cluster-state~="align=start"][data-v-81b18ffc] {
1268
+ [data-component~="ui-cluster"][data-ui-cluster-state~="align=start"][data-v-b98d575c] {
1269
1269
  align-items: flex-start;
1270
1270
  }
1271
- [data-component~="ui-cluster"][data-ui-cluster-state~="align=end"][data-v-81b18ffc] {
1271
+ [data-component~="ui-cluster"][data-ui-cluster-state~="align=end"][data-v-b98d575c] {
1272
1272
  align-items: flex-end;
1273
1273
  }
1274
- [data-component~="ui-cluster"][data-ui-cluster-state~="align=stretch"][data-v-81b18ffc] {
1274
+ [data-component~="ui-cluster"][data-ui-cluster-state~="align=stretch"][data-v-b98d575c] {
1275
1275
  align-items: stretch;
1276
1276
  }
1277
- [data-component~="ui-cluster"][data-ui-cluster-state~="justify=center"][data-v-81b18ffc] {
1277
+ [data-component~="ui-cluster"][data-ui-cluster-state~="justify=center"][data-v-b98d575c] {
1278
1278
  justify-content: center;
1279
1279
  }
1280
- [data-component~="ui-cluster"][data-ui-cluster-state~="justify=end"][data-v-81b18ffc] {
1280
+ [data-component~="ui-cluster"][data-ui-cluster-state~="justify=end"][data-v-b98d575c] {
1281
1281
  justify-content: flex-end;
1282
1282
  }
1283
- [data-component~="ui-cluster"][data-ui-cluster-state~="justify=between"][data-v-81b18ffc] {
1283
+ [data-component~="ui-cluster"][data-ui-cluster-state~="justify=between"][data-v-b98d575c] {
1284
1284
  justify-content: space-between;
1285
1285
  }
1286
+ [data-component~="ui-cluster"][data-ui-cluster-state~="wrap=nowrap"][data-v-b98d575c] {
1287
+ flex-wrap: nowrap;
1288
+ }
1286
1289
 
1287
1290
  /* A component that sets its display still honours the hidden attribute on its root. */
1288
- [data-component~="ui-cluster"][hidden][data-v-81b18ffc] {
1291
+ [data-component~="ui-cluster"][hidden][data-v-b98d575c] {
1289
1292
  display: none;
1290
1293
  }
1291
1294
 
@@ -3875,7 +3878,7 @@ svg[data-v-70304a1a-s] {
3875
3878
  syntax: "*";
3876
3879
  inherits: false;
3877
3880
  }
3878
- [data-component~="ui-form-field"][data-v-27bff7f1] {
3881
+ [data-component~="ui-form-field"][data-v-453329c8] {
3879
3882
  /* Hooks do not inherit, so the parts below read them from the root. */
3880
3883
  --_ui-form-field-danger: var(--ui-form-field-danger);
3881
3884
  --_ui-form-field-help-min-block-size: var(--ui-form-field-help-min-block-size);
@@ -3889,38 +3892,55 @@ svg[data-v-70304a1a-s] {
3889
3892
 
3890
3893
  /* Each named region styles whatever fills it, however it was slotted (slot="…" in HTML, #label
3891
3894
  in Vue), and generates no box of its own, so an empty region adds no gap. */
3892
- .label[data-v-27bff7f1],
3893
- .help[data-v-27bff7f1],
3894
- .error[data-v-27bff7f1] {
3895
+ .label[data-v-453329c8],
3896
+ .help[data-v-453329c8],
3897
+ .error[data-v-453329c8] {
3895
3898
  display: contents;
3896
3899
  }
3897
- .label[data-v-27bff7f1] {
3900
+ .label[data-v-453329c8] {
3898
3901
  color: var(--ui-text-secondary);
3899
3902
  font-size: var(--ui-font-size-sm);
3900
3903
  }
3901
- .help[data-v-27bff7f1] {
3904
+
3905
+ /* A label action, such as a small help button, sits after the label text in a box one line
3906
+ tall, centred on the first line and overflowing it. A button taller than a line of label
3907
+ text then never makes this field's label row taller than its neighbours' in a grid row. */
3908
+ .label[data-v-453329c8]:has(> .label-action > *) {
3909
+ display: flex;
3910
+ align-items: start;
3911
+ gap: var(--ui-space-1);
3912
+ }
3913
+ .label-action[data-v-453329c8] {
3914
+ display: flex;
3915
+ align-items: unsafe center;
3916
+ block-size: 1lh;
3917
+ }
3918
+ .label-action[data-v-453329c8]:not(:has(> *)) {
3919
+ display: none;
3920
+ }
3921
+ .help[data-v-453329c8] {
3902
3922
  color: var(--ui-text-muted);
3903
3923
  font-size: var(--ui-font-size-sm);
3904
3924
  line-height: var(--ui-line-height-md);
3905
3925
  }
3906
- .help > [data-v-27bff7f1-s] {
3926
+ .help > [data-v-453329c8-s] {
3907
3927
  min-block-size: var(--_ui-form-field-help-min-block-size, 0);
3908
3928
  }
3909
- .error[data-v-27bff7f1] {
3929
+ .error[data-v-453329c8] {
3910
3930
  display: none;
3911
3931
  }
3912
- [data-component~="ui-form-field"][data-ui-form-field-state~="invalid"] .error[data-v-27bff7f1] {
3932
+ [data-component~="ui-form-field"][data-ui-form-field-state~="invalid"] .error[data-v-453329c8] {
3913
3933
  display: contents;
3914
3934
  }
3915
3935
 
3916
3936
  /* An error reads in the danger colour, the one the invalid control's border takes. */
3917
- .error[data-v-27bff7f1],[role="alert"][data-v-27bff7f1-s] {
3937
+ .error[data-v-453329c8],[role="alert"][data-v-453329c8-s] {
3918
3938
  color: var(--_ui-form-field-danger, var(--ui-field-danger, var(--ui-danger)));
3919
3939
  font-size: var(--ui-font-size-sm);
3920
3940
  }
3921
3941
 
3922
3942
  /* A component that sets its display still honours the hidden attribute on its root. */
3923
- [data-component~="ui-form-field"][hidden][data-v-27bff7f1] {
3943
+ [data-component~="ui-form-field"][hidden][data-v-453329c8] {
3924
3944
  display: none;
3925
3945
  }
3926
3946