@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.
- package/components/shared/overlay.js +1 -1
- package/components/ui-image/ui-image.html +177 -0
- package/components/ui-image/ui-image.js +107 -0
- package/components/ui-menu/ui-menu.js +1 -1
- package/components/ui-popover/ui-popover.js +4 -1
- package/components/ui-separator/ui-separator.html +18 -0
- package/dist/index.js +24 -23
- package/editor/chunks/extensions.js +633 -20
- package/editor/extensions/block-selection.d.ts +6 -0
- package/editor/extensions/expand.d.ts +31 -0
- package/editor/extensions/image.d.ts +12 -0
- package/editor/extensions/index.d.ts +4 -1
- package/editor/extensions/index.js +2 -2
- package/editor/extensions/preset.d.ts +8 -0
- package/editor/extensions/slash-command.d.ts +31 -3
- package/editor/extensions/table-of-contents.d.ts +58 -0
- package/editor/index.js +2 -2
- package/package.json +1 -1
- package/styles/ui-image.css +153 -0
- package/styles/ui-separator.css +16 -0
- package/vanilla/UiImage.d.ts +21 -0
- package/vanilla/UiImage.js +84 -0
- package/vanilla/UiSeparator.d.ts +2 -0
- package/vanilla/UiSeparator.js +10 -2
- package/vanilla/index.js +1 -0
- package/vue/UiImage.d.ts +100 -0
- package/vue/UiImage.js +2 -0
- package/vue/UiImage.vue +463 -0
- package/vue/UiSeparator.d.ts +18 -0
- package/vue/UiSeparator.vue +66 -1
- package/vue/UiSidebar.d.ts +1 -1
- package/vue/chunks/UiImage.js +313 -0
- package/vue/chunks/UiSeparator.js +79 -22
- package/vue/components.css +169 -9
- package/vue/editor/LoomaEditor.d.ts +19 -2
- package/vue/editor/divider.d.ts +6 -0
- package/vue/editor/expand.d.ts +7 -0
- package/vue/editor/image-controls.d.ts +19 -0
- package/vue/editor/image-delivery.d.ts +2 -2
- package/vue/editor/image.d.ts +7 -0
- package/vue/editor/index.d.ts +1 -1
- package/vue/editor/index.js +1188 -52
- package/vue/editor/table-of-contents.d.ts +7 -0
- package/vue/index.d.ts +1 -0
- package/vue/index.js +2 -1
package/vue/UiImage.d.ts
ADDED
|
@@ -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
package/vue/UiImage.vue
ADDED
|
@@ -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>
|
package/vue/UiSeparator.d.ts
CHANGED
|
@@ -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;
|
package/vue/UiSeparator.vue
CHANGED
|
@@ -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> = {
|
|
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>
|