@sveltia/ui 0.11.1 → 0.12.1
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/package/components/button/button.svelte +4 -1
- package/package/components/button/button.svelte.d.ts +4 -4
- package/package/components/button/select-button-group.svelte.d.ts +4 -4
- package/package/components/button/select-button.svelte.d.ts +4 -4
- package/package/components/button/split-button.svelte +2 -5
- package/package/components/button/split-button.svelte.d.ts +4 -4
- package/package/components/calendar/calendar.svelte +1 -1
- package/package/components/checkbox/checkbox.svelte +2 -2
- package/package/components/checkbox/checkbox.svelte.d.ts +6 -6
- package/package/components/dialog/dialog.svelte +2 -2
- package/package/components/dialog/dialog.svelte.d.ts +2 -2
- package/package/components/dialog/prompt-dialog.svelte +7 -1
- package/package/components/dialog/prompt-dialog.svelte.d.ts +2 -0
- package/package/components/disclosure/disclosure.svelte +2 -2
- package/package/components/disclosure/disclosure.svelte.d.ts +2 -2
- package/package/components/drawer/drawer.svelte +2 -2
- package/package/components/drawer/drawer.svelte.d.ts +2 -2
- package/package/components/grid/grid-body.svelte +2 -2
- package/package/components/grid/grid.svelte.d.ts +2 -2
- package/package/components/icon/icon.svelte +6 -0
- package/package/components/listbox/listbox.svelte.d.ts +8 -8
- package/package/components/listbox/option-group.svelte +2 -2
- package/package/components/listbox/option-group.svelte.d.ts +2 -2
- package/package/components/menu/menu-button.svelte +0 -8
- package/package/components/menu/menu-button.svelte.d.ts +4 -4
- package/package/components/menu/menu-item-checkbox.svelte.d.ts +4 -4
- package/package/components/menu/menu-item-group.svelte +2 -2
- package/package/components/menu/menu-item-group.svelte.d.ts +2 -2
- package/package/components/menu/menu-item-radio.svelte.d.ts +4 -4
- package/package/components/menu/menu-item.svelte +1 -1
- package/package/components/menu/menu-item.svelte.d.ts +2 -2
- package/package/components/radio/radio-group.svelte.d.ts +4 -4
- package/package/components/radio/radio.svelte +2 -2
- package/package/components/radio/radio.svelte.d.ts +4 -4
- package/package/components/select/combobox.svelte +3 -3
- package/package/components/select/combobox.svelte.d.ts +4 -4
- package/package/components/select/select.svelte.d.ts +4 -4
- package/package/components/slider/slider.svelte +1 -1
- package/package/components/slider/slider.svelte.d.ts +8 -8
- package/package/components/switch/switch.svelte.d.ts +6 -6
- package/package/components/table/table-body.svelte +2 -2
- package/package/components/tabs/tab-list.svelte +1 -1
- package/package/components/text-editor/core.d.ts +2 -0
- package/package/components/text-editor/core.js +206 -0
- package/package/components/text-editor/index.d.ts +23 -0
- package/package/components/text-editor/index.js +102 -0
- package/package/components/text-editor/lexical-root.svelte +123 -0
- package/package/components/text-editor/lexical-root.svelte.d.ts +27 -0
- package/package/components/text-editor/text-editor.svelte +154 -0
- package/package/components/{text-field/markdown-editor.svelte.d.ts → text-editor/text-editor.svelte.d.ts} +16 -12
- package/package/components/text-editor/toolbar/editor-toolbar.svelte +150 -0
- package/package/components/text-editor/toolbar/editor-toolbar.svelte.d.ts +25 -0
- package/package/components/text-editor/toolbar/format-text-button.svelte +33 -0
- package/package/components/text-editor/toolbar/format-text-button.svelte.d.ts +23 -0
- package/package/components/text-editor/toolbar/insert-link-button.svelte +231 -0
- package/package/components/text-editor/toolbar/insert-link-button.svelte.d.ts +23 -0
- package/package/components/text-editor/toolbar/toggle-block-menu-item.svelte +83 -0
- package/package/components/text-editor/toolbar/toggle-block-menu-item.svelte.d.ts +23 -0
- package/package/components/text-field/number-input.svelte +2 -2
- package/package/components/text-field/number-input.svelte.d.ts +10 -10
- package/package/components/text-field/password-input.svelte +2 -2
- package/package/components/text-field/password-input.svelte.d.ts +4 -4
- package/package/components/text-field/search-bar.svelte +3 -3
- package/package/components/text-field/search-bar.svelte.d.ts +4 -4
- package/package/components/text-field/text-area.svelte +3 -0
- package/package/components/text-field/text-area.svelte.d.ts +4 -4
- package/package/components/text-field/text-input.svelte +2 -2
- package/package/components/text-field/text-input.svelte.d.ts +4 -4
- package/package/components/toast/toast.svelte +2 -2
- package/package/components/util/app-shell.svelte +56 -4
- package/package/components/util/modal.svelte +3 -3
- package/package/components/util/modal.svelte.d.ts +4 -4
- package/package/components/util/popup.svelte +2 -2
- package/package/index.d.ts +1 -2
- package/package/index.js +9 -4
- package/package/locales/en.d.ts +25 -7
- package/package/locales/en.js +25 -6
- package/package/locales/ja.d.ts +25 -7
- package/package/locales/ja.js +25 -6
- package/package/services/events.js +2 -2
- package/package/services/group.js +7 -6
- package/package/services/popup.js +2 -2
- package/package/styles/core.scss +51 -2
- package/package/styles/variables.scss +3 -1
- package/package/typedef.d.ts +48 -0
- package/package/typedef.js +36 -0
- package/package.json +22 -18
- package/package/components/text-field/markdown-editor.svelte +0 -141
- package/package/services/util.d.ts +0 -3
- package/package/services/util.js +0 -35
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { getContext, onMount } from 'svelte';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Whether to disable the widget. An alias of the `aria-disabled` attribute.
|
|
6
|
+
* @type {boolean}
|
|
7
|
+
*/
|
|
8
|
+
export let disabled = false;
|
|
9
|
+
/**
|
|
10
|
+
* Whether to disable the widget. An alias of `aria-readonly` attribute.
|
|
11
|
+
* @type {boolean}
|
|
12
|
+
*/
|
|
13
|
+
export let readonly = false;
|
|
14
|
+
/**
|
|
15
|
+
* Input value.
|
|
16
|
+
* @type {string | undefined}
|
|
17
|
+
*/
|
|
18
|
+
export let value = undefined;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Text editor state.
|
|
22
|
+
* @type {TextEditorState}
|
|
23
|
+
*/
|
|
24
|
+
const {
|
|
25
|
+
editor,
|
|
26
|
+
editorId,
|
|
27
|
+
selectionBlockType,
|
|
28
|
+
selectionInlineTypes,
|
|
29
|
+
useRichText,
|
|
30
|
+
hasConverterError,
|
|
31
|
+
} = getContext('state');
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Reference to the Lexical editor root element.
|
|
35
|
+
* @type {HTMLElement | undefined}
|
|
36
|
+
*/
|
|
37
|
+
let lexicalRoot = undefined;
|
|
38
|
+
|
|
39
|
+
$: editable = !(disabled || readonly);
|
|
40
|
+
$: $editor.setEditable(editable);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Update {@link value} and other state variables whenever the editor content is updated.
|
|
44
|
+
* @param {Event} event - `Update` custom event.
|
|
45
|
+
*/
|
|
46
|
+
const onUpdate = (event) => {
|
|
47
|
+
if ($hasConverterError) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const { detail } = /** @type {CustomEvent} */ (event);
|
|
52
|
+
const newValue = detail.value;
|
|
53
|
+
|
|
54
|
+
if (value !== newValue) {
|
|
55
|
+
value = newValue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
$selectionBlockType = detail.selectionBlockType;
|
|
59
|
+
$selectionInlineTypes = detail.selectionInlineTypes;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Listen to `click` events on the editor. Ignore a click on a link.
|
|
64
|
+
* @param {MouseEvent} event - `click` event.
|
|
65
|
+
*/
|
|
66
|
+
const onClick = (event) => {
|
|
67
|
+
if (/** @type {HTMLElement} */ (event.target)?.matches('a')) {
|
|
68
|
+
event.preventDefault();
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
onMount(() => {
|
|
73
|
+
if (lexicalRoot) {
|
|
74
|
+
$editor.setRootElement(lexicalRoot);
|
|
75
|
+
lexicalRoot.addEventListener('Update', onUpdate);
|
|
76
|
+
lexicalRoot.addEventListener('click', onClick);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return () => {
|
|
80
|
+
lexicalRoot?.removeEventListener('Update', onUpdate);
|
|
81
|
+
lexicalRoot?.removeEventListener('click', onClick);
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
</script>
|
|
85
|
+
|
|
86
|
+
<div
|
|
87
|
+
role="textbox"
|
|
88
|
+
aria-multiline="true"
|
|
89
|
+
aria-disabled={disabled}
|
|
90
|
+
aria-readonly={readonly}
|
|
91
|
+
class="lexical-root"
|
|
92
|
+
id="{$editorId}-lexical-root"
|
|
93
|
+
contenteditable={editable}
|
|
94
|
+
bind:this={lexicalRoot}
|
|
95
|
+
hidden={!$useRichText}
|
|
96
|
+
/>
|
|
97
|
+
|
|
98
|
+
<style>.lexical-root {
|
|
99
|
+
border: 1px solid var(--sui-textbox-border-color);
|
|
100
|
+
border-radius: 0 0 var(--sui-textbox-border-radius) var(--sui-textbox-border-radius) !important;
|
|
101
|
+
padding: 16px;
|
|
102
|
+
min-height: 8em;
|
|
103
|
+
color: var(--sui-textbox-foreground-color);
|
|
104
|
+
background-color: var(--sui-textbox-background-color);
|
|
105
|
+
font-family: var(--sui-textbox-font-family);
|
|
106
|
+
font-size: var(--sui-textbox-font-size);
|
|
107
|
+
line-height: var(--sui-textbox-multiline-line-height);
|
|
108
|
+
}
|
|
109
|
+
.lexical-root:focus-visible {
|
|
110
|
+
outline: 0;
|
|
111
|
+
}
|
|
112
|
+
.lexical-root > :global(:first-child) {
|
|
113
|
+
margin-top: 0;
|
|
114
|
+
}
|
|
115
|
+
.lexical-root > :global(:last-child) {
|
|
116
|
+
margin-bottom: 0;
|
|
117
|
+
}
|
|
118
|
+
.lexical-root :global(strong.italic) {
|
|
119
|
+
font-style: italic;
|
|
120
|
+
}
|
|
121
|
+
.lexical-root :global([data-lexical-text="true"]) {
|
|
122
|
+
cursor: text;
|
|
123
|
+
}</style>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** @typedef {typeof __propDef.props} LexicalRootProps */
|
|
2
|
+
/** @typedef {typeof __propDef.events} LexicalRootEvents */
|
|
3
|
+
/** @typedef {typeof __propDef.slots} LexicalRootSlots */
|
|
4
|
+
export default class LexicalRoot extends SvelteComponent<{
|
|
5
|
+
disabled?: boolean | undefined;
|
|
6
|
+
value?: string | undefined;
|
|
7
|
+
readonly?: boolean | undefined;
|
|
8
|
+
}, {
|
|
9
|
+
[evt: string]: CustomEvent<any>;
|
|
10
|
+
}, {}> {
|
|
11
|
+
}
|
|
12
|
+
export type LexicalRootProps = typeof __propDef.props;
|
|
13
|
+
export type LexicalRootEvents = typeof __propDef.events;
|
|
14
|
+
export type LexicalRootSlots = typeof __propDef.slots;
|
|
15
|
+
import { SvelteComponent } from "svelte";
|
|
16
|
+
declare const __propDef: {
|
|
17
|
+
props: {
|
|
18
|
+
disabled?: boolean | undefined;
|
|
19
|
+
value?: string | undefined;
|
|
20
|
+
readonly?: boolean | undefined;
|
|
21
|
+
};
|
|
22
|
+
events: {
|
|
23
|
+
[evt: string]: CustomEvent<any>;
|
|
24
|
+
};
|
|
25
|
+
slots: {};
|
|
26
|
+
};
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component
|
|
3
|
+
A rich text editor based on Lexical.
|
|
4
|
+
-->
|
|
5
|
+
<script>
|
|
6
|
+
import { generateElementId } from '@sveltia/utils/element';
|
|
7
|
+
import { onMount, setContext } from 'svelte';
|
|
8
|
+
import { _ } from 'svelte-i18n';
|
|
9
|
+
import { writable } from 'svelte/store';
|
|
10
|
+
import Alert from '../alert/alert.svelte';
|
|
11
|
+
import { blockButtonTypes, inlineButtonTypes } from './';
|
|
12
|
+
import {
|
|
13
|
+
convertMarkdown as convertMarkdownToLexical,
|
|
14
|
+
initEditor,
|
|
15
|
+
} from './core';
|
|
16
|
+
import LexicalRoot from './lexical-root.svelte';
|
|
17
|
+
import EditorToolbar from './toolbar/editor-toolbar.svelte';
|
|
18
|
+
import TextArea from '../text-field/text-area.svelte';
|
|
19
|
+
import Toast from '../toast/toast.svelte';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Make the text input container flexible.
|
|
23
|
+
* @type {boolean}
|
|
24
|
+
*/
|
|
25
|
+
export let flex = false;
|
|
26
|
+
/**
|
|
27
|
+
* Whether to hide the widget. An alias of the `aria-hidden` attribute.
|
|
28
|
+
* @type {boolean | undefined}
|
|
29
|
+
*/
|
|
30
|
+
export let hidden = undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Whether to disable the widget. An alias of the `aria-disabled` attribute.
|
|
33
|
+
* @type {boolean}
|
|
34
|
+
*/
|
|
35
|
+
export let disabled = false;
|
|
36
|
+
/**
|
|
37
|
+
* Whether to disable the widget. An alias of `aria-readonly` attribute.
|
|
38
|
+
* @type {boolean}
|
|
39
|
+
*/
|
|
40
|
+
export let readonly = false;
|
|
41
|
+
/**
|
|
42
|
+
* Whether to mark the widget required. An alias of the `aria-required` attribute.
|
|
43
|
+
* @type {boolean}
|
|
44
|
+
*/
|
|
45
|
+
export let required = false;
|
|
46
|
+
/**
|
|
47
|
+
* Whether to mark the widget invalid. An alias of the `aria-invalid` attribute.
|
|
48
|
+
* @type {boolean}
|
|
49
|
+
*/
|
|
50
|
+
export let invalid = false;
|
|
51
|
+
/**
|
|
52
|
+
* Input value.
|
|
53
|
+
* @type {string | undefined}
|
|
54
|
+
*/
|
|
55
|
+
export let value = undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Enabled modes.
|
|
58
|
+
* @type {TextEditorMode[]}
|
|
59
|
+
*/
|
|
60
|
+
export let modes = ['rich-text', 'plain-text'];
|
|
61
|
+
/**
|
|
62
|
+
* Enabled buttons.
|
|
63
|
+
* @type {(TextEditorBlockType | TextEditorInlineType)[]}
|
|
64
|
+
*/
|
|
65
|
+
export let buttons = [...inlineButtonTypes, ...blockButtonTypes];
|
|
66
|
+
|
|
67
|
+
const editor = writable(initEditor());
|
|
68
|
+
const selectionBlockType = writable('paragraph');
|
|
69
|
+
const selectionInlineTypes = writable([]);
|
|
70
|
+
const editorId = writable(generateElementId('editor'));
|
|
71
|
+
const useRichText = writable(modes.includes('rich-text'));
|
|
72
|
+
const hasConverterError = writable(false);
|
|
73
|
+
let showConverterError = false;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Convert the Markdown {@link value} to Lexical nodes. Disable the rich text mode and restore the
|
|
77
|
+
* original value when there is an error while conversion.
|
|
78
|
+
*/
|
|
79
|
+
const convertMarkdown = async () => {
|
|
80
|
+
const originalValue = value;
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
// We should avoid an empty editor; there should be at least one `<p>`, so give it an empty
|
|
84
|
+
// string if the `value` is `undefined`
|
|
85
|
+
// @see https://github.com/facebook/lexical/issues/2308
|
|
86
|
+
await convertMarkdownToLexical($editor, value ?? '');
|
|
87
|
+
} catch {
|
|
88
|
+
$hasConverterError = true;
|
|
89
|
+
value = originalValue;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
$: {
|
|
94
|
+
if ($hasConverterError) {
|
|
95
|
+
$useRichText = false;
|
|
96
|
+
showConverterError = true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
setContext(
|
|
101
|
+
'state',
|
|
102
|
+
/** @type {TextEditorState} */ ({
|
|
103
|
+
editor,
|
|
104
|
+
editorId,
|
|
105
|
+
selectionBlockType,
|
|
106
|
+
selectionInlineTypes,
|
|
107
|
+
modes,
|
|
108
|
+
useRichText,
|
|
109
|
+
hasConverterError,
|
|
110
|
+
enabledButtons: buttons,
|
|
111
|
+
convertMarkdown,
|
|
112
|
+
}),
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
onMount(() => {
|
|
116
|
+
if ($useRichText) {
|
|
117
|
+
convertMarkdown();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
</script>
|
|
121
|
+
|
|
122
|
+
<div role="none" class="sui text-editor" hidden={hidden || undefined} {...$$restProps}>
|
|
123
|
+
<EditorToolbar {disabled} {readonly} />
|
|
124
|
+
<LexicalRoot {disabled} {readonly} bind:value />
|
|
125
|
+
<TextArea
|
|
126
|
+
autoResize={true}
|
|
127
|
+
bind:value
|
|
128
|
+
{flex}
|
|
129
|
+
hidden={$useRichText || hidden}
|
|
130
|
+
{disabled}
|
|
131
|
+
{readonly}
|
|
132
|
+
{required}
|
|
133
|
+
{invalid}
|
|
134
|
+
/>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
{#if showConverterError}
|
|
138
|
+
<Toast bind:show={showConverterError}>
|
|
139
|
+
<Alert status="error">{$_('_sui.text_editor.converter_error')}</Alert>
|
|
140
|
+
</Toast>
|
|
141
|
+
{/if}
|
|
142
|
+
|
|
143
|
+
<style>.text-editor {
|
|
144
|
+
margin: var(--sui-focus-ring-width);
|
|
145
|
+
width: calc(100% - var(--sui-focus-ring-width) * 2);
|
|
146
|
+
}
|
|
147
|
+
.text-editor :global(.sui.text-area) {
|
|
148
|
+
margin: 0 !important;
|
|
149
|
+
width: 100% !important;
|
|
150
|
+
min-width: auto;
|
|
151
|
+
}
|
|
152
|
+
.text-editor :global(.sui.text-area) :global(textarea) {
|
|
153
|
+
border-radius: 0 0 var(--sui-textbox-border-radius) var(--sui-textbox-border-radius) !important;
|
|
154
|
+
}</style>
|
|
@@ -1,34 +1,38 @@
|
|
|
1
|
-
/** @typedef {typeof __propDef.props}
|
|
2
|
-
/** @typedef {typeof __propDef.events}
|
|
3
|
-
/** @typedef {typeof __propDef.slots}
|
|
4
|
-
/** A
|
|
5
|
-
export default class
|
|
1
|
+
/** @typedef {typeof __propDef.props} TextEditorProps */
|
|
2
|
+
/** @typedef {typeof __propDef.events} TextEditorEvents */
|
|
3
|
+
/** @typedef {typeof __propDef.slots} TextEditorSlots */
|
|
4
|
+
/** A rich text editor based on Lexical. */
|
|
5
|
+
export default class TextEditor extends SvelteComponent<{
|
|
6
6
|
[x: string]: any;
|
|
7
|
-
disabled?: boolean | undefined;
|
|
8
7
|
invalid?: boolean | undefined;
|
|
9
|
-
|
|
8
|
+
disabled?: boolean | undefined;
|
|
10
9
|
value?: string | undefined;
|
|
11
10
|
hidden?: boolean | undefined;
|
|
12
11
|
readonly?: boolean | undefined;
|
|
13
12
|
flex?: boolean | undefined;
|
|
13
|
+
required?: boolean | undefined;
|
|
14
|
+
modes?: TextEditorMode[] | undefined;
|
|
15
|
+
buttons?: (TextEditorInlineType | TextEditorBlockType)[] | undefined;
|
|
14
16
|
}, {
|
|
15
17
|
[evt: string]: CustomEvent<any>;
|
|
16
18
|
}, {}> {
|
|
17
19
|
}
|
|
18
|
-
export type
|
|
19
|
-
export type
|
|
20
|
-
export type
|
|
20
|
+
export type TextEditorProps = typeof __propDef.props;
|
|
21
|
+
export type TextEditorEvents = typeof __propDef.events;
|
|
22
|
+
export type TextEditorSlots = typeof __propDef.slots;
|
|
21
23
|
import { SvelteComponent } from "svelte";
|
|
22
24
|
declare const __propDef: {
|
|
23
25
|
props: {
|
|
24
26
|
[x: string]: any;
|
|
25
|
-
disabled?: boolean | undefined;
|
|
26
27
|
invalid?: boolean | undefined;
|
|
27
|
-
|
|
28
|
+
disabled?: boolean | undefined;
|
|
28
29
|
value?: string | undefined;
|
|
29
30
|
hidden?: boolean | undefined;
|
|
30
31
|
readonly?: boolean | undefined;
|
|
31
32
|
flex?: boolean | undefined;
|
|
33
|
+
required?: boolean | undefined;
|
|
34
|
+
modes?: TextEditorMode[] | undefined;
|
|
35
|
+
buttons?: (TextEditorInlineType | TextEditorBlockType)[] | undefined;
|
|
32
36
|
};
|
|
33
37
|
events: {
|
|
34
38
|
[evt: string]: CustomEvent<any>;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { getContext, tick } from 'svelte';
|
|
3
|
+
import { _ } from 'svelte-i18n';
|
|
4
|
+
import ButtonGroup from '../../button/button-group.svelte';
|
|
5
|
+
import Divider from '../../divider/divider.svelte';
|
|
6
|
+
import Spacer from '../../divider/spacer.svelte';
|
|
7
|
+
import Icon from '../../icon/icon.svelte';
|
|
8
|
+
import MenuButton from '../../menu/menu-button.svelte';
|
|
9
|
+
import MenuItemCheckbox from '../../menu/menu-item-checkbox.svelte';
|
|
10
|
+
import Menu from '../../menu/menu.svelte';
|
|
11
|
+
import {
|
|
12
|
+
availableButtons,
|
|
13
|
+
blockButtonTypes,
|
|
14
|
+
inlineButtonTypes,
|
|
15
|
+
} from '..';
|
|
16
|
+
import FormatTextButton from './format-text-button.svelte';
|
|
17
|
+
import InsertLinkButton from './insert-link-button.svelte';
|
|
18
|
+
import ToggleBlockMenuItem from './toggle-block-menu-item.svelte';
|
|
19
|
+
import Toolbar from '../../toolbar/toolbar.svelte';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Whether to disable the widget. An alias of the `aria-disabled` attribute.
|
|
23
|
+
* @type {boolean}
|
|
24
|
+
*/
|
|
25
|
+
export let disabled = false;
|
|
26
|
+
/**
|
|
27
|
+
* Whether to disable the widget. An alias of `aria-readonly` attribute.
|
|
28
|
+
* @type {boolean}
|
|
29
|
+
*/
|
|
30
|
+
export let readonly = false;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Text editor state.
|
|
34
|
+
* @type {TextEditorState}
|
|
35
|
+
*/
|
|
36
|
+
const {
|
|
37
|
+
editorId,
|
|
38
|
+
selectionBlockType,
|
|
39
|
+
modes,
|
|
40
|
+
useRichText,
|
|
41
|
+
hasConverterError,
|
|
42
|
+
enabledButtons,
|
|
43
|
+
convertMarkdown,
|
|
44
|
+
} = getContext('state');
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Enabled block level buttons.
|
|
48
|
+
*/
|
|
49
|
+
$: blockLevelButtons = Array.from(
|
|
50
|
+
new Set(
|
|
51
|
+
/** @type {TextEditorBlockType[]} */ ([
|
|
52
|
+
'paragraph', // Always needed
|
|
53
|
+
...enabledButtons.filter((type) => blockButtonTypes.includes(/** @type {any} */ (type))),
|
|
54
|
+
]),
|
|
55
|
+
),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Enabled inline level buttons.
|
|
60
|
+
*/
|
|
61
|
+
$: inlineLevelButtons = Array.from(
|
|
62
|
+
new Set(
|
|
63
|
+
/** @type {TextEditorInlineType[]} */ ([
|
|
64
|
+
...enabledButtons.filter((type) => inlineButtonTypes.includes(/** @type {any} */ (type))),
|
|
65
|
+
]),
|
|
66
|
+
),
|
|
67
|
+
);
|
|
68
|
+
</script>
|
|
69
|
+
|
|
70
|
+
<div role="none" class="wrapper">
|
|
71
|
+
<Toolbar disabled={disabled || readonly} aria-label={$_('_sui.text_editor.text_editor')}>
|
|
72
|
+
<MenuButton
|
|
73
|
+
disabled={!$useRichText}
|
|
74
|
+
aria-label={$_('_sui.text_editor.show_text_style_options')}
|
|
75
|
+
aria-controls="{$editorId}-lexical-root"
|
|
76
|
+
>
|
|
77
|
+
<Icon
|
|
78
|
+
slot="start-icon"
|
|
79
|
+
name={availableButtons[$selectionBlockType]?.icon ?? 'format_paragraph'}
|
|
80
|
+
/>
|
|
81
|
+
<Icon slot="end-icon" name="arrow_drop_down" class="small-arrow" />
|
|
82
|
+
<Menu slot="popup" aria-label={$_('_sui.text_editor.text_style_options')}>
|
|
83
|
+
{#each blockLevelButtons as type (type)}
|
|
84
|
+
<ToggleBlockMenuItem {type} />
|
|
85
|
+
{/each}
|
|
86
|
+
</Menu>
|
|
87
|
+
</MenuButton>
|
|
88
|
+
{#if inlineLevelButtons.length}
|
|
89
|
+
<Divider orientation="vertical" />
|
|
90
|
+
<ButtonGroup>
|
|
91
|
+
{#each inlineLevelButtons as type (type)}
|
|
92
|
+
{#if type === 'link'}
|
|
93
|
+
<InsertLinkButton />
|
|
94
|
+
{:else}
|
|
95
|
+
<FormatTextButton {type} />
|
|
96
|
+
{/if}
|
|
97
|
+
{/each}
|
|
98
|
+
</ButtonGroup>
|
|
99
|
+
{/if}
|
|
100
|
+
<Spacer flex />
|
|
101
|
+
{#if modes.length > 1}
|
|
102
|
+
<MenuButton
|
|
103
|
+
variant="ghost"
|
|
104
|
+
iconic
|
|
105
|
+
disabled={$hasConverterError}
|
|
106
|
+
popupPosition="bottom-right"
|
|
107
|
+
aria-label={$_('_sui.text_editor.show_edit_options')}
|
|
108
|
+
>
|
|
109
|
+
<Icon slot="start-icon" name="more_vert" />
|
|
110
|
+
<Menu slot="popup" aria-label={$_('_sui.text_editor.edit_options')}>
|
|
111
|
+
<MenuItemCheckbox
|
|
112
|
+
label={$_('_sui.text_editor.use_rich_text')}
|
|
113
|
+
bind:checked={$useRichText}
|
|
114
|
+
on:change={async () => {
|
|
115
|
+
// Wait for `$useRichText` to be updated
|
|
116
|
+
await tick();
|
|
117
|
+
|
|
118
|
+
if ($useRichText) {
|
|
119
|
+
convertMarkdown();
|
|
120
|
+
}
|
|
121
|
+
}}
|
|
122
|
+
/>
|
|
123
|
+
</Menu>
|
|
124
|
+
</MenuButton>
|
|
125
|
+
{/if}
|
|
126
|
+
</Toolbar>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<style>.wrapper {
|
|
130
|
+
display: contents;
|
|
131
|
+
}
|
|
132
|
+
.wrapper :global([role="toolbar"]) {
|
|
133
|
+
position: sticky;
|
|
134
|
+
top: 0;
|
|
135
|
+
display: flex;
|
|
136
|
+
gap: 8px;
|
|
137
|
+
border-width: 1px 1px 0;
|
|
138
|
+
border-style: solid;
|
|
139
|
+
border-color: var(--sui-textbox-border-color);
|
|
140
|
+
border-radius: var(--sui-textbox-border-radius) var(--sui-textbox-border-radius) 0 0;
|
|
141
|
+
padding: 8px;
|
|
142
|
+
background-color: var(--sui-tertiary-background-color);
|
|
143
|
+
}
|
|
144
|
+
.wrapper :global(button) {
|
|
145
|
+
flex: none;
|
|
146
|
+
margin: 0 !important;
|
|
147
|
+
}
|
|
148
|
+
.wrapper :global(.sui.button-group) {
|
|
149
|
+
gap: 4px;
|
|
150
|
+
}</style>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** @typedef {typeof __propDef.props} EditorToolbarProps */
|
|
2
|
+
/** @typedef {typeof __propDef.events} EditorToolbarEvents */
|
|
3
|
+
/** @typedef {typeof __propDef.slots} EditorToolbarSlots */
|
|
4
|
+
export default class EditorToolbar extends SvelteComponent<{
|
|
5
|
+
disabled?: boolean | undefined;
|
|
6
|
+
readonly?: boolean | undefined;
|
|
7
|
+
}, {
|
|
8
|
+
[evt: string]: CustomEvent<any>;
|
|
9
|
+
}, {}> {
|
|
10
|
+
}
|
|
11
|
+
export type EditorToolbarProps = typeof __propDef.props;
|
|
12
|
+
export type EditorToolbarEvents = typeof __propDef.events;
|
|
13
|
+
export type EditorToolbarSlots = typeof __propDef.slots;
|
|
14
|
+
import { SvelteComponent } from "svelte";
|
|
15
|
+
declare const __propDef: {
|
|
16
|
+
props: {
|
|
17
|
+
disabled?: boolean | undefined;
|
|
18
|
+
readonly?: boolean | undefined;
|
|
19
|
+
};
|
|
20
|
+
events: {
|
|
21
|
+
[evt: string]: CustomEvent<any>;
|
|
22
|
+
};
|
|
23
|
+
slots: {};
|
|
24
|
+
};
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { FORMAT_TEXT_COMMAND } from 'lexical';
|
|
3
|
+
import { getContext } from 'svelte';
|
|
4
|
+
import { _ } from 'svelte-i18n';
|
|
5
|
+
import Button from '../../button/button.svelte';
|
|
6
|
+
import Icon from '../../icon/icon.svelte';
|
|
7
|
+
import { availableButtons } from '..';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Button type.
|
|
11
|
+
* @type {TextEditorFormatType}
|
|
12
|
+
*/
|
|
13
|
+
export let type;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Text editor state.
|
|
17
|
+
* @type {TextEditorState}
|
|
18
|
+
*/
|
|
19
|
+
const { editor, editorId, selectionInlineTypes, useRichText } = getContext('state');
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<Button
|
|
23
|
+
iconic
|
|
24
|
+
aria-label={$_(`_sui.text_editor.${availableButtons[type].labelKey}`)}
|
|
25
|
+
aria-controls="{$editorId}-lexical-root"
|
|
26
|
+
disabled={!$useRichText}
|
|
27
|
+
pressed={$selectionInlineTypes.includes(type)}
|
|
28
|
+
on:click={() => {
|
|
29
|
+
$editor.dispatchCommand(FORMAT_TEXT_COMMAND, type);
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
<Icon slot="start-icon" name={availableButtons[type].icon} />
|
|
33
|
+
</Button>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** @typedef {typeof __propDef.props} FormatTextButtonProps */
|
|
2
|
+
/** @typedef {typeof __propDef.events} FormatTextButtonEvents */
|
|
3
|
+
/** @typedef {typeof __propDef.slots} FormatTextButtonSlots */
|
|
4
|
+
export default class FormatTextButton extends SvelteComponent<{
|
|
5
|
+
type: TextEditorFormatType;
|
|
6
|
+
}, {
|
|
7
|
+
[evt: string]: CustomEvent<any>;
|
|
8
|
+
}, {}> {
|
|
9
|
+
}
|
|
10
|
+
export type FormatTextButtonProps = typeof __propDef.props;
|
|
11
|
+
export type FormatTextButtonEvents = typeof __propDef.events;
|
|
12
|
+
export type FormatTextButtonSlots = typeof __propDef.slots;
|
|
13
|
+
import { SvelteComponent } from "svelte";
|
|
14
|
+
declare const __propDef: {
|
|
15
|
+
props: {
|
|
16
|
+
type: TextEditorFormatType;
|
|
17
|
+
};
|
|
18
|
+
events: {
|
|
19
|
+
[evt: string]: CustomEvent<any>;
|
|
20
|
+
};
|
|
21
|
+
slots: {};
|
|
22
|
+
};
|
|
23
|
+
export {};
|