@threadlabs/looma 0.23.21 → 0.23.23

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 (54) hide show
  1. package/components/shared/overlay.js +1 -1
  2. package/components/ui-button/ui-button.html +68 -2
  3. package/components/ui-image/ui-image.html +177 -0
  4. package/components/ui-image/ui-image.js +107 -0
  5. package/components/ui-menu/ui-menu.js +1 -1
  6. package/components/ui-popover/ui-popover.js +4 -1
  7. package/components/ui-separator/ui-separator.html +26 -2
  8. package/dist/index.js +24 -23
  9. package/editor/chunks/extensions.js +633 -20
  10. package/editor/extensions/block-selection.d.ts +6 -0
  11. package/editor/extensions/expand.d.ts +31 -0
  12. package/editor/extensions/image.d.ts +12 -0
  13. package/editor/extensions/index.d.ts +4 -1
  14. package/editor/extensions/index.js +2 -2
  15. package/editor/extensions/preset.d.ts +8 -0
  16. package/editor/extensions/slash-command.d.ts +31 -3
  17. package/editor/extensions/table-of-contents.d.ts +58 -0
  18. package/editor/index.js +2 -2
  19. package/package.json +1 -1
  20. package/styles/ui-button.css +39 -0
  21. package/styles/ui-image.css +153 -0
  22. package/styles/ui-separator.css +22 -2
  23. package/vanilla/UiButton.d.ts +1 -1
  24. package/vanilla/UiButton.js +126 -10
  25. package/vanilla/UiImage.d.ts +21 -0
  26. package/vanilla/UiImage.js +84 -0
  27. package/vanilla/UiSeparator.d.ts +3 -0
  28. package/vanilla/UiSeparator.js +14 -4
  29. package/vanilla/index.js +1 -0
  30. package/vue/UiButton.d.ts +18 -6
  31. package/vue/UiButton.vue +269 -8
  32. package/vue/UiImage.d.ts +100 -0
  33. package/vue/UiImage.js +2 -0
  34. package/vue/UiImage.vue +463 -0
  35. package/vue/UiListItem.d.ts +1 -1
  36. package/vue/UiSection.d.ts +1 -1
  37. package/vue/UiSeparator.d.ts +27 -0
  38. package/vue/UiSeparator.vue +94 -3
  39. package/vue/UiSidebar.d.ts +1 -1
  40. package/vue/chunks/UiButton.js +196 -12
  41. package/vue/chunks/UiImage.js +313 -0
  42. package/vue/chunks/UiSeparator.js +106 -22
  43. package/vue/components.css +252 -53
  44. package/vue/editor/LoomaEditor.d.ts +19 -2
  45. package/vue/editor/divider.d.ts +6 -0
  46. package/vue/editor/expand.d.ts +7 -0
  47. package/vue/editor/image-controls.d.ts +19 -0
  48. package/vue/editor/image-delivery.d.ts +2 -2
  49. package/vue/editor/image.d.ts +7 -0
  50. package/vue/editor/index.d.ts +1 -1
  51. package/vue/editor/index.js +1188 -52
  52. package/vue/editor/table-of-contents.d.ts +7 -0
  53. package/vue/index.d.ts +1 -0
  54. package/vue/index.js +2 -1
@@ -0,0 +1,463 @@
1
+ <!-- Generated by HTML Next 1.0.0-alpha.26 for Vue 3.5. Do not edit. -->
2
+ <script setup lang="ts">
3
+ import { computed, getCurrentInstance, ref, useTemplateRef, watchSyncEffect } from 'vue'
4
+ import type { PropType } from 'vue'
5
+ import { createDispatch, useComponentHost } from './host'
6
+ import { checkedProp, propValidityState, vPropValidity } from './props'
7
+
8
+ defineOptions({ inheritAttrs: false })
9
+
10
+ const propValidityContract = {
11
+ props: {
12
+ alt: {
13
+ type: { kind: 'terminal', name: 'string' },
14
+ required: false,
15
+ target: { attribute: 'alt' },
16
+ description: 'A text alternative for the image.',
17
+ default: '',
18
+ },
19
+ height: {
20
+ type: { kind: 'terminal', name: 'integer' },
21
+ required: false,
22
+ target: { attribute: 'height' },
23
+ description: 'Image height in CSS pixels; resizing preserves the intrinsic aspect ratio.',
24
+ },
25
+ placement: {
26
+ type: { kind: 'terminal', name: 'keyword' },
27
+ required: false,
28
+ target: { attribute: 'placement' },
29
+ description:
30
+ 'Block keeps text below; center centers the image; wrap-left and wrap-right let following text flow beside it.',
31
+ values: ['block', 'center', 'wrap-left', 'wrap-right'],
32
+ default: 'block',
33
+ },
34
+ resizable: {
35
+ type: { kind: 'terminal', name: 'boolean' },
36
+ required: false,
37
+ target: { attribute: 'resizable' },
38
+ description:
39
+ 'Shows four corner handles while selected. Drag to resize, or focus a handle and use arrow keys; Shift uses a larger increment.',
40
+ default: false,
41
+ },
42
+ selected: {
43
+ type: { kind: 'terminal', name: 'boolean' },
44
+ required: false,
45
+ target: { attribute: 'selected' },
46
+ description: 'Outlines the media as the current selection.',
47
+ default: false,
48
+ },
49
+ src: {
50
+ type: { kind: 'terminal', name: 'string' },
51
+ required: false,
52
+ target: { attribute: 'src' },
53
+ description: 'The image source, used by the default media.',
54
+ default: '',
55
+ },
56
+ width: {
57
+ type: { kind: 'terminal', name: 'integer' },
58
+ required: false,
59
+ target: { attribute: 'width' },
60
+ description:
61
+ 'Image width in CSS pixels; omitted uses intrinsic size in block/center placement or responsive sizing when wrapping.',
62
+ },
63
+ },
64
+ } as const
65
+ const props = defineProps({
66
+ alt: { type: null as unknown as PropType<string | null>, default: '' },
67
+ height: { type: null as unknown as PropType<number | null>, default: null },
68
+ placement: {
69
+ type: null as unknown as PropType<'block' | 'center' | 'wrap-left' | 'wrap-right' | null>,
70
+ default: 'block',
71
+ },
72
+ resizable: { type: null as unknown as PropType<boolean | null>, default: false },
73
+ selected: { type: null as unknown as PropType<boolean | null>, default: false },
74
+ src: { type: null as unknown as PropType<string | null>, default: '' },
75
+ width: { type: null as unknown as PropType<number | null>, default: null },
76
+ })
77
+ const acceptedProps: Record<string, unknown> = {
78
+ alt: '',
79
+ height: null,
80
+ placement: 'block',
81
+ resizable: false,
82
+ selected: false,
83
+ src: '',
84
+ width: null,
85
+ }
86
+ const checkedProps = computed(() => ({
87
+ alt: checkedProp<string>(
88
+ props['alt'],
89
+ { kind: 'terminal', name: 'string' },
90
+ false,
91
+ 'alt',
92
+ acceptedProps,
93
+ ) as string,
94
+ height: checkedProp<number>(
95
+ props['height'],
96
+ { kind: 'terminal', name: 'integer' },
97
+ false,
98
+ 'height',
99
+ acceptedProps,
100
+ ),
101
+ placement: checkedProp<'block' | 'center' | 'wrap-left' | 'wrap-right'>(
102
+ props['placement'],
103
+ { kind: 'terminal', name: 'keyword' },
104
+ false,
105
+ 'placement',
106
+ acceptedProps,
107
+ ) as 'block' | 'center' | 'wrap-left' | 'wrap-right',
108
+ resizable: checkedProp<boolean>(
109
+ props['resizable'],
110
+ { kind: 'terminal', name: 'boolean' },
111
+ false,
112
+ 'resizable',
113
+ acceptedProps,
114
+ ) as boolean,
115
+ selected: checkedProp<boolean>(
116
+ props['selected'],
117
+ { kind: 'terminal', name: 'boolean' },
118
+ false,
119
+ 'selected',
120
+ acceptedProps,
121
+ ) as boolean,
122
+ src: checkedProp<string>(
123
+ props['src'],
124
+ { kind: 'terminal', name: 'string' },
125
+ false,
126
+ 'src',
127
+ acceptedProps,
128
+ ) as string,
129
+ width: checkedProp<number>(
130
+ props['width'],
131
+ { kind: 'terminal', name: 'integer' },
132
+ false,
133
+ 'width',
134
+ acceptedProps,
135
+ ),
136
+ }))
137
+ void checkedProps.value
138
+ watchSyncEffect(() => {
139
+ void checkedProps.value
140
+ })
141
+ function nativeAttrs(attrs: Record<string, unknown>): Record<string, unknown> {
142
+ const names = Object.keys(attrs)
143
+ if (!names.some((name) => Object.hasOwn(Object.prototype, name))) return attrs
144
+ return Object.fromEntries(names.map((name) => [
145
+ Object.hasOwn(Object.prototype, name)
146
+ ? name === '__proto__' ? '__Proto__' : name[0]!.toUpperCase() + name.slice(1)
147
+ : name,
148
+ attrs[name],
149
+ ]))
150
+ }
151
+ const componentInstance = getCurrentInstance()
152
+ function reflectedProp(
153
+ name: string,
154
+ kebab: string,
155
+ value: unknown,
156
+ fallback: string | undefined,
157
+ bound: boolean,
158
+ separator?: string,
159
+ ): string | undefined {
160
+ const incoming = componentInstance?.vnode.props
161
+ if (
162
+ !bound
163
+ && (incoming === null || incoming === undefined
164
+ || (!Object.hasOwn(incoming, name) && !Object.hasOwn(incoming, kebab)))
165
+ ) return fallback
166
+ if (value === null || value === undefined) return undefined
167
+ return Array.isArray(value) && separator !== undefined
168
+ ? value.join(separator)
169
+ : typeof value === 'object'
170
+ ? JSON.stringify(value)
171
+ : String(value)
172
+ }
173
+ const hydrationInstance = getCurrentInstance()
174
+ const emit = defineEmits<{
175
+ 'update:height': [value: number]
176
+ 'update:width': [value: number]
177
+ resize: [
178
+ event: CustomEvent<
179
+ {
180
+ readonly width: number
181
+ readonly height: number
182
+ readonly phase: 'preview' | 'commit' | 'cancel'
183
+ readonly trigger: 'pointer' | 'keyboard'
184
+ }
185
+ >,
186
+ ]
187
+ }>()
188
+
189
+ const root = ref<HTMLElement | null>(null)
190
+ const frameElement = useTemplateRef<HTMLElement>('frame')
191
+ const hydrationNode = hydrationInstance?.vnode.el
192
+ if (
193
+ typeof window !== 'undefined' && hydrationNode != null
194
+ && (!(hydrationNode instanceof Element) || hydrationNode.localName !== ('figure'))
195
+ ) {
196
+ throw Object.assign(new Error('HR005: Server markup for <ui-image> has an incompatible root.'), {
197
+ name: 'HtmlDiagnosticError',
198
+ diagnostic: Object.freeze({
199
+ code: 'HR005',
200
+ message: 'Server markup for <ui-image> has an incompatible root.',
201
+ }),
202
+ })
203
+ }
204
+
205
+ /** The values the styles' :host-state() rules test. */
206
+ const hostState = computed(() =>
207
+ [
208
+ checkedProps.value.placement && 'placement',
209
+ checkedProps.value.placement != null
210
+ && `placement=${encodeURIComponent(checkedProps.value.placement)}`,
211
+ checkedProps.value.selected && 'selected',
212
+ ].filter(Boolean).join(' ')
213
+ )
214
+
215
+ const isResizeDetail = (detail: unknown): boolean =>
216
+ detail !== null && typeof detail === 'object' && !Array.isArray(detail)
217
+ && Number.isInteger((detail as Record<string, unknown>)['width'])
218
+ && Number.isInteger((detail as Record<string, unknown>)['height'])
219
+ && ((detail as Record<string, unknown>)['phase'] === 'preview'
220
+ || (detail as Record<string, unknown>)['phase'] === 'commit'
221
+ || (detail as Record<string, unknown>)['phase'] === 'cancel')
222
+ && ((detail as Record<string, unknown>)['trigger'] === 'pointer'
223
+ || (detail as Record<string, unknown>)['trigger'] === 'keyboard')
224
+ && Object.keys(detail as object).every((key) =>
225
+ ['width', 'height', 'phase', 'trigger'].includes(key)
226
+ )
227
+ const dispatch = createDispatch(root, emit as (name: string, detail: unknown) => void, {
228
+ checks: {
229
+ resize: isResizeDetail,
230
+ },
231
+ modeled: ['height', 'width'],
232
+ })
233
+
234
+ useComponentHost(() => import('../components/ui-image/ui-image.js'), {
235
+ root,
236
+ dispatch,
237
+ controllerSource: {
238
+ specifier: '../components/ui-image/ui-image.js',
239
+ definition: './components/ui-image/ui-image.html',
240
+ },
241
+ props: checkedProps,
242
+ propInputs: (name: string) => {
243
+ const incoming = hydrationInstance?.vnode.props
244
+ const kebab = name.replace(/[A-Z]/g, (letter) => '-' + letter.toLowerCase())
245
+ return incoming !== null && incoming !== undefined
246
+ ? (incoming[name] ?? incoming[kebab] ?? null)
247
+ : null
248
+ },
249
+ propNames: ['alt', 'height', 'placement', 'resizable', 'selected', 'src', 'width'],
250
+ propValidity: (name: string) =>
251
+ propValidityState({ contract: propValidityContract, values: { ...props } }, name),
252
+ refs: { frame: frameElement },
253
+ })
254
+ </script>
255
+
256
+ <template>
257
+ <figure
258
+ data-component="ui-image"
259
+ v-bind="nativeAttrs($attrs)"
260
+ v-prop-validity="{ contract: propValidityContract, values: { ...props } }"
261
+ :data-alt="reflectedProp('alt', 'alt', checkedProps.alt, undefined, false)"
262
+ :data-height="reflectedProp('height', 'height', checkedProps.height, undefined, false)"
263
+ :data-placement="reflectedProp('placement', 'placement', checkedProps.placement, undefined, false)"
264
+ :data-resizable="reflectedProp('resizable', 'resizable', checkedProps.resizable, undefined, false)"
265
+ :data-selected="reflectedProp('selected', 'selected', checkedProps.selected, undefined, false)"
266
+ :data-src="reflectedProp('src', 'src', checkedProps.src, undefined, false)"
267
+ :data-width="reflectedProp('width', 'width', checkedProps.width, undefined, false)"
268
+ :data-ui-image-state="hostState || undefined"
269
+ ref="root"
270
+ >
271
+ <div class="frame" ref="frame">
272
+ <slot name="media"><img
273
+ :src="checkedProps.src ?? undefined"
274
+ :alt="checkedProps.alt ?? undefined"
275
+ :width="checkedProps.width ?? undefined"
276
+ :height="checkedProps.height ?? undefined"
277
+ ></slot>
278
+ <div class="handles" :hidden="!(checkedProps.selected && checkedProps.resizable)">
279
+ <button
280
+ type="button"
281
+ class="handle"
282
+ data-corner="top-left"
283
+ aria-label="Resize image from top left"
284
+ >
285
+ </button>
286
+ <button
287
+ type="button"
288
+ class="handle"
289
+ data-corner="top-right"
290
+ aria-label="Resize image from top right"
291
+ >
292
+ </button>
293
+ <button
294
+ type="button"
295
+ class="handle"
296
+ data-corner="bottom-left"
297
+ aria-label="Resize image from bottom left"
298
+ >
299
+ </button>
300
+ <button
301
+ type="button"
302
+ class="handle"
303
+ data-corner="bottom-right"
304
+ aria-label="Resize image from bottom right"
305
+ >
306
+ </button>
307
+ </div>
308
+ <slot name="controls" />
309
+ </div>
310
+ </figure>
311
+ </template>
312
+
313
+ <style scoped>
314
+ [data-component~="ui-image"] {
315
+ display: flex;
316
+ justify-content: flex-start;
317
+ box-sizing: border-box;
318
+ max-inline-size: 100%;
319
+ min-inline-size: 0;
320
+ margin: 0;
321
+ padding: var(--ui-space-3) 0;
322
+ }
323
+ [data-component~="ui-image"][data-ui-image-state~="placement=center"] {
324
+ justify-content: center;
325
+ }
326
+ [data-component~="ui-image"][data-ui-image-state~="placement=wrap-left"] {
327
+ float: left;
328
+ padding-inline-end: var(--ui-space-4);
329
+ }
330
+ [data-component~="ui-image"][data-ui-image-state~="placement=wrap-right"] {
331
+ float: right;
332
+ padding-inline-start: var(--ui-space-4);
333
+ }
334
+ [data-component~="ui-image"]:is([data-component~="ui-image"][data-ui-image-state~="placement=wrap-left"], [data-component~="ui-image"][data-ui-image-state~="placement=wrap-right"]) {
335
+ inline-size: calc(var(--_ui-image-width, calc(100% / 3)) + var(--ui-space-4));
336
+ }
337
+ [data-component~="ui-image"]:is([data-component~="ui-image"][data-ui-image-state~="placement=wrap-left"], [data-component~="ui-image"][data-ui-image-state~="placement=wrap-right"])
338
+ .frame {
339
+ inline-size: 100%;
340
+ }
341
+ [data-component~="ui-image"]:is([data-component~="ui-image"][data-ui-image-state~="placement=wrap-left"], [data-component~="ui-image"][data-ui-image-state~="placement=wrap-right"])
342
+ .frame
343
+ img,
344
+ [data-component~="ui-image"]:is([data-component~="ui-image"][data-ui-image-state~="placement=wrap-left"], [data-component~="ui-image"][data-ui-image-state~="placement=wrap-right"])
345
+ .frame
346
+ :slotted(img) {
347
+ inline-size: 100%;
348
+ }
349
+ .frame {
350
+ position: relative;
351
+ flex: 0 1 auto;
352
+ inline-size: var(--_ui-image-width, fit-content);
353
+ max-inline-size: 100%;
354
+ min-inline-size: 0;
355
+ align-self: flex-start;
356
+ border-radius: var(--ui-radius-md);
357
+ }
358
+ .frame img, .frame :slotted(img) {
359
+ display: block;
360
+ max-inline-size: 100%;
361
+ block-size: auto;
362
+ margin: 0;
363
+ border-radius: inherit;
364
+ }
365
+ [data-component~="ui-image"][data-resizing] .frame img,
366
+ [data-component~="ui-image"][data-resizing] .frame :slotted(img) {
367
+ inline-size: 100%;
368
+ }
369
+ [data-component~="ui-image"][data-ui-image-state~="selected"] .frame {
370
+ outline: var(--ui-focus-width) solid var(--ui-focus-ring);
371
+ outline-offset: var(--ui-border-width);
372
+ }
373
+ .handle {
374
+ position: absolute;
375
+ z-index: 1;
376
+ inline-size: 24px;
377
+ block-size: 24px;
378
+ padding: 0;
379
+ border: 0;
380
+ background: transparent;
381
+ touch-action: none;
382
+ }
383
+ .handle::after {
384
+ content: "";
385
+ position: absolute;
386
+ inset: 8px;
387
+ background: var(--ui-surface);
388
+ border: var(--ui-border-width) solid var(--ui-focus-ring);
389
+ border-radius: var(--ui-radius-sm);
390
+ }
391
+ .handle:focus-visible {
392
+ outline: var(--ui-focus-width) solid var(--ui-focus-ring);
393
+ border-radius: var(--ui-radius-sm);
394
+ }
395
+ .handle[data-corner="top-left"] {
396
+ top: -12px;
397
+ left: -12px;
398
+ cursor: nwse-resize;
399
+ }
400
+ .handle[data-corner="top-right"] {
401
+ top: -12px;
402
+ right: -12px;
403
+ cursor: nesw-resize;
404
+ }
405
+ .handle[data-corner="bottom-left"] {
406
+ bottom: -12px;
407
+ left: -12px;
408
+ cursor: nesw-resize;
409
+ }
410
+ .handle[data-corner="bottom-right"] {
411
+ bottom: -12px;
412
+ right: -12px;
413
+ cursor: nwse-resize;
414
+ }
415
+ .handles[hidden], [data-component~="ui-image"][hidden] {
416
+ display: none;
417
+ }
418
+ @media (max-width: 767px) {
419
+ [data-component~="ui-image"]:is([data-component~="ui-image"][data-ui-image-state~="placement=wrap-left"], [data-component~="ui-image"][data-ui-image-state~="placement=wrap-right"]) {
420
+ float: none;
421
+ inline-size: 100%;
422
+ padding-inline: 0;
423
+ }
424
+ [data-component~="ui-image"]:is([data-component~="ui-image"][data-ui-image-state~="placement=wrap-left"], [data-component~="ui-image"][data-ui-image-state~="placement=wrap-right"])
425
+ .handles {
426
+ display: none;
427
+ }
428
+ }
429
+ @media (pointer: coarse) {
430
+ .handle {
431
+ inline-size: 44px;
432
+ block-size: 44px;
433
+ }
434
+ .handle::after {
435
+ inset: 18px;
436
+ }
437
+ .handle[data-corner="top-left"] {
438
+ top: -22px;
439
+ left: -22px;
440
+ }
441
+ .handle[data-corner="top-right"] {
442
+ top: -22px;
443
+ right: -22px;
444
+ }
445
+ .handle[data-corner="bottom-left"] {
446
+ bottom: -22px;
447
+ left: -22px;
448
+ }
449
+ .handle[data-corner="bottom-right"] {
450
+ bottom: -22px;
451
+ right: -22px;
452
+ }
453
+ }
454
+ @media (forced-colors: active) {
455
+ [data-component~="ui-image"][data-ui-image-state~="selected"] .frame, .handle:focus-visible {
456
+ outline-color: Highlight;
457
+ }
458
+ .handle::after {
459
+ border-color: Highlight;
460
+ background: Canvas;
461
+ }
462
+ }
463
+ </style>
@@ -44,7 +44,7 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
44
44
  default: boolean;
45
45
  };
46
46
  }>> & Readonly<{}>, {
47
- variant: "row" | "card" | null;
47
+ variant: "card" | "row" | null;
48
48
  current: boolean | null;
49
49
  wrap: boolean | null;
50
50
  highlighted: boolean | null;
@@ -35,7 +35,7 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
35
35
  };
36
36
  }>> & Readonly<{}>, {
37
37
  tone: "neutral" | "danger" | null;
38
- variant: "plain" | "card" | "divided" | null;
38
+ variant: "card" | "plain" | "divided" | null;
39
39
  heading: string | null;
40
40
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
41
41
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -1,16 +1,43 @@
1
1
  import type { PropType } from 'vue';
2
2
  declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
3
+ emphasis: {
4
+ type: PropType<"normal" | "strong" | null>;
5
+ default: string;
6
+ };
3
7
  orientation: {
4
8
  type: PropType<"horizontal" | "vertical" | null>;
5
9
  default: string;
6
10
  };
11
+ selectable: {
12
+ type: PropType<boolean | null>;
13
+ default: boolean;
14
+ };
15
+ selected: {
16
+ type: PropType<boolean | null>;
17
+ default: boolean;
18
+ };
7
19
  }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
20
+ emphasis: {
21
+ type: PropType<"normal" | "strong" | null>;
22
+ default: string;
23
+ };
8
24
  orientation: {
9
25
  type: PropType<"horizontal" | "vertical" | null>;
10
26
  default: string;
11
27
  };
28
+ selectable: {
29
+ type: PropType<boolean | null>;
30
+ default: boolean;
31
+ };
32
+ selected: {
33
+ type: PropType<boolean | null>;
34
+ default: boolean;
35
+ };
12
36
  }>> & Readonly<{}>, {
37
+ selected: boolean | null;
13
38
  orientation: "vertical" | "horizontal" | null;
39
+ emphasis: "strong" | "normal" | null;
40
+ selectable: boolean | null;
14
41
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
15
42
  declare const _default: typeof __VLS_export;
16
43
  export default _default;
@@ -8,6 +8,15 @@ defineOptions({ inheritAttrs: false })
8
8
 
9
9
  const propValidityContract = {
10
10
  props: {
11
+ emphasis: {
12
+ type: { kind: 'terminal', name: 'keyword' },
13
+ required: false,
14
+ target: { attribute: 'emphasis' },
15
+ description:
16
+ 'Normal uses the decorative divider color; strong uses the stronger enclosing-edge color for clearer separation.',
17
+ values: ['normal', 'strong'],
18
+ default: 'normal',
19
+ },
11
20
  orientation: {
12
21
  type: { kind: 'terminal', name: 'keyword' },
13
22
  required: false,
@@ -17,16 +26,47 @@ const propValidityContract = {
17
26
  values: ['horizontal', 'vertical'],
18
27
  default: 'horizontal',
19
28
  },
29
+ selectable: {
30
+ type: { kind: 'terminal', name: 'boolean' },
31
+ required: false,
32
+ target: { attribute: 'selectable' },
33
+ description:
34
+ 'Gives a document divider a pointer-sized hit area. The editor owns selection and deletion.',
35
+ default: false,
36
+ },
37
+ selected: {
38
+ type: { kind: 'terminal', name: 'boolean' },
39
+ required: false,
40
+ target: { attribute: 'selected' },
41
+ description:
42
+ 'Shows the selected document divider. Selection does not change separator semantics.',
43
+ default: false,
44
+ },
20
45
  },
21
46
  } as const
22
47
  const props = defineProps({
48
+ emphasis: { type: null as unknown as PropType<'normal' | 'strong' | null>, default: 'normal' },
23
49
  orientation: {
24
50
  type: null as unknown as PropType<'horizontal' | 'vertical' | null>,
25
51
  default: 'horizontal',
26
52
  },
53
+ selectable: { type: null as unknown as PropType<boolean | null>, default: false },
54
+ selected: { type: null as unknown as PropType<boolean | null>, default: false },
27
55
  })
28
- const acceptedProps: Record<string, unknown> = { orientation: 'horizontal' }
56
+ const acceptedProps: Record<string, unknown> = {
57
+ emphasis: 'normal',
58
+ orientation: 'horizontal',
59
+ selectable: false,
60
+ selected: false,
61
+ }
29
62
  const checkedProps = computed(() => ({
63
+ emphasis: checkedProp<'normal' | 'strong'>(
64
+ props['emphasis'],
65
+ { kind: 'terminal', name: 'keyword' },
66
+ false,
67
+ 'emphasis',
68
+ acceptedProps,
69
+ ) as 'normal' | 'strong',
30
70
  orientation: checkedProp<'horizontal' | 'vertical'>(
31
71
  props['orientation'],
32
72
  { kind: 'terminal', name: 'keyword' },
@@ -34,6 +74,20 @@ const checkedProps = computed(() => ({
34
74
  'orientation',
35
75
  acceptedProps,
36
76
  ) as 'horizontal' | 'vertical',
77
+ selectable: checkedProp<boolean>(
78
+ props['selectable'],
79
+ { kind: 'terminal', name: 'boolean' },
80
+ false,
81
+ 'selectable',
82
+ acceptedProps,
83
+ ) as boolean,
84
+ selected: checkedProp<boolean>(
85
+ props['selected'],
86
+ { kind: 'terminal', name: 'boolean' },
87
+ false,
88
+ 'selected',
89
+ acceptedProps,
90
+ ) as boolean,
37
91
  }))
38
92
  void checkedProps.value
39
93
  watchSyncEffect(() => {
@@ -89,6 +143,17 @@ if (
89
143
  },
90
144
  )
91
145
  }
146
+
147
+ /** The values the styles' :host-state() rules test. */
148
+ const hostState = computed(() =>
149
+ [
150
+ checkedProps.value.emphasis && 'emphasis',
151
+ checkedProps.value.emphasis != null
152
+ && `emphasis=${encodeURIComponent(checkedProps.value.emphasis)}`,
153
+ checkedProps.value.selectable && 'selectable',
154
+ checkedProps.value.selected && 'selected',
155
+ ].filter(Boolean).join(' ')
156
+ )
92
157
  </script>
93
158
 
94
159
  <template>
@@ -97,7 +162,11 @@ if (
97
162
  v-bind="nativeAttrs($attrs)"
98
163
  :aria-orientation="checkedProps.orientation ?? undefined"
99
164
  v-prop-validity="{ contract: propValidityContract, values: { ...props } }"
165
+ :data-emphasis="reflectedProp('emphasis', 'emphasis', checkedProps.emphasis, undefined, false)"
100
166
  :data-orientation="reflectedProp('orientation', 'orientation', checkedProps.orientation, undefined, false)"
167
+ :data-selectable="reflectedProp('selectable', 'selectable', checkedProps.selectable, undefined, false)"
168
+ :data-selected="reflectedProp('selected', 'selected', checkedProps.selected, undefined, false)"
169
+ :data-ui-separator-state="hostState || undefined"
101
170
  >
102
171
  </template>
103
172
 
@@ -115,13 +184,35 @@ if (
115
184
  margin: 0;
116
185
  border: 0;
117
186
  border-block-start: var(--ui-border-width) solid
118
- var(--ui-separator-color, var(--ui-border-divider));
187
+ var(--ui-separator-color, var(--_separator-color, var(--ui-border-divider)));
188
+ }
189
+
190
+ [data-component~="ui-separator"][data-ui-separator-state~="emphasis=strong"] {
191
+ --_separator-color: var(--ui-border-strong);
119
192
  }
120
193
 
121
194
  [data-component~="ui-separator"][aria-orientation="vertical"] {
122
195
  block-size: auto;
123
196
  border-block-start: 0;
124
197
  border-inline-start: var(--ui-border-width) solid
125
- var(--ui-separator-color, var(--ui-border-divider));
198
+ var(--ui-separator-color, var(--_separator-color, var(--ui-border-divider)));
199
+ }
200
+
201
+ [data-component~="ui-separator"][data-ui-separator-state~="selectable"] {
202
+ min-block-size: var(--ui-control-size-sm);
203
+ border: 0;
204
+ background: linear-gradient(
205
+ var(--ui-separator-color, var(--ui-border-divider)),
206
+ var(--ui-separator-color, var(--ui-border-divider))
207
+ ) center / 100% var(--ui-border-width) no-repeat;
208
+ cursor: pointer;
209
+ }
210
+ [data-component~="ui-separator"][data-ui-separator-state~="selectable"]:hover {
211
+ background-color: var(--ui-surface-hover);
212
+ }
213
+ [data-component~="ui-separator"][data-ui-separator-state~="selected"] {
214
+ outline: var(--ui-focus-width) solid var(--ui-focus-ring);
215
+ outline-offset: var(--ui-focus-width);
216
+ border-radius: var(--ui-radius-sm);
126
217
  }
127
218
  </style>
@@ -101,9 +101,9 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
101
101
  "onUpdate:width"?: ((value: number) => any) | undefined;
102
102
  }>, {
103
103
  width: number | null;
104
+ resizable: boolean | null;
104
105
  breakpoint: "sm" | "md" | "lg" | null;
105
106
  collapsed: boolean | null;
106
- resizable: boolean | null;
107
107
  side: "start" | "end" | null;
108
108
  maxWidth: number | null;
109
109
  minWidth: number | null;