@sveltia/ui 0.55.1 → 0.56.0
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/dist/components/listbox/option.svelte +107 -33
- package/dist/components/select/combobox.svelte +126 -50
- package/dist/components/text-editor/constants.d.ts +0 -4
- package/dist/components/text-editor/constants.js +0 -5
- package/dist/components/text-editor/toolbar/{insert-image-button.svelte → insert-item-button.svelte} +12 -5
- package/dist/components/text-editor/toolbar/{insert-image-button.svelte.d.ts → insert-item-button.svelte.d.ts} +5 -5
- package/dist/components/text-editor/toolbar/insert-menu-button.svelte +6 -2
- package/dist/components/text-editor/toolbar/text-editor-toolbar.svelte +12 -16
- package/dist/components/util/modal.svelte +210 -66
- package/dist/components/util/popup.svelte +28 -20
- package/dist/locales/pl.yaml +113 -0
- package/dist/services/group.svelte.d.ts +57 -0
- package/dist/services/group.svelte.js +294 -7
- package/dist/services/popup.svelte.d.ts +37 -5
- package/dist/services/popup.svelte.js +132 -19
- package/dist/services/select.svelte.d.ts +49 -0
- package/dist/services/select.svelte.js +99 -1
- package/dist/typedefs.d.ts +38 -2
- package/dist/typedefs.js +17 -2
- package/package.json +5 -5
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
@see https://w3c.github.io/aria/#option
|
|
6
6
|
-->
|
|
7
7
|
<script>
|
|
8
|
+
import { onDestroy } from 'svelte';
|
|
9
|
+
import { getOptionRegistry } from '../../services/select.svelte.js';
|
|
8
10
|
import Button from '../button/button.svelte';
|
|
9
11
|
import Icon from '../icon/icon.svelte';
|
|
10
12
|
|
|
@@ -47,42 +49,114 @@
|
|
|
47
49
|
...restProps
|
|
48
50
|
/* eslint-enable prefer-const */
|
|
49
51
|
} = $props();
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The registry provided by an ancestor `<Combobox>`. This is `undefined` when the option is used
|
|
55
|
+
* standalone within a `<Listbox>`, in which case it always renders itself.
|
|
56
|
+
*/
|
|
57
|
+
const registry = getOptionRegistry();
|
|
58
|
+
|
|
59
|
+
if (registry) {
|
|
60
|
+
// Expose the props as accessors, so the combobox always reads the current values
|
|
61
|
+
const unregister = registry.register({
|
|
62
|
+
/**
|
|
63
|
+
* Get the option’s value.
|
|
64
|
+
* @returns {any} Value.
|
|
65
|
+
*/
|
|
66
|
+
get value() {
|
|
67
|
+
return value;
|
|
68
|
+
},
|
|
69
|
+
/**
|
|
70
|
+
* Get the option’s text label.
|
|
71
|
+
* @returns {string} Label.
|
|
72
|
+
*/
|
|
73
|
+
get label() {
|
|
74
|
+
return label;
|
|
75
|
+
},
|
|
76
|
+
/**
|
|
77
|
+
* Get the option’s name.
|
|
78
|
+
* @returns {string | undefined} Name.
|
|
79
|
+
*/
|
|
80
|
+
get name() {
|
|
81
|
+
return restProps.name;
|
|
82
|
+
},
|
|
83
|
+
/**
|
|
84
|
+
* Get the data type of the option’s value.
|
|
85
|
+
* @returns {string} Type.
|
|
86
|
+
*/
|
|
87
|
+
get type() {
|
|
88
|
+
return restProps.valueType ?? typeof value;
|
|
89
|
+
},
|
|
90
|
+
/**
|
|
91
|
+
* Get whether the option is selected.
|
|
92
|
+
* @returns {boolean} Result.
|
|
93
|
+
*/
|
|
94
|
+
get selected() {
|
|
95
|
+
return selected;
|
|
96
|
+
},
|
|
97
|
+
/**
|
|
98
|
+
* Select or deselect the option.
|
|
99
|
+
* @param {boolean} newValue `true` to select.
|
|
100
|
+
*/
|
|
101
|
+
set selected(newValue) {
|
|
102
|
+
selected = newValue;
|
|
103
|
+
},
|
|
104
|
+
/**
|
|
105
|
+
* Get whether the option is disabled.
|
|
106
|
+
* @returns {boolean} Result.
|
|
107
|
+
*/
|
|
108
|
+
get disabled() {
|
|
109
|
+
return disabled;
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
onDestroy(unregister);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Whether to render the option. Within a `<Combobox>`, the options are only rendered while the
|
|
118
|
+
* dropdown is expanded; the registration above is what keeps the collapsed combobox working.
|
|
119
|
+
* @type {boolean}
|
|
120
|
+
*/
|
|
121
|
+
const rendered = $derived(!registry || registry.expanded);
|
|
50
122
|
</script>
|
|
51
123
|
|
|
52
|
-
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
{#if
|
|
75
|
-
{
|
|
76
|
-
|
|
77
|
-
|
|
124
|
+
{#if rendered}
|
|
125
|
+
<div role="none" class="sui option {className}" class:wrap {hidden}>
|
|
126
|
+
<Button
|
|
127
|
+
{...restProps}
|
|
128
|
+
role="option"
|
|
129
|
+
tabindex="-1"
|
|
130
|
+
aria-selected={selected}
|
|
131
|
+
{label}
|
|
132
|
+
{value}
|
|
133
|
+
{hidden}
|
|
134
|
+
{disabled}
|
|
135
|
+
data-search-value={searchValue}
|
|
136
|
+
onChange={(event) => {
|
|
137
|
+
selected = event.detail.selected;
|
|
138
|
+
onChange?.(event);
|
|
139
|
+
}}
|
|
140
|
+
onToggle={(event) => {
|
|
141
|
+
hidden = event.detail.hidden;
|
|
142
|
+
if (hidden) selected = false;
|
|
143
|
+
onToggle?.(event);
|
|
144
|
+
}}
|
|
145
|
+
>
|
|
146
|
+
{#if selected}
|
|
147
|
+
{#if checkIcon}
|
|
148
|
+
{@render checkIcon()}
|
|
149
|
+
{:else}
|
|
150
|
+
<Icon class="check" name="check" />
|
|
151
|
+
{/if}
|
|
78
152
|
{/if}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
{
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
</
|
|
85
|
-
|
|
153
|
+
{#snippet startIcon()}
|
|
154
|
+
{@render _startIcon?.()}
|
|
155
|
+
{/snippet}
|
|
156
|
+
{@render children?.()}
|
|
157
|
+
</Button>
|
|
158
|
+
</div>
|
|
159
|
+
{/if}
|
|
86
160
|
|
|
87
161
|
<style>.option {
|
|
88
162
|
display: contents;
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
-->
|
|
8
8
|
<script>
|
|
9
9
|
import { _ } from '@sveltia/i18n';
|
|
10
|
-
import {
|
|
10
|
+
import { onMount } from 'svelte';
|
|
11
|
+
import { createOptionRegistry, getSelectedItemDetail } from '../../services/select.svelte.js';
|
|
11
12
|
import Button from '../button/button.svelte';
|
|
12
13
|
import Icon from '../icon/icon.svelte';
|
|
13
14
|
import Listbox from '../listbox/listbox.svelte';
|
|
@@ -20,12 +21,6 @@
|
|
|
20
21
|
* @import { ComboboxProps, TextInputProps } from '../../typedefs';
|
|
21
22
|
*/
|
|
22
23
|
|
|
23
|
-
/**
|
|
24
|
-
* Selector for the currently selected option in the popup. Used to update the selected option
|
|
25
|
-
* when the value is changed externally.
|
|
26
|
-
*/
|
|
27
|
-
const SELECTED_SELECTOR = '[role="option"][aria-selected="true"]';
|
|
28
|
-
|
|
29
24
|
/**
|
|
30
25
|
* @type {ComboboxProps & TextInputProps & Record<string, any>}
|
|
31
26
|
*/
|
|
@@ -58,32 +53,61 @@
|
|
|
58
53
|
let inputElement = $state();
|
|
59
54
|
/** @type {HTMLElement | undefined} */
|
|
60
55
|
let popupContent = $state();
|
|
56
|
+
/**
|
|
57
|
+
* Wrapper holding the options while the dropdown is collapsed.
|
|
58
|
+
* @type {HTMLElement | undefined}
|
|
59
|
+
*/
|
|
60
|
+
let idleHost = $state();
|
|
61
|
+
/**
|
|
62
|
+
* Wrapper holding the options, moved between {@link idleHost} and {@link listboxSlot}.
|
|
63
|
+
* @type {HTMLElement | undefined}
|
|
64
|
+
*/
|
|
65
|
+
let optionHost = $state();
|
|
66
|
+
/**
|
|
67
|
+
* Where the options go while the dropdown is expanded.
|
|
68
|
+
* @type {HTMLElement | undefined}
|
|
69
|
+
*/
|
|
70
|
+
let listboxSlot = $state();
|
|
61
71
|
/** @type {string} */
|
|
62
72
|
let label = $state('');
|
|
63
73
|
/** @type {boolean} */
|
|
64
74
|
let showFilter = $state(false);
|
|
65
75
|
/** @type {string} */
|
|
66
76
|
let searchTerms = $state('');
|
|
67
|
-
/**
|
|
68
|
-
|
|
69
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Number of options matching the current search terms, as last reported by the listbox.
|
|
79
|
+
* @type {number}
|
|
80
|
+
*/
|
|
81
|
+
let matchedOptions = $state(0);
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Whether to keep the option list free of the “no matching options” notice. That notice is about
|
|
85
|
+
* filtering, so it’s only meaningful once search terms have been entered — the listbox also
|
|
86
|
+
* reports zero matches while the options are unrendered, which says nothing about the filter.
|
|
87
|
+
* @type {boolean}
|
|
88
|
+
*/
|
|
89
|
+
const hasMatchingOptions = $derived(!searchTerms || matchedOptions > 0);
|
|
90
|
+
/**
|
|
91
|
+
* @type {HTMLElement}
|
|
92
|
+
*/
|
|
70
93
|
const anchor = $derived(/** @type {HTMLElement} */ (comboboxElement ?? inputElement));
|
|
71
94
|
|
|
95
|
+
/**
|
|
96
|
+
* The options declared as `<Option>` children. They only render while the dropdown is expanded,
|
|
97
|
+
* so the label and the initial value below are resolved through the registry rather than the DOM
|
|
98
|
+
* tree.
|
|
99
|
+
*/
|
|
100
|
+
const registry = createOptionRegistry();
|
|
101
|
+
|
|
72
102
|
/**
|
|
73
103
|
* Update the {@link label} and selected option when the {@link value} is changed.
|
|
74
104
|
*/
|
|
75
105
|
const _onChange = () => {
|
|
76
|
-
const
|
|
77
|
-
popupContent?.querySelector(`[role="option"][data-value="${value}"]`)
|
|
78
|
-
);
|
|
106
|
+
const entry = registry.find(value);
|
|
79
107
|
|
|
80
|
-
if (
|
|
81
|
-
label =
|
|
82
|
-
|
|
83
|
-
if (target.getAttribute('aria-selected') !== 'true') {
|
|
84
|
-
popupContent?.querySelector(SELECTED_SELECTOR)?.setAttribute('aria-selected', 'false');
|
|
85
|
-
target.setAttribute('aria-selected', 'true');
|
|
86
|
-
}
|
|
108
|
+
if (entry) {
|
|
109
|
+
label = entry.label;
|
|
110
|
+
registry.selectOnly(value);
|
|
87
111
|
}
|
|
88
112
|
};
|
|
89
113
|
|
|
@@ -100,24 +124,65 @@
|
|
|
100
124
|
onChange?.(new CustomEvent('Change', { detail }));
|
|
101
125
|
};
|
|
102
126
|
|
|
127
|
+
// Let the options know whether they should render themselves
|
|
103
128
|
$effect(() => {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const selected = popupContent?.querySelector(SELECTED_SELECTOR);
|
|
129
|
+
registry.expanded = isPopupOpen;
|
|
130
|
+
});
|
|
107
131
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
132
|
+
// Move the options into the popup while it’s expanded, and back out before it’s unmounted. Only
|
|
133
|
+
// the wrapper is moved, never its children, so Svelte keeps full ownership of the subtree.
|
|
134
|
+
$effect(() => {
|
|
135
|
+
if (!optionHost) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const parent = isPopupOpen ? listboxSlot : idleHost;
|
|
140
|
+
|
|
141
|
+
if (parent && optionHost.parentElement !== parent) {
|
|
142
|
+
parent.append(optionHost);
|
|
112
143
|
}
|
|
113
144
|
});
|
|
114
145
|
|
|
146
|
+
// Derive the initial value from the `<Option>` that is marked as selected in the markup. This has
|
|
147
|
+
// to be a one-off read rather than an effect on `registry.selectedEntry`: where the markup marks
|
|
148
|
+
// nothing as selected, the first entry to become selected is the one the user picks, and
|
|
149
|
+
// reporting that here would raise a second `Change` for a single choice. The options register
|
|
150
|
+
// during their own initialization, so they are all present by the time this runs.
|
|
151
|
+
onMount(() => {
|
|
152
|
+
const entry = registry.selectedEntry;
|
|
153
|
+
|
|
154
|
+
if (!entry) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
value = entry.value;
|
|
159
|
+
label = entry.label;
|
|
160
|
+
|
|
161
|
+
// There is no element to report while the dropdown has never been expanded
|
|
162
|
+
onChange?.(
|
|
163
|
+
new CustomEvent('Change', {
|
|
164
|
+
detail: {
|
|
165
|
+
target: undefined,
|
|
166
|
+
type: entry.type,
|
|
167
|
+
name: entry.name,
|
|
168
|
+
label: entry.label,
|
|
169
|
+
value: entry.value,
|
|
170
|
+
},
|
|
171
|
+
}),
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
|
|
115
175
|
$effect(() => {
|
|
116
176
|
void value;
|
|
117
177
|
_onChange();
|
|
118
178
|
});
|
|
119
179
|
</script>
|
|
120
180
|
|
|
181
|
+
<!--
|
|
182
|
+
`aria-controls` is deliberately absent from the `role="combobox"` element below: the popup is
|
|
183
|
+
only in the DOM tree while it’s expanded, and a reference to a missing element is worse than no
|
|
184
|
+
reference at all. The popup service adds it on open and removes it on close.
|
|
185
|
+
-->
|
|
121
186
|
<div {...restProps} role="none" class="sui combobox {className}" class:editable {hidden}>
|
|
122
187
|
{#if !editable}
|
|
123
188
|
<div
|
|
@@ -127,7 +192,6 @@
|
|
|
127
192
|
{id}
|
|
128
193
|
class:selected={value !== undefined}
|
|
129
194
|
tabindex={disabled ? -1 : 0}
|
|
130
|
-
aria-controls="{id}-popup"
|
|
131
195
|
aria-expanded={isPopupOpen}
|
|
132
196
|
aria-hidden={hidden}
|
|
133
197
|
aria-disabled={disabled}
|
|
@@ -156,7 +220,6 @@
|
|
|
156
220
|
{readonly}
|
|
157
221
|
{required}
|
|
158
222
|
{invalid}
|
|
159
|
-
aria-controls="{id}-popup"
|
|
160
223
|
aria-expanded={isPopupOpen}
|
|
161
224
|
aria-haspopup="listbox"
|
|
162
225
|
aria-label={ariaLabel}
|
|
@@ -169,7 +232,7 @@
|
|
|
169
232
|
{disabled}
|
|
170
233
|
tabindex={!editable || readonly || disabled ? -1 : 0}
|
|
171
234
|
aria-label={isPopupOpen ? _('_sui.collapse') : _('_sui.expand')}
|
|
172
|
-
aria-controls=
|
|
235
|
+
aria-controls={isPopupOpen ? `${id}-popup` : undefined}
|
|
173
236
|
aria-expanded={isPopupOpen}
|
|
174
237
|
onclick={(event) => {
|
|
175
238
|
event.preventDefault();
|
|
@@ -189,6 +252,32 @@
|
|
|
189
252
|
{/snippet}
|
|
190
253
|
</Button>
|
|
191
254
|
</div>
|
|
255
|
+
<!--
|
|
256
|
+
The `<Option>`s have to stay instantiated while the dropdown is collapsed, so that the registry
|
|
257
|
+
above can resolve the current label. They render no DOM of their own in that state, so this host
|
|
258
|
+
is empty apart from any other markup the consumer interleaved, such as a `<Divider>`. It’s moved
|
|
259
|
+
into the popup when the dropdown expands, which keeps a single set of component instances and lets
|
|
260
|
+
Svelte own the rendering order.
|
|
261
|
+
-->
|
|
262
|
+
<div bind:this={idleHost} role="none" class="idle-host" hidden>
|
|
263
|
+
<div bind:this={optionHost} role="none" class="option-host">
|
|
264
|
+
<Listbox
|
|
265
|
+
id="{id}-listbox"
|
|
266
|
+
class="in-combobox"
|
|
267
|
+
{searchTerms}
|
|
268
|
+
onclick={(event) => {
|
|
269
|
+
if (/** @type {HTMLElement} */ (event.target).matches('[role="option"]')) {
|
|
270
|
+
_onSelect(/** @type {HTMLButtonElement} */ (event.target));
|
|
271
|
+
}
|
|
272
|
+
}}
|
|
273
|
+
onFilter={(event) => {
|
|
274
|
+
matchedOptions = /** @type {CustomEvent} */ (event).detail.matched;
|
|
275
|
+
}}
|
|
276
|
+
>
|
|
277
|
+
{@render children?.()}
|
|
278
|
+
</Listbox>
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
192
281
|
<Popup
|
|
193
282
|
bind:content={popupContent}
|
|
194
283
|
id="{id}-popup"
|
|
@@ -198,10 +287,7 @@
|
|
|
198
287
|
touchOptimized={true}
|
|
199
288
|
bind:open={isPopupOpen}
|
|
200
289
|
onOpen={() => {
|
|
201
|
-
showFilter =
|
|
202
|
-
filterThreshold === -1
|
|
203
|
-
? false
|
|
204
|
-
: (popupContent?.querySelectorAll('[role="option"]')?.length ?? 0) > filterThreshold;
|
|
290
|
+
showFilter = filterThreshold === -1 ? false : registry.count > filterThreshold;
|
|
205
291
|
searchTerms = '';
|
|
206
292
|
}}
|
|
207
293
|
>
|
|
@@ -222,21 +308,7 @@
|
|
|
222
308
|
}}
|
|
223
309
|
/>
|
|
224
310
|
{/if}
|
|
225
|
-
<
|
|
226
|
-
id="{id}-listbox"
|
|
227
|
-
class="in-combobox"
|
|
228
|
-
{searchTerms}
|
|
229
|
-
onclick={(event) => {
|
|
230
|
-
if (/** @type {HTMLElement} */ (event.target).matches('[role="option"]')) {
|
|
231
|
-
_onSelect(/** @type {HTMLButtonElement} */ (event.target));
|
|
232
|
-
}
|
|
233
|
-
}}
|
|
234
|
-
onFilter={(event) => {
|
|
235
|
-
hasMatchingOptions = !!(/** @type {CustomEvent} */ (event).detail.matched);
|
|
236
|
-
}}
|
|
237
|
-
>
|
|
238
|
-
{@render children?.()}
|
|
239
|
-
</Listbox>
|
|
311
|
+
<div bind:this={listboxSlot} role="none" class="listbox-slot"></div>
|
|
240
312
|
{#if !hasMatchingOptions}
|
|
241
313
|
<div role="alert" class="no-options" aria-live="assertive">
|
|
242
314
|
{_('_sui.combobox.no_matching_options')}
|
|
@@ -245,7 +317,11 @@
|
|
|
245
317
|
</div>
|
|
246
318
|
</Popup>
|
|
247
319
|
|
|
248
|
-
<style
|
|
320
|
+
<style>:is(.option-host, .listbox-slot) {
|
|
321
|
+
display: contents;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.combobox {
|
|
249
325
|
margin: var(--sui-focus-ring-width);
|
|
250
326
|
display: flex;
|
|
251
327
|
align-items: center;
|
|
@@ -36,10 +36,6 @@ export const INLINE_BUTTON_TYPES: TextEditorInlineType[];
|
|
|
36
36
|
* @type {TextEditorBlockType[]}
|
|
37
37
|
*/
|
|
38
38
|
export const BLOCK_BUTTON_TYPES: TextEditorBlockType[];
|
|
39
|
-
/**
|
|
40
|
-
* Image related components IDs. `linked-image` is used in Sveltia CMS.
|
|
41
|
-
*/
|
|
42
|
-
export const IMAGE_COMPONENT_IDS: string[];
|
|
43
39
|
/**
|
|
44
40
|
* Map of Lexical nodes for each block type. The `paragraph` block type is excluded because it is
|
|
45
41
|
* the default block type.
|
|
@@ -199,11 +199,6 @@ export const BLOCK_BUTTON_TYPES = [
|
|
|
199
199
|
'code-block',
|
|
200
200
|
];
|
|
201
201
|
|
|
202
|
-
/**
|
|
203
|
-
* Image related components IDs. `linked-image` is used in Sveltia CMS.
|
|
204
|
-
*/
|
|
205
|
-
export const IMAGE_COMPONENT_IDS = ['image', 'linked-image'];
|
|
206
|
-
|
|
207
202
|
/**
|
|
208
203
|
* Map of Lexical nodes for each block type. The `paragraph` block type is excluded because it is
|
|
209
204
|
* the default block type.
|
package/dist/components/text-editor/toolbar/{insert-image-button.svelte → insert-item-button.svelte}
RENAMED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* @typedef {object} Props
|
|
16
|
-
* @property {TextEditorComponent} component
|
|
16
|
+
* @property {TextEditorComponent} component Editor component.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
/** @type {Props} */
|
|
@@ -25,20 +25,27 @@
|
|
|
25
25
|
|
|
26
26
|
/** @type {TextEditorStore} */
|
|
27
27
|
const editorStore = getContext('editorStore');
|
|
28
|
+
|
|
29
|
+
const { label, icon, createNode } = $derived(component);
|
|
28
30
|
</script>
|
|
29
31
|
|
|
30
32
|
<Button
|
|
31
|
-
iconic
|
|
32
|
-
|
|
33
|
+
iconic={!!icon}
|
|
34
|
+
label={icon ? undefined : label}
|
|
35
|
+
title={label}
|
|
36
|
+
aria-label={label}
|
|
33
37
|
aria-controls="{editorStore.editorId}-lexical-root"
|
|
34
38
|
disabled={!editorStore.useRichText}
|
|
35
39
|
onclick={() => {
|
|
36
40
|
editorStore.editor?.update(() => {
|
|
37
|
-
|
|
41
|
+
// Add an additional paragraph for easier editing
|
|
42
|
+
insertNodes([createNode(), createParagraphNode()]);
|
|
38
43
|
});
|
|
39
44
|
}}
|
|
40
45
|
>
|
|
41
46
|
{#snippet startIcon()}
|
|
42
|
-
|
|
47
|
+
{#if icon}
|
|
48
|
+
<Icon name={icon} />
|
|
49
|
+
{/if}
|
|
43
50
|
{/snippet}
|
|
44
51
|
</Button>
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
export default
|
|
2
|
-
type
|
|
1
|
+
export default InsertItemButton;
|
|
2
|
+
type InsertItemButton = {
|
|
3
3
|
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
4
|
$set?(props: Partial<Props>): void;
|
|
5
5
|
};
|
|
6
|
-
declare const
|
|
6
|
+
declare const InsertItemButton: import("svelte").Component<{
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* Editor component.
|
|
9
9
|
*/
|
|
10
10
|
component: TextEditorComponent;
|
|
11
11
|
}, {}, "">;
|
|
12
12
|
type Props = {
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
14
|
+
* Editor component.
|
|
15
15
|
*/
|
|
16
16
|
component: TextEditorComponent;
|
|
17
17
|
};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { _ } from '@sveltia/i18n';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
$createParagraphNode as createParagraphNode,
|
|
5
|
+
$insertNodes as insertNodes,
|
|
6
|
+
} from 'lexical';
|
|
4
7
|
import { getContext } from 'svelte';
|
|
5
8
|
import Icon from '../../icon/icon.svelte';
|
|
6
9
|
import MenuButton from '../../menu/menu-button.svelte';
|
|
@@ -38,7 +41,8 @@
|
|
|
38
41
|
{label}
|
|
39
42
|
onclick={() => {
|
|
40
43
|
editorStore.editor?.update(() => {
|
|
41
|
-
|
|
44
|
+
// Add an additional paragraph for easier editing
|
|
45
|
+
insertNodes([createNode(), createParagraphNode()]);
|
|
42
46
|
});
|
|
43
47
|
}}
|
|
44
48
|
>
|
|
@@ -9,15 +9,10 @@
|
|
|
9
9
|
import Icon from '../../icon/icon.svelte';
|
|
10
10
|
import MenuButton from '../../menu/menu-button.svelte';
|
|
11
11
|
import Menu from '../../menu/menu.svelte';
|
|
12
|
-
import {
|
|
13
|
-
AVAILABLE_BUTTONS,
|
|
14
|
-
BLOCK_BUTTON_TYPES,
|
|
15
|
-
IMAGE_COMPONENT_IDS,
|
|
16
|
-
INLINE_BUTTON_TYPES,
|
|
17
|
-
} from '../constants.js';
|
|
12
|
+
import { AVAILABLE_BUTTONS, BLOCK_BUTTON_TYPES, INLINE_BUTTON_TYPES } from '../constants.js';
|
|
18
13
|
import CodeLanguageSwitcher from './code-language-switcher.svelte';
|
|
19
14
|
import FormatTextButton from './format-text-button.svelte';
|
|
20
|
-
import
|
|
15
|
+
import InsertItemButton from './insert-item-button.svelte';
|
|
21
16
|
import InsertLinkButton from './insert-link-button.svelte';
|
|
22
17
|
import InsertMenuButton from './insert-menu-button.svelte';
|
|
23
18
|
import ToggleBlockMenuItem from './toggle-block-menu-item.svelte';
|
|
@@ -48,11 +43,12 @@
|
|
|
48
43
|
|
|
49
44
|
/** @type {TextEditorStore} */
|
|
50
45
|
const editorStore = getContext('editorStore');
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
|
|
47
|
+
const buttons = $derived(
|
|
48
|
+
editorStore.config.components.filter(({ trigger = 'menuitem' }) => trigger === 'button'),
|
|
53
49
|
);
|
|
54
|
-
const
|
|
55
|
-
editorStore.config.components.filter(({
|
|
50
|
+
const menuitems = $derived(
|
|
51
|
+
editorStore.config.components.filter(({ trigger = 'menuitem' }) => trigger === 'menuitem'),
|
|
56
52
|
);
|
|
57
53
|
|
|
58
54
|
/**
|
|
@@ -121,11 +117,11 @@
|
|
|
121
117
|
{/if}
|
|
122
118
|
{#if editorStore.config.components.length}
|
|
123
119
|
<Divider orientation="vertical" />
|
|
124
|
-
{#
|
|
125
|
-
<
|
|
126
|
-
{/
|
|
127
|
-
{#if
|
|
128
|
-
<InsertMenuButton components={
|
|
120
|
+
{#each buttons as button (button.id)}
|
|
121
|
+
<InsertItemButton component={button} />
|
|
122
|
+
{/each}
|
|
123
|
+
{#if menuitems.length}
|
|
124
|
+
<InsertMenuButton components={menuitems} />
|
|
129
125
|
{/if}
|
|
130
126
|
{/if}
|
|
131
127
|
{/if}
|