@threadlabs/looma 0.25.4 → 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 (58) hide show
  1. package/components/ui-card/ui-card.html +27 -2
  2. package/components/ui-cluster/ui-cluster.html +30 -0
  3. package/components/ui-editor-surface/ui-editor-surface.html +23 -0
  4. package/components/ui-scroll-area/ui-scroll-area.html +5 -0
  5. package/components/ui-stack/ui-stack.html +17 -1
  6. package/components/ui-text/ui-text.html +5 -0
  7. package/components/ui-textarea/ui-textarea.html +7 -0
  8. package/dist/index.js +1 -1
  9. package/package.json +1 -1
  10. package/styles/ui-card.css +24 -0
  11. package/styles/ui-cluster.css +25 -0
  12. package/styles/ui-editor-surface.css +19 -0
  13. package/styles/ui-scroll-area.css +4 -0
  14. package/styles/ui-stack.css +13 -0
  15. package/styles/ui-text.css +4 -0
  16. package/styles/ui-textarea.css +4 -0
  17. package/vanilla/UiCard.d.ts +1 -1
  18. package/vanilla/UiCard.js +1 -1
  19. package/vanilla/UiCluster.d.ts +3 -0
  20. package/vanilla/UiCluster.js +9 -3
  21. package/vanilla/UiEditorSurface.d.ts +14 -0
  22. package/vanilla/UiEditorSurface.js +27 -0
  23. package/vanilla/UiScrollArea.d.ts +1 -0
  24. package/vanilla/UiScrollArea.js +1 -1
  25. package/vanilla/UiStack.d.ts +2 -0
  26. package/vanilla/UiStack.js +8 -9
  27. package/vanilla/UiText.d.ts +1 -0
  28. package/vanilla/UiText.js +1 -1
  29. package/vanilla/UiTextarea.d.ts +1 -0
  30. package/vanilla/UiTextarea.js +4 -1
  31. package/vanilla/index.js +1 -0
  32. package/vue/UiCard.d.ts +3 -3
  33. package/vue/UiCard.vue +34 -5
  34. package/vue/UiCluster.d.ts +27 -0
  35. package/vue/UiCluster.vue +95 -1
  36. package/vue/UiEditorSurface.d.ts +26 -0
  37. package/vue/UiEditorSurface.js +2 -0
  38. package/vue/UiEditorSurface.vue +129 -0
  39. package/vue/UiScrollArea.d.ts +9 -0
  40. package/vue/UiScrollArea.vue +28 -2
  41. package/vue/UiStack.d.ts +18 -0
  42. package/vue/UiStack.vue +58 -1
  43. package/vue/UiText.d.ts +9 -0
  44. package/vue/UiText.vue +33 -0
  45. package/vue/UiTextarea.d.ts +9 -0
  46. package/vue/UiTextarea.vue +37 -2
  47. package/vue/chunks/UiCard.js +10 -3
  48. package/vue/chunks/UiCluster.js +82 -1
  49. package/vue/chunks/UiEditorSurface.js +71 -0
  50. package/vue/chunks/UiScrollArea.js +28 -2
  51. package/vue/chunks/UiStack.js +46 -1
  52. package/vue/chunks/UiText.js +43 -1
  53. package/vue/chunks/UiTextarea.js +28 -2
  54. package/vue/components.css +188 -104
  55. package/vue/editor/LoomaEditor.d.ts +12 -0
  56. package/vue/editor/index.js +7 -2
  57. package/vue/index.d.ts +1 -0
  58. package/vue/index.js +2 -1
@@ -8,6 +8,10 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
8
8
  type: PropType<"start" | "center" | "end" | "stretch" | null>;
9
9
  default: null;
10
10
  };
11
+ fixed: {
12
+ type: PropType<boolean | null>;
13
+ default: boolean;
14
+ };
11
15
  gap: {
12
16
  type: PropType<"xs" | "s" | "m" | "l" | "xl" | null>;
13
17
  default: null;
@@ -16,6 +20,14 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
16
20
  type: PropType<"start" | "center" | "end" | "between" | null>;
17
21
  default: null;
18
22
  };
23
+ padding: {
24
+ type: PropType<"none" | "s" | "m" | "l" | null>;
25
+ default: string;
26
+ };
27
+ paddingEnd: {
28
+ type: PropType<"none" | "s" | "m" | "l" | "xl" | null>;
29
+ default: string;
30
+ };
19
31
  wrap: {
20
32
  type: PropType<"wrap" | "nowrap" | null>;
21
33
  default: null;
@@ -25,6 +37,10 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
25
37
  type: PropType<"start" | "center" | "end" | "stretch" | null>;
26
38
  default: null;
27
39
  };
40
+ fixed: {
41
+ type: PropType<boolean | null>;
42
+ default: boolean;
43
+ };
28
44
  gap: {
29
45
  type: PropType<"xs" | "s" | "m" | "l" | "xl" | null>;
30
46
  default: null;
@@ -33,15 +49,26 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
33
49
  type: PropType<"start" | "center" | "end" | "between" | null>;
34
50
  default: null;
35
51
  };
52
+ padding: {
53
+ type: PropType<"none" | "s" | "m" | "l" | null>;
54
+ default: string;
55
+ };
56
+ paddingEnd: {
57
+ type: PropType<"none" | "s" | "m" | "l" | "xl" | null>;
58
+ default: string;
59
+ };
36
60
  wrap: {
37
61
  type: PropType<"wrap" | "nowrap" | null>;
38
62
  default: null;
39
63
  };
40
64
  }>> & Readonly<{}>, {
41
65
  align: "center" | "start" | "stretch" | "end" | null;
66
+ padding: "s" | "none" | "m" | "l" | null;
67
+ fixed: boolean | null;
42
68
  gap: "s" | "xs" | "m" | "l" | "xl" | null;
43
69
  justify: "center" | "start" | "end" | "between" | null;
44
70
  wrap: "wrap" | "nowrap" | null;
71
+ paddingEnd: "s" | "none" | "m" | "l" | "xl" | null;
45
72
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
46
73
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
47
74
  declare const _default: typeof __VLS_export;
package/vue/UiCluster.vue CHANGED
@@ -16,6 +16,13 @@ const propValidityContract = {
16
16
  'How items line up within each row: center (the default) aligns their middles, start their tops, end their bottoms, and stretch makes every item as tall as\n its row.',
17
17
  values: ['start', 'center', 'end', 'stretch'],
18
18
  },
19
+ fixed: {
20
+ type: { kind: 'terminal', name: 'boolean' },
21
+ required: false,
22
+ target: { attribute: 'fixed' },
23
+ description: 'Keeps this group at its content size inside a flex layout.',
24
+ default: false,
25
+ },
19
26
  gap: {
20
27
  type: { kind: 'terminal', name: 'keyword' },
21
28
  required: false,
@@ -32,6 +39,23 @@ const propValidityContract = {
32
39
  'Where items sit along each row: start (the default), center, end, or between, which pushes the first and last items to the ends of the row and spreads the\n spare space between them, as for a title with its actions.',
33
40
  values: ['start', 'center', 'end', 'between'],
34
41
  },
42
+ padding: {
43
+ type: { kind: 'terminal', name: 'keyword' },
44
+ required: false,
45
+ target: { attribute: 'padding' },
46
+ description: 'Optional internal space for an action group, using the shared spacing scale.',
47
+ values: ['none', 's', 'm', 'l'],
48
+ default: 'none',
49
+ },
50
+ paddingEnd: {
51
+ type: { kind: 'terminal', name: 'keyword' },
52
+ required: false,
53
+ target: { attribute: 'paddingend' },
54
+ description:
55
+ 'Optional extra space at the inline end, such as clearance beside another control. xl is 4rem.',
56
+ values: ['none', 's', 'm', 'l', 'xl'],
57
+ default: 'none',
58
+ },
35
59
  wrap: {
36
60
  type: { kind: 'terminal', name: 'keyword' },
37
61
  required: false,
@@ -47,14 +71,28 @@ const props = defineProps({
47
71
  type: null as unknown as PropType<'start' | 'center' | 'end' | 'stretch' | null>,
48
72
  default: null,
49
73
  },
74
+ fixed: { type: null as unknown as PropType<boolean | null>, default: false },
50
75
  gap: { type: null as unknown as PropType<'xs' | 's' | 'm' | 'l' | 'xl' | null>, default: null },
51
76
  justify: {
52
77
  type: null as unknown as PropType<'start' | 'center' | 'end' | 'between' | null>,
53
78
  default: null,
54
79
  },
80
+ padding: { type: null as unknown as PropType<'none' | 's' | 'm' | 'l' | null>, default: 'none' },
81
+ paddingEnd: {
82
+ type: null as unknown as PropType<'none' | 's' | 'm' | 'l' | 'xl' | null>,
83
+ default: 'none',
84
+ },
55
85
  wrap: { type: null as unknown as PropType<'wrap' | 'nowrap' | null>, default: null },
56
86
  })
57
- const acceptedProps: Record<string, unknown> = { align: null, gap: null, justify: null, wrap: null }
87
+ const acceptedProps: Record<string, unknown> = {
88
+ align: null,
89
+ fixed: false,
90
+ gap: null,
91
+ justify: null,
92
+ padding: 'none',
93
+ paddingEnd: 'none',
94
+ wrap: null,
95
+ }
58
96
  const checkedProps = computed(() => ({
59
97
  align: checkedProp<'start' | 'center' | 'end' | 'stretch'>(
60
98
  props['align'],
@@ -63,6 +101,13 @@ const checkedProps = computed(() => ({
63
101
  'align',
64
102
  acceptedProps,
65
103
  ),
104
+ fixed: checkedProp<boolean>(
105
+ props['fixed'],
106
+ { kind: 'terminal', name: 'boolean' },
107
+ false,
108
+ 'fixed',
109
+ acceptedProps,
110
+ ) as boolean,
66
111
  gap: checkedProp<'xs' | 's' | 'm' | 'l' | 'xl'>(
67
112
  props['gap'],
68
113
  { kind: 'terminal', name: 'keyword' },
@@ -77,6 +122,20 @@ const checkedProps = computed(() => ({
77
122
  'justify',
78
123
  acceptedProps,
79
124
  ),
125
+ padding: checkedProp<'none' | 's' | 'm' | 'l'>(
126
+ props['padding'],
127
+ { kind: 'terminal', name: 'keyword' },
128
+ false,
129
+ 'padding',
130
+ acceptedProps,
131
+ ) as 'none' | 's' | 'm' | 'l',
132
+ paddingEnd: checkedProp<'none' | 's' | 'm' | 'l' | 'xl'>(
133
+ props['paddingEnd'],
134
+ { kind: 'terminal', name: 'keyword' },
135
+ false,
136
+ 'paddingEnd',
137
+ acceptedProps,
138
+ ) as 'none' | 's' | 'm' | 'l' | 'xl',
80
139
  wrap: checkedProp<'wrap' | 'nowrap'>(
81
140
  props['wrap'],
82
141
  { kind: 'terminal', name: 'keyword' },
@@ -143,6 +202,13 @@ if (
143
202
  /** The values the styles' :host-state() rules test. */
144
203
  const hostState = computed(() =>
145
204
  [
205
+ checkedProps.value.fixed && 'fixed',
206
+ checkedProps.value.padding && 'padding',
207
+ checkedProps.value.padding != null
208
+ && `padding=${encodeURIComponent(checkedProps.value.padding)}`,
209
+ checkedProps.value.paddingEnd && 'paddingEnd',
210
+ checkedProps.value.paddingEnd != null
211
+ && `paddingEnd=${encodeURIComponent(checkedProps.value.paddingEnd)}`,
146
212
  checkedProps.value.gap && 'gap',
147
213
  checkedProps.value.gap != null && `gap=${encodeURIComponent(checkedProps.value.gap)}`,
148
214
  checkedProps.value.align && 'align',
@@ -162,8 +228,11 @@ const hostState = computed(() =>
162
228
  v-bind="nativeAttrs($attrs)"
163
229
  v-prop-validity="{ contract: propValidityContract, values: { ...props } }"
164
230
  :data-align="reflectedProp('align', 'align', checkedProps.align, undefined, false)"
231
+ :data-fixed="reflectedProp('fixed', 'fixed', checkedProps.fixed, undefined, false)"
165
232
  :data-gap="reflectedProp('gap', 'gap', checkedProps.gap, undefined, false)"
166
233
  :data-justify="reflectedProp('justify', 'justify', checkedProps.justify, undefined, false)"
234
+ :data-padding="reflectedProp('padding', 'padding', checkedProps.padding, undefined, false)"
235
+ :data-padding-end="reflectedProp('paddingEnd', 'padding-end', checkedProps.paddingEnd, undefined, false)"
167
236
  :data-wrap="reflectedProp('wrap', 'wrap', checkedProps.wrap, undefined, false)"
168
237
  :data-ui-cluster-state="hostState || undefined"
169
238
  >
@@ -189,6 +258,31 @@ const hostState = computed(() =>
189
258
  margin: 0;
190
259
  }
191
260
 
261
+ [data-component~="ui-cluster"][data-ui-cluster-state~="fixed"] {
262
+ flex: none;
263
+ }
264
+ [data-component~="ui-cluster"][data-ui-cluster-state~="padding=s"] {
265
+ padding: var(--ui-space-2);
266
+ }
267
+ [data-component~="ui-cluster"][data-ui-cluster-state~="padding=m"] {
268
+ padding: var(--ui-space-3);
269
+ }
270
+ [data-component~="ui-cluster"][data-ui-cluster-state~="padding=l"] {
271
+ padding: var(--ui-space-4);
272
+ }
273
+ [data-component~="ui-cluster"][data-ui-cluster-state~="paddingEnd=s"] {
274
+ padding-inline-end: var(--ui-space-2);
275
+ }
276
+ [data-component~="ui-cluster"][data-ui-cluster-state~="paddingEnd=m"] {
277
+ padding-inline-end: var(--ui-space-3);
278
+ }
279
+ [data-component~="ui-cluster"][data-ui-cluster-state~="paddingEnd=l"] {
280
+ padding-inline-end: var(--ui-space-4);
281
+ }
282
+ [data-component~="ui-cluster"][data-ui-cluster-state~="paddingEnd=xl"] {
283
+ padding-inline-end: calc(var(--ui-space-4) * 4);
284
+ }
285
+
192
286
  [data-component~="ui-cluster"][data-ui-cluster-state~="gap=xs"] {
193
287
  --_layout-gap: var(--ui-space-2);
194
288
  }
@@ -0,0 +1,26 @@
1
+ import type { PropType } from 'vue';
2
+ declare var __VLS_1: {};
3
+ type __VLS_Slots = {} & {
4
+ default?: (props: typeof __VLS_1) => any;
5
+ };
6
+ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
7
+ density: {
8
+ type: PropType<"comfortable" | "compact" | null>;
9
+ default: string;
10
+ };
11
+ }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
12
+ density: {
13
+ type: PropType<"comfortable" | "compact" | null>;
14
+ default: string;
15
+ };
16
+ }>> & Readonly<{}>, {
17
+ density: "comfortable" | "compact" | null;
18
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
19
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
20
+ declare const _default: typeof __VLS_export;
21
+ export default _default;
22
+ type __VLS_WithSlots<T, S> = T & {
23
+ new (): {
24
+ $slots: S;
25
+ };
26
+ };
@@ -0,0 +1,2 @@
1
+ import { t as UiEditorSurface_default } from "./chunks/UiEditorSurface.js";
2
+ export { UiEditorSurface_default as default };
@@ -0,0 +1,129 @@
1
+ <!-- Generated by HTML Next 1.0.0-alpha.29 for Vue 3.5. Do not edit. -->
2
+ <script setup lang="ts">
3
+ import { computed, getCurrentInstance, watchSyncEffect } from 'vue'
4
+ import type { PropType } from 'vue'
5
+ import { checkedProp, vPropValidity } from './props'
6
+
7
+ defineOptions({ inheritAttrs: false })
8
+
9
+ const propValidityContract = {
10
+ props: {
11
+ density: {
12
+ type: { kind: 'terminal', name: 'keyword' },
13
+ required: false,
14
+ target: { attribute: 'density' },
15
+ description:
16
+ 'The document canvas. comfortable preserves the editor\'s normal room and mobile toolbar clearance. compact fits a read-only excerpt to its content without\n outer padding.',
17
+ values: ['comfortable', 'compact'],
18
+ default: 'comfortable',
19
+ },
20
+ },
21
+ } as const
22
+ const props = defineProps({
23
+ density: {
24
+ type: null as unknown as PropType<'comfortable' | 'compact' | null>,
25
+ default: 'comfortable',
26
+ },
27
+ })
28
+ const acceptedProps: Record<string, unknown> = { density: 'comfortable' }
29
+ const checkedProps = computed(() => ({
30
+ density: checkedProp<'comfortable' | 'compact'>(
31
+ props['density'],
32
+ { kind: 'terminal', name: 'keyword' },
33
+ false,
34
+ 'density',
35
+ acceptedProps,
36
+ ) as 'comfortable' | 'compact',
37
+ }))
38
+ void checkedProps.value
39
+ watchSyncEffect(() => {
40
+ void checkedProps.value
41
+ })
42
+ function nativeAttrs(attrs: Record<string, unknown>): Record<string, unknown> {
43
+ const names = Object.keys(attrs)
44
+ if (!names.some((name) => Object.hasOwn(Object.prototype, name))) return attrs
45
+ return Object.fromEntries(names.map((name) => [
46
+ Object.hasOwn(Object.prototype, name)
47
+ ? name === '__proto__' ? '__Proto__' : name[0]!.toUpperCase() + name.slice(1)
48
+ : name,
49
+ attrs[name],
50
+ ]))
51
+ }
52
+ const componentInstance = getCurrentInstance()
53
+ function reflectedProp(
54
+ name: string,
55
+ kebab: string,
56
+ value: unknown,
57
+ fallback: string | undefined,
58
+ bound: boolean,
59
+ separator?: string,
60
+ ): string | undefined {
61
+ const incoming = componentInstance?.vnode.props
62
+ if (
63
+ !bound
64
+ && (incoming === null || incoming === undefined
65
+ || (!Object.hasOwn(incoming, name) && !Object.hasOwn(incoming, kebab)))
66
+ ) return fallback
67
+ if (value === null || value === undefined) return undefined
68
+ return Array.isArray(value) && separator !== undefined
69
+ ? value.join(separator)
70
+ : typeof value === 'object'
71
+ ? JSON.stringify(value)
72
+ : String(value)
73
+ }
74
+ const hydrationInstance = getCurrentInstance()
75
+
76
+ const hydrationNode = hydrationInstance?.vnode.el
77
+ if (
78
+ typeof window !== 'undefined' && hydrationNode != null
79
+ && (!(hydrationNode instanceof Element) || hydrationNode.localName !== ('div'))
80
+ ) {
81
+ throw Object.assign(
82
+ new Error('HR005: Server markup for <ui-editor-surface> has an incompatible root.'),
83
+ {
84
+ name: 'HtmlDiagnosticError',
85
+ diagnostic: Object.freeze({
86
+ code: 'HR005',
87
+ message: 'Server markup for <ui-editor-surface> has an incompatible root.',
88
+ }),
89
+ },
90
+ )
91
+ }
92
+
93
+ /** The values the styles' :host-state() rules test. */
94
+ const hostState = computed(() =>
95
+ [
96
+ checkedProps.value.density && 'density',
97
+ checkedProps.value.density != null
98
+ && `density=${encodeURIComponent(checkedProps.value.density)}`,
99
+ ].filter(Boolean).join(' ')
100
+ )
101
+ </script>
102
+
103
+ <template>
104
+ <div
105
+ data-component="ui-editor-surface"
106
+ v-bind="nativeAttrs($attrs)"
107
+ v-prop-validity="{ contract: propValidityContract, values: { ...props } }"
108
+ :data-density="reflectedProp('density', 'density', checkedProps.density, undefined, false)"
109
+ :data-ui-editor-surface-state="hostState || undefined"
110
+ >
111
+ <slot />
112
+ </div>
113
+ </template>
114
+
115
+ <style scoped>
116
+ [data-component~="ui-editor-surface"] {
117
+ display: flex;
118
+ flex-direction: column;
119
+ min-inline-size: 0;
120
+ max-inline-size: 100%;
121
+ }
122
+ /* The document renderer consumes these inherited surface settings. They do not style controls. */
123
+ [data-component~="ui-editor-surface"][data-ui-editor-surface-state~="density=compact"] {
124
+ --_ui-editor-content-min-height: 0px;
125
+ --_ui-editor-content-padding-block: 0px;
126
+ --_ui-editor-content-padding-inline: 0px;
127
+ --_ui-editor-mobile-bottom-space: 0px;
128
+ }
129
+ </style>
@@ -4,6 +4,10 @@ type __VLS_Slots = {} & {
4
4
  default?: (props: typeof __VLS_1) => any;
5
5
  };
6
6
  declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
7
+ fill: {
8
+ type: PropType<boolean | null>;
9
+ default: boolean;
10
+ };
7
11
  orientation: {
8
12
  type: PropType<"vertical" | "horizontal" | null>;
9
13
  default: string;
@@ -13,6 +17,10 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
13
17
  default: string;
14
18
  };
15
19
  }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
20
+ fill: {
21
+ type: PropType<boolean | null>;
22
+ default: boolean;
23
+ };
16
24
  orientation: {
17
25
  type: PropType<"vertical" | "horizontal" | null>;
18
26
  default: string;
@@ -22,6 +30,7 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
22
30
  default: string;
23
31
  };
24
32
  }>> & Readonly<{}>, {
33
+ fill: boolean | null;
25
34
  size: "sm" | "md" | "content" | "lg" | null;
26
35
  orientation: "vertical" | "horizontal" | null;
27
36
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -9,6 +9,14 @@ defineOptions({ inheritAttrs: false })
9
9
 
10
10
  const propValidityContract = {
11
11
  props: {
12
+ fill: {
13
+ type: { kind: 'terminal', name: 'boolean' },
14
+ required: false,
15
+ target: { attribute: 'fill' },
16
+ description:
17
+ 'Grows into remaining space within a flex layout while keeping content independently scrollable.',
18
+ default: false,
19
+ },
12
20
  orientation: {
13
21
  type: { kind: 'terminal', name: 'keyword' },
14
22
  required: false,
@@ -30,6 +38,7 @@ const propValidityContract = {
30
38
  },
31
39
  } as const
32
40
  const props = defineProps({
41
+ fill: { type: null as unknown as PropType<boolean | null>, default: false },
33
42
  orientation: {
34
43
  type: null as unknown as PropType<'vertical' | 'horizontal' | null>,
35
44
  default: 'vertical',
@@ -39,8 +48,19 @@ const props = defineProps({
39
48
  default: 'content',
40
49
  },
41
50
  })
42
- const acceptedProps: Record<string, unknown> = { orientation: 'vertical', size: 'content' }
51
+ const acceptedProps: Record<string, unknown> = {
52
+ fill: false,
53
+ orientation: 'vertical',
54
+ size: 'content',
55
+ }
43
56
  const checkedProps = computed(() => ({
57
+ fill: checkedProp<boolean>(
58
+ props['fill'],
59
+ { kind: 'terminal', name: 'boolean' },
60
+ false,
61
+ 'fill',
62
+ acceptedProps,
63
+ ) as boolean,
44
64
  orientation: checkedProp<'vertical' | 'horizontal'>(
45
65
  props['orientation'],
46
66
  { kind: 'terminal', name: 'keyword' },
@@ -116,6 +136,7 @@ if (
116
136
  /** The values the styles' :host-state() rules test. */
117
137
  const hostState = computed(() =>
118
138
  [
139
+ checkedProps.value.fill && 'fill',
119
140
  checkedProps.value.orientation && 'orientation',
120
141
  checkedProps.value.orientation != null
121
142
  && `orientation=${encodeURIComponent(checkedProps.value.orientation)}`,
@@ -141,7 +162,7 @@ useComponentHost(() => import('../components/ui-scroll-area/ui-scroll-area.js'),
141
162
  ? (incoming[name] ?? incoming[kebab] ?? null)
142
163
  : null
143
164
  },
144
- propNames: ['orientation', 'size'],
165
+ propNames: ['fill', 'orientation', 'size'],
145
166
  propValidity: (name: string) =>
146
167
  propValidityState({ contract: propValidityContract, values: { ...props } }, name),
147
168
  refs: { content: contentElement },
@@ -153,6 +174,7 @@ useComponentHost(() => import('../components/ui-scroll-area/ui-scroll-area.js'),
153
174
  data-component="ui-scroll-area"
154
175
  v-bind="nativeAttrs($attrs)"
155
176
  v-prop-validity="{ contract: propValidityContract, values: { ...props } }"
177
+ :data-fill="reflectedProp('fill', 'fill', checkedProps.fill, undefined, false)"
156
178
  :data-orientation="reflectedProp('orientation', 'orientation', checkedProps.orientation, undefined, false)"
157
179
  :data-size="reflectedProp('size', 'size', checkedProps.size, undefined, false)"
158
180
  :data-ui-scroll-area-state="hostState || undefined"
@@ -166,6 +188,10 @@ useComponentHost(() => import('../components/ui-scroll-area/ui-scroll-area.js'),
166
188
  /* The controller sets each fade length from overflow and scroll position. The mask blends
167
189
  into whatever is behind the area without needing to know its colour. Before upgrade, its
168
190
  zero-length defaults leave the authored scroll area fully visible. */
191
+ [data-component~="ui-scroll-area"][data-ui-scroll-area-state~="fill"] {
192
+ flex: 1;
193
+ }
194
+
169
195
  [data-component~="ui-scroll-area"] {
170
196
  display: block;
171
197
  box-sizing: border-box;
package/vue/UiStack.d.ts CHANGED
@@ -8,10 +8,18 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
8
8
  type: PropType<"start" | "center" | "end" | "stretch" | null>;
9
9
  default: null;
10
10
  };
11
+ fill: {
12
+ type: PropType<boolean | null>;
13
+ default: boolean;
14
+ };
11
15
  gap: {
12
16
  type: PropType<"none" | "xs" | "s" | "m" | "l" | "xl" | null>;
13
17
  default: null;
14
18
  };
19
+ height: {
20
+ type: PropType<number | null>;
21
+ default: null;
22
+ };
15
23
  justify: {
16
24
  type: PropType<"start" | "center" | "end" | "between" | null>;
17
25
  default: null;
@@ -21,15 +29,25 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
21
29
  type: PropType<"start" | "center" | "end" | "stretch" | null>;
22
30
  default: null;
23
31
  };
32
+ fill: {
33
+ type: PropType<boolean | null>;
34
+ default: boolean;
35
+ };
24
36
  gap: {
25
37
  type: PropType<"none" | "xs" | "s" | "m" | "l" | "xl" | null>;
26
38
  default: null;
27
39
  };
40
+ height: {
41
+ type: PropType<number | null>;
42
+ default: null;
43
+ };
28
44
  justify: {
29
45
  type: PropType<"start" | "center" | "end" | "between" | null>;
30
46
  default: null;
31
47
  };
32
48
  }>> & Readonly<{}>, {
49
+ fill: boolean | null;
50
+ height: number | null;
33
51
  align: "center" | "start" | "stretch" | "end" | null;
34
52
  gap: "s" | "none" | "xs" | "m" | "l" | "xl" | null;
35
53
  justify: "center" | "start" | "end" | "between" | null;
package/vue/UiStack.vue CHANGED
@@ -16,6 +16,14 @@ const propValidityContract = {
16
16
  'How items sit across the stack: stretch (the default) makes each fill the stack\'s width; start, center, and end keep each at its own width, against the\n start, in the middle, or against the end.',
17
17
  values: ['start', 'center', 'end', 'stretch'],
18
18
  },
19
+ fill: {
20
+ type: { kind: 'terminal', name: 'boolean' },
21
+ required: false,
22
+ target: { attribute: 'fill' },
23
+ description:
24
+ 'Grows into the remaining space of a flex layout and allows its scrolling children to shrink.',
25
+ default: false,
26
+ },
19
27
  gap: {
20
28
  type: { kind: 'terminal', name: 'keyword' },
21
29
  required: false,
@@ -24,6 +32,13 @@ const propValidityContract = {
24
32
  '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\n custom property, set on the stack itself, overrides it; set on an ancestor, it does nothing.',
25
33
  values: ['none', 'xs', 's', 'm', 'l', 'xl'],
26
34
  },
35
+ height: {
36
+ type: { kind: 'terminal', name: 'number' },
37
+ required: false,
38
+ target: { attribute: 'height' },
39
+ description:
40
+ 'An optional measured height in CSS pixels, for a viewport-bounded composition. Omit it to retain intrinsic sizing.',
41
+ },
27
42
  justify: {
28
43
  type: { kind: 'terminal', name: 'keyword' },
29
44
  required: false,
@@ -39,16 +54,24 @@ const props = defineProps({
39
54
  type: null as unknown as PropType<'start' | 'center' | 'end' | 'stretch' | null>,
40
55
  default: null,
41
56
  },
57
+ fill: { type: null as unknown as PropType<boolean | null>, default: false },
42
58
  gap: {
43
59
  type: null as unknown as PropType<'none' | 'xs' | 's' | 'm' | 'l' | 'xl' | null>,
44
60
  default: null,
45
61
  },
62
+ height: { type: null as unknown as PropType<number | null>, default: null },
46
63
  justify: {
47
64
  type: null as unknown as PropType<'start' | 'center' | 'end' | 'between' | null>,
48
65
  default: null,
49
66
  },
50
67
  })
51
- const acceptedProps: Record<string, unknown> = { align: null, gap: null, justify: null }
68
+ const acceptedProps: Record<string, unknown> = {
69
+ align: null,
70
+ fill: false,
71
+ gap: null,
72
+ height: null,
73
+ justify: null,
74
+ }
52
75
  const checkedProps = computed(() => ({
53
76
  align: checkedProp<'start' | 'center' | 'end' | 'stretch'>(
54
77
  props['align'],
@@ -57,6 +80,13 @@ const checkedProps = computed(() => ({
57
80
  'align',
58
81
  acceptedProps,
59
82
  ),
83
+ fill: checkedProp<boolean>(
84
+ props['fill'],
85
+ { kind: 'terminal', name: 'boolean' },
86
+ false,
87
+ 'fill',
88
+ acceptedProps,
89
+ ) as boolean,
60
90
  gap: checkedProp<'none' | 'xs' | 's' | 'm' | 'l' | 'xl'>(
61
91
  props['gap'],
62
92
  { kind: 'terminal', name: 'keyword' },
@@ -64,6 +94,13 @@ const checkedProps = computed(() => ({
64
94
  'gap',
65
95
  acceptedProps,
66
96
  ),
97
+ height: checkedProp<number>(
98
+ props['height'],
99
+ { kind: 'terminal', name: 'number' },
100
+ false,
101
+ 'height',
102
+ acceptedProps,
103
+ ),
67
104
  justify: checkedProp<'start' | 'center' | 'end' | 'between'>(
68
105
  props['justify'],
69
106
  { kind: 'terminal', name: 'keyword' },
@@ -127,6 +164,9 @@ if (
127
164
  /** The values the styles' :host-state() rules test. */
128
165
  const hostState = computed(() =>
129
166
  [
167
+ checkedProps.value.fill && 'fill',
168
+ checkedProps.value.height && 'height',
169
+ checkedProps.value.height != null && `height=${checkedProps.value.height}`,
130
170
  checkedProps.value.gap && 'gap',
131
171
  checkedProps.value.gap != null && `gap=${encodeURIComponent(checkedProps.value.gap)}`,
132
172
  checkedProps.value.align && 'align',
@@ -142,9 +182,12 @@ const hostState = computed(() =>
142
182
  <div
143
183
  data-component="ui-stack"
144
184
  v-bind="nativeAttrs($attrs)"
185
+ :style="{ '--ui-stack-height': checkedProps.height ?? undefined }"
145
186
  v-prop-validity="{ contract: propValidityContract, values: { ...props } }"
146
187
  :data-align="reflectedProp('align', 'align', checkedProps.align, undefined, false)"
188
+ :data-fill="reflectedProp('fill', 'fill', checkedProps.fill, undefined, false)"
147
189
  :data-gap="reflectedProp('gap', 'gap', checkedProps.gap, undefined, false)"
190
+ :data-height="reflectedProp('height', 'height', checkedProps.height, undefined, false)"
148
191
  :data-justify="reflectedProp('justify', 'justify', checkedProps.justify, undefined, false)"
149
192
  :data-ui-stack-state="hostState || undefined"
150
193
  >
@@ -154,6 +197,11 @@ const hostState = computed(() =>
154
197
 
155
198
  <style scoped>
156
199
  /* This component's hooks style the element they are set on, and never a nested one. */
200
+ @property --ui-stack-height {
201
+ syntax: "*";
202
+ inherits: false;
203
+ }
204
+
157
205
  @property --ui-stack-gap {
158
206
  syntax: "*";
159
207
  inherits: false;
@@ -170,6 +218,15 @@ const hostState = computed(() =>
170
218
  margin: 0;
171
219
  }
172
220
 
221
+ [data-component~="ui-stack"][data-ui-stack-state~="fill"] {
222
+ flex: 1;
223
+ min-block-size: 0;
224
+ }
225
+ [data-component~="ui-stack"][data-ui-stack-state~="height"] {
226
+ block-size: calc(var(--ui-stack-height, 0) * 1px);
227
+ min-block-size: 0;
228
+ }
229
+
173
230
  [data-component~="ui-stack"][data-ui-stack-state~="gap=none"] {
174
231
  --_layout-gap: 0px;
175
232
  }