@streamscloud/kit 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/dist/styles/_input.scss +98 -0
  2. package/dist/styles/_mixins.scss +2 -2
  3. package/dist/styles/reset.css +1 -1
  4. package/dist/ui/button/resources/button-base.svelte +2 -2
  5. package/dist/ui/button/resources/button-theme.svelte +18 -15
  6. package/dist/ui/color-picker/cmp.color-picker.svelte +3 -12
  7. package/dist/ui/color-picker/cmp.color-picker.svelte.d.ts +3 -9
  8. package/dist/ui/cropper/img-cropper/cmp.img-cropper-toolbar.svelte +1 -1
  9. package/dist/ui/dialog/cmp.dialog.svelte +1 -1
  10. package/dist/ui/dropdown/cmp.dropdown-item.svelte +93 -0
  11. package/dist/ui/dropdown/cmp.dropdown-item.svelte.d.ts +32 -0
  12. package/dist/ui/dropdown/cmp.dropdown-panel.svelte +29 -0
  13. package/dist/ui/dropdown/cmp.dropdown-panel.svelte.d.ts +18 -0
  14. package/dist/ui/dropdown/cmp.dropdown.svelte +72 -7
  15. package/dist/ui/dropdown/cmp.dropdown.svelte.d.ts +3 -1
  16. package/dist/ui/dropdown/index.d.ts +2 -0
  17. package/dist/ui/dropdown/index.js +2 -0
  18. package/dist/ui/dynamic-component/cmp.dynamic-component.svelte +0 -5
  19. package/dist/ui/dynamic-component/cmp.dynamic-component.svelte.d.ts +2 -8
  20. package/dist/ui/emoji-picker/cmp.emoji-panel.svelte +186 -0
  21. package/dist/ui/emoji-picker/cmp.emoji-panel.svelte.d.ts +21 -0
  22. package/dist/ui/emoji-picker/cmp.emoji-picker.svelte +35 -0
  23. package/dist/ui/emoji-picker/cmp.emoji-picker.svelte.d.ts +15 -0
  24. package/dist/ui/emoji-picker/emoji-list.d.ts +2 -0
  25. package/dist/ui/emoji-picker/emoji-list.js +1754 -0
  26. package/dist/ui/emoji-picker/emoji-picker-localization.d.ts +5 -0
  27. package/dist/ui/emoji-picker/emoji-picker-localization.js +40 -0
  28. package/dist/ui/emoji-picker/index.d.ts +2 -0
  29. package/dist/ui/emoji-picker/index.js +2 -0
  30. package/dist/ui/emoji-picker/types.d.ts +8 -0
  31. package/dist/ui/emoji-picker/types.js +1 -0
  32. package/dist/ui/form-group/cmp.form-group-label.svelte.d.ts +1 -0
  33. package/dist/ui/form-group/cmp.form-group.svelte.d.ts +1 -0
  34. package/dist/ui/icon-text/cmp.icon-text.svelte +34 -22
  35. package/dist/ui/icon-text/cmp.icon-text.svelte.d.ts +14 -13
  36. package/dist/ui/inputs/index.d.ts +6 -0
  37. package/dist/ui/inputs/index.js +5 -0
  38. package/dist/ui/inputs/input/cmp.input-validatable.svelte +57 -0
  39. package/dist/ui/inputs/input/cmp.input-validatable.svelte.d.ts +56 -0
  40. package/dist/ui/inputs/input/cmp.input.svelte +235 -0
  41. package/dist/ui/inputs/input/cmp.input.svelte.d.ts +60 -0
  42. package/dist/ui/inputs/input/index.d.ts +2 -0
  43. package/dist/ui/inputs/input/index.js +2 -0
  44. package/dist/ui/inputs/input-emoji-picker/cmp.input-emoji-picker.svelte +44 -0
  45. package/dist/ui/inputs/input-emoji-picker/cmp.input-emoji-picker.svelte.d.ts +9 -0
  46. package/dist/ui/inputs/input-emoji-picker/index.d.ts +2 -0
  47. package/dist/ui/inputs/input-emoji-picker/index.js +2 -0
  48. package/dist/ui/inputs/input-emoji-picker/input-emoji-picker-container.d.ts +2 -0
  49. package/dist/ui/inputs/input-emoji-picker/input-emoji-picker-container.js +16 -0
  50. package/dist/ui/inputs/numeral-input/cmp.numeral-input-validatable.svelte +55 -0
  51. package/dist/ui/inputs/numeral-input/cmp.numeral-input-validatable.svelte.d.ts +62 -0
  52. package/dist/ui/inputs/numeral-input/cmp.numeral-input.svelte +248 -0
  53. package/dist/ui/inputs/numeral-input/cmp.numeral-input.svelte.d.ts +66 -0
  54. package/dist/ui/inputs/numeral-input/index.d.ts +2 -0
  55. package/dist/ui/inputs/numeral-input/index.js +2 -0
  56. package/dist/ui/inputs/pin-input/cmp.pin-input.svelte +58 -0
  57. package/dist/ui/inputs/pin-input/cmp.pin-input.svelte.d.ts +23 -0
  58. package/dist/ui/inputs/pin-input/index.d.ts +1 -0
  59. package/dist/ui/inputs/pin-input/index.js +1 -0
  60. package/dist/ui/inputs/pin-input/pin-input-generator.d.ts +27 -0
  61. package/dist/ui/inputs/pin-input/pin-input-generator.js +114 -0
  62. package/dist/ui/inputs/rich-text-input/cmp.rich-text-input.svelte +55 -0
  63. package/dist/ui/inputs/rich-text-input/cmp.rich-text-input.svelte.d.ts +43 -0
  64. package/dist/ui/inputs/rich-text-input/index.d.ts +2 -0
  65. package/dist/ui/inputs/rich-text-input/index.js +1 -0
  66. package/dist/ui/inputs/rich-text-input/rich-text-input-localization.d.ts +12 -0
  67. package/dist/ui/inputs/rich-text-input/rich-text-input-localization.js +48 -0
  68. package/dist/ui/inputs/rich-text-input/tinymce-input.svelte +250 -0
  69. package/dist/ui/inputs/rich-text-input/tinymce-input.svelte.d.ts +25 -0
  70. package/dist/ui/inputs/rich-text-input/tinymce.declarations.d.ts +7 -0
  71. package/dist/ui/inputs/rich-text-input/types.d.ts +4 -0
  72. package/dist/ui/inputs/rich-text-input/types.js +1 -0
  73. package/dist/ui/inputs/rich-text-input/validated-link-button.d.ts +3 -0
  74. package/dist/ui/inputs/rich-text-input/validated-link-button.js +78 -0
  75. package/dist/ui/inputs/textarea/cmp.textarea-validatable.svelte +35 -0
  76. package/dist/ui/inputs/textarea/cmp.textarea-validatable.svelte.d.ts +53 -0
  77. package/dist/ui/inputs/textarea/cmp.textarea.svelte +247 -0
  78. package/dist/ui/inputs/textarea/cmp.textarea.svelte.d.ts +57 -0
  79. package/dist/ui/inputs/textarea/index.d.ts +2 -0
  80. package/dist/ui/inputs/textarea/index.js +2 -0
  81. package/dist/ui/media-viewer-dialog/cmp.media-viewer-dialog.svelte.d.ts +2 -0
  82. package/dist/ui/selects/_multiselect.scss +282 -0
  83. package/dist/ui/selects/_singleselect.scss +175 -0
  84. package/dist/ui/selects/cmp.multiselect.svelte +530 -0
  85. package/dist/ui/selects/cmp.multiselect.svelte.d.ts +85 -0
  86. package/dist/ui/selects/cmp.search-multiselect.svelte +532 -0
  87. package/dist/ui/selects/cmp.search-multiselect.svelte.d.ts +67 -0
  88. package/dist/ui/selects/cmp.singleselect.svelte +381 -0
  89. package/dist/ui/selects/cmp.singleselect.svelte.d.ts +78 -0
  90. package/dist/ui/selects/index.d.ts +5 -0
  91. package/dist/ui/selects/index.js +4 -0
  92. package/dist/ui/selects/select-localization.d.ts +6 -0
  93. package/dist/ui/selects/select-localization.js +27 -0
  94. package/dist/ui/selects/types.d.ts +29 -0
  95. package/dist/ui/selects/types.js +1 -0
  96. package/dist/ui/time-ago/cmp.time-ago.svelte +0 -6
  97. package/dist/ui/time-ago/cmp.time-ago.svelte.d.ts +2 -6
  98. package/dist/ui/validatable/_validatable.scss +34 -0
  99. package/dist/ui/validatable/cmp.validatable.svelte +57 -0
  100. package/dist/ui/validatable/cmp.validatable.svelte.d.ts +49 -0
  101. package/dist/ui/validatable/cmp.validation-error.svelte +52 -0
  102. package/dist/ui/validatable/cmp.validation-error.svelte.d.ts +42 -0
  103. package/dist/ui/validatable/index.d.ts +2 -0
  104. package/dist/ui/validatable/index.js +2 -0
  105. package/package.json +31 -5
  106. package/dist/ui/color-picker/cmp.input-stub.svelte +0 -98
  107. package/dist/ui/color-picker/cmp.input-stub.svelte.d.ts +0 -40
@@ -0,0 +1,114 @@
1
+ const CELL_REGEXP = '^\\d{1}$';
2
+ export class PinInputGenerator {
3
+ _container;
4
+ _onInput;
5
+ _count;
6
+ _secure;
7
+ _previewDuration;
8
+ _numeric;
9
+ _cells;
10
+ _focusedCellIndex;
11
+ constructor(container, args) {
12
+ const { count = 6, secure = false, previewDuration = 200, numeric = true, onInput } = args;
13
+ this._container = container;
14
+ this._count = count;
15
+ this._secure = secure;
16
+ this._previewDuration = previewDuration;
17
+ this._cells = [];
18
+ this._focusedCellIndex = 0;
19
+ this._numeric = numeric;
20
+ this._onInput = onInput;
21
+ this._initCells();
22
+ }
23
+ _initCells = () => {
24
+ for (let i = 0; i < this._count; i++) {
25
+ const input = document.createElement('input');
26
+ input.classList.add('pin-input__input');
27
+ if (this._numeric) {
28
+ input.setAttribute('inputmode', 'numeric');
29
+ }
30
+ this._cells.push(input);
31
+ this._container.appendChild(input);
32
+ input.addEventListener('input', (_event) => {
33
+ const value = input.value;
34
+ this._onCellChanged(i, value);
35
+ });
36
+ const onFocus = () => {
37
+ this._focusedCellIndex = i;
38
+ input.classList.toggle('pin-input__input--focused', true);
39
+ setTimeout(() => {
40
+ input.setSelectionRange(0, input.value.length);
41
+ });
42
+ };
43
+ input.addEventListener('focus', onFocus);
44
+ input.addEventListener('click', onFocus);
45
+ input.addEventListener('blur', () => {
46
+ input.classList.toggle('pin-input__input--focused', false);
47
+ });
48
+ input.addEventListener('keydown', (event) => {
49
+ this._onKeyDown(event, i);
50
+ if (!['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Backspace', 'Delete'].includes(event.key)) {
51
+ this._cells[i].setAttribute('type', 'text');
52
+ }
53
+ });
54
+ }
55
+ };
56
+ _onCellChanged(index, newVal) {
57
+ if (!this._isTheCellValid(newVal)) {
58
+ this._cells[index].classList.remove('pin-input__input--filled');
59
+ this._cells[index].value = '';
60
+ this._notifyValueChanged();
61
+ return;
62
+ }
63
+ this._cells[index].classList.add('pin-input__input--filled');
64
+ if (this._secure && this._previewDuration) {
65
+ setTimeout(() => {
66
+ this._cells[index].setAttribute('type', 'password');
67
+ }, this._previewDuration);
68
+ }
69
+ this._notifyValueChanged();
70
+ this._focusNextCell();
71
+ }
72
+ _onKeyDown(e, index) {
73
+ switch (e.key) {
74
+ case 'ArrowLeft':
75
+ this._focusPreviousCell();
76
+ break;
77
+ case 'ArrowRight':
78
+ this._focusNextCell();
79
+ break;
80
+ case 'Backspace':
81
+ if (!this._cells[index].value.length) {
82
+ this._focusPreviousCell();
83
+ e.preventDefault();
84
+ }
85
+ }
86
+ }
87
+ _focusPreviousCell() {
88
+ if (!this._focusedCellIndex) {
89
+ return;
90
+ }
91
+ this._focusCellByIndex(this._focusedCellIndex - 1);
92
+ }
93
+ _focusNextCell() {
94
+ if (this._focusedCellIndex === this._cells.length - 1) {
95
+ return;
96
+ }
97
+ this._focusCellByIndex(this._focusedCellIndex + 1);
98
+ }
99
+ _focusCellByIndex(index = 0) {
100
+ const el = this._cells[index];
101
+ el.focus();
102
+ this._focusedCellIndex = index;
103
+ }
104
+ _isTheCellValid(cell) {
105
+ return this._numeric ? !!cell.match(CELL_REGEXP) : cell.length <= 1;
106
+ }
107
+ _notifyValueChanged() {
108
+ let value = '';
109
+ this._cells.forEach((p) => {
110
+ value += p.value;
111
+ });
112
+ this._onInput(value);
113
+ }
114
+ }
@@ -0,0 +1,55 @@
1
+ <script lang="ts">import { InputEmojiPicker, inputEmojiPickerContainer } from '../input-emoji-picker';
2
+ import { default as TinymceInput } from './tinymce-input.svelte';
3
+ let { value, options = undefined, debounce = 0, placeholder = undefined, autofocus = false, disabled = false, rows = 0, emoji = false, borderless = false, on } = $props();
4
+ let insertEmojiToTinymce = $state(null);
5
+ const onEmojiSelected = (emojiChar) => {
6
+ insertEmojiToTinymce?.(emojiChar);
7
+ };
8
+ </script>
9
+
10
+ <div class="rich-text-input" class:rich-text-input--has-emoji={emoji} use:inputEmojiPickerContainer>
11
+ <TinymceInput
12
+ value={value}
13
+ options={options}
14
+ placeholder={placeholder}
15
+ disabled={disabled}
16
+ borderless={borderless}
17
+ rows={rows}
18
+ autofocus={autofocus || ''}
19
+ debounce={debounce}
20
+ on={{
21
+ change: (e) => on?.change?.(e),
22
+ mounted: (e) => (insertEmojiToTinymce = e.insertEmoji)
23
+ }} />
24
+ {#if emoji}
25
+ <InputEmojiPicker on={{ select: onEmojiSelected }} />
26
+ {/if}
27
+ </div>
28
+
29
+ <!--
30
+ @component
31
+ Rich text editor powered by TinyMCE with optional emoji picker support.
32
+
33
+ ### CSS Custom Properties
34
+ | Property | Description | Default |
35
+ |---|---|---|
36
+ | `--sc-kit--rich-text-input--root--font-size` | Root font size for em scaling | `1rem` |
37
+ | `--sc-kit--rich-text-input--width` | Container width | `100%` |
38
+ | `--sc-kit--rich-text-input--padding--block` | Block (vertical) padding | `0.5em` |
39
+ | `--sc-kit--rich-text-input--padding--inline` | Inline (horizontal) padding | `0.5em` |
40
+ | `--sc-kit--rich-text-input--accent-color` | Focus accent color | light-dark primary-500/primary-400 |
41
+ | `--sc-kit--rich-text-input--background` | Background color | light-dark white/gray-900 |
42
+ | `--sc-kit--rich-text-input--background--disabled` | Disabled background | light-dark neutral-50/neutral-800 |
43
+ | `--sc-kit--rich-text-input--border-color` | Border color | light-dark neutral-300/neutral-600 |
44
+ | `--sc-kit--rich-text-input--border-radius` | Border radius | `0.25em` |
45
+ | `--sc-kit--rich-text-input--text--font-size` | Text font size | `0.875em` |
46
+ | `--sc-kit--rich-text-input--text--color` | Text color | light-dark gray-800/white |
47
+ | `--sc-kit--rich-text-input--placeholder--color` | Placeholder color | inherited from border |
48
+ -->
49
+
50
+ <style>.rich-text-input {
51
+ position: relative;
52
+ }
53
+ .rich-text-input--has-emoji {
54
+ --_--input--padding-right: 1.5em;
55
+ }</style>
@@ -0,0 +1,43 @@
1
+ import type { RichTextInputOptions } from './types';
2
+ type Props = {
3
+ /** HTML content */
4
+ value: string | null | undefined;
5
+ /** TinyMCE configuration options */
6
+ options?: RichTextInputOptions;
7
+ /** Debounce delay in ms for change events @default 0 */
8
+ debounce?: number;
9
+ placeholder?: string;
10
+ autofocus?: boolean;
11
+ disabled?: boolean;
12
+ /** Minimum visible rows @default 0 */
13
+ rows?: number;
14
+ /** Show emoji picker trigger */
15
+ emoji?: boolean;
16
+ /** Remove border, background, and accent shadow */
17
+ borderless?: boolean;
18
+ on: {
19
+ change: (value: string) => void;
20
+ };
21
+ };
22
+ /**
23
+ * Rich text editor powered by TinyMCE with optional emoji picker support.
24
+ *
25
+ * ### CSS Custom Properties
26
+ * | Property | Description | Default |
27
+ * |---|---|---|
28
+ * | `--sc-kit--rich-text-input--root--font-size` | Root font size for em scaling | `1rem` |
29
+ * | `--sc-kit--rich-text-input--width` | Container width | `100%` |
30
+ * | `--sc-kit--rich-text-input--padding--block` | Block (vertical) padding | `0.5em` |
31
+ * | `--sc-kit--rich-text-input--padding--inline` | Inline (horizontal) padding | `0.5em` |
32
+ * | `--sc-kit--rich-text-input--accent-color` | Focus accent color | light-dark primary-500/primary-400 |
33
+ * | `--sc-kit--rich-text-input--background` | Background color | light-dark white/gray-900 |
34
+ * | `--sc-kit--rich-text-input--background--disabled` | Disabled background | light-dark neutral-50/neutral-800 |
35
+ * | `--sc-kit--rich-text-input--border-color` | Border color | light-dark neutral-300/neutral-600 |
36
+ * | `--sc-kit--rich-text-input--border-radius` | Border radius | `0.25em` |
37
+ * | `--sc-kit--rich-text-input--text--font-size` | Text font size | `0.875em` |
38
+ * | `--sc-kit--rich-text-input--text--color` | Text color | light-dark gray-800/white |
39
+ * | `--sc-kit--rich-text-input--placeholder--color` | Placeholder color | inherited from border |
40
+ */
41
+ declare const Cmp: import("svelte").Component<Props, {}, "">;
42
+ type Cmp = ReturnType<typeof Cmp>;
43
+ export default Cmp;
@@ -0,0 +1,2 @@
1
+ export { default as RichTextInput } from './cmp.rich-text-input.svelte';
2
+ export type { RichTextInputOptions } from './types';
@@ -0,0 +1 @@
1
+ export { default as RichTextInput } from './cmp.rich-text-input.svelte';
@@ -0,0 +1,12 @@
1
+ export declare class RichTextInputLocalization {
2
+ get linkButton(): string;
3
+ get linkDialogTitle(): string;
4
+ get linkUrlLabel(): string;
5
+ get linkTextLabel(): string;
6
+ get linkTargetLabel(): string;
7
+ get linkTargetSelf(): string;
8
+ get linkTargetBlank(): string;
9
+ get linkCancelButton(): string;
10
+ get linkSaveButton(): string;
11
+ get linkUrlInvalid(): string;
12
+ }
@@ -0,0 +1,48 @@
1
+ import { AppLocale } from '../../../core/locale';
2
+ const loc = {
3
+ linkButton: { en: 'Insert/Edit Link', no: 'Sett inn/rediger lenke' },
4
+ linkDialogTitle: { en: 'Insert/Edit Link', no: 'Sett inn/rediger lenke' },
5
+ linkUrlLabel: { en: 'URL', no: 'URL' },
6
+ linkTextLabel: { en: 'Text to display', no: 'Tekst a vise' },
7
+ linkTargetLabel: { en: 'Open link in...', no: 'Apne lenke i...' },
8
+ linkTargetSelf: { en: 'Current window', no: 'Gjeldende vindu' },
9
+ linkTargetBlank: { en: 'New window', no: 'Nytt vindu' },
10
+ linkCancelButton: { en: 'Cancel', no: 'Avbryt' },
11
+ linkSaveButton: { en: 'Save', no: 'Lagre' },
12
+ linkUrlInvalid: {
13
+ en: 'The URL you entered is not valid. Please enter a valid URL.',
14
+ no: 'URLen du skrev inn er ikke gyldig. Vennligst skriv inn en gyldig URL.'
15
+ }
16
+ };
17
+ export class RichTextInputLocalization {
18
+ get linkButton() {
19
+ return loc.linkButton[AppLocale.current];
20
+ }
21
+ get linkDialogTitle() {
22
+ return loc.linkDialogTitle[AppLocale.current];
23
+ }
24
+ get linkUrlLabel() {
25
+ return loc.linkUrlLabel[AppLocale.current];
26
+ }
27
+ get linkTextLabel() {
28
+ return loc.linkTextLabel[AppLocale.current];
29
+ }
30
+ get linkTargetLabel() {
31
+ return loc.linkTargetLabel[AppLocale.current];
32
+ }
33
+ get linkTargetSelf() {
34
+ return loc.linkTargetSelf[AppLocale.current];
35
+ }
36
+ get linkTargetBlank() {
37
+ return loc.linkTargetBlank[AppLocale.current];
38
+ }
39
+ get linkCancelButton() {
40
+ return loc.linkCancelButton[AppLocale.current];
41
+ }
42
+ get linkSaveButton() {
43
+ return loc.linkSaveButton[AppLocale.current];
44
+ }
45
+ get linkUrlInvalid() {
46
+ return loc.linkUrlInvalid[AppLocale.current];
47
+ }
48
+ }
@@ -0,0 +1,250 @@
1
+ <script lang="ts">import { AppTheme } from '../../../core/theme';
2
+ import { DomHelper, HtmlHelper, Utils } from '../../../core/utils';
3
+ import { HtmlBlock } from '../../html-block';
4
+ import { RichTextInputLocalization } from './rich-text-input-localization';
5
+ import { addValidatedLinkButton } from './validated-link-button';
6
+ import { onDestroy, onMount } from 'svelte';
7
+ let { value, options = undefined, debounce = 0, placeholder = undefined, disabled = false, borderless = false, autofocus = 'false', modelEvents = 'change input undo redo', rows = 0, conf = undefined, on } = $props();
8
+ const id = `ti-${crypto.randomUUID()}`;
9
+ const fixedToolbarId = `fixed-toolbar-${id}`;
10
+ let element;
11
+ let editorRef = $state.raw(null);
12
+ let everFocused = false;
13
+ let lastVal = $state.raw(undefined);
14
+ let tinymce;
15
+ $effect(() => {
16
+ if (editorRef && lastVal !== value) {
17
+ setEditorValue(editorRef, value ?? '');
18
+ }
19
+ });
20
+ $effect(() => {
21
+ if (editorRef) {
22
+ editorRef.mode.set(disabled ? 'readonly' : 'design');
23
+ }
24
+ });
25
+ onMount(async () => {
26
+ const loc = new RichTextInputLocalization();
27
+ on?.mounted?.({
28
+ insertEmoji: (emoji) => {
29
+ if (!everFocused && editorRef) {
30
+ editorRef.selection.select(editorRef.getBody(), true);
31
+ editorRef.selection.collapse(false);
32
+ }
33
+ return new Promise((resolve) => {
34
+ setTimeout(() => {
35
+ editorRef?.execCommand('mceInsertContent', false, emoji);
36
+ resolve(true);
37
+ });
38
+ });
39
+ }
40
+ });
41
+ if (debounce) {
42
+ applyChange = Utils.debounce(applyChange, debounce);
43
+ }
44
+ const { default: tmi } = await import('tinymce');
45
+ await Promise.all([
46
+ import('tinymce/themes/silver'),
47
+ AppTheme.isDarkMode ? import('tinymce/skins/ui/oxide-dark/skin.min.css') : import('tinymce/skins/ui/oxide/skin.min.css'),
48
+ import('tinymce/skins/ui/oxide/content.inline.min.css'),
49
+ import('tinymce/icons/default'),
50
+ import('tinymce/plugins/link'),
51
+ import('tinymce/plugins/lists'),
52
+ import('tinymce/plugins/paste'),
53
+ import('tinymce/plugins/advlist'),
54
+ import('tinymce/plugins/table')
55
+ ]);
56
+ tinymce = tmi;
57
+ await tinymce.init({
58
+ target: element,
59
+ inline: true,
60
+ skin: false,
61
+ fixed_toolbar_container: `#${options?.fixedToolbarId || fixedToolbarId}`,
62
+ menubar: false,
63
+ auto_focus: autofocus,
64
+ placeholder: placeholder,
65
+ readonly: disabled,
66
+ toolbar: 'undo redo | formatselect | fontselect fontsizeselect | bold italic underline | forecolor backcolor | alignleft aligncenter alignright | bullist numlist outdent indent | lineheight | table | validatedlink',
67
+ toolbar_persist: options?.persistToolbar,
68
+ plugins: ['lists', 'paste', 'advlist', 'table'],
69
+ advlist_bullet_styles: 'default,circle,square,disc',
70
+ advlist_number_styles: 'default,lower-alpha,lower-roman,upper-alpha,upper-roman',
71
+ lineheight_formats: '1 1.15 1.5 2 2.5 3',
72
+ font_formats: 'Arial=arial,helvetica,sans-serif; Comic Sans MS=comic sans ms,cursive; Courier New=courier new,courier,monospace; Georgia=georgia,palatino; Helvetica=helvetica; Impact=impact,charcoal,sans-serif; Lucida Console=lucida console,monaco,monospace; Palatino Linotype=palatino linotype,book antiqua,palatino,serif; Tahoma=tahoma,geneva,sans-serif; Times New Roman=times new roman,times,serif; Trebuchet MS=trebuchet ms,sans-serif; Verdana=verdana,geneva,sans-serif',
73
+ fontsize_formats: '8px 10px 12px 14px 16px 18px 24px 36px 48px',
74
+ block_formats: 'Paragraph=p; Heading 1=h1; Heading 2=h2; Heading 3=h3; Heading 4=h4; Heading 5=h5; Heading 6=h6',
75
+ contextmenu: 'link',
76
+ paste_data_images: false,
77
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
78
+ paste_preprocess: (_, args) => {
79
+ if (!args.wordContent && !args.internal) {
80
+ const text = HtmlHelper.stripHtml(args.content);
81
+ const lines = text.split('\n');
82
+ args.content = '';
83
+ lines.forEach((line) => {
84
+ if (!line) {
85
+ line = '&nbsp;';
86
+ }
87
+ args.content += `<p>${line}</p>`;
88
+ });
89
+ }
90
+ },
91
+ ...conf,
92
+ setup: (editor) => {
93
+ editorRef = editor;
94
+ editor.on('init', () => {
95
+ if (rows) {
96
+ editor.bodyElement.style.minHeight = DomHelper.calcHeightBasedOnRows(editor.bodyElement, rows);
97
+ }
98
+ editor.on(modelEvents, () => applyChange(editor));
99
+ });
100
+ editor.on('blur', () => {
101
+ HtmlHelper.clearSelection();
102
+ notifyFocused(false);
103
+ });
104
+ editor.on('focus', () => {
105
+ everFocused = true;
106
+ notifyFocused(true);
107
+ });
108
+ addValidatedLinkButton(editor, loc);
109
+ if (typeof conf?.setup === 'function') {
110
+ conf.setup(editor);
111
+ }
112
+ }
113
+ });
114
+ });
115
+ onDestroy(() => {
116
+ if (editorRef) {
117
+ tinymce.remove(editorRef);
118
+ }
119
+ editorRef = null;
120
+ });
121
+ const notifyFocused = (foc) => {
122
+ on?.focused?.(foc);
123
+ };
124
+ let applyChange = (editor) => {
125
+ lastVal = editor.getContent();
126
+ if (lastVal !== value) {
127
+ value = lastVal;
128
+ }
129
+ on?.change?.(lastVal);
130
+ };
131
+ const setEditorValue = (editor, val) => {
132
+ editor.setContent(val);
133
+ lastVal = val;
134
+ };
135
+ </script>
136
+
137
+ <div class="tinymce-input" class:tinymce-input--disabled={disabled} class:tinymce-input--borderless={borderless}>
138
+ <span class="tinymce-input__toolbar" id={fixedToolbarId}></span>
139
+ <HtmlBlock>
140
+ <div class="tinymce-input__input" id={id} bind:this={element}></div>
141
+ </HtmlBlock>
142
+ </div>
143
+
144
+ <style>.tinymce-input {
145
+ --_tinymce-input--padding--block: var(--sc-kit--rich-text-input--padding--block, 0.5em);
146
+ --_tinymce-input--placeholder--color: var(--sc-kit--rich-text-input--placeholder--color, var(--_input--border-color));
147
+ --_--input--root--font-size: var(--sc-kit--rich-text-input--root--font-size);
148
+ --_--input--width: var(--sc-kit--rich-text-input--width);
149
+ --_--input--background: var(--sc-kit--rich-text-input--background);
150
+ --_--input--background--disabled: var(--sc-kit--rich-text-input--background--disabled);
151
+ --_--input--border-color: var(--sc-kit--rich-text-input--border-color);
152
+ --_--input--border-radius: var(--sc-kit--rich-text-input--border-radius);
153
+ --_--input--text--font-size: var(--sc-kit--rich-text-input--text--font-size);
154
+ --_--input--text--color: var(--sc-kit--rich-text-input--text--color);
155
+ --_--input--placeholder--color: var(--_tinymce-input--placeholder--color);
156
+ --_--input--accent-color: var(--sc-kit--rich-text-input--accent-color);
157
+ --_--input--padding--inline: var(--sc-kit--rich-text-input--padding--inline);
158
+ --_--input--padding--block: var(--_tinymce-input--padding--block);
159
+ --_input--root--font-size: var(--_--input--root--font-size, 1rem);
160
+ --_input--height: var(--_--input--height, 2em);
161
+ --_input--width: var(--_--input--width, 100%);
162
+ --_input--background: var(--_--input--background, light-dark(#ffffff, #1c1c1c));
163
+ --_input--background--disabled: var(--_--input--background--disabled, light-dark(#f9fafb, #1f2937));
164
+ --_input--border-color: var(--_--input--border-color, light-dark(#d1d5db, #4b5563));
165
+ --_input--border-radius: var(--_--input--border-radius, 0.25em);
166
+ --_input--icon--size: var(--_--input--icon--size, 1em);
167
+ --_input--icon--color: var(--_--input--icon--color, var(--_input--border-color));
168
+ --_input--text--font-size: var(--_--input--text--font-size, 0.875em);
169
+ --_input--text--color: var(--_--input--text--color, light-dark(#2e2e2e, #ffffff));
170
+ --_input--placeholder--color: var(--_--input--placeholder--color, var(--_input--border-color));
171
+ --_input--accent-color: var(--_--input--accent-color, light-dark(#144ab0, #5a8dec));
172
+ --_input--padding--inline: var(--_--input--padding--inline, 0.5em);
173
+ --_input--padding--block: var(--_--input--padding--block, 0);
174
+ --_input--padding-top: var(--_--input--padding-top, var(--_input--padding--block));
175
+ --_input--padding-right: var(--_--input--padding-right, var(--_input--padding--inline));
176
+ --_input--padding-bottom: var(--_--input--padding-bottom, var(--_input--padding--block));
177
+ --_input--padding-left: var(--_--input--padding-left, var(--_input--padding--inline));
178
+ font-size: var(--_input--root--font-size);
179
+ height: var(--_input--height);
180
+ color: var(--_input--text--color);
181
+ border: 1px solid var(--_input--border-color);
182
+ border-radius: var(--_input--border-radius);
183
+ width: var(--_input--width);
184
+ min-width: var(--_input--width);
185
+ background: var(--_input--background);
186
+ padding-top: var(--_input--padding-top);
187
+ padding-right: var(--_input--padding-right);
188
+ padding-bottom: var(--_input--padding-bottom);
189
+ padding-left: var(--_input--padding-left);
190
+ --_input--default-shadow-color: transparent;
191
+ --_input--accent-shadow: var(--_input--explicit-shadow-color, var(--_input--default-shadow-color));
192
+ position: relative;
193
+ box-shadow: inset 0 -0.13em var(--_input--accent-shadow);
194
+ transition: box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1);
195
+ }
196
+ .tinymce-input:focus, .tinymce-input:focus-within {
197
+ --_input--default-shadow-color: var(--_input--accent-color);
198
+ }
199
+ .tinymce-input--disabled {
200
+ background-color: var(--_input--background--disabled);
201
+ cursor: default;
202
+ }
203
+ .tinymce-input {
204
+ cursor: text;
205
+ min-height: 2em;
206
+ height: auto;
207
+ display: block;
208
+ position: relative;
209
+ }
210
+ .tinymce-input--borderless {
211
+ --_input--border-color: transparent;
212
+ --_input--background: transparent;
213
+ --_input--background--disabled: transparent;
214
+ --_input--accent-color: transparent;
215
+ --_input--explicit-shadow-color: transparent;
216
+ --_input--height: auto;
217
+ }
218
+ .tinymce-input :global(.tox-tinymce-inline .tox-editor-header) {
219
+ border-radius: 0.375em !important;
220
+ overflow: hidden;
221
+ }
222
+ .tinymce-input__input {
223
+ font-size: var(--_input--text--font-size);
224
+ color: var(--_input--text--color);
225
+ }
226
+ .tinymce-input__input::placeholder {
227
+ color: var(--_input--placeholder--color);
228
+ }
229
+ .tinymce-input__input:-webkit-autofill {
230
+ -webkit-text-fill-color: var(--_input--text--color) !important;
231
+ }
232
+ .tinymce-input__input {
233
+ min-height: 100%;
234
+ background: transparent;
235
+ }
236
+ .tinymce-input__input::before {
237
+ color: var(--_tinymce-input--placeholder--color) !important;
238
+ }
239
+ .tinymce-input__input:focus {
240
+ outline: none;
241
+ }
242
+ .tinymce-input__toolbar {
243
+ position: absolute;
244
+ left: 0;
245
+ bottom: 100%;
246
+ }
247
+
248
+ :global(.tox .tox-collection__item-label) {
249
+ white-space: nowrap;
250
+ }</style>
@@ -0,0 +1,25 @@
1
+ import type { RichTextInputOptions } from './types';
2
+ import type { RawEditorSettings } from 'tinymce';
3
+ type Props = {
4
+ value: string | null | undefined;
5
+ options?: RichTextInputOptions;
6
+ debounce?: number;
7
+ placeholder?: string;
8
+ disabled?: boolean;
9
+ /** Remove border, background, and accent shadow */
10
+ borderless?: boolean;
11
+ autofocus?: true | string;
12
+ modelEvents?: string;
13
+ rows?: number;
14
+ conf?: RawEditorSettings;
15
+ on: {
16
+ change: (value: string) => void;
17
+ focused?: (focused: boolean) => void;
18
+ mounted?: (data: {
19
+ insertEmoji: (emoji: string) => void;
20
+ }) => void;
21
+ };
22
+ };
23
+ declare const TinymceInput: import("svelte").Component<Props, {}, "">;
24
+ type TinymceInput = ReturnType<typeof TinymceInput>;
25
+ export default TinymceInput;
@@ -0,0 +1,7 @@
1
+ declare module 'tinymce/themes/silver';
2
+ declare module 'tinymce/icons/default';
3
+ declare module 'tinymce/plugins/link';
4
+ declare module 'tinymce/plugins/lists';
5
+ declare module 'tinymce/plugins/paste';
6
+ declare module 'tinymce/plugins/advlist';
7
+ declare module 'tinymce/plugins/table';
@@ -0,0 +1,4 @@
1
+ export type RichTextInputOptions = {
2
+ persistToolbar?: boolean;
3
+ fixedToolbarId?: string;
4
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import type { RichTextInputLocalization } from './rich-text-input-localization';
2
+ import type { Editor } from 'tinymce';
3
+ export declare const addValidatedLinkButton: (editor: Editor, loc: RichTextInputLocalization) => void;
@@ -0,0 +1,78 @@
1
+ import { validateHref } from '../../../core/utils';
2
+ const DEFAULT_TARGET = '_blank';
3
+ export const addValidatedLinkButton = (editor, loc) => {
4
+ editor.ui.registry.addButton('validatedlink', {
5
+ icon: 'link',
6
+ tooltip: loc.linkButton,
7
+ onAction: () => {
8
+ const linkNode = editor.dom.getParent(editor.selection.getNode(), 'a');
9
+ let initialUrl = '';
10
+ let initialText = '';
11
+ let initialTarget = DEFAULT_TARGET;
12
+ if (linkNode) {
13
+ initialUrl = linkNode.getAttribute('href') ?? '';
14
+ initialText = linkNode.textContent ?? '';
15
+ initialTarget = linkNode.getAttribute('target') ?? DEFAULT_TARGET;
16
+ }
17
+ else {
18
+ initialText = editor.selection.getContent({ format: 'text' }) ?? '';
19
+ }
20
+ let userChangedText = false;
21
+ editor.windowManager.open({
22
+ title: loc.linkDialogTitle,
23
+ body: {
24
+ type: 'panel',
25
+ items: [
26
+ { type: 'input', name: 'url', label: loc.linkUrlLabel },
27
+ { type: 'input', name: 'text', label: loc.linkTextLabel },
28
+ {
29
+ type: 'listbox',
30
+ name: 'openIn',
31
+ label: loc.linkTargetLabel,
32
+ items: [
33
+ { text: loc.linkTargetSelf, value: '_self' },
34
+ { text: loc.linkTargetBlank, value: '_blank' }
35
+ ]
36
+ }
37
+ ]
38
+ },
39
+ initialData: {
40
+ url: initialUrl,
41
+ text: initialText,
42
+ openIn: initialTarget
43
+ },
44
+ buttons: [
45
+ { type: 'cancel', text: loc.linkCancelButton },
46
+ { type: 'submit', text: loc.linkSaveButton, primary: true }
47
+ ],
48
+ onChange: (api, details) => {
49
+ if (details.name === 'text' && !userChangedText) {
50
+ userChangedText = true;
51
+ }
52
+ if (details.name === 'url') {
53
+ const data = api.getData();
54
+ if (!userChangedText && !initialText) {
55
+ api.setData({ text: data.url });
56
+ }
57
+ }
58
+ },
59
+ onSubmit: (api) => {
60
+ const data = api.getData();
61
+ if (!validateHref(data.url, { allowRelative: 'prefixed' }).valid) {
62
+ editor.windowManager.alert(loc.linkUrlInvalid);
63
+ return;
64
+ }
65
+ if (linkNode) {
66
+ editor.dom.setAttrib(linkNode, 'href', data.url);
67
+ editor.dom.setAttrib(linkNode, 'target', data.openIn);
68
+ linkNode.textContent = data.text ?? data.url;
69
+ }
70
+ else {
71
+ editor.insertContent(`<a href="${data.url}" target="${data.openIn}">${data.text ?? data.url}</a>`);
72
+ }
73
+ api.close();
74
+ }
75
+ });
76
+ }
77
+ });
78
+ };