@threadlabs/looma 0.28.7 → 0.28.8
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/ui-editor-link-form/ui-editor-link-form.html +118 -0
- package/components/ui-editor-link-form/ui-editor-link-form.js +71 -0
- package/dist/index.js +33 -32
- package/package.json +1 -1
- package/styles/ui-editor-link-form.css +1 -0
- package/vanilla/UiEditorLinkForm.d.ts +32 -0
- package/vanilla/UiEditorLinkForm.js +354 -0
- package/vanilla/index.js +1 -0
- package/vue/UiEditorLinkForm.d.ts +165 -0
- package/vue/UiEditorLinkForm.js +2 -0
- package/vue/UiEditorLinkForm.vue +557 -0
- package/vue/chunks/UiEditorLinkForm.js +669 -0
- package/vue/components.css +407 -407
- package/vue/editor/index.js +72 -159
- package/vue/editor/primitives.d.ts +17 -1
- package/vue/index.d.ts +1 -0
- package/vue/index.js +4 -3
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
<!-- Generated by HTML Next 1.0.0-alpha.51 for Vue 3.5. Do not edit. -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
import { computed, getCurrentInstance, provide, ref, watchSyncEffect } from 'vue'
|
|
4
|
+
import type { PropType } from 'vue'
|
|
5
|
+
import {
|
|
6
|
+
checkHydratedRoot,
|
|
7
|
+
concat,
|
|
8
|
+
createDispatch,
|
|
9
|
+
cycleCheckedComputed,
|
|
10
|
+
KeyedBoundary,
|
|
11
|
+
nativeAttrs,
|
|
12
|
+
text,
|
|
13
|
+
uniqueKeys,
|
|
14
|
+
useComponentHost,
|
|
15
|
+
useReflectedProp,
|
|
16
|
+
} from './host'
|
|
17
|
+
import { acceptsControllerWrite } from './props'
|
|
18
|
+
import { checkedProp, propValidityState, vPropValidity } from './props'
|
|
19
|
+
import UiCheckbox from './UiCheckbox.vue'
|
|
20
|
+
import UiFormField from './UiFormField.vue'
|
|
21
|
+
import UiIcon from './UiIcon.vue'
|
|
22
|
+
import UiIconButton from './UiIconButton.vue'
|
|
23
|
+
import UiInput from './UiInput.vue'
|
|
24
|
+
import UiInputGroup from './UiInputGroup.vue'
|
|
25
|
+
import UiScrollArea from './UiScrollArea.vue'
|
|
26
|
+
import UiSearchResultRow from './UiSearchResultRow.vue'
|
|
27
|
+
import UiStack from './UiStack.vue'
|
|
28
|
+
import UiText from './UiText.vue'
|
|
29
|
+
|
|
30
|
+
defineOptions({ inheritAttrs: false })
|
|
31
|
+
|
|
32
|
+
const propValidityContract = {
|
|
33
|
+
props: {
|
|
34
|
+
error: {
|
|
35
|
+
type: { kind: 'terminal', name: 'string' },
|
|
36
|
+
required: false,
|
|
37
|
+
target: { attribute: 'error' },
|
|
38
|
+
description: 'A problem to show under the fields, announced as an alert.',
|
|
39
|
+
default: '',
|
|
40
|
+
},
|
|
41
|
+
existing: {
|
|
42
|
+
type: { kind: 'terminal', name: 'boolean' },
|
|
43
|
+
required: false,
|
|
44
|
+
target: { attribute: 'existing' },
|
|
45
|
+
description:
|
|
46
|
+
'Whether the link already exists, so the heading reads Edit link rather than Add link.',
|
|
47
|
+
default: false,
|
|
48
|
+
},
|
|
49
|
+
mode: {
|
|
50
|
+
type: { kind: 'terminal', name: 'keyword' },
|
|
51
|
+
required: false,
|
|
52
|
+
target: { attribute: 'mode' },
|
|
53
|
+
description:
|
|
54
|
+
'What the destination field holds: an address, which offers Open in new tab, or a search, which lists results.',
|
|
55
|
+
values: ['url', 'target'],
|
|
56
|
+
default: 'url',
|
|
57
|
+
},
|
|
58
|
+
newTab: {
|
|
59
|
+
type: { kind: 'terminal', name: 'boolean' },
|
|
60
|
+
required: false,
|
|
61
|
+
target: { attribute: 'checked' },
|
|
62
|
+
description: 'Whether Open in new tab is checked.',
|
|
63
|
+
default: false,
|
|
64
|
+
},
|
|
65
|
+
placeholder: {
|
|
66
|
+
type: { kind: 'terminal', name: 'string' },
|
|
67
|
+
required: false,
|
|
68
|
+
target: { attribute: 'placeholder' },
|
|
69
|
+
description: 'The destination field\'s placeholder.',
|
|
70
|
+
default: 'https://example.com',
|
|
71
|
+
},
|
|
72
|
+
results: {
|
|
73
|
+
type: {
|
|
74
|
+
kind: 'list',
|
|
75
|
+
item: {
|
|
76
|
+
kind: 'object',
|
|
77
|
+
fields: [{ name: 'id', type: { kind: 'terminal', name: 'string' }, optional: false }, {
|
|
78
|
+
name: 'label',
|
|
79
|
+
type: { kind: 'terminal', name: 'string' },
|
|
80
|
+
optional: false,
|
|
81
|
+
}, { name: 'detail', type: { kind: 'terminal', name: 'string' }, optional: true }],
|
|
82
|
+
open: false,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
required: false,
|
|
86
|
+
target: { attribute: 'results' },
|
|
87
|
+
description: 'The destinations a search found.',
|
|
88
|
+
default: [],
|
|
89
|
+
},
|
|
90
|
+
searchable: {
|
|
91
|
+
type: { kind: 'terminal', name: 'boolean' },
|
|
92
|
+
required: false,
|
|
93
|
+
target: { attribute: 'searchable' },
|
|
94
|
+
description:
|
|
95
|
+
'Whether the destination field searches for a destination as well as taking an address. A searchable field has a search icon and a Clear search\n button.',
|
|
96
|
+
default: false,
|
|
97
|
+
},
|
|
98
|
+
selected: {
|
|
99
|
+
type: { kind: 'terminal', name: 'string' },
|
|
100
|
+
required: false,
|
|
101
|
+
target: { attribute: 'selected' },
|
|
102
|
+
description: 'The id of the chosen result, which shows a check.',
|
|
103
|
+
default: '',
|
|
104
|
+
},
|
|
105
|
+
status: {
|
|
106
|
+
type: { kind: 'terminal', name: 'string' },
|
|
107
|
+
required: false,
|
|
108
|
+
target: { attribute: 'status' },
|
|
109
|
+
description:
|
|
110
|
+
'A line above the results, such as Searching… or No results found., announced politely. Shown only for a search.',
|
|
111
|
+
default: '',
|
|
112
|
+
},
|
|
113
|
+
text: {
|
|
114
|
+
type: { kind: 'terminal', name: 'string' },
|
|
115
|
+
required: false,
|
|
116
|
+
target: { attribute: 'value' },
|
|
117
|
+
description: 'The Text field\'s value.',
|
|
118
|
+
default: '',
|
|
119
|
+
},
|
|
120
|
+
textEditable: {
|
|
121
|
+
type: { kind: 'terminal', name: 'boolean' },
|
|
122
|
+
required: false,
|
|
123
|
+
target: { attribute: 'texteditable' },
|
|
124
|
+
description: 'Shows a Text field, for a link added where no text is selected.',
|
|
125
|
+
default: false,
|
|
126
|
+
},
|
|
127
|
+
value: {
|
|
128
|
+
type: { kind: 'terminal', name: 'string' },
|
|
129
|
+
required: false,
|
|
130
|
+
target: { attribute: 'value' },
|
|
131
|
+
description: 'The destination field\'s text: the address, or the search.',
|
|
132
|
+
default: '',
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
} as const
|
|
136
|
+
const props = defineProps({
|
|
137
|
+
error: { type: null as unknown as PropType<string | null>, default: '' },
|
|
138
|
+
existing: { type: null as unknown as PropType<boolean | null>, default: false },
|
|
139
|
+
mode: { type: null as unknown as PropType<'url' | 'target' | null>, default: 'url' },
|
|
140
|
+
newTab: { type: null as unknown as PropType<boolean | null>, default: false },
|
|
141
|
+
placeholder: { type: null as unknown as PropType<string | null>, default: 'https://example.com' },
|
|
142
|
+
results: {
|
|
143
|
+
type: null as unknown as PropType<
|
|
144
|
+
readonly ({ readonly id: string; readonly label: string; readonly detail?: string })[] | null
|
|
145
|
+
>,
|
|
146
|
+
default: () => [],
|
|
147
|
+
},
|
|
148
|
+
searchable: { type: null as unknown as PropType<boolean | null>, default: false },
|
|
149
|
+
selected: { type: null as unknown as PropType<string | null>, default: '' },
|
|
150
|
+
status: { type: null as unknown as PropType<string | null>, default: '' },
|
|
151
|
+
text: { type: null as unknown as PropType<string | null>, default: '' },
|
|
152
|
+
textEditable: { type: null as unknown as PropType<boolean | null>, default: false },
|
|
153
|
+
value: { type: null as unknown as PropType<string | null>, default: '' },
|
|
154
|
+
})
|
|
155
|
+
const acceptedProps: Record<string, unknown> = {
|
|
156
|
+
error: '',
|
|
157
|
+
existing: false,
|
|
158
|
+
mode: 'url',
|
|
159
|
+
newTab: false,
|
|
160
|
+
placeholder: 'https://example.com',
|
|
161
|
+
results: () => [],
|
|
162
|
+
searchable: false,
|
|
163
|
+
selected: '',
|
|
164
|
+
status: '',
|
|
165
|
+
text: '',
|
|
166
|
+
textEditable: false,
|
|
167
|
+
value: '',
|
|
168
|
+
}
|
|
169
|
+
const checkedProps = computed(() => ({
|
|
170
|
+
error: checkedProp<string>(
|
|
171
|
+
props['error'],
|
|
172
|
+
{ kind: 'terminal', name: 'string' },
|
|
173
|
+
false,
|
|
174
|
+
'error',
|
|
175
|
+
acceptedProps,
|
|
176
|
+
) as string,
|
|
177
|
+
existing: checkedProp<boolean>(
|
|
178
|
+
props['existing'],
|
|
179
|
+
{ kind: 'terminal', name: 'boolean' },
|
|
180
|
+
false,
|
|
181
|
+
'existing',
|
|
182
|
+
acceptedProps,
|
|
183
|
+
) as boolean,
|
|
184
|
+
mode: checkedProp<'url' | 'target'>(
|
|
185
|
+
props['mode'],
|
|
186
|
+
{ kind: 'terminal', name: 'keyword' },
|
|
187
|
+
false,
|
|
188
|
+
'mode',
|
|
189
|
+
acceptedProps,
|
|
190
|
+
) as 'url' | 'target',
|
|
191
|
+
newTab: checkedProp<boolean>(
|
|
192
|
+
props['newTab'],
|
|
193
|
+
{ kind: 'terminal', name: 'boolean' },
|
|
194
|
+
false,
|
|
195
|
+
'newTab',
|
|
196
|
+
acceptedProps,
|
|
197
|
+
) as boolean,
|
|
198
|
+
placeholder: checkedProp<string>(
|
|
199
|
+
props['placeholder'],
|
|
200
|
+
{ kind: 'terminal', name: 'string' },
|
|
201
|
+
false,
|
|
202
|
+
'placeholder',
|
|
203
|
+
acceptedProps,
|
|
204
|
+
) as string,
|
|
205
|
+
results: checkedProp<
|
|
206
|
+
readonly ({ readonly id: string; readonly label: string; readonly detail?: string })[]
|
|
207
|
+
>(
|
|
208
|
+
props['results'],
|
|
209
|
+
{
|
|
210
|
+
kind: 'list',
|
|
211
|
+
item: {
|
|
212
|
+
kind: 'object',
|
|
213
|
+
fields: [{ name: 'id', type: { kind: 'terminal', name: 'string' }, optional: false }, {
|
|
214
|
+
name: 'label',
|
|
215
|
+
type: { kind: 'terminal', name: 'string' },
|
|
216
|
+
optional: false,
|
|
217
|
+
}, { name: 'detail', type: { kind: 'terminal', name: 'string' }, optional: true }],
|
|
218
|
+
open: false,
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
false,
|
|
222
|
+
'results',
|
|
223
|
+
acceptedProps,
|
|
224
|
+
) as readonly ({ readonly id: string; readonly label: string; readonly detail?: string })[],
|
|
225
|
+
searchable: checkedProp<boolean>(
|
|
226
|
+
props['searchable'],
|
|
227
|
+
{ kind: 'terminal', name: 'boolean' },
|
|
228
|
+
false,
|
|
229
|
+
'searchable',
|
|
230
|
+
acceptedProps,
|
|
231
|
+
) as boolean,
|
|
232
|
+
selected: checkedProp<string>(
|
|
233
|
+
props['selected'],
|
|
234
|
+
{ kind: 'terminal', name: 'string' },
|
|
235
|
+
false,
|
|
236
|
+
'selected',
|
|
237
|
+
acceptedProps,
|
|
238
|
+
) as string,
|
|
239
|
+
status: checkedProp<string>(
|
|
240
|
+
props['status'],
|
|
241
|
+
{ kind: 'terminal', name: 'string' },
|
|
242
|
+
false,
|
|
243
|
+
'status',
|
|
244
|
+
acceptedProps,
|
|
245
|
+
) as string,
|
|
246
|
+
text: checkedProp<string>(
|
|
247
|
+
props['text'],
|
|
248
|
+
{ kind: 'terminal', name: 'string' },
|
|
249
|
+
false,
|
|
250
|
+
'text',
|
|
251
|
+
acceptedProps,
|
|
252
|
+
) as string,
|
|
253
|
+
textEditable: checkedProp<boolean>(
|
|
254
|
+
props['textEditable'],
|
|
255
|
+
{ kind: 'terminal', name: 'boolean' },
|
|
256
|
+
false,
|
|
257
|
+
'textEditable',
|
|
258
|
+
acceptedProps,
|
|
259
|
+
) as boolean,
|
|
260
|
+
value: checkedProp<string>(
|
|
261
|
+
props['value'],
|
|
262
|
+
{ kind: 'terminal', name: 'string' },
|
|
263
|
+
false,
|
|
264
|
+
'value',
|
|
265
|
+
acceptedProps,
|
|
266
|
+
) as string,
|
|
267
|
+
}))
|
|
268
|
+
void checkedProps.value
|
|
269
|
+
watchSyncEffect(() => {
|
|
270
|
+
void checkedProps.value
|
|
271
|
+
})
|
|
272
|
+
const reflectedProp = useReflectedProp()
|
|
273
|
+
const hydrationInstance = getCurrentInstance()
|
|
274
|
+
const emit = defineEmits<{
|
|
275
|
+
'update:newTab': [value: boolean]
|
|
276
|
+
'update:text': [value: string]
|
|
277
|
+
'update:value': [value: string]
|
|
278
|
+
destination: [event: CustomEvent<{ readonly value: string }>]
|
|
279
|
+
text: [event: CustomEvent<{ readonly text: string }>]
|
|
280
|
+
'new-tab': [event: CustomEvent<{ readonly newTab: boolean }>]
|
|
281
|
+
choose: [event: CustomEvent<{ readonly id: string }>]
|
|
282
|
+
clear: [event: CustomEvent<Readonly<Record<string, unknown>>>]
|
|
283
|
+
save: [event: CustomEvent<Readonly<Record<string, unknown>>>]
|
|
284
|
+
dismiss: [event: CustomEvent<Readonly<Record<string, unknown>>>]
|
|
285
|
+
}>()
|
|
286
|
+
|
|
287
|
+
const root = ref<HTMLElement | null>(null)
|
|
288
|
+
const base = ref('ui-editor-link-form')
|
|
289
|
+
let headingPrevious: any = null
|
|
290
|
+
const heading = cycleCheckedComputed(() => {
|
|
291
|
+
const next: any = ((objectValue: any, indexValue: any) =>
|
|
292
|
+
objectValue === Symbol.for('html-next.invalid-result')
|
|
293
|
+
|| indexValue === Symbol.for('html-next.invalid-result')
|
|
294
|
+
? Symbol.for('html-next.invalid-result')
|
|
295
|
+
: objectValue[indexValue])(
|
|
296
|
+
{ true: 'Edit link', false: 'Add link' },
|
|
297
|
+
concat(checkedProps.value.existing),
|
|
298
|
+
)
|
|
299
|
+
if (next === Symbol.for('html-next.invalid-result')) {
|
|
300
|
+
return headingPrevious
|
|
301
|
+
}
|
|
302
|
+
return headingPrevious = next
|
|
303
|
+
})
|
|
304
|
+
let fieldLabelPrevious: any = null
|
|
305
|
+
const fieldLabel = cycleCheckedComputed(() => {
|
|
306
|
+
const next: any = ((objectValue: any, indexValue: any) =>
|
|
307
|
+
objectValue === Symbol.for('html-next.invalid-result')
|
|
308
|
+
|| indexValue === Symbol.for('html-next.invalid-result')
|
|
309
|
+
? Symbol.for('html-next.invalid-result')
|
|
310
|
+
: objectValue[indexValue])(
|
|
311
|
+
{ true: 'Link destination', false: 'URL' },
|
|
312
|
+
concat(checkedProps.value.searchable),
|
|
313
|
+
)
|
|
314
|
+
if (next === Symbol.for('html-next.invalid-result')) {
|
|
315
|
+
return fieldLabelPrevious
|
|
316
|
+
}
|
|
317
|
+
return fieldLabelPrevious = next
|
|
318
|
+
})
|
|
319
|
+
let guardedPrevious: any = null
|
|
320
|
+
const guarded = computed(() => {
|
|
321
|
+
const candidate: any = concat(base.value, '-destination')
|
|
322
|
+
if (candidate === Symbol.for('html-next.invalid-result')) return guardedPrevious
|
|
323
|
+
return guardedPrevious = candidate
|
|
324
|
+
})
|
|
325
|
+
let guardedBindingPrevious: any = null
|
|
326
|
+
const guardedBinding = computed(() => {
|
|
327
|
+
const candidate: any = ((objectValue: any, indexValue: any) =>
|
|
328
|
+
objectValue === Symbol.for('html-next.invalid-result')
|
|
329
|
+
|| indexValue === Symbol.for('html-next.invalid-result')
|
|
330
|
+
? Symbol.for('html-next.invalid-result')
|
|
331
|
+
: objectValue[indexValue])(
|
|
332
|
+
{ true: 'search', false: 'link' },
|
|
333
|
+
concat(checkedProps.value.mode === 'target'),
|
|
334
|
+
)
|
|
335
|
+
if (candidate === Symbol.for('html-next.invalid-result')) {
|
|
336
|
+
return guardedBindingPrevious
|
|
337
|
+
}
|
|
338
|
+
return guardedBindingPrevious = candidate
|
|
339
|
+
})
|
|
340
|
+
let guardedBinding2Previous: any = null
|
|
341
|
+
const guardedBinding2 = computed(() => {
|
|
342
|
+
const candidate: any = concat(base.value, '-destination')
|
|
343
|
+
if (candidate === Symbol.for('html-next.invalid-result')) return guardedBinding2Previous
|
|
344
|
+
return guardedBinding2Previous = candidate
|
|
345
|
+
})
|
|
346
|
+
let guardedBinding3Previous: any = null
|
|
347
|
+
const guardedBinding3 = computed(() => {
|
|
348
|
+
const candidate: any = concat(base.value, '-destination')
|
|
349
|
+
if (candidate === Symbol.for('html-next.invalid-result')) return guardedBinding3Previous
|
|
350
|
+
return guardedBinding3Previous = candidate
|
|
351
|
+
})
|
|
352
|
+
let guardedBinding4Previous: any = null
|
|
353
|
+
const guardedBinding4 = computed(() => {
|
|
354
|
+
const candidate: any = concat(base.value, '-text')
|
|
355
|
+
if (candidate === Symbol.for('html-next.invalid-result')) return guardedBinding4Previous
|
|
356
|
+
return guardedBinding4Previous = candidate
|
|
357
|
+
})
|
|
358
|
+
let guardedBinding5Previous: any = null
|
|
359
|
+
const guardedBinding5 = computed(() => {
|
|
360
|
+
const candidate: any = concat(base.value, '-text')
|
|
361
|
+
if (candidate === Symbol.for('html-next.invalid-result')) return guardedBinding5Previous
|
|
362
|
+
return guardedBinding5Previous = candidate
|
|
363
|
+
})
|
|
364
|
+
provide('html-next:ui-editor-link-form:base', base)
|
|
365
|
+
checkHydratedRoot('form', 'ui-editor-link-form')
|
|
366
|
+
|
|
367
|
+
const isDestinationDetail = (
|
|
368
|
+
detail: unknown,
|
|
369
|
+
): boolean => (detail !== null && typeof detail === 'object' && !Array.isArray(detail)
|
|
370
|
+
&& typeof (detail as Record<string, unknown>)['value'] === 'string'
|
|
371
|
+
&& Object.keys(detail as object).every((key) => ['value'].includes(key)))
|
|
372
|
+
const isTextDetail = (
|
|
373
|
+
detail: unknown,
|
|
374
|
+
): boolean => (detail !== null && typeof detail === 'object' && !Array.isArray(detail)
|
|
375
|
+
&& typeof (detail as Record<string, unknown>)['text'] === 'string'
|
|
376
|
+
&& Object.keys(detail as object).every((key) => ['text'].includes(key)))
|
|
377
|
+
const isNewTabDetail = (
|
|
378
|
+
detail: unknown,
|
|
379
|
+
): boolean => (detail !== null && typeof detail === 'object' && !Array.isArray(detail)
|
|
380
|
+
&& typeof (detail as Record<string, unknown>)['newTab'] === 'boolean'
|
|
381
|
+
&& Object.keys(detail as object).every((key) => ['newTab'].includes(key)))
|
|
382
|
+
const isChooseDetail = (
|
|
383
|
+
detail: unknown,
|
|
384
|
+
): boolean => (detail !== null && typeof detail === 'object' && !Array.isArray(detail)
|
|
385
|
+
&& typeof (detail as Record<string, unknown>)['id'] === 'string'
|
|
386
|
+
&& Object.keys(detail as object).every((key) => ['id'].includes(key)))
|
|
387
|
+
const isClearDetail = (
|
|
388
|
+
detail: unknown,
|
|
389
|
+
): boolean => (detail !== null && typeof detail === 'object' && !Array.isArray(detail)
|
|
390
|
+
&& Object.values(detail as object).every((item: unknown) => true))
|
|
391
|
+
const dispatch = createDispatch(root, emit as (name: string, detail: unknown) => void, {
|
|
392
|
+
checks: {
|
|
393
|
+
destination: isDestinationDetail,
|
|
394
|
+
text: isTextDetail,
|
|
395
|
+
'new-tab': isNewTabDetail,
|
|
396
|
+
choose: isChooseDetail,
|
|
397
|
+
clear: isClearDetail,
|
|
398
|
+
save: isClearDetail,
|
|
399
|
+
dismiss: isClearDetail,
|
|
400
|
+
},
|
|
401
|
+
modeled: ['newTab', 'text', 'value'],
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
useComponentHost(() => import('../components/ui-editor-link-form/ui-editor-link-form.js'), {
|
|
405
|
+
root,
|
|
406
|
+
dispatch,
|
|
407
|
+
acceptsState: (name: string, keys: readonly string[], value: unknown) =>
|
|
408
|
+
acceptsControllerWrite(
|
|
409
|
+
value,
|
|
410
|
+
({ base: { kind: 'terminal', name: 'string' } } as Record<string, any>)[name],
|
|
411
|
+
keys,
|
|
412
|
+
),
|
|
413
|
+
controllerSource: {
|
|
414
|
+
specifier: '../components/ui-editor-link-form/ui-editor-link-form.js',
|
|
415
|
+
definition: './components/ui-editor-link-form/ui-editor-link-form.html',
|
|
416
|
+
},
|
|
417
|
+
props: checkedProps,
|
|
418
|
+
propInputs: (name: string) => {
|
|
419
|
+
const incoming = hydrationInstance?.vnode.props
|
|
420
|
+
const kebab = name.replace(/[A-Z]/g, (letter) => '-' + letter.toLowerCase())
|
|
421
|
+
return incoming !== null && incoming !== undefined
|
|
422
|
+
? (incoming[name] ?? incoming[kebab] ?? null)
|
|
423
|
+
: null
|
|
424
|
+
},
|
|
425
|
+
propNames: [
|
|
426
|
+
'error',
|
|
427
|
+
'existing',
|
|
428
|
+
'mode',
|
|
429
|
+
'newTab',
|
|
430
|
+
'placeholder',
|
|
431
|
+
'results',
|
|
432
|
+
'searchable',
|
|
433
|
+
'selected',
|
|
434
|
+
'status',
|
|
435
|
+
'text',
|
|
436
|
+
'textEditable',
|
|
437
|
+
'value',
|
|
438
|
+
],
|
|
439
|
+
propValidity: (name: string) =>
|
|
440
|
+
propValidityState({ contract: propValidityContract, values: { ...props } }, name),
|
|
441
|
+
state: { base },
|
|
442
|
+
computed: { heading, fieldLabel },
|
|
443
|
+
})
|
|
444
|
+
</script>
|
|
445
|
+
|
|
446
|
+
<template>
|
|
447
|
+
<form
|
|
448
|
+
aria-label="Edit link"
|
|
449
|
+
v-bind="nativeAttrs($attrs)"
|
|
450
|
+
v-prop-validity="{ contract: propValidityContract, values: { ...props } }"
|
|
451
|
+
:data-error="reflectedProp('error', 'error', checkedProps.error, undefined, false)"
|
|
452
|
+
:data-existing="reflectedProp('existing', 'existing', checkedProps.existing, undefined, false)"
|
|
453
|
+
:data-mode="reflectedProp('mode', 'mode', checkedProps.mode, undefined, false)"
|
|
454
|
+
:data-new-tab="reflectedProp('newTab', 'new-tab', checkedProps.newTab, undefined, false)"
|
|
455
|
+
:data-placeholder="reflectedProp('placeholder', 'placeholder', checkedProps.placeholder, undefined, false)"
|
|
456
|
+
:data-results="reflectedProp('results', 'results', checkedProps.results, undefined, false)"
|
|
457
|
+
:data-searchable="reflectedProp('searchable', 'searchable', checkedProps.searchable, undefined, false)"
|
|
458
|
+
:data-selected="reflectedProp('selected', 'selected', checkedProps.selected, undefined, false)"
|
|
459
|
+
:data-status="reflectedProp('status', 'status', checkedProps.status, undefined, false)"
|
|
460
|
+
:data-text="reflectedProp('text', 'text', checkedProps.text, undefined, false)"
|
|
461
|
+
:data-text-editable="reflectedProp('textEditable', 'text-editable', checkedProps.textEditable, undefined, false)"
|
|
462
|
+
:data-value="reflectedProp('value', 'value', checkedProps.value, undefined, false)"
|
|
463
|
+
ref="root"
|
|
464
|
+
>
|
|
465
|
+
<UiStack gap="s"><UiText as="h3" size="sm" weight="semibold">{{ text(heading) }}</UiText>
|
|
466
|
+
<UiStack gap="s"><UiFormField :invalid="checkedProps.error !== ''"><label
|
|
467
|
+
slot="label"
|
|
468
|
+
:for="guarded"
|
|
469
|
+
>{{ text(fieldLabel) }}</label>
|
|
470
|
+
<UiInputGroup v-if="checkedProps.searchable" prefix-kind="icon"><UiIcon
|
|
471
|
+
slot="prefix"
|
|
472
|
+
:name="guardedBinding"
|
|
473
|
+
/>
|
|
474
|
+
<UiInput
|
|
475
|
+
data-link-destination=""
|
|
476
|
+
data-link-search=""
|
|
477
|
+
type="text"
|
|
478
|
+
role="searchbox"
|
|
479
|
+
autocomplete="off"
|
|
480
|
+
:id="guardedBinding2"
|
|
481
|
+
:placeholder="checkedProps.placeholder"
|
|
482
|
+
:value="checkedProps.value"
|
|
483
|
+
/>
|
|
484
|
+
<UiIconButton
|
|
485
|
+
v-if="checkedProps.value !== ''"
|
|
486
|
+
slot="action"
|
|
487
|
+
label="Clear search"
|
|
488
|
+
variant="ghost"
|
|
489
|
+
size="sm"
|
|
490
|
+
data-link-clear=""
|
|
491
|
+
><UiIcon name="x" /></UiIconButton></UiInputGroup>
|
|
492
|
+
<template v-if="!checkedProps.searchable"><UiInput
|
|
493
|
+
data-link-destination=""
|
|
494
|
+
type="text"
|
|
495
|
+
inputmode="url"
|
|
496
|
+
size="sm"
|
|
497
|
+
:id="guardedBinding3"
|
|
498
|
+
:placeholder="checkedProps.placeholder"
|
|
499
|
+
:value="checkedProps.value"
|
|
500
|
+
/></template></UiFormField>
|
|
501
|
+
<UiScrollArea
|
|
502
|
+
v-if="checkedProps.mode === 'target'"
|
|
503
|
+
size="sm"
|
|
504
|
+
aria-label="Destination search results"
|
|
505
|
+
><UiText v-if="checkedProps.status !== ''" size="sm" tone="secondary" role="status">{{
|
|
506
|
+
checkedProps.status
|
|
507
|
+
}}</UiText>
|
|
508
|
+
<KeyedBoundary><UiSearchResultRow
|
|
509
|
+
v-for="result in uniqueKeys(checkedProps.results, (result, index, loop) => result.id)"
|
|
510
|
+
:key="result.id"
|
|
511
|
+
density="compact"
|
|
512
|
+
data-link-result=""
|
|
513
|
+
:selected="result.id === checkedProps.selected"
|
|
514
|
+
:data-link-id="result.id"
|
|
515
|
+
><UiIcon slot="leading" name="file-text" />
|
|
516
|
+
<UiText
|
|
517
|
+
slot="title"
|
|
518
|
+
as="strong"
|
|
519
|
+
size="sm"
|
|
520
|
+
weight="medium"
|
|
521
|
+
:truncate="true"
|
|
522
|
+
:title="result.label"
|
|
523
|
+
>{{ result.label }}</UiText>
|
|
524
|
+
<UiText
|
|
525
|
+
v-if="result.detail"
|
|
526
|
+
slot="meta"
|
|
527
|
+
as="span"
|
|
528
|
+
size="xs"
|
|
529
|
+
tone="secondary"
|
|
530
|
+
:truncate="true"
|
|
531
|
+
:title="result.detail"
|
|
532
|
+
>{{ result.detail }}</UiText>
|
|
533
|
+
<UiIcon
|
|
534
|
+
v-if="result.id === checkedProps.selected"
|
|
535
|
+
slot="trailing"
|
|
536
|
+
name="check"
|
|
537
|
+
/></UiSearchResultRow></KeyedBoundary></UiScrollArea>
|
|
538
|
+
<template v-if="checkedProps.mode === 'url'"><UiCheckbox
|
|
539
|
+
size="sm"
|
|
540
|
+
data-link-new-tab=""
|
|
541
|
+
:checked="checkedProps.newTab"
|
|
542
|
+
>Open in new tab</UiCheckbox></template></UiStack>
|
|
543
|
+
<template v-if="checkedProps.textEditable"><UiFormField><label
|
|
544
|
+
slot="label"
|
|
545
|
+
:for="guardedBinding4"
|
|
546
|
+
>Text</label>
|
|
547
|
+
<UiInput
|
|
548
|
+
data-link-text=""
|
|
549
|
+
size="sm"
|
|
550
|
+
:id="guardedBinding5"
|
|
551
|
+
:value="checkedProps.text"
|
|
552
|
+
/></UiFormField></template>
|
|
553
|
+
<UiText v-if="checkedProps.error !== ''" size="sm" tone="danger" role="alert">{{
|
|
554
|
+
checkedProps.error
|
|
555
|
+
}}</UiText></UiStack>
|
|
556
|
+
</form>
|
|
557
|
+
</template>
|