@threadlabs/looma 0.15.1 → 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.1",
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",
@@ -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
+ }
@@ -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
  });
@@ -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);
@@ -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 };
@@ -3878,7 +3878,7 @@ svg[data-v-70304a1a-s] {
3878
3878
  syntax: "*";
3879
3879
  inherits: false;
3880
3880
  }
3881
- [data-component~="ui-form-field"][data-v-27bff7f1] {
3881
+ [data-component~="ui-form-field"][data-v-453329c8] {
3882
3882
  /* Hooks do not inherit, so the parts below read them from the root. */
3883
3883
  --_ui-form-field-danger: var(--ui-form-field-danger);
3884
3884
  --_ui-form-field-help-min-block-size: var(--ui-form-field-help-min-block-size);
@@ -3892,38 +3892,55 @@ svg[data-v-70304a1a-s] {
3892
3892
 
3893
3893
  /* Each named region styles whatever fills it, however it was slotted (slot="…" in HTML, #label
3894
3894
  in Vue), and generates no box of its own, so an empty region adds no gap. */
3895
- .label[data-v-27bff7f1],
3896
- .help[data-v-27bff7f1],
3897
- .error[data-v-27bff7f1] {
3895
+ .label[data-v-453329c8],
3896
+ .help[data-v-453329c8],
3897
+ .error[data-v-453329c8] {
3898
3898
  display: contents;
3899
3899
  }
3900
- .label[data-v-27bff7f1] {
3900
+ .label[data-v-453329c8] {
3901
3901
  color: var(--ui-text-secondary);
3902
3902
  font-size: var(--ui-font-size-sm);
3903
3903
  }
3904
- .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] {
3905
3922
  color: var(--ui-text-muted);
3906
3923
  font-size: var(--ui-font-size-sm);
3907
3924
  line-height: var(--ui-line-height-md);
3908
3925
  }
3909
- .help > [data-v-27bff7f1-s] {
3926
+ .help > [data-v-453329c8-s] {
3910
3927
  min-block-size: var(--_ui-form-field-help-min-block-size, 0);
3911
3928
  }
3912
- .error[data-v-27bff7f1] {
3929
+ .error[data-v-453329c8] {
3913
3930
  display: none;
3914
3931
  }
3915
- [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] {
3916
3933
  display: contents;
3917
3934
  }
3918
3935
 
3919
3936
  /* An error reads in the danger colour, the one the invalid control's border takes. */
3920
- .error[data-v-27bff7f1],[role="alert"][data-v-27bff7f1-s] {
3937
+ .error[data-v-453329c8],[role="alert"][data-v-453329c8-s] {
3921
3938
  color: var(--_ui-form-field-danger, var(--ui-field-danger, var(--ui-danger)));
3922
3939
  font-size: var(--ui-font-size-sm);
3923
3940
  }
3924
3941
 
3925
3942
  /* A component that sets its display still honours the hidden attribute on its root. */
3926
- [data-component~="ui-form-field"][hidden][data-v-27bff7f1] {
3943
+ [data-component~="ui-form-field"][hidden][data-v-453329c8] {
3927
3944
  display: none;
3928
3945
  }
3929
3946