@threadlabs/looma 0.25.3 → 0.25.5

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 (69) hide show
  1. package/components/ui-badge/ui-badge.html +59 -3
  2. package/components/ui-button/ui-button.html +1 -1
  3. package/components/ui-card/ui-card.html +27 -2
  4. package/components/ui-cluster/ui-cluster.html +30 -0
  5. package/components/ui-editor-surface/ui-editor-surface.html +23 -0
  6. package/components/ui-scroll-area/ui-scroll-area.html +5 -0
  7. package/components/ui-stack/ui-stack.html +17 -1
  8. package/components/ui-text/ui-text.html +5 -0
  9. package/components/ui-textarea/ui-textarea.html +7 -0
  10. package/dist/index.js +1 -1
  11. package/package.json +1 -1
  12. package/styles/ui-badge.css +61 -4
  13. package/styles/ui-button.css +1 -1
  14. package/styles/ui-card.css +24 -0
  15. package/styles/ui-cluster.css +25 -0
  16. package/styles/ui-editor-surface.css +19 -0
  17. package/styles/ui-scroll-area.css +4 -0
  18. package/styles/ui-stack.css +13 -0
  19. package/styles/ui-text.css +4 -0
  20. package/styles/ui-textarea.css +4 -0
  21. package/vanilla/UiBadge.js +16 -3
  22. package/vanilla/UiCard.d.ts +1 -1
  23. package/vanilla/UiCard.js +1 -1
  24. package/vanilla/UiCluster.d.ts +3 -0
  25. package/vanilla/UiCluster.js +9 -3
  26. package/vanilla/UiEditorSurface.d.ts +14 -0
  27. package/vanilla/UiEditorSurface.js +27 -0
  28. package/vanilla/UiScrollArea.d.ts +1 -0
  29. package/vanilla/UiScrollArea.js +1 -1
  30. package/vanilla/UiStack.d.ts +2 -0
  31. package/vanilla/UiStack.js +8 -9
  32. package/vanilla/UiText.d.ts +1 -0
  33. package/vanilla/UiText.js +1 -1
  34. package/vanilla/UiTextarea.d.ts +1 -0
  35. package/vanilla/UiTextarea.js +4 -1
  36. package/vanilla/index.js +1 -0
  37. package/vue/UiBadge.d.ts +4 -2
  38. package/vue/UiBadge.vue +57 -3
  39. package/vue/UiButton.vue +1 -1
  40. package/vue/UiCard.d.ts +3 -3
  41. package/vue/UiCard.vue +34 -5
  42. package/vue/UiCluster.d.ts +27 -0
  43. package/vue/UiCluster.vue +95 -1
  44. package/vue/UiEditorSlashMenuItem.d.ts +1 -1
  45. package/vue/UiEditorSurface.d.ts +26 -0
  46. package/vue/UiEditorSurface.js +2 -0
  47. package/vue/UiEditorSurface.vue +129 -0
  48. package/vue/UiScrollArea.d.ts +9 -0
  49. package/vue/UiScrollArea.vue +28 -2
  50. package/vue/UiStack.d.ts +18 -0
  51. package/vue/UiStack.vue +58 -1
  52. package/vue/UiText.d.ts +9 -0
  53. package/vue/UiText.vue +33 -0
  54. package/vue/UiTextarea.d.ts +9 -0
  55. package/vue/UiTextarea.vue +37 -2
  56. package/vue/chunks/UiBadge.js +7 -3
  57. package/vue/chunks/UiButton.js +1 -1
  58. package/vue/chunks/UiCard.js +10 -3
  59. package/vue/chunks/UiCluster.js +82 -1
  60. package/vue/chunks/UiEditorSurface.js +71 -0
  61. package/vue/chunks/UiScrollArea.js +28 -2
  62. package/vue/chunks/UiStack.js +46 -1
  63. package/vue/chunks/UiText.js +43 -1
  64. package/vue/chunks/UiTextarea.js +28 -2
  65. package/vue/components.css +321 -190
  66. package/vue/editor/LoomaEditor.d.ts +12 -0
  67. package/vue/editor/index.js +7 -2
  68. package/vue/index.d.ts +1 -0
  69. package/vue/index.js +2 -1
@@ -14,7 +14,10 @@
14
14
  <prop name="size" type="keyword" values="xs, md" default="md"
15
15
  >Scale. xs is a compact count beside small controls; md is the usual text badge. A square is 24px at xs and 32px at md with default spacing.</prop>
16
16
  </defs>
17
- <span><span class="label"><slot></slot></span></span>
17
+ <span>
18
+ <span class="icon" aria-hidden="true"><slot name="icon"></slot></span>
19
+ <span class="label"><slot></slot></span>
20
+ </span>
18
21
  <style>
19
22
  /* This component's hooks style the element they are set on, and never a nested one. */
20
23
  @property --ui-badge-border {
@@ -89,6 +92,7 @@
89
92
  display: inline-flex;
90
93
  align-items: center;
91
94
  justify-content: center;
95
+ gap: var(--ui-space-1);
92
96
  box-sizing: border-box;
93
97
  min-inline-size: 0;
94
98
  max-inline-size: 100%;
@@ -113,8 +117,43 @@
113
117
  text-overflow: ellipsis;
114
118
  }
115
119
 
116
- /* Trim leading while retaining the font's ascent and descent. A cap-to-baseline box
117
- would crop glyphs where the label clips horizontal overflow for ellipsis. */
120
+ .label:empty {
121
+ display: none;
122
+ }
123
+
124
+ /* Icon content has its own flex item so label ellipsis never clips its strokes. */
125
+ .icon {
126
+ display: inline-flex;
127
+ align-items: center;
128
+ justify-content: center;
129
+ flex: none;
130
+ }
131
+
132
+ .icon:empty {
133
+ display: none;
134
+ }
135
+
136
+ .icon :slotted(svg) {
137
+ display: block;
138
+ inline-size: 1em;
139
+ block-size: 1em;
140
+ flex: none;
141
+ }
142
+
143
+ /* Ordinary icon-plus-text children keep inline ellipsis, with explicit glyph spacing. */
144
+ .label :slotted(svg) {
145
+ inline-size: 1em;
146
+ block-size: 1em;
147
+ vertical-align: middle;
148
+ margin-inline-end: var(--ui-space-1);
149
+ }
150
+
151
+ .label:has(svg) {
152
+ text-box-trim: none;
153
+ }
154
+
155
+ /* Text-only badges retain their trimmed inline baseline. Icon content needs a flex row;
156
+ trimming its label separately keeps the glyph centered without changing adjacent lines. */
118
157
  @supports (text-box-trim: trim-both) {
119
158
  :host {
120
159
  display: inline-block;
@@ -124,6 +163,15 @@
124
163
  text-box-trim: trim-both;
125
164
  text-box-edge: text;
126
165
  }
166
+ :host:has(.icon:not(:empty)),
167
+ :host:has(.label svg) {
168
+ display: inline-flex;
169
+ text-box-trim: none;
170
+ }
171
+ :host:has(.icon:not(:empty)) .label {
172
+ text-box-trim: trim-both;
173
+ text-box-edge: text;
174
+ }
127
175
  }
128
176
 
129
177
  :host([size="xs"]) {
@@ -262,6 +310,14 @@
262
310
  justify-content: center;
263
311
  }
264
312
 
313
+ :host([shape="square"]) .label :slotted(svg) {
314
+ margin-inline-end: 0;
315
+ }
316
+
317
+ :host([shape="square"]) .label:empty {
318
+ display: none;
319
+ }
320
+
265
321
  /* A dot uses the badge's tone recipe; only its silhouette and visible label change. */
266
322
  :host([shape="dot"]) {
267
323
  position: relative;
@@ -181,6 +181,7 @@
181
181
  display: inline-flex;
182
182
  align-items: center;
183
183
  justify-content: center;
184
+ column-gap: var(--ui-space-2);
184
185
  vertical-align: middle;
185
186
  color: var(--_tone-text);
186
187
  }
@@ -230,7 +231,6 @@
230
231
  .spinner-wrap {
231
232
  display: inline-flex;
232
233
  flex: none;
233
- margin-inline-end: var(--ui-space-2);
234
234
  vertical-align: middle;
235
235
  }
236
236
 
@@ -7,8 +7,9 @@
7
7
  elevated floats above the page with a shadow, for a view's one card, such as sign-in.</prop>
8
8
  <prop name="padding" type="keyword" values="xs, sm, md, lg" default="md"
9
9
  >The space inside the card: xs is compact (0.5rem), sm is snug (0.75rem), md is the default (1.5rem), and lg is roomy (2rem).</prop>
10
- <prop name="tone" type="keyword" values="neutral, danger" default="neutral"
11
- >danger adds a soft danger surface and a strong leading edge for consequential content.</prop>
10
+ <prop name="tone" type="keyword" values="neutral, accent, info, success, warning, danger" default="neutral"
11
+ >accent, info, success and warning add a quiet semantic surface. danger retains its strong leading edge for consequential content. Tone never changes child
12
+ controls.</prop>
12
13
  <prop name="as" type="keyword" values="div, section, article, aside" default="div"
13
14
  >The native element the card renders. Choose the one that names the content: a section of a page, a self-contained article, or an aside.</prop>
14
15
  </defs>
@@ -57,6 +58,30 @@
57
58
  box-shadow: var(--ui-shadow-lg);
58
59
  }
59
60
 
61
+ :host([tone="accent"]) {
62
+ border-color: color-mix(in srgb, var(--ui-accent) 25%, var(--ui-border-default));
63
+ background: color-mix(in srgb, var(--ui-accent) 6%, var(--ui-surface-default));
64
+ }
65
+
66
+ :host([tone="info"]) {
67
+ border-color: color-mix(in srgb, var(--ui-info) 25%, var(--ui-border-default));
68
+ background: color-mix(in srgb, var(--ui-info) 6%, var(--ui-surface-default));
69
+ }
70
+
71
+ :host([tone="success"]) {
72
+ border-color: color-mix(in srgb, var(--ui-success) 25%, var(--ui-border-default));
73
+ background: color-mix(in srgb, var(--ui-success) 6%, var(--ui-surface-default));
74
+ }
75
+
76
+ :host([tone="warning"]) {
77
+ border-color: color-mix(in srgb, var(--ui-warning) 25%, var(--ui-border-default));
78
+ background: color-mix(in srgb, var(--ui-warning) 6%, var(--ui-surface-default));
79
+ }
80
+
81
+ :host([variant="subtle"]:is([tone="accent"], [tone="info"], [tone="success"], [tone="warning"])) {
82
+ border-color: transparent;
83
+ }
84
+
60
85
  :host([tone="danger"]) {
61
86
  border-color: color-mix(in srgb, var(--ui-danger) 45%, var(--ui-border-default));
62
87
  border-inline-start: var(--ui-accent-line-width) solid var(--ui-danger);
@@ -1,5 +1,10 @@
1
1
  <template component="ui-cluster">
2
2
  <defs>
3
+ <prop name="padding" type="keyword" values="none, s, m, l" default="none"
4
+ >Optional internal space for an action group, using the shared spacing scale.</prop>
5
+ <prop name="paddingEnd" type="keyword" values="none, s, m, l, xl" default="none"
6
+ >Optional extra space at the inline end, such as clearance beside another control. xl is 4rem.</prop>
7
+ <prop name="fixed" type="boolean" default="false">Keeps this group at its content size inside a flex layout.</prop>
3
8
  <prop name="gap" type="keyword" values="xs, s, m, l, xl"
4
9
  >Space between items, on the spacing scale: xs is 0.5rem, s 0.75rem (also the default), m 1rem, l 1.5rem, and xl 2rem. The --ui-cluster-gap custom property,
5
10
  set on the cluster itself, overrides it; set on an ancestor, it does nothing.</prop>
@@ -33,6 +38,31 @@
33
38
  margin: 0;
34
39
  }
35
40
 
41
+ :host([fixed]) {
42
+ flex: none;
43
+ }
44
+ :host([padding="s"]) {
45
+ padding: var(--ui-space-2);
46
+ }
47
+ :host([padding="m"]) {
48
+ padding: var(--ui-space-3);
49
+ }
50
+ :host([padding="l"]) {
51
+ padding: var(--ui-space-4);
52
+ }
53
+ :host([paddingEnd="s"]) {
54
+ padding-inline-end: var(--ui-space-2);
55
+ }
56
+ :host([paddingEnd="m"]) {
57
+ padding-inline-end: var(--ui-space-3);
58
+ }
59
+ :host([paddingEnd="l"]) {
60
+ padding-inline-end: var(--ui-space-4);
61
+ }
62
+ :host([paddingEnd="xl"]) {
63
+ padding-inline-end: calc(var(--ui-space-4) * 4);
64
+ }
65
+
36
66
  :host([gap="xs"]) {
37
67
  --_layout-gap: var(--ui-space-2);
38
68
  }
@@ -0,0 +1,23 @@
1
+ <template component="ui-editor-surface">
2
+ <defs>
3
+ <prop name="density" type="keyword" values="comfortable, compact" default="comfortable"
4
+ >The document canvas. comfortable preserves the editor's normal room and mobile toolbar clearance. compact fits a read-only excerpt to its content without
5
+ outer padding.</prop>
6
+ </defs>
7
+ <div><slot></slot></div>
8
+ <style>
9
+ :host {
10
+ display: flex;
11
+ flex-direction: column;
12
+ min-inline-size: 0;
13
+ max-inline-size: 100%;
14
+ }
15
+ /* The document renderer consumes these inherited surface settings. They do not style controls. */
16
+ :host([density="compact"]) {
17
+ --_ui-editor-content-min-height: 0px;
18
+ --_ui-editor-content-padding-block: 0px;
19
+ --_ui-editor-content-padding-inline: 0px;
20
+ --_ui-editor-mobile-bottom-space: 0px;
21
+ }
22
+ </style>
23
+ </template>
@@ -1,5 +1,6 @@
1
1
  <template component="ui-scroll-area" controller="./ui-scroll-area.js">
2
2
  <defs>
3
+ <prop name="fill" type="boolean" default="false">Grows into remaining space within a flex layout while keeping content independently scrollable.</prop>
3
4
  <prop name="size" type="keyword" values="content, sm, md, lg" default="content"
4
5
  >Optional height preset. content keeps intrinsic sizing; sm fits three compact 3.125rem result rows before the overflow fade, md is 20rem, and lg is
5
6
  30rem.</prop>
@@ -12,6 +13,10 @@
12
13
  /* The controller sets each fade length from overflow and scroll position. The mask blends
13
14
  into whatever is behind the area without needing to know its colour. Before upgrade, its
14
15
  zero-length defaults leave the authored scroll area fully visible. */
16
+ :host([fill]) {
17
+ flex: 1;
18
+ }
19
+
15
20
  :host {
16
21
  display: block;
17
22
  box-sizing: border-box;
@@ -1,5 +1,7 @@
1
1
  <template component="ui-stack">
2
2
  <defs>
3
+ <prop name="fill" type="boolean" default="false">Grows into the remaining space of a flex layout and allows its scrolling children to shrink.</prop>
4
+ <prop name="height" type="number">An optional measured height in CSS pixels, for a viewport-bounded composition. Omit it to retain intrinsic sizing.</prop>
3
5
  <prop name="gap" type="keyword" values="none, xs, s, m, l, xl"
4
6
  >Space between items, on the spacing scale: none is zero, xs is 0.5rem, s 0.75rem, m 1rem (also the default), l 1.5rem, and xl 2rem. The --ui-stack-gap
5
7
  custom property, set on the stack itself, overrides it; set on an ancestor, it does nothing.</prop>
@@ -10,9 +12,14 @@
10
12
  >Where items sit along a stack that is taller than its content: start (the default), center, end, or between, which spreads the spare space between items.
11
13
  It has no effect unless the stack is given a height or stretched to one.</prop>
12
14
  </defs>
13
- <div><slot></slot></div>
15
+ <div style:--ui-stack-height="height"><slot></slot></div>
14
16
  <style>
15
17
  /* This component's hooks style the element they are set on, and never a nested one. */
18
+ @property --ui-stack-height {
19
+ syntax: "*";
20
+ inherits: false;
21
+ }
22
+
16
23
  @property --ui-stack-gap {
17
24
  syntax: "*";
18
25
  inherits: false;
@@ -29,6 +36,15 @@
29
36
  margin: 0;
30
37
  }
31
38
 
39
+ :host([fill]) {
40
+ flex: 1;
41
+ min-block-size: 0;
42
+ }
43
+ :host([height]) {
44
+ block-size: calc(var(--ui-stack-height, 0) * 1px);
45
+ min-block-size: 0;
46
+ }
47
+
32
48
  :host([gap="none"]) {
33
49
  --_layout-gap: 0px;
34
50
  }
@@ -1,5 +1,6 @@
1
1
  <template component="ui-text">
2
2
  <defs>
3
+ <prop name="grow" type="boolean" default="false">Lets this text use remaining inline space in a flex row, including when truncated.</prop>
3
4
  <prop name="font" type="keyword" values="inherit, sans" default="inherit"
4
5
  >Use sans for UI text rendered outside its original font context; inherit preserves the surrounding typeface.</prop>
5
6
  <prop name="wrap" type="keyword" values="normal, anywhere" default="normal"
@@ -39,6 +40,10 @@
39
40
  /* Running text, set on the type scale and in a tone that says what it is, so a view never styles
40
41
  text of its own. The root is a paragraph unless as= names a span, div, small, strong, label, or
41
42
  heading. */
43
+ :host([grow]) {
44
+ flex: 1;
45
+ }
46
+
42
47
  :host {
43
48
  margin: 0;
44
49
  min-inline-size: 0;
@@ -8,6 +8,8 @@
8
8
  <prop name="readonly" type="boolean" default="false">Allows selection without allowing edits.</prop>
9
9
  <prop name="required" type="boolean" default="false">Marks the control as required for form validation.</prop>
10
10
  <prop name="rows" type="integer" default="4">Initial number of visible text rows.</prop>
11
+ <prop name="maxRows" type="integer"
12
+ >Optional maximum visible rows, for a growing field in a bounded layout. Extra text scrolls inside the field. Omit it to retain unbounded growth.</prop>
11
13
  <prop name="autosize" type="boolean" default="false"
12
14
  >Grow with the text while keeping rows as the minimum visible height. Uses CSS field sizing where supported; other browsers keep the native rows
13
15
  behavior.</prop>
@@ -23,6 +25,7 @@
23
25
  from:aria-invalid="invalid"
24
26
  from:rows="rows"
25
27
  style:--_rows="rows"
28
+ style:--_max-rows="maxRows"
26
29
  ></textarea>
27
30
  <style>
28
31
  /* This component's hooks style the element they are set on, and never a nested one. */
@@ -164,6 +167,10 @@
164
167
  font-size: var(--ui-font-size-md);
165
168
  }
166
169
 
170
+ :host([maxRows]) {
171
+ max-block-size: calc(max(var(--_rows), var(--_max-rows)) * 1lh + var(--_padding-block) + var(--_padding-block) + 2px);
172
+ }
173
+
167
174
  @supports (field-sizing: content) {
168
175
  :host([autosize]) {
169
176
  field-sizing: content;