@threadlabs/looma 0.17.4 → 0.18.0

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 (47) hide show
  1. package/components/ui-avatar/ui-avatar.html +8 -4
  2. package/components/ui-badge/ui-badge.html +28 -34
  3. package/components/ui-combobox/ui-combobox.html +4 -48
  4. package/components/ui-input/ui-input.html +1 -1
  5. package/components/ui-input-group/ui-input-group.html +1 -1
  6. package/components/ui-listbox/ui-listbox.html +88 -12
  7. package/components/ui-listbox/ui-listbox.js +162 -18
  8. package/components/ui-select/ui-select.html +1 -1
  9. package/components/ui-spinner/ui-spinner.html +47 -14
  10. package/components/ui-textarea/ui-textarea.html +1 -1
  11. package/dist/index.js +1 -1
  12. package/editor/chunks/extensions.js +5 -0
  13. package/package.json +1 -1
  14. package/styles/ui-avatar.css +6 -2
  15. package/styles/ui-badge.css +30 -40
  16. package/styles/ui-combobox.css +0 -52
  17. package/styles/ui-input-group.css +1 -1
  18. package/styles/ui-input.css +1 -1
  19. package/styles/ui-listbox.css +33 -8
  20. package/styles/ui-select.css +1 -1
  21. package/styles/ui-spinner.css +40 -15
  22. package/styles/ui-textarea.css +1 -1
  23. package/tokens.css +67 -2
  24. package/vanilla/UiCombobox.js +9 -1
  25. package/vanilla/UiListbox.d.ts +3 -1
  26. package/vanilla/UiListbox.js +65 -18
  27. package/vanilla/UiSpinner.js +19 -1
  28. package/vue/UiAvatar.vue +11 -2
  29. package/vue/UiBadge.vue +30 -36
  30. package/vue/UiCombobox.vue +4 -49
  31. package/vue/UiInput.vue +1 -1
  32. package/vue/UiInputGroup.vue +1 -1
  33. package/vue/UiListbox.d.ts +18 -5
  34. package/vue/UiListbox.vue +142 -25
  35. package/vue/UiSelect.vue +1 -1
  36. package/vue/UiSpinner.vue +47 -14
  37. package/vue/UiTextarea.vue +1 -1
  38. package/vue/chunks/UiAvatar.js +2 -1
  39. package/vue/chunks/UiBadge.js +1 -1
  40. package/vue/chunks/UiCombobox.js +11 -2
  41. package/vue/chunks/UiInput.js +1 -1
  42. package/vue/chunks/UiInputGroup.js +1 -1
  43. package/vue/chunks/UiListbox.js +134 -20
  44. package/vue/chunks/UiSelect.js +1 -1
  45. package/vue/chunks/UiSpinner.js +29 -3
  46. package/vue/chunks/UiTextarea.js +1 -1
  47. package/vue/components.css +248 -251
package/vue/UiSpinner.vue CHANGED
@@ -45,16 +45,21 @@ function attribute(value: unknown): any {
45
45
  :aria-label="typeof (({ true: label, false: null })[`${label !== ''}`]) === 'boolean'
46
46
  ? String(({ true: label, false: null })[`${label !== ''}`])
47
47
  : attribute(({ true: label, false: null })[`${label !== ''}`])"
48
+ :aria-live="typeof (({ true: 'polite', false: null })[`${label !== ''}`]) === 'boolean'
49
+ ? String(({ true: 'polite', false: null })[`${label !== ''}`])
50
+ : attribute(({ true: 'polite', false: null })[`${label !== ''}`])"
48
51
  :aria-hidden="typeof (({ true: null, false: 'true' })[`${label !== ''}`]) === 'boolean'
49
52
  ? String(({ true: null, false: 'true' })[`${label !== ''}`])
50
53
  : attribute(({ true: null, false: 'true' })[`${label !== ''}`])"
51
54
  :data-ui-spinner-state="hostState || undefined"
52
- ></span>
55
+ ><svg viewBox="0 0 50 50" fill="none" aria-hidden="true" focusable="false">
56
+ <circle class="track" cx="25" cy="25" r="20"></circle>
57
+ <circle class="arc" cx="25" cy="25" r="20"></circle>
58
+ </svg></span>
53
59
  </template>
54
60
 
55
61
  <style scoped>
56
- /* Something is on its way. The ring chases in the current text colour; with reduced motion it holds still,
57
- and the label (or the text beside it) still says what is happening. */
62
+ /* A rounded SVG arc grows and recedes over a quiet full-circle track. */
58
63
  [data-component~="ui-spinner"] {
59
64
  display: inline-flex;
60
65
  flex: none;
@@ -63,34 +68,62 @@ function attribute(value: unknown): any {
63
68
  box-sizing: border-box;
64
69
  inline-size: 1em;
65
70
  block-size: 1em;
66
- border: 2px solid transparent;
67
- border-block-start-color: currentColor;
68
- border-inline-end-color: color-mix(in srgb, currentColor 52%, transparent);
69
- border-radius: 50%;
70
71
  color: currentColor;
71
72
  font-size: var(--ui-font-size-lg);
72
73
  line-height: 1;
73
- animation: ui-spinner-chase 1s linear infinite;
74
74
  }
75
75
 
76
76
  [data-component~="ui-spinner"][data-ui-spinner-state~="size=sm"] {
77
77
  font-size: var(--ui-font-size-md);
78
78
  }
79
79
 
80
- @keyframes ui-spinner-chase {
80
+ svg {
81
+ display: block;
82
+ inline-size: 100%;
83
+ block-size: 100%;
84
+ animation: ui-spinner-rotate 2s linear infinite;
85
+ }
86
+
87
+ circle {
88
+ stroke-width: 3;
89
+ stroke-linecap: round;
90
+ }
91
+
92
+ .track {
93
+ stroke: color-mix(in srgb, currentColor 16%, transparent);
94
+ }
95
+
96
+ .arc {
97
+ stroke: currentColor;
98
+ stroke-dasharray: 90, 150;
99
+ stroke-dashoffset: 0;
100
+ animation: ui-spinner-dash 1.5s ease-in-out infinite;
101
+ }
102
+
103
+ @keyframes ui-spinner-rotate {
104
+ 100% {
105
+ transform: rotate(1turn);
106
+ }
107
+ }
108
+
109
+ @keyframes ui-spinner-dash {
81
110
  0% {
82
- transform: rotate(0);
111
+ stroke-dasharray: 1, 150;
112
+ stroke-dashoffset: 0;
83
113
  }
84
- 70% {
85
- transform: rotate(160deg);
114
+ 50% {
115
+ stroke-dasharray: 90, 150;
116
+ stroke-dashoffset: -35;
86
117
  }
87
118
  100% {
88
- transform: rotate(1turn);
119
+ stroke-dasharray: 90, 150;
120
+ stroke-dashoffset: -124;
89
121
  }
90
122
  }
91
123
 
92
124
  @media (prefers-reduced-motion: reduce) {
93
- [data-component~="ui-spinner"] {
125
+ svg,
126
+ .arc {
94
127
  animation: none;
95
128
  }
96
129
  }
@@ -129,7 +129,7 @@ useComponentHost(controllerModule.default, {
129
129
  opacity: 1;
130
130
  }
131
131
 
132
- [data-component~="ui-textarea"]:hover:not(:focus):not(:disabled) {
132
+ [data-component~="ui-textarea"]:hover:not(:focus):not(:disabled):not(:read-only):not([aria-invalid="true"]):not(:is(:user-invalid, [data-user-invalid])) {
133
133
  border-color: var(--ui-control-border-hover, var(--ui-text-secondary));
134
134
  }
135
135
 
@@ -41,6 +41,7 @@ var UiAvatar_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PUR
41
41
  /** The values the styles' :host-state() rules test. */
42
42
  const hostState = computed(() => [
43
43
  props.size && `size size=${props.size}`,
44
+ hasImage.value && "hasImage",
44
45
  props.shape && `shape shape=${props.shape}`,
45
46
  props.active && "active"
46
47
  ].filter(Boolean).join(" "));
@@ -80,6 +81,6 @@ var UiAvatar_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PUR
80
81
  ], 16, _hoisted_1);
81
82
  };
82
83
  }
83
- }), [["__scopeId", "data-v-f9a69ac3"]]);
84
+ }), [["__scopeId", "data-v-f0891fb1"]]);
84
85
  //#endregion
85
86
  export { UiAvatar_default as t };
@@ -24,6 +24,6 @@ var UiBadge_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE
24
24
  return openBlock(), createElementBlock("span", mergeProps({ "data-component": "ui-badge" }, _ctx.$attrs, { "data-ui-badge-state": hostState.value || void 0 }), [renderSlot(_ctx.$slots, "default", {}, void 0, true)], 16, _hoisted_1);
25
25
  };
26
26
  }
27
- }), [["__scopeId", "data-v-38a29166"]]);
27
+ }), [["__scopeId", "data-v-fe098822"]]);
28
28
  //#endregion
29
29
  export { UiBadge_default as t };
@@ -55,6 +55,7 @@ var _hoisted_16 = {
55
55
  class: "group"
56
56
  };
57
57
  var _hoisted_17 = [
58
+ "data-multiple",
58
59
  "id",
59
60
  "aria-selected",
60
61
  "aria-disabled",
@@ -74,7 +75,11 @@ var _hoisted_23 = {
74
75
  key: 1,
75
76
  class: "primary"
76
77
  };
77
- var _hoisted_24 = ["id", "data-index"];
78
+ var _hoisted_24 = [
79
+ "data-multiple",
80
+ "id",
81
+ "data-index"
82
+ ];
78
83
  var _hoisted_25 = {
79
84
  key: 1,
80
85
  class: "message"
@@ -521,6 +526,8 @@ var UiCombobox_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__P
521
526
  key: row.id,
522
527
  class: normalizeClass(["option", { active: row.index === active.value }]),
523
528
  role: "option",
529
+ "data-ui-choice-option": "",
530
+ "data-multiple": __props.multiple ? "" : void 0,
524
531
  id: `${uid.value}-option-${row.index}`,
525
532
  "aria-selected": row.selected,
526
533
  "aria-disabled": row.disabled,
@@ -548,7 +555,9 @@ var UiCombobox_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__P
548
555
  key: 0,
549
556
  class: normalizeClass(["option", { active: createIndex.value === active.value }]),
550
557
  role: "option",
558
+ "data-ui-choice-option": "",
551
559
  "aria-selected": "false",
560
+ "data-multiple": __props.multiple ? "" : void 0,
552
561
  id: `${uid.value}-option-${createIndex.value}`,
553
562
  "data-index": createIndex.value
554
563
  }, toDisplayString(`Create “${raw.value}”`), 11, _hoisted_24)) : createCommentVNode("v-if", true),
@@ -567,6 +576,6 @@ var UiCombobox_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__P
567
576
  ], 16, _hoisted_1);
568
577
  };
569
578
  }
570
- }), [["__scopeId", "data-v-87a4808b"]]);
579
+ }), [["__scopeId", "data-v-f90db7fa"]]);
571
580
  //#endregion
572
581
  export { UiCombobox_default as t };
@@ -66,6 +66,6 @@ var UiInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE
66
66
  }), null, 16, _hoisted_1)), [[vModelDynamic, model.value]]);
67
67
  };
68
68
  }
69
- }), [["__scopeId", "data-v-aa3c35f4"]]);
69
+ }), [["__scopeId", "data-v-0d457505"]]);
70
70
  //#endregion
71
71
  export { UiInput_default as t };
@@ -41,6 +41,6 @@ var UiInputGroup_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @_
41
41
  ], 16, _hoisted_1);
42
42
  };
43
43
  }
44
- }), [["__scopeId", "data-v-51eefe72"]]);
44
+ }), [["__scopeId", "data-v-5ed34845"]]);
45
45
  //#endregion
46
46
  export { UiInputGroup_default as t };
@@ -1,14 +1,35 @@
1
1
  import { t as _plugin_vue_export_helper_default } from "./_plugin-vue_export-helper.js";
2
2
  import { n as useComponentHost, t as createDispatch } from "./host.js";
3
- import { computed, createElementBlock, defineComponent, mergeProps, openBlock, ref, renderSlot, vModelSelect, withDirectives } from "vue";
3
+ import { t as UiScrollArea_default } from "./UiScrollArea.js";
4
+ import { Fragment, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, mergeProps, normalizeClass, openBlock, ref, renderList, renderSlot, toDisplayString, useTemplateRef, withCtx } from "vue";
4
5
  import * as controllerModule from "@threadlabs/looma/components/ui-listbox/ui-listbox.js";
5
6
  //#region .build/vue/UiListbox.vue?vue&type=script&setup=true&lang.ts
6
7
  var _hoisted_1 = [
8
+ "tabindex",
9
+ "aria-multiselectable",
10
+ "aria-disabled",
11
+ "aria-required",
12
+ "aria-invalid"
13
+ ];
14
+ var _hoisted_2 = [
15
+ "data-multiple",
16
+ "id",
17
+ "data-index",
18
+ "aria-selected",
19
+ "aria-disabled"
20
+ ];
21
+ var _hoisted_3 = [
7
22
  "size",
8
23
  "multiple",
9
24
  "disabled",
10
25
  "required",
11
- "aria-invalid"
26
+ "name"
27
+ ];
28
+ var _hoisted_4 = ["name", "value"];
29
+ var _hoisted_5 = [
30
+ "required",
31
+ "checked",
32
+ "disabled"
12
33
  ];
13
34
  //#endregion
14
35
  //#region .build/vue/UiListbox.vue
@@ -28,43 +49,136 @@ var UiListbox_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
28
49
  type: Boolean,
29
50
  default: false
30
51
  },
52
+ name: { default: "" },
31
53
  required: {
32
54
  type: Boolean,
33
55
  default: false
34
56
  },
35
57
  rows: { default: 5 },
36
58
  value: {},
37
- values: {},
38
- modelValue: {}
59
+ values: {}
39
60
  },
40
- emits: ["update:modelValue"],
61
+ emits: [
62
+ "update:value",
63
+ "update:values",
64
+ "change"
65
+ ],
41
66
  setup(__props, { emit: __emit }) {
42
67
  const props = __props;
43
68
  const emit = __emit;
44
- const model = computed({
45
- get: () => props.modelValue ?? props.value ?? void 0,
46
- set: (value) => emit("update:modelValue", value)
47
- });
48
69
  const root = ref(null);
49
- const dispatch = createDispatch(root);
70
+ const choicesElement = useTemplateRef("choices");
71
+ const optionsElement = useTemplateRef("options");
72
+ const validityElement = useTemplateRef("validity");
73
+ const choices = ref([]);
74
+ const selection = ref([]);
75
+ const hasSelection = ref(false);
76
+ const active = ref(-1);
77
+ const uid = ref("");
78
+ const validationError = ref(false);
79
+ const isChangeDetail = (detail) => detail !== null && typeof detail === "object" && !Array.isArray(detail) && (typeof detail["value"] === "string" || detail["value"] === null) && Array.isArray(detail["values"]) && detail["values"].every((item) => typeof item === "string") && (detail["trigger"] === "keyboard" || detail["trigger"] === "pointer") && Object.keys(detail).every((key) => [
80
+ "value",
81
+ "values",
82
+ "trigger"
83
+ ].includes(key));
84
+ const dispatch = createDispatch(root, emit, {
85
+ checks: { change: isChangeDetail },
86
+ modeled: ["value", "values"]
87
+ });
50
88
  useComponentHost(controllerModule.default, {
51
89
  root,
52
90
  dispatch,
53
- props
91
+ props,
92
+ refs: {
93
+ choices: choicesElement,
94
+ options: optionsElement,
95
+ validity: validityElement
96
+ },
97
+ state: {
98
+ choices,
99
+ selection,
100
+ hasSelection,
101
+ active,
102
+ uid,
103
+ validationError
104
+ }
54
105
  });
106
+ function text(value) {
107
+ if (Array.isArray(value)) return value.map(text).join(" ");
108
+ return value === null || value === void 0 || typeof value === "object" ? "" : String(value);
109
+ }
110
+ function attribute(value) {
111
+ if (value === null || value === void 0 || value === false || typeof value === "object" && !Array.isArray(value)) return void 0;
112
+ return value === true ? "" : text(value);
113
+ }
55
114
  return (_ctx, _cache) => {
56
- return withDirectives((openBlock(), createElementBlock("select", mergeProps({ "data-component": "ui-listbox" }, _ctx.$attrs, {
57
- size: __props.rows,
58
- multiple: __props.multiple,
59
- disabled: __props.disabled,
60
- required: __props.required,
61
- "aria-invalid": __props.invalid,
62
- "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => model.value = $event),
115
+ return openBlock(), createElementBlock("div", mergeProps({
116
+ "data-component": "ui-listbox",
117
+ role: "listbox"
118
+ }, _ctx.$attrs, {
119
+ tabindex: attribute({
120
+ true: "-1",
121
+ false: "0"
122
+ }[`${__props.disabled}`]),
123
+ "aria-multiselectable": __props.multiple,
124
+ "aria-disabled": __props.disabled,
125
+ "aria-required": __props.required,
126
+ "aria-invalid": __props.invalid || validationError.value,
127
+ style: { "--_rows": __props.rows },
63
128
  ref_key: "root",
64
129
  ref: root
65
- }), [renderSlot(_ctx.$slots, "default", {}, void 0, true)], 16, _hoisted_1)), [[vModelSelect, model.value]]);
130
+ }), [
131
+ createVNode(UiScrollArea_default, {
132
+ class: "choices",
133
+ ref_key: "choices",
134
+ ref: choices
135
+ }, {
136
+ default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(choices.value, (choice) => {
137
+ return openBlock(), createElementBlock("div", {
138
+ key: choice.index,
139
+ class: normalizeClass(["option", { active: choice.index === active.value }]),
140
+ role: "option",
141
+ "data-ui-choice-option": "",
142
+ "data-multiple": __props.multiple ? "" : void 0,
143
+ id: `${uid.value}-option-${choice.index}`,
144
+ "data-index": choice.index,
145
+ "aria-selected": choice.selected,
146
+ "aria-disabled": choice.disabled
147
+ }, [createElementVNode("span", null, toDisplayString(choice.label), 1)], 10, _hoisted_2);
148
+ }), 128))]),
149
+ _: 1
150
+ }, 512),
151
+ createElementVNode("select", {
152
+ class: "fallback",
153
+ "aria-label": "Choices",
154
+ size: __props.rows,
155
+ multiple: __props.multiple,
156
+ disabled: __props.disabled,
157
+ required: __props.required,
158
+ name: __props.name,
159
+ ref: "options"
160
+ }, [renderSlot(_ctx.$slots, "default", {}, void 0, true)], 8, _hoisted_3),
161
+ __props.name && !__props.disabled ? (openBlock(true), createElementBlock(Fragment, { key: 0 }, renderList(selection.value, (selected, index) => {
162
+ return openBlock(), createElementBlock("input", {
163
+ key: index,
164
+ type: "hidden",
165
+ name: __props.name,
166
+ value: selected
167
+ }, null, 8, _hoisted_4);
168
+ }), 128)) : createCommentVNode("v-if", true),
169
+ createElementVNode("input", {
170
+ class: "validity",
171
+ type: "checkbox",
172
+ "aria-hidden": "true",
173
+ tabindex: "-1",
174
+ required: __props.required && !hasSelection.value && !__props.disabled,
175
+ checked: hasSelection.value,
176
+ disabled: __props.disabled,
177
+ ref: "validity"
178
+ }, null, 8, _hoisted_5)
179
+ ], 16, _hoisted_1);
66
180
  };
67
181
  }
68
- }), [["__scopeId", "data-v-44b33b05"]]);
182
+ }), [["__scopeId", "data-v-db56e4d0"]]);
69
183
  //#endregion
70
184
  export { UiListbox_default as t };
@@ -60,6 +60,6 @@ var UiSelect_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PUR
60
60
  }), [renderSlot(_ctx.$slots, "default", {}, void 0, true)], 16, _hoisted_1)), [[vModelSelect, model.value]]);
61
61
  };
62
62
  }
63
- }), [["__scopeId", "data-v-3f68a24f"]]);
63
+ }), [["__scopeId", "data-v-699dcb22"]]);
64
64
  //#endregion
65
65
  export { UiSelect_default as t };
@@ -1,9 +1,10 @@
1
1
  import { t as _plugin_vue_export_helper_default } from "./_plugin-vue_export-helper.js";
2
- import { computed, createElementBlock, defineComponent, mergeProps, openBlock } from "vue";
2
+ import { computed, createElementBlock, createElementVNode, defineComponent, mergeProps, openBlock } from "vue";
3
3
  //#region .build/vue/UiSpinner.vue?vue&type=script&setup=true&lang.ts
4
4
  var _hoisted_1 = [
5
5
  "role",
6
6
  "aria-label",
7
+ "aria-live",
7
8
  "aria-hidden",
8
9
  "data-ui-spinner-state"
9
10
  ];
@@ -47,6 +48,16 @@ var UiSpinner_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
47
48
  true: __props.label,
48
49
  false: null
49
50
  }[`${__props.label !== ""}`]),
51
+ "aria-live": typeof {
52
+ true: "polite",
53
+ false: null
54
+ }[`${__props.label !== ""}`] === "boolean" ? String({
55
+ true: "polite",
56
+ false: null
57
+ }[`${__props.label !== ""}`]) : attribute({
58
+ true: "polite",
59
+ false: null
60
+ }[`${__props.label !== ""}`]),
50
61
  "aria-hidden": typeof {
51
62
  true: null,
52
63
  false: "true"
@@ -58,9 +69,24 @@ var UiSpinner_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
58
69
  false: "true"
59
70
  }[`${__props.label !== ""}`]),
60
71
  "data-ui-spinner-state": hostState.value || void 0
61
- }), null, 16, _hoisted_1);
72
+ }), [..._cache[0] || (_cache[0] = [createElementVNode("svg", {
73
+ viewBox: "0 0 50 50",
74
+ fill: "none",
75
+ "aria-hidden": "true",
76
+ focusable: "false"
77
+ }, [createElementVNode("circle", {
78
+ class: "track",
79
+ cx: "25",
80
+ cy: "25",
81
+ r: "20"
82
+ }), createElementVNode("circle", {
83
+ class: "arc",
84
+ cx: "25",
85
+ cy: "25",
86
+ r: "20"
87
+ })], -1)])], 16, _hoisted_1);
62
88
  };
63
89
  }
64
- }), [["__scopeId", "data-v-f5f5cd89"]]);
90
+ }), [["__scopeId", "data-v-7dbd4f40"]]);
65
91
  //#endregion
66
92
  export { UiSpinner_default as t };
@@ -74,6 +74,6 @@ var UiTextarea_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__P
74
74
  }), null, 16, _hoisted_1)), [[vModelText, model.value]]);
75
75
  };
76
76
  }
77
- }), [["__scopeId", "data-v-f371e58f"]]);
77
+ }), [["__scopeId", "data-v-a2e0e09f"]]);
78
78
  //#endregion
79
79
  export { UiTextarea_default as t };