@threadlabs/looma 0.23.21 → 0.23.22

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 (45) hide show
  1. package/components/shared/overlay.js +1 -1
  2. package/components/ui-image/ui-image.html +177 -0
  3. package/components/ui-image/ui-image.js +107 -0
  4. package/components/ui-menu/ui-menu.js +1 -1
  5. package/components/ui-popover/ui-popover.js +4 -1
  6. package/components/ui-separator/ui-separator.html +18 -0
  7. package/dist/index.js +24 -23
  8. package/editor/chunks/extensions.js +633 -20
  9. package/editor/extensions/block-selection.d.ts +6 -0
  10. package/editor/extensions/expand.d.ts +31 -0
  11. package/editor/extensions/image.d.ts +12 -0
  12. package/editor/extensions/index.d.ts +4 -1
  13. package/editor/extensions/index.js +2 -2
  14. package/editor/extensions/preset.d.ts +8 -0
  15. package/editor/extensions/slash-command.d.ts +31 -3
  16. package/editor/extensions/table-of-contents.d.ts +58 -0
  17. package/editor/index.js +2 -2
  18. package/package.json +1 -1
  19. package/styles/ui-image.css +153 -0
  20. package/styles/ui-separator.css +16 -0
  21. package/vanilla/UiImage.d.ts +21 -0
  22. package/vanilla/UiImage.js +84 -0
  23. package/vanilla/UiSeparator.d.ts +2 -0
  24. package/vanilla/UiSeparator.js +10 -2
  25. package/vanilla/index.js +1 -0
  26. package/vue/UiImage.d.ts +100 -0
  27. package/vue/UiImage.js +2 -0
  28. package/vue/UiImage.vue +463 -0
  29. package/vue/UiSeparator.d.ts +18 -0
  30. package/vue/UiSeparator.vue +66 -1
  31. package/vue/UiSidebar.d.ts +1 -1
  32. package/vue/chunks/UiImage.js +313 -0
  33. package/vue/chunks/UiSeparator.js +79 -22
  34. package/vue/components.css +169 -9
  35. package/vue/editor/LoomaEditor.d.ts +19 -2
  36. package/vue/editor/divider.d.ts +6 -0
  37. package/vue/editor/expand.d.ts +7 -0
  38. package/vue/editor/image-controls.d.ts +19 -0
  39. package/vue/editor/image-delivery.d.ts +2 -2
  40. package/vue/editor/image.d.ts +7 -0
  41. package/vue/editor/index.d.ts +1 -1
  42. package/vue/editor/index.js +1188 -52
  43. package/vue/editor/table-of-contents.d.ts +7 -0
  44. package/vue/index.d.ts +1 -0
  45. package/vue/index.js +2 -1
@@ -0,0 +1,100 @@
1
+ import type { PropType } from 'vue';
2
+ declare var __VLS_1: {}, __VLS_3: {};
3
+ type __VLS_Slots = {} & {
4
+ media?: (props: typeof __VLS_1) => any;
5
+ } & {
6
+ controls?: (props: typeof __VLS_3) => any;
7
+ };
8
+ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
9
+ alt: {
10
+ type: PropType<string | null>;
11
+ default: string;
12
+ };
13
+ height: {
14
+ type: PropType<number | null>;
15
+ default: null;
16
+ };
17
+ placement: {
18
+ type: PropType<"block" | "center" | "wrap-left" | "wrap-right" | null>;
19
+ default: string;
20
+ };
21
+ resizable: {
22
+ type: PropType<boolean | null>;
23
+ default: boolean;
24
+ };
25
+ selected: {
26
+ type: PropType<boolean | null>;
27
+ default: boolean;
28
+ };
29
+ src: {
30
+ type: PropType<string | null>;
31
+ default: string;
32
+ };
33
+ width: {
34
+ type: PropType<number | null>;
35
+ default: null;
36
+ };
37
+ }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
38
+ resize: (event: CustomEvent<{
39
+ readonly width: number;
40
+ readonly height: number;
41
+ readonly phase: "preview" | "commit" | "cancel";
42
+ readonly trigger: "pointer" | "keyboard";
43
+ }>) => any;
44
+ "update:height": (value: number) => any;
45
+ "update:width": (value: number) => any;
46
+ }, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
47
+ alt: {
48
+ type: PropType<string | null>;
49
+ default: string;
50
+ };
51
+ height: {
52
+ type: PropType<number | null>;
53
+ default: null;
54
+ };
55
+ placement: {
56
+ type: PropType<"block" | "center" | "wrap-left" | "wrap-right" | null>;
57
+ default: string;
58
+ };
59
+ resizable: {
60
+ type: PropType<boolean | null>;
61
+ default: boolean;
62
+ };
63
+ selected: {
64
+ type: PropType<boolean | null>;
65
+ default: boolean;
66
+ };
67
+ src: {
68
+ type: PropType<string | null>;
69
+ default: string;
70
+ };
71
+ width: {
72
+ type: PropType<number | null>;
73
+ default: null;
74
+ };
75
+ }>> & Readonly<{
76
+ onResize?: ((event: CustomEvent<{
77
+ readonly width: number;
78
+ readonly height: number;
79
+ readonly phase: "preview" | "commit" | "cancel";
80
+ readonly trigger: "pointer" | "keyboard";
81
+ }>) => any) | undefined;
82
+ "onUpdate:height"?: ((value: number) => any) | undefined;
83
+ "onUpdate:width"?: ((value: number) => any) | undefined;
84
+ }>, {
85
+ alt: string | null;
86
+ selected: boolean | null;
87
+ width: number | null;
88
+ height: number | null;
89
+ src: string | null;
90
+ placement: "center" | "block" | "wrap-left" | "wrap-right" | null;
91
+ resizable: boolean | null;
92
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
93
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
94
+ declare const _default: typeof __VLS_export;
95
+ export default _default;
96
+ type __VLS_WithSlots<T, S> = T & {
97
+ new (): {
98
+ $slots: S;
99
+ };
100
+ };
package/vue/UiImage.js ADDED
@@ -0,0 +1,2 @@
1
+ import { t as UiImage_default } from "./chunks/UiImage.js";
2
+ export { UiImage_default as default };
@@ -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>
@@ -4,13 +4,31 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
4
4
  type: PropType<"horizontal" | "vertical" | null>;
5
5
  default: string;
6
6
  };
7
+ selectable: {
8
+ type: PropType<boolean | null>;
9
+ default: boolean;
10
+ };
11
+ selected: {
12
+ type: PropType<boolean | null>;
13
+ default: boolean;
14
+ };
7
15
  }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
8
16
  orientation: {
9
17
  type: PropType<"horizontal" | "vertical" | null>;
10
18
  default: string;
11
19
  };
20
+ selectable: {
21
+ type: PropType<boolean | null>;
22
+ default: boolean;
23
+ };
24
+ selected: {
25
+ type: PropType<boolean | null>;
26
+ default: boolean;
27
+ };
12
28
  }>> & Readonly<{}>, {
29
+ selected: boolean | null;
13
30
  orientation: "vertical" | "horizontal" | null;
31
+ selectable: boolean | null;
14
32
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
15
33
  declare const _default: typeof __VLS_export;
16
34
  export default _default;
@@ -17,6 +17,22 @@ const propValidityContract = {
17
17
  values: ['horizontal', 'vertical'],
18
18
  default: 'horizontal',
19
19
  },
20
+ selectable: {
21
+ type: { kind: 'terminal', name: 'boolean' },
22
+ required: false,
23
+ target: { attribute: 'selectable' },
24
+ description:
25
+ 'Gives a document divider a pointer-sized hit area. The editor owns selection and deletion.',
26
+ default: false,
27
+ },
28
+ selected: {
29
+ type: { kind: 'terminal', name: 'boolean' },
30
+ required: false,
31
+ target: { attribute: 'selected' },
32
+ description:
33
+ 'Shows the selected document divider. Selection does not change separator semantics.',
34
+ default: false,
35
+ },
20
36
  },
21
37
  } as const
22
38
  const props = defineProps({
@@ -24,8 +40,14 @@ const props = defineProps({
24
40
  type: null as unknown as PropType<'horizontal' | 'vertical' | null>,
25
41
  default: 'horizontal',
26
42
  },
43
+ selectable: { type: null as unknown as PropType<boolean | null>, default: false },
44
+ selected: { type: null as unknown as PropType<boolean | null>, default: false },
27
45
  })
28
- const acceptedProps: Record<string, unknown> = { orientation: 'horizontal' }
46
+ const acceptedProps: Record<string, unknown> = {
47
+ orientation: 'horizontal',
48
+ selectable: false,
49
+ selected: false,
50
+ }
29
51
  const checkedProps = computed(() => ({
30
52
  orientation: checkedProp<'horizontal' | 'vertical'>(
31
53
  props['orientation'],
@@ -34,6 +56,20 @@ const checkedProps = computed(() => ({
34
56
  'orientation',
35
57
  acceptedProps,
36
58
  ) as 'horizontal' | 'vertical',
59
+ selectable: checkedProp<boolean>(
60
+ props['selectable'],
61
+ { kind: 'terminal', name: 'boolean' },
62
+ false,
63
+ 'selectable',
64
+ acceptedProps,
65
+ ) as boolean,
66
+ selected: checkedProp<boolean>(
67
+ props['selected'],
68
+ { kind: 'terminal', name: 'boolean' },
69
+ false,
70
+ 'selected',
71
+ acceptedProps,
72
+ ) as boolean,
37
73
  }))
38
74
  void checkedProps.value
39
75
  watchSyncEffect(() => {
@@ -89,6 +125,14 @@ if (
89
125
  },
90
126
  )
91
127
  }
128
+
129
+ /** The values the styles' :host-state() rules test. */
130
+ const hostState = computed(() =>
131
+ [
132
+ checkedProps.value.selectable && 'selectable',
133
+ checkedProps.value.selected && 'selected',
134
+ ].filter(Boolean).join(' ')
135
+ )
92
136
  </script>
93
137
 
94
138
  <template>
@@ -98,6 +142,9 @@ if (
98
142
  :aria-orientation="checkedProps.orientation ?? undefined"
99
143
  v-prop-validity="{ contract: propValidityContract, values: { ...props } }"
100
144
  :data-orientation="reflectedProp('orientation', 'orientation', checkedProps.orientation, undefined, false)"
145
+ :data-selectable="reflectedProp('selectable', 'selectable', checkedProps.selectable, undefined, false)"
146
+ :data-selected="reflectedProp('selected', 'selected', checkedProps.selected, undefined, false)"
147
+ :data-ui-separator-state="hostState || undefined"
101
148
  >
102
149
  </template>
103
150
 
@@ -124,4 +171,22 @@ if (
124
171
  border-inline-start: var(--ui-border-width) solid
125
172
  var(--ui-separator-color, var(--ui-border-divider));
126
173
  }
174
+
175
+ [data-component~="ui-separator"][data-ui-separator-state~="selectable"] {
176
+ min-block-size: var(--ui-control-size-sm);
177
+ border: 0;
178
+ background: linear-gradient(
179
+ var(--ui-separator-color, var(--ui-border-divider)),
180
+ var(--ui-separator-color, var(--ui-border-divider))
181
+ ) center / 100% var(--ui-border-width) no-repeat;
182
+ cursor: pointer;
183
+ }
184
+ [data-component~="ui-separator"][data-ui-separator-state~="selectable"]:hover {
185
+ background-color: var(--ui-surface-hover);
186
+ }
187
+ [data-component~="ui-separator"][data-ui-separator-state~="selected"] {
188
+ outline: var(--ui-focus-width) solid var(--ui-focus-ring);
189
+ outline-offset: var(--ui-focus-width);
190
+ border-radius: var(--ui-radius-sm);
191
+ }
127
192
  </style>