@widgetic/editor 4.0.10 → 4.1.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.
Files changed (84) hide show
  1. package/README.md +242 -0
  2. package/dist/components/WidgetPreview.svelte +123 -86
  3. package/dist/components/WidgetPreview.svelte.d.ts +8 -0
  4. package/dist/components/color-select/ColorSelect.svelte +4 -4
  5. package/dist/components/editors/CompositionEditorApp.svelte +562 -0
  6. package/dist/components/editors/CompositionEditorApp.svelte.d.ts +95 -0
  7. package/dist/components/editors/EmbedEditor.svelte +33 -22
  8. package/dist/components/editors/EmbedEditor.svelte.d.ts +1 -1
  9. package/dist/components/editors/PropsEditor.svelte +267 -850
  10. package/dist/components/editors/PropsEditor.svelte.d.ts +10 -3
  11. package/dist/components/ic/AngleInputController.svelte +1 -1
  12. package/dist/components/ic/BrowserInputController.svelte +58 -31
  13. package/dist/components/ic/ColorPickerInputController.svelte +55 -21
  14. package/dist/components/ic/DateFieldInputController.svelte +34 -14
  15. package/dist/components/ic/DatePickerInputController.svelte +37 -21
  16. package/dist/components/ic/DropdownInputController.svelte +21 -9
  17. package/dist/components/ic/DropdownInputController.svelte.d.ts +1 -1
  18. package/dist/components/ic/FixedPositionInputController.svelte +12 -6
  19. package/dist/components/ic/FontInputController.svelte +100 -76
  20. package/dist/components/ic/InputControllerHeader.svelte +16 -9
  21. package/dist/components/ic/NumberControls.svelte +4 -4
  22. package/dist/components/ic/NumberInputController.svelte +6 -4
  23. package/dist/components/ic/PositionInputController.svelte +23 -15
  24. package/dist/components/ic/RangeCalendarInputController.svelte +36 -20
  25. package/dist/components/ic/RoundedCornersInputController.svelte +39 -26
  26. package/dist/components/ic/SliderInputController.svelte +7 -2
  27. package/dist/components/ic/SwitchInputController.svelte +12 -3
  28. package/dist/components/ic/TagsInputController.svelte +19 -4
  29. package/dist/components/ic/TextAreaInputController.svelte +79 -51
  30. package/dist/components/ic/TextAreaPreviewInputController.svelte +27 -72
  31. package/dist/components/ic/TextInputController.svelte +17 -1
  32. package/dist/components/ic/TimeInputController.svelte +11 -4
  33. package/dist/components/ic/VectorInputController.svelte +101 -70
  34. package/dist/components/ic/shared/ItemGrid.svelte +12 -4
  35. package/dist/components/ic/shared/RecentItemsSection.svelte +16 -11
  36. package/dist/components/ic/shared/RecentItemsSection.svelte.d.ts +1 -1
  37. package/dist/composition/compositionApi.d.ts +95 -0
  38. package/dist/composition/compositionApi.js +289 -0
  39. package/dist/composition/compositionEditorController.d.ts +263 -0
  40. package/dist/composition/compositionEditorController.js +746 -0
  41. package/dist/composition/compositionHelpers.d.ts +74 -0
  42. package/dist/composition/compositionHelpers.js +405 -0
  43. package/dist/i18n/index.d.ts +4 -0
  44. package/dist/i18n/index.js +4 -0
  45. package/dist/i18n/labels.d.ts +15 -0
  46. package/dist/i18n/labels.js +57 -0
  47. package/dist/i18n/language.d.ts +228 -0
  48. package/dist/i18n/language.js +34 -0
  49. package/dist/i18n/languageKit.d.ts +54 -0
  50. package/dist/i18n/languageKit.js +102 -0
  51. package/dist/i18n/messages.d.ts +241 -0
  52. package/dist/i18n/messages.js +253 -0
  53. package/dist/i18n/translate.d.ts +6 -0
  54. package/dist/i18n/translate.js +8 -0
  55. package/dist/index.d.ts +4 -0
  56. package/dist/index.js +8 -0
  57. package/dist/mediaLibraryContext.d.ts +10 -0
  58. package/dist/mediaLibraryContext.js +30 -0
  59. package/dist/utils/FontManager.d.ts +21 -5
  60. package/dist/utils/FontManager.js +62 -67
  61. package/dist/utils/VectorManager.d.ts +0 -1
  62. package/dist/utils/VectorManager.js +10 -12
  63. package/dist/utils/dockPlacement.d.ts +12 -0
  64. package/dist/utils/dockPlacement.js +13 -0
  65. package/dist/utils/editorStorage.d.ts +23 -0
  66. package/dist/utils/editorStorage.js +95 -0
  67. package/dist/utils/fieldDirection.d.ts +13 -0
  68. package/dist/utils/fieldDirection.js +25 -0
  69. package/dist/utils/fontUtils.js +5 -3
  70. package/dist/utils/htmlSafety.d.ts +21 -0
  71. package/dist/utils/htmlSafety.js +38 -0
  72. package/dist/utils/itemReorder.d.ts +90 -0
  73. package/dist/utils/itemReorder.js +354 -0
  74. package/dist/utils/recentColors.js +4 -2
  75. package/dist/utils/richTextPreview.d.ts +38 -0
  76. package/dist/utils/richTextPreview.js +207 -0
  77. package/dist/utils/scrollActivity.d.ts +14 -0
  78. package/dist/utils/scrollActivity.js +63 -0
  79. package/dist/utils/shadowDom.d.ts +67 -0
  80. package/dist/utils/shadowDom.js +211 -0
  81. package/dist/utils/vectorUtils.js +5 -3
  82. package/dist/utils/visibilityCondition.d.ts +40 -0
  83. package/dist/utils/visibilityCondition.js +535 -0
  84. package/package.json +7 -6
@@ -0,0 +1,228 @@
1
+ import { SHAD_EDITOR_MESSAGES } from '@widgetic/design-system/shad-editor';
2
+ export type { EditorCatalogs, EditorLanguage, EditorLanguageHandle } from './languageKit.js';
3
+ /**
4
+ * The language of an editor on a page. The editor has two packages inside it (the File Browser that picks a file, and
5
+ * the rich text editor of the text boxes), each with its own catalog; a page gives ONE flat `messages` object and the
6
+ * editor hands each package the keys that are its own. See `languageKit.ts` for how, and for what a test can swap.
7
+ */
8
+ /** The catalogs of the editor and of the packages inside it. A key belongs to one of them. */
9
+ export declare const EDITOR_CATALOGS: {
10
+ readonly editor: {
11
+ readonly 'editor.app.loading': "Loading editor…";
12
+ readonly 'editor.app.tryAgain': "Try again";
13
+ readonly 'editor.app.retry': "Retry";
14
+ readonly 'editor.failure.widgetNotPublished': "Publish the widget before editing a composition.";
15
+ readonly 'editor.failure.sessionEndedWhileSaving': "Your session has ended, so your changes could not be saved. Reload the page to continue.";
16
+ readonly 'editor.failure.sessionEnded': "Your session has ended. Reload the page to continue.";
17
+ readonly 'editor.failure.compositionNotFound': "This composition could not be found.";
18
+ readonly 'editor.failure.noAccess': "You do not have access to this composition.";
19
+ readonly 'editor.failure.noWidget': "This composition has no widget.";
20
+ readonly 'editor.failure.widgetNotFound': "The widget this composition is made with could not be found.";
21
+ readonly 'editor.failure.saveNeedsConnection': "Your changes could not be saved. Check the connection and try again.";
22
+ readonly 'editor.failure.loadNeedsConnection': "The editor could not be loaded. Check the connection and try again.";
23
+ readonly 'editor.failure.saveFailed': "Your changes could not be saved.";
24
+ readonly 'editor.failure.loadFailed': "The editor could not be loaded.";
25
+ readonly 'editor.failure.settingsLoadFailed': "The editor settings could not be loaded. Try again.";
26
+ readonly 'editor.failure.noSettings': "No editable settings found. Generate and publish the widget first.";
27
+ readonly 'editor.failure.errorCode': "Error code: {code}";
28
+ readonly 'editor.props.title': "Edit Properties";
29
+ readonly 'editor.props.designTab': "Design";
30
+ readonly 'editor.props.contentTab': "Content";
31
+ readonly 'editor.props.contentCard': "Content";
32
+ readonly 'editor.props.save': "Save";
33
+ readonly 'editor.props.saving': "Saving";
34
+ readonly 'editor.props.noConfiguration': "No schema configuration available";
35
+ readonly 'editor.props.noDesignSchema': "No design schema available";
36
+ readonly 'editor.props.noSchemaData': "No schema data available";
37
+ readonly 'editor.props.noComponents': "No components defined in schema";
38
+ readonly 'editor.props.noProperties': "No properties available";
39
+ readonly 'editor.props.addItem': "Add new";
40
+ readonly 'editor.props.deleteItem': "Delete item";
41
+ readonly 'editor.embed.title': "Edit Options";
42
+ readonly 'editor.embed.loading': "Loading...";
43
+ readonly 'editor.embed.noCode': "No embed code available";
44
+ readonly 'editor.embed.codeHeader': "Embed code";
45
+ readonly 'editor.embed.headerArea': "Embed code header area";
46
+ readonly 'editor.embed.readMore': "Read more about embedding";
47
+ readonly 'editor.embed.copy': "Copy to clipboard";
48
+ readonly 'editor.embed.copied': "Copied to clipboard";
49
+ readonly 'editor.embed.copiedHint': "Paste it anywhere on your site.";
50
+ readonly 'editor.embed.copiedDone': "Content copied successfully.";
51
+ readonly 'editor.embed.copyFailed': "Copy failed";
52
+ readonly 'editor.embed.copyFailedHint': "Please try manual selection";
53
+ readonly 'editor.preview.title': "Preview";
54
+ readonly 'editor.preview.dragHandle': "Drag preview";
55
+ readonly 'editor.preview.desktop': "Desktop preview";
56
+ readonly 'editor.preview.mobile': "Mobile preview";
57
+ readonly 'editor.preview.widthShort': "W";
58
+ readonly 'editor.preview.heightShort': "H";
59
+ readonly 'editor.preview.noUrl': "no URL";
60
+ readonly 'editor.preview.copyUrl': "Copy URL";
61
+ readonly 'editor.preview.refresh': "Refresh preview";
62
+ readonly 'editor.preview.fitToPanel': "Fit preview to panel";
63
+ readonly 'editor.preview.mobileViewport': "Mobile viewport";
64
+ readonly 'editor.preview.desktopViewport': "Desktop viewport (full window)";
65
+ readonly 'editor.preview.openInNewTab': "Open in new tab";
66
+ readonly 'editor.preview.newTab': "↗ New tab";
67
+ readonly 'editor.preview.selectWidget': "Select a widget to preview";
68
+ readonly 'editor.preview.widgetName': "Widget";
69
+ readonly 'editor.preview.notPublished': "Not published yet";
70
+ readonly 'editor.preview.publishToPreview': "Publish the widget in Widget Creator to preview it here.";
71
+ readonly 'editor.preview.frameTitle': "Widget preview";
72
+ readonly 'editor.preview.frameTitleNamed': "Widget preview – {name}";
73
+ readonly 'editor.preview.unavailable': "Preview Unavailable";
74
+ readonly 'editor.preview.loadFailedPreview': "Failed to load widget preview.";
75
+ readonly 'editor.preview.retry': "Retry";
76
+ readonly 'editor.preview.retryBuild': "Retry Build";
77
+ readonly 'editor.preview.retryPreview': "Retry Preview";
78
+ readonly 'editor.preview.fixErrors': "Fix Errors";
79
+ readonly 'editor.preview.viewErrors': "View Errors";
80
+ readonly 'editor.preview.runtimeError': "Widget runtime error";
81
+ readonly 'editor.preview.compilationErrors': "Code Has Compilation Errors";
82
+ readonly 'editor.preview.compilationErrorsDetail': "The generated code has syntax or compilation errors (HTTP {status}).";
83
+ readonly 'editor.preview.noCodeYet': "No Code Generated Yet";
84
+ readonly 'editor.preview.noPreview': "No Preview Available";
85
+ readonly 'editor.preview.building': "Building Preview...";
86
+ readonly 'editor.preview.loadingWidget': "Loading widget…";
87
+ readonly 'editor.preview.loadingPreview': "Loading Preview…";
88
+ readonly 'editor.preview.checkingWidget': "Checking widget on Worker…";
89
+ readonly 'editor.preview.applyingSettings': "Applying composition settings…";
90
+ readonly 'editor.preview.connecting': "Connecting to the widget preview…";
91
+ readonly 'editor.preview.generatingCode': "Generating code…";
92
+ readonly 'editor.list.recentlyUsed': "Recently used";
93
+ readonly 'editor.list.more': "More";
94
+ readonly 'editor.list.searchResults': "Search results";
95
+ readonly 'editor.list.loadingMore': "Loading more items...";
96
+ readonly 'editor.list.deleteItem': "Delete";
97
+ readonly 'editor.category.all': "All";
98
+ readonly 'editor.category.mine': "Mine";
99
+ readonly 'editor.category.sansSerif': "Sans Serif";
100
+ readonly 'editor.category.serif': "Serif";
101
+ readonly 'editor.category.script': "Script";
102
+ readonly 'editor.category.other': "Other";
103
+ readonly 'editor.category.icons': "Icons";
104
+ readonly 'editor.category.emojis': "Emojis";
105
+ readonly 'editor.category.clipart': "Clipart";
106
+ readonly 'editor.font.select': "Select font...";
107
+ readonly 'editor.font.title': "Fonts";
108
+ readonly 'editor.font.search': "Search fonts...";
109
+ readonly 'editor.font.list': "Font list";
110
+ readonly 'editor.font.loading': "Loading fonts";
111
+ readonly 'editor.font.noneFound': "No fonts found";
112
+ readonly 'editor.font.noneAvailable': "No fonts available";
113
+ readonly 'editor.font.remove': "Remove font";
114
+ readonly 'editor.font.weight': "Font Weight";
115
+ readonly 'editor.font.weightList': "Font weights";
116
+ readonly 'editor.fontWeight.withItalic': "{weight} Italic";
117
+ readonly 'editor.fontWeight.italic': "Italic";
118
+ readonly 'editor.fontWeight.thin': "Thin";
119
+ readonly 'editor.fontWeight.extraLight': "ExtraLight";
120
+ readonly 'editor.fontWeight.light': "Light";
121
+ readonly 'editor.fontWeight.regular': "Regular";
122
+ readonly 'editor.fontWeight.medium': "Medium";
123
+ readonly 'editor.fontWeight.semiBold': "SemiBold";
124
+ readonly 'editor.fontWeight.bold': "Bold";
125
+ readonly 'editor.fontWeight.extraBold': "ExtraBold";
126
+ readonly 'editor.fontWeight.black': "Black";
127
+ readonly 'editor.vector.select': "Select vector...";
128
+ readonly 'editor.vector.title': "Vectors";
129
+ readonly 'editor.vector.search': "Search vectors...";
130
+ readonly 'editor.vector.list': "Vector list";
131
+ readonly 'editor.vector.noneFound': "No vectors found";
132
+ readonly 'editor.vector.noneAvailable': "No vectors available";
133
+ readonly 'editor.file.useFile': "Use file";
134
+ readonly 'editor.file.useImage': "Use image";
135
+ readonly 'editor.file.useVideo': "Use video";
136
+ readonly 'editor.file.useAudio': "Use audio";
137
+ readonly 'editor.file.useDocument': "Use document";
138
+ readonly 'editor.file.addFile': "Add file...";
139
+ readonly 'editor.file.addImage': "Add image...";
140
+ readonly 'editor.file.addVideo': "Add video...";
141
+ readonly 'editor.file.addAudio': "Add audio...";
142
+ readonly 'editor.file.addDocument': "Add docs...";
143
+ readonly 'editor.file.play': "Play";
144
+ readonly 'editor.file.pause': "Pause";
145
+ readonly 'editor.file.notFound': "File not found";
146
+ readonly 'editor.file.emptyFolder': "Empty folder";
147
+ readonly 'editor.file.clear': "Clear file";
148
+ readonly 'editor.file.dialogTitle': "Select file";
149
+ readonly 'editor.file.closeDialog': "Close file browser";
150
+ readonly 'editor.color.noRecent': "No recent colors";
151
+ readonly 'editor.color.picker': "Color picker";
152
+ readonly 'editor.date.pick': "Pick a date";
153
+ readonly 'editor.date.pickRange': "Pick a date range";
154
+ readonly 'editor.date.invalid': "Invalid date";
155
+ readonly 'editor.date.selectMonth': "Select month";
156
+ readonly 'editor.date.selectYear': "Select year";
157
+ readonly 'editor.date.calendarLabel': "Date";
158
+ readonly 'editor.date.rangeCalendarLabel': "Date range";
159
+ readonly 'editor.date.previousMonth': "Previous month";
160
+ readonly 'editor.date.nextMonth': "Next month";
161
+ readonly 'editor.dateField.dayHint': "DD";
162
+ readonly 'editor.dateField.monthHint': "MM";
163
+ readonly 'editor.dateField.yearHint': "YYYY";
164
+ readonly 'editor.dateField.dayLabel': "Day input";
165
+ readonly 'editor.dateField.monthLabel': "Month input";
166
+ readonly 'editor.dateField.yearLabel': "Year input";
167
+ readonly 'editor.dropdown.select': "Select an option";
168
+ readonly 'editor.switch.off': "Off";
169
+ readonly 'editor.switch.on': "On";
170
+ readonly 'editor.tags.add': "Add tag...";
171
+ readonly 'editor.position.x': "X:";
172
+ readonly 'editor.position.y': "Y:";
173
+ readonly 'editor.fixedPosition.set': "Set position to {position}";
174
+ readonly 'editor.fixedPosition.topLeft': "top-left";
175
+ readonly 'editor.fixedPosition.topCenter': "top-center";
176
+ readonly 'editor.fixedPosition.topRight': "top-right";
177
+ readonly 'editor.fixedPosition.centerLeft': "center-left";
178
+ readonly 'editor.fixedPosition.centerCenter': "center-center";
179
+ readonly 'editor.fixedPosition.centerRight': "center-right";
180
+ readonly 'editor.fixedPosition.bottomLeft': "bottom-left";
181
+ readonly 'editor.fixedPosition.bottomCenter': "bottom-center";
182
+ readonly 'editor.fixedPosition.bottomRight': "bottom-right";
183
+ readonly 'editor.corners.group': "Corner radius";
184
+ readonly 'editor.corners.topLeft': "Top left corner radius";
185
+ readonly 'editor.corners.topRight': "Top right corner radius";
186
+ readonly 'editor.corners.bottomLeft': "Bottom left corner radius";
187
+ readonly 'editor.corners.bottomRight': "Bottom right corner radius";
188
+ readonly 'editor.corners.lock': "Lock corner radius values";
189
+ readonly 'editor.corners.unlock': "Unlock corner radius values";
190
+ readonly 'editor.textArea.toolbar': "Text formatting toolbar";
191
+ readonly 'editor.textArea.toggleBold': "Toggle bold";
192
+ readonly 'editor.textArea.toggleItalic': "Toggle italic";
193
+ readonly 'editor.textArea.toggleUnderline': "Toggle underline";
194
+ readonly 'editor.textArea.toggleLink': "Toggle link";
195
+ readonly 'editor.textArea.text': "Text";
196
+ readonly 'editor.textArea.paragraph': "Paragraph";
197
+ readonly 'editor.textArea.heading1': "H1";
198
+ readonly 'editor.textArea.heading2': "H2";
199
+ readonly 'editor.textArea.heading3': "H3";
200
+ readonly 'editor.textArea.bulletList': "Bullet List";
201
+ readonly 'editor.textArea.numberedList': "Numbered List";
202
+ readonly 'editor.textArea.checkList': "Check List";
203
+ readonly 'editor.textArea.textStyles': "Text styles";
204
+ readonly 'editor.textArea.listStyles': "List styles";
205
+ readonly 'editor.textArea.enterUrl': "Enter URL";
206
+ readonly 'editor.schema.subtitleFont': "Subtitle Font";
207
+ };
208
+ readonly fileBrowser: any;
209
+ readonly shadEditor: any;
210
+ };
211
+ /** Every message of the editor and of the packages inside it, as one catalog: what a page's `messages` may have a key of. */
212
+ export declare const ALL_EDITOR_MESSAGES: Readonly<Record<string, string>>;
213
+ /**
214
+ * Checks a page's `messages` against all the editor's catalogs without showing anything: a key that does not exist (with
215
+ * the nearest real one), a value that is not text, a template that does not parse, or one that asks for a value the message
216
+ * is never given. An empty list means every message will be used. For a test or a build step of the page.
217
+ */
218
+ export declare const checkEditorMessages: (messages: SHAD_EDITOR_MESSAGES) => SHAD_EDITOR_MESSAGES[];
219
+ /**
220
+ * Called by the root component of an editor while it is created, so everything inside it speaks the page's language.
221
+ * The context belongs to one root component, so two editors on a page can speak two languages.
222
+ */
223
+ export declare const provideEditorLanguage: (initial: SHAD_EDITOR_MESSAGES) => import("./languageKit.js").EditorLanguageHandle;
224
+ /**
225
+ * Called by a part of the editor while it is created. Gives its words as stores, so a part that is already on the screen
226
+ * changes when the page sets the language again. Outside an editor (a control used on its own) it speaks English.
227
+ */
228
+ export declare const useEditorLanguage: () => import("./languageKit.js").EditorLanguage;
@@ -0,0 +1,34 @@
1
+ import { SHAD_EDITOR_MESSAGES } from '@widgetic/design-system/shad-editor';
2
+ import { FILE_BROWSER_MESSAGES } from '@widgetic/file-browser';
3
+ import { EDITOR_MESSAGES } from './messages.js';
4
+ import { createEditorLanguageKit } from './languageKit.js';
5
+ /**
6
+ * The language of an editor on a page. The editor has two packages inside it (the File Browser that picks a file, and
7
+ * the rich text editor of the text boxes), each with its own catalog; a page gives ONE flat `messages` object and the
8
+ * editor hands each package the keys that are its own. See `languageKit.ts` for how, and for what a test can swap.
9
+ */
10
+ /** The catalogs of the editor and of the packages inside it. A key belongs to one of them. */
11
+ export const EDITOR_CATALOGS = {
12
+ editor: EDITOR_MESSAGES,
13
+ fileBrowser: FILE_BROWSER_MESSAGES,
14
+ shadEditor: SHAD_EDITOR_MESSAGES
15
+ };
16
+ const editorLanguageKit = createEditorLanguageKit({ catalogs: EDITOR_CATALOGS });
17
+ /** Every message of the editor and of the packages inside it, as one catalog: what a page's `messages` may have a key of. */
18
+ export const ALL_EDITOR_MESSAGES = editorLanguageKit.allMessages;
19
+ /**
20
+ * Checks a page's `messages` against all the editor's catalogs without showing anything: a key that does not exist (with
21
+ * the nearest real one), a value that is not text, a template that does not parse, or one that asks for a value the message
22
+ * is never given. An empty list means every message will be used. For a test or a build step of the page.
23
+ */
24
+ export const checkEditorMessages = editorLanguageKit.checkMessages;
25
+ /**
26
+ * Called by the root component of an editor while it is created, so everything inside it speaks the page's language.
27
+ * The context belongs to one root component, so two editors on a page can speak two languages.
28
+ */
29
+ export const provideEditorLanguage = editorLanguageKit.provideLanguage;
30
+ /**
31
+ * Called by a part of the editor while it is created. Gives its words as stores, so a part that is already on the screen
32
+ * changes when the page sets the language again. Outside an editor (a control used on its own) it speaks English.
33
+ */
34
+ export const useEditorLanguage = editorLanguageKit.useLanguage;
@@ -0,0 +1,54 @@
1
+ import { type Readable } from 'svelte/store';
2
+ import { type I18nContext, type I18nSettings, type MessageOverrides, type MessageProblem, type SvelteContextApi } from '@widgetic/design-system/i18n';
3
+ import type { EditorMessageKey } from './messages.js';
4
+ /**
5
+ * The language of an editor on a page, shared with every part of it through Svelte's component context.
6
+ *
7
+ * The editor has two packages inside it (the File Browser that picks a file, and the rich text editor of the text boxes),
8
+ * and each speaks from its own catalog. A page does not have to know that: it gives ONE flat `messages` object, and this
9
+ * hands each package the keys that are its own.
10
+ *
11
+ * It is made from the catalogs, in a function, so a test can make one from catalogs of its own and with stand-ins for
12
+ * Svelte's context (the packages inside the editor are Svelte components, which a test in Node cannot load). The editor
13
+ * makes the one it uses in `language.ts`.
14
+ */
15
+ /** The catalogs of the editor and of the packages inside it. A key belongs to one of them. */
16
+ export interface EditorCatalogs {
17
+ editor: Readonly<Record<EditorMessageKey, string>>;
18
+ fileBrowser: Readonly<Record<string, string>>;
19
+ shadEditor: Readonly<Record<string, string>>;
20
+ }
21
+ /** What the screens of the editor read their words from. */
22
+ export interface EditorLanguage extends I18nContext<EditorMessageKey> {
23
+ /** What the File Browser inside the editor is given: the page's language and the keys that are the File Browser's. */
24
+ fileBrowser: Readable<I18nSettings>;
25
+ /** What the rich text editor inside the editor is given: the page's language and the keys that are its own. */
26
+ shadEditor: Readable<I18nSettings>;
27
+ }
28
+ /** What `provideEditorLanguage` gives back to the component that called it. */
29
+ export interface EditorLanguageHandle extends EditorLanguage {
30
+ /** Changes the language every part below speaks. Cheap to call on every change of the props. */
31
+ update(settings: I18nSettings): void;
32
+ }
33
+ export interface EditorLanguageKitOptions {
34
+ catalogs: EditorCatalogs;
35
+ /** Stand-ins for Svelte's `getContext` and `setContext`; tests only. */
36
+ contextApi?: SvelteContextApi;
37
+ /** Where the warnings go; tests only. */
38
+ logger?: Pick<Console, 'warn'>;
39
+ }
40
+ export interface EditorLanguageKit {
41
+ /** Every message of the editor and of the packages inside it, as one catalog: what a page's `messages` may have a key of. */
42
+ allMessages: Readonly<Record<string, string>>;
43
+ /**
44
+ * Checks a page's `messages` against all the editor's catalogs without showing anything: a key that does not exist
45
+ * (with the nearest real one), a value that is not text, a template that does not parse, or one that asks for a value
46
+ * the message is never given. An empty list means every message will be used. For a test or a build step of the page.
47
+ */
48
+ checkMessages(messages: MessageOverrides): MessageProblem[];
49
+ /** Called by the root component of an editor while it is created, so everything inside it speaks the page's language. */
50
+ provideLanguage(initial: I18nSettings): EditorLanguageHandle;
51
+ /** Called by a part of the editor while it is created. Outside an editor it speaks English. */
52
+ useLanguage(): EditorLanguage;
53
+ }
54
+ export declare function createEditorLanguageKit(options: EditorLanguageKitOptions): EditorLanguageKit;
@@ -0,0 +1,102 @@
1
+ import { getContext, setContext } from 'svelte';
2
+ import { readable, writable } from 'svelte/store';
3
+ import { createI18n, describeMessageProblem, findMessageProblems, splitMessages } from '@widgetic/design-system/i18n';
4
+ /** How many different problems the console is told about, so a page that keeps changing its messages cannot fill memory. */
5
+ const MAX_REPORTED_KEYS = 200;
6
+ /** Two settings are the same language when they have the same locale and the same words. */
7
+ function signatureOf(settings) {
8
+ try {
9
+ return JSON.stringify([settings.locale ?? null, settings.messages ?? null]);
10
+ }
11
+ catch {
12
+ // Not serializable (a cycle, a BigInt): treat it as new every time rather than guess.
13
+ return `${Math.random()}`;
14
+ }
15
+ }
16
+ /** What the packages inside the editor are told when no editor above chose a language: nothing. */
17
+ const NOTHING_CHOSEN = readable({});
18
+ export function createEditorLanguageKit(options) {
19
+ const { catalogs } = options;
20
+ const api = options.contextApi ?? { getContext, setContext };
21
+ const logger = options.logger ?? console;
22
+ // The context belongs to one root component, so two editors on a page can speak two languages. The key is shared by
23
+ // every copy of the package, like the media library's.
24
+ const contextKey = Symbol.for('widgetic.editor.language');
25
+ const allMessages = {
26
+ ...catalogs.fileBrowser,
27
+ ...catalogs.shadEditor,
28
+ ...catalogs.editor
29
+ };
30
+ const editorWords = createI18n({
31
+ catalog: catalogs.editor,
32
+ label: 'Editor',
33
+ contextApi: options.contextApi
34
+ });
35
+ /** What a part speaks when no editor above it chose a language: English, with nothing to tell the packages inside. */
36
+ const englishLanguage = Object.freeze({
37
+ t: readable(editorWords.translateEnglish),
38
+ locale: readable('en'),
39
+ fileBrowser: NOTHING_CHOSEN,
40
+ shadEditor: NOTHING_CHOSEN
41
+ });
42
+ const reportedKeys = new Set();
43
+ /** Says a key of the page's `messages` that no catalog has, once per page however often the language is set. */
44
+ function reportUnclaimedKeys(unclaimed) {
45
+ if (Object.keys(unclaimed).length === 0)
46
+ return;
47
+ for (const problem of findMessageProblems(allMessages, unclaimed)) {
48
+ const text = describeMessageProblem(problem);
49
+ if (reportedKeys.has(text) || reportedKeys.size >= MAX_REPORTED_KEYS)
50
+ continue;
51
+ reportedKeys.add(text);
52
+ logger.warn(`[Editor] ${text}`);
53
+ }
54
+ }
55
+ const slicesOf = (settings) => splitMessages(settings.messages, catalogs);
56
+ function provideLanguage(initial) {
57
+ const first = slicesOf(initial);
58
+ // The editor's own translator hears only the editor's keys: a key of the File Browser is not "unknown" to it.
59
+ const translator = editorWords.provideI18n({ locale: initial.locale, messages: first.slices.editor });
60
+ const fileBrowser = writable({ locale: initial.locale, messages: first.slices.fileBrowser });
61
+ const shadEditor = writable({ locale: initial.locale, messages: first.slices.shadEditor });
62
+ reportUnclaimedKeys(first.unclaimed);
63
+ const language = {
64
+ t: translator.t,
65
+ locale: translator.locale,
66
+ fileBrowser: { subscribe: fileBrowser.subscribe },
67
+ shadEditor: { subscribe: shadEditor.subscribe }
68
+ };
69
+ api.setContext(contextKey, language);
70
+ let lastSignature = signatureOf(initial);
71
+ return {
72
+ ...language,
73
+ update(next) {
74
+ // Setting the same language again would rebuild every message for nothing.
75
+ const signature = signatureOf(next);
76
+ if (signature === lastSignature)
77
+ return;
78
+ lastSignature = signature;
79
+ const nextSlices = slicesOf(next);
80
+ translator.update({ locale: next.locale, messages: nextSlices.slices.editor });
81
+ fileBrowser.set({ locale: next.locale, messages: nextSlices.slices.fileBrowser });
82
+ shadEditor.set({ locale: next.locale, messages: nextSlices.slices.shadEditor });
83
+ reportUnclaimedKeys(nextSlices.unclaimed);
84
+ }
85
+ };
86
+ }
87
+ function useLanguage() {
88
+ try {
89
+ return api.getContext(contextKey) ?? englishLanguage;
90
+ }
91
+ catch {
92
+ // getContext only works while a component is being created; anywhere else there is no editor above to ask.
93
+ return englishLanguage;
94
+ }
95
+ }
96
+ return {
97
+ allMessages,
98
+ checkMessages: (messages) => findMessageProblems(allMessages, messages),
99
+ provideLanguage,
100
+ useLanguage
101
+ };
102
+ }