@widgetic/editor 4.0.11 → 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.
- package/README.md +124 -0
- package/dist/components/WidgetPreview.svelte +58 -59
- package/dist/components/color-select/ColorSelect.svelte +4 -4
- package/dist/components/editors/CompositionEditorApp.svelte +69 -15
- package/dist/components/editors/CompositionEditorApp.svelte.d.ts +20 -0
- package/dist/components/editors/EmbedEditor.svelte +33 -22
- package/dist/components/editors/EmbedEditor.svelte.d.ts +1 -1
- package/dist/components/editors/PropsEditor.svelte +74 -58
- package/dist/components/editors/PropsEditor.svelte.d.ts +3 -3
- package/dist/components/ic/AngleInputController.svelte +1 -1
- package/dist/components/ic/BrowserInputController.svelte +37 -29
- package/dist/components/ic/ColorPickerInputController.svelte +31 -15
- package/dist/components/ic/DateFieldInputController.svelte +27 -11
- package/dist/components/ic/DatePickerInputController.svelte +27 -17
- package/dist/components/ic/DropdownInputController.svelte +10 -5
- package/dist/components/ic/DropdownInputController.svelte.d.ts +1 -1
- package/dist/components/ic/FixedPositionInputController.svelte +12 -6
- package/dist/components/ic/FontInputController.svelte +32 -27
- package/dist/components/ic/InputControllerHeader.svelte +5 -1
- package/dist/components/ic/NumberControls.svelte +4 -4
- package/dist/components/ic/NumberInputController.svelte +2 -2
- package/dist/components/ic/PositionInputController.svelte +9 -4
- package/dist/components/ic/RangeCalendarInputController.svelte +26 -16
- package/dist/components/ic/RoundedCornersInputController.svelte +22 -17
- package/dist/components/ic/SliderInputController.svelte +7 -2
- package/dist/components/ic/SwitchInputController.svelte +12 -3
- package/dist/components/ic/TagsInputController.svelte +19 -4
- package/dist/components/ic/TextAreaInputController.svelte +59 -44
- package/dist/components/ic/TextAreaPreviewInputController.svelte +8 -3
- package/dist/components/ic/TextInputController.svelte +17 -1
- package/dist/components/ic/TimeInputController.svelte +2 -1
- package/dist/components/ic/VectorInputController.svelte +21 -16
- package/dist/components/ic/shared/ItemGrid.svelte +7 -3
- package/dist/components/ic/shared/RecentItemsSection.svelte +16 -11
- package/dist/components/ic/shared/RecentItemsSection.svelte.d.ts +1 -1
- package/dist/composition/compositionEditorController.d.ts +11 -2
- package/dist/composition/compositionEditorController.js +33 -21
- package/dist/composition/compositionHelpers.d.ts +3 -1
- package/dist/composition/compositionHelpers.js +4 -2
- package/dist/i18n/index.d.ts +4 -0
- package/dist/i18n/index.js +4 -0
- package/dist/i18n/labels.d.ts +15 -0
- package/dist/i18n/labels.js +57 -0
- package/dist/i18n/language.d.ts +228 -0
- package/dist/i18n/language.js +34 -0
- package/dist/i18n/languageKit.d.ts +54 -0
- package/dist/i18n/languageKit.js +102 -0
- package/dist/i18n/messages.d.ts +241 -0
- package/dist/i18n/messages.js +253 -0
- package/dist/i18n/translate.d.ts +6 -0
- package/dist/i18n/translate.js +8 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -0
- package/dist/utils/dockPlacement.d.ts +12 -0
- package/dist/utils/dockPlacement.js +13 -0
- package/dist/utils/fieldDirection.d.ts +13 -0
- package/dist/utils/fieldDirection.js +25 -0
- package/package.json +2 -2
|
@@ -68,6 +68,10 @@
|
|
|
68
68
|
// Import Shad Editor
|
|
69
69
|
import { ShadEditor, type TipTapEditor, type TipTapEditorLevel } from '@widgetic/design-system/shad-editor';
|
|
70
70
|
|
|
71
|
+
// The words of the editor in the language of the page, and the share of them that belongs to the rich text editor below.
|
|
72
|
+
import { useEditorLanguage } from '../../i18n/language';
|
|
73
|
+
import type { EditorMessageKey } from '../../i18n/messages';
|
|
74
|
+
|
|
71
75
|
// Add local ColorPickerInputController import
|
|
72
76
|
import ColorPickerInputController from './ColorPickerInputController.svelte';
|
|
73
77
|
|
|
@@ -94,6 +98,8 @@
|
|
|
94
98
|
// Use config values with fallbacks
|
|
95
99
|
const textColor = $derived(config.textColor || '#1a1a1a');
|
|
96
100
|
|
|
101
|
+
const { t, shadEditor: shadEditorLanguage } = useEditorLanguage();
|
|
102
|
+
|
|
97
103
|
/**
|
|
98
104
|
* State Variables
|
|
99
105
|
*/
|
|
@@ -111,7 +117,7 @@
|
|
|
111
117
|
// Add type definition for formats
|
|
112
118
|
type FormatType = 'bold' | 'italic' | 'underline' | 'link';
|
|
113
119
|
type ActiveFormats = Record<FormatType, boolean>;
|
|
114
|
-
type FormatMapping = Record<FormatType, { value: string; icon: any }>;
|
|
120
|
+
type FormatMapping = Record<FormatType, { value: string; icon: any; messageKey: EditorMessageKey }>;
|
|
115
121
|
|
|
116
122
|
let activeFormats = $state<ActiveFormats>({
|
|
117
123
|
bold: false,
|
|
@@ -121,10 +127,10 @@
|
|
|
121
127
|
});
|
|
122
128
|
|
|
123
129
|
const formatMapping: FormatMapping = {
|
|
124
|
-
bold: { value: 'b', icon: IconBold },
|
|
125
|
-
italic: { value: 'i', icon: IconItalic },
|
|
126
|
-
underline: { value: 'u', icon: IconUnderline },
|
|
127
|
-
link: { value: 'link', icon: IconLink }
|
|
130
|
+
bold: { value: 'b', icon: IconBold, messageKey: 'editor.textArea.toggleBold' },
|
|
131
|
+
italic: { value: 'i', icon: IconItalic, messageKey: 'editor.textArea.toggleItalic' },
|
|
132
|
+
underline: { value: 'u', icon: IconUnderline, messageKey: 'editor.textArea.toggleUnderline' },
|
|
133
|
+
link: { value: 'link', icon: IconLink, messageKey: 'editor.textArea.toggleLink' }
|
|
128
134
|
};
|
|
129
135
|
|
|
130
136
|
let currentTextStyle = $state('text'); // Track current text style
|
|
@@ -133,17 +139,23 @@
|
|
|
133
139
|
let currentColor = $derived(textColor);
|
|
134
140
|
let selectedTextRange: { from: number, to: number } | null = $state(null);
|
|
135
141
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
{ value: '
|
|
139
|
-
{ value: '
|
|
140
|
-
{ value: '
|
|
142
|
+
// Each option is shown by its message (`messageKey`), in the language of the page.
|
|
143
|
+
const textOptions: { value: string; messageKey: EditorMessageKey; icon?: any }[] = [
|
|
144
|
+
{ value: 'text', messageKey: 'editor.textArea.paragraph', icon: IconPilcrow },
|
|
145
|
+
{ value: 'h1', messageKey: 'editor.textArea.heading1' },
|
|
146
|
+
{ value: 'h2', messageKey: 'editor.textArea.heading2' },
|
|
147
|
+
{ value: 'h3', messageKey: 'editor.textArea.heading3' }
|
|
141
148
|
];
|
|
142
149
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
150
|
+
/** The message of the style the toolbar button shows: the plain text's when the style is none of the options. */
|
|
151
|
+
let currentTextStyleMessageKey = $derived<EditorMessageKey>(
|
|
152
|
+
textOptions.find((option) => option.value === currentTextStyle)?.messageKey ?? 'editor.textArea.text'
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const listOptions: { value: string; messageKey: EditorMessageKey; icon?: any }[] = [
|
|
156
|
+
{ value: 'bulletList', messageKey: 'editor.textArea.bulletList', icon: IconList },
|
|
157
|
+
{ value: 'orderedList', messageKey: 'editor.textArea.numberedList', icon: IconListNumbers },
|
|
158
|
+
{ value: 'checkList', messageKey: 'editor.textArea.checkList', icon: IconListCheck }
|
|
147
159
|
];
|
|
148
160
|
|
|
149
161
|
const content = writable(value);
|
|
@@ -395,7 +407,7 @@
|
|
|
395
407
|
break;
|
|
396
408
|
case 'link':
|
|
397
409
|
if (isAdding) {
|
|
398
|
-
const href = window.prompt('
|
|
410
|
+
const href = window.prompt($t('editor.textArea.enterUrl'));
|
|
399
411
|
if (href) {
|
|
400
412
|
chain.setLink({ href }).run();
|
|
401
413
|
}
|
|
@@ -1116,17 +1128,17 @@
|
|
|
1116
1128
|
<!-- Toolbar at the top -->
|
|
1117
1129
|
<div
|
|
1118
1130
|
bind:this={toolbarRef}
|
|
1119
|
-
class="toolbar-container sticky top-0
|
|
1131
|
+
class="toolbar-container sticky top-0 start-0 end-0 h-[56px] bg-grey1 z-10 border-b border-grey2 overflow-visible select-none"
|
|
1120
1132
|
on:mousedown={handleToolbarInteraction}
|
|
1121
1133
|
on:click={handleToolbarInteraction}
|
|
1122
1134
|
on:keydown={handleToolbarKeyDown}
|
|
1123
1135
|
role="toolbar"
|
|
1124
|
-
aria-label=
|
|
1136
|
+
aria-label={$t('editor.textArea.toolbar')}
|
|
1125
1137
|
tabindex="-1"
|
|
1126
1138
|
>
|
|
1127
1139
|
<div class="toolbar-items-row relative flex items-center h-full w-full">
|
|
1128
1140
|
<!-- Format cluster centered; close stays on the right -->
|
|
1129
|
-
<div class={`toolbar-items-container absolute inset-y-0
|
|
1141
|
+
<div class={`toolbar-items-container absolute inset-y-0 start-0 end-10 flex items-center justify-center gap-2 transition-transform duration-200 ease-out ${
|
|
1130
1142
|
isColorPickerAnimating ? 'translate-y-[-200%]' :
|
|
1131
1143
|
showColorPicker ? 'translate-y-[-200%]' :
|
|
1132
1144
|
'translate-y-0'
|
|
@@ -1164,10 +1176,10 @@
|
|
|
1164
1176
|
handleToggleChange(value);
|
|
1165
1177
|
}}
|
|
1166
1178
|
>
|
|
1167
|
-
{#each Object.entries(formatMapping) as [format, { value, icon: Icon }] (format)}
|
|
1179
|
+
{#each Object.entries(formatMapping) as [format, { value, icon: Icon, messageKey }] (format)}
|
|
1168
1180
|
<ToggleGroup.Item
|
|
1169
1181
|
{value}
|
|
1170
|
-
aria-label={
|
|
1182
|
+
aria-label={$t(messageKey)}
|
|
1171
1183
|
data-state={activeFormats[format as FormatType] ? 'on' : 'off'}
|
|
1172
1184
|
class="toolbar-button shrink-0 !h-8 !w-8 !min-w-8 !p-0 {activeFormats[format as FormatType] ? 'bg-muted' : ''}"
|
|
1173
1185
|
>
|
|
@@ -1212,7 +1224,7 @@
|
|
|
1212
1224
|
{#if currentTextStyle === 'text'}
|
|
1213
1225
|
<IconPilcrow class="h-3.5 w-3.5 shrink-0" />
|
|
1214
1226
|
{:else}
|
|
1215
|
-
<span class="text-xs">{
|
|
1227
|
+
<span class="text-xs">{$t(currentTextStyleMessageKey)}</span>
|
|
1216
1228
|
{/if}
|
|
1217
1229
|
<ChevronDown class="h-3 w-3 shrink-0" />
|
|
1218
1230
|
</div>
|
|
@@ -1223,7 +1235,7 @@
|
|
|
1223
1235
|
class="w-[200px] p-0"
|
|
1224
1236
|
>
|
|
1225
1237
|
<Command.Root>
|
|
1226
|
-
<Command.List>
|
|
1238
|
+
<Command.List aria-label={$t('editor.textArea.textStyles')}>
|
|
1227
1239
|
{#each textOptions as option}
|
|
1228
1240
|
<Command.Item
|
|
1229
1241
|
value={option.value}
|
|
@@ -1239,10 +1251,10 @@
|
|
|
1239
1251
|
{#if option.icon}
|
|
1240
1252
|
<option.icon class="h-4 w-4" />
|
|
1241
1253
|
{/if}
|
|
1242
|
-
<span>{option.
|
|
1254
|
+
<span>{$t(option.messageKey)}</span>
|
|
1243
1255
|
</div>
|
|
1244
1256
|
{:else}
|
|
1245
|
-
<span>{option.
|
|
1257
|
+
<span>{$t(option.messageKey)}</span>
|
|
1246
1258
|
{/if}
|
|
1247
1259
|
{#if option.value === currentTextStyle}
|
|
1248
1260
|
<div class="h-4 w-4">✓</div>
|
|
@@ -1296,7 +1308,7 @@
|
|
|
1296
1308
|
class="style-dropdown-container w-[300px] p-0"
|
|
1297
1309
|
>
|
|
1298
1310
|
<Command.Root>
|
|
1299
|
-
<Command.List>
|
|
1311
|
+
<Command.List aria-label={$t('editor.textArea.listStyles')}>
|
|
1300
1312
|
{#each listOptions as option}
|
|
1301
1313
|
<Command.Item
|
|
1302
1314
|
value={option.value}
|
|
@@ -1311,7 +1323,7 @@
|
|
|
1311
1323
|
{#if option.icon}
|
|
1312
1324
|
<option.icon class="h-4 w-4" />
|
|
1313
1325
|
{/if}
|
|
1314
|
-
<span>{option.
|
|
1326
|
+
<span>{$t(option.messageKey)}</span>
|
|
1315
1327
|
</div>
|
|
1316
1328
|
{#if option.value === currentTextStyle}
|
|
1317
1329
|
<div class="h-4 w-4">✓</div>
|
|
@@ -1328,7 +1340,7 @@
|
|
|
1328
1340
|
{#if onClose && !showColorPicker && !isColorPickerAnimating}
|
|
1329
1341
|
<Button
|
|
1330
1342
|
variant="ghost"
|
|
1331
|
-
class="toolbar-close-button
|
|
1343
|
+
class="toolbar-close-button ms-auto shrink-0 p-0 hover:bg-transparent h-8 w-8 me-2 z-10"
|
|
1332
1344
|
onclick={(e) => {
|
|
1333
1345
|
e.stopPropagation();
|
|
1334
1346
|
e.preventDefault();
|
|
@@ -1402,6 +1414,8 @@
|
|
|
1402
1414
|
<ShadEditor
|
|
1403
1415
|
bind:content={value}
|
|
1404
1416
|
showToolbar={false}
|
|
1417
|
+
locale={$shadEditorLanguage.locale ?? undefined}
|
|
1418
|
+
messages={$shadEditorLanguage.messages ?? undefined}
|
|
1405
1419
|
class="shad-editor subtext {editorClass} {showColorPicker || textStyleDropdownOpen ? 'color-picking-active' : ''} {editorHasFocus ? 'editor-focused' : ''} {isSelectionActive ? 'selection-active' : ''}"
|
|
1406
1420
|
on:mount={handleEditorMount}
|
|
1407
1421
|
on:update={handleEditorUpdate}
|
|
@@ -1446,18 +1460,18 @@
|
|
|
1446
1460
|
/* Keep scrollbar styles */
|
|
1447
1461
|
:global(.ProseMirror::-webkit-scrollbar) {
|
|
1448
1462
|
width: 6px;
|
|
1449
|
-
margin-
|
|
1463
|
+
margin-inline-end: 2px;
|
|
1450
1464
|
}
|
|
1451
1465
|
|
|
1452
1466
|
:global(.ProseMirror::-webkit-scrollbar-track) {
|
|
1453
1467
|
background-color: transparent;
|
|
1454
|
-
margin-
|
|
1468
|
+
margin-inline-end: 2px;
|
|
1455
1469
|
}
|
|
1456
1470
|
|
|
1457
1471
|
:global(.ProseMirror::-webkit-scrollbar-thumb) {
|
|
1458
1472
|
background-color: hsl(var(--border)) !important;
|
|
1459
1473
|
border-radius: 3px;
|
|
1460
|
-
margin-
|
|
1474
|
+
margin-inline-end: 2px;
|
|
1461
1475
|
}
|
|
1462
1476
|
|
|
1463
1477
|
:global(.ProseMirror::-webkit-scrollbar-thumb:hover) {
|
|
@@ -1484,7 +1498,7 @@
|
|
|
1484
1498
|
:global(.ProseMirror ul),
|
|
1485
1499
|
:global(.ProseMirror ol),
|
|
1486
1500
|
:global(.ProseMirror [data-type="taskList"]) {
|
|
1487
|
-
padding-
|
|
1501
|
+
padding-inline-start: 1.5rem;
|
|
1488
1502
|
margin: 0;
|
|
1489
1503
|
line-height: 1.4;
|
|
1490
1504
|
}
|
|
@@ -1492,21 +1506,21 @@
|
|
|
1492
1506
|
:global(.ProseMirror li),
|
|
1493
1507
|
:global(.ProseMirror [data-type="taskItem"]) {
|
|
1494
1508
|
position: relative;
|
|
1495
|
-
padding-
|
|
1509
|
+
padding-inline-start: 0.5rem;
|
|
1496
1510
|
margin: 0;
|
|
1497
1511
|
}
|
|
1498
1512
|
|
|
1499
1513
|
/* Task list specific styles */
|
|
1500
1514
|
:global(.ProseMirror [data-type="taskList"]) {
|
|
1501
1515
|
list-style: none;
|
|
1502
|
-
padding-
|
|
1516
|
+
padding-inline-start: 0rem !important;
|
|
1503
1517
|
}
|
|
1504
1518
|
|
|
1505
1519
|
:global(.ProseMirror [data-type="taskItem"]) {
|
|
1506
1520
|
display: flex;
|
|
1507
1521
|
align-items: flex-start;
|
|
1508
|
-
padding-
|
|
1509
|
-
margin-
|
|
1522
|
+
padding-inline-start: 0 !important;
|
|
1523
|
+
margin-inline-start: 0 !important;
|
|
1510
1524
|
gap: 0.5rem;
|
|
1511
1525
|
}
|
|
1512
1526
|
|
|
@@ -1799,11 +1813,12 @@
|
|
|
1799
1813
|
gap: 8px !important;
|
|
1800
1814
|
height: 100% !important;
|
|
1801
1815
|
position: absolute !important;
|
|
1802
|
-
inset: 0
|
|
1816
|
+
inset-block: 0 !important;
|
|
1817
|
+
inset-inline: 0 2.5rem !important;
|
|
1803
1818
|
}
|
|
1804
1819
|
|
|
1805
1820
|
:global(.text-area #text-style-trigger) {
|
|
1806
|
-
margin-
|
|
1821
|
+
margin-inline-end: 0 !important;
|
|
1807
1822
|
}
|
|
1808
1823
|
|
|
1809
1824
|
/* Color the brush icon based on selected color */
|
|
@@ -1816,12 +1831,12 @@
|
|
|
1816
1831
|
}
|
|
1817
1832
|
/* Button spacing */
|
|
1818
1833
|
:global(.text-area .color-picker-preview-button) {
|
|
1819
|
-
margin-
|
|
1820
|
-
margin-
|
|
1834
|
+
margin-inline-start: 0 !important;
|
|
1835
|
+
margin-inline-end: 0 !important;
|
|
1821
1836
|
}
|
|
1822
1837
|
|
|
1823
1838
|
:global(.text-area .text-formatting-toggle-group) {
|
|
1824
|
-
margin-
|
|
1839
|
+
margin-inline-end: 0 !important;
|
|
1825
1840
|
display: flex !important;
|
|
1826
1841
|
gap: 8px !important;
|
|
1827
1842
|
}
|
|
@@ -1831,7 +1846,7 @@
|
|
|
1831
1846
|
}
|
|
1832
1847
|
|
|
1833
1848
|
:global(.text-area .text-formatting-toggle-group button:not(:last-child)) {
|
|
1834
|
-
margin-
|
|
1849
|
+
margin-inline-end: 0 !important;
|
|
1835
1850
|
}
|
|
1836
1851
|
|
|
1837
1852
|
:global(.text-area #text-style-trigger),
|
|
@@ -1874,7 +1889,7 @@
|
|
|
1874
1889
|
|
|
1875
1890
|
:global(.text-area .toolbar-text-style-ct),
|
|
1876
1891
|
:global(.text-area .toolbar-list-style-ct) {
|
|
1877
|
-
margin-
|
|
1892
|
+
margin-inline-start: 0 !important;
|
|
1878
1893
|
}
|
|
1879
1894
|
|
|
1880
1895
|
/* Update the color picker button to show selected color */
|
|
@@ -1915,8 +1930,8 @@
|
|
|
1915
1930
|
width: 44px !important;
|
|
1916
1931
|
min-width: 44px !important;
|
|
1917
1932
|
max-width: 44px !important;
|
|
1918
|
-
margin-
|
|
1919
|
-
padding-
|
|
1933
|
+
margin-inline-start: 0 !important;
|
|
1934
|
+
padding-inline-start: 0 !important;
|
|
1920
1935
|
}
|
|
1921
1936
|
|
|
1922
1937
|
/* Update color picker container styles */
|
|
@@ -16,12 +16,16 @@
|
|
|
16
16
|
<script lang="ts">
|
|
17
17
|
import { Portal } from '@widgetic/design-system/components';
|
|
18
18
|
import { IconArrowsDiagonal } from '@widgetic/design-system/icons';
|
|
19
|
+
import { dirForUserText, useTextDirection } from '@widgetic/design-system/components/textDirection';
|
|
19
20
|
import { onMount, onDestroy } from 'svelte';
|
|
20
21
|
|
|
21
22
|
import { TextAreaInputController } from './index';
|
|
22
23
|
import { createPreviewHtml } from '../../utils/richTextPreview';
|
|
23
24
|
import { escapeHtml } from '../../utils/htmlSafety';
|
|
24
25
|
|
|
26
|
+
// The way the screen is read: the text in the preview is what a person wrote, so on a right to left screen it is read by its own first letter.
|
|
27
|
+
const textDirection = useTextDirection();
|
|
28
|
+
|
|
25
29
|
/**
|
|
26
30
|
* Component props
|
|
27
31
|
*/
|
|
@@ -195,15 +199,16 @@
|
|
|
195
199
|
role="button"
|
|
196
200
|
tabindex="0"
|
|
197
201
|
>
|
|
198
|
-
<div class="textarea-ic-preview-copy min-w-0 overflow-hidden
|
|
202
|
+
<div class="textarea-ic-preview-copy min-w-0 overflow-hidden pe-10 pt-[13px] pb-4 ps-4">
|
|
199
203
|
<div
|
|
200
204
|
class="textarea-ic-preview-html break-words text-grey7 text-sm leading-5"
|
|
201
205
|
class:text-grey3={isShowingPlaceholder}
|
|
206
|
+
dir={dirForUserText($textDirection)}
|
|
202
207
|
>
|
|
203
208
|
{@html previewText}
|
|
204
209
|
</div>
|
|
205
210
|
</div>
|
|
206
|
-
<div class="textarea-ic-expand-icon absolute top-[14px]
|
|
211
|
+
<div class="textarea-ic-expand-icon absolute top-[14px] end-[12px]">
|
|
207
212
|
<IconArrowsDiagonal
|
|
208
213
|
class="w-5 h-5 text-grey5 group-hover:text-grey7 transition-colors duration-200"
|
|
209
214
|
/>
|
|
@@ -220,7 +225,7 @@
|
|
|
220
225
|
{#if isShowingTextArea}
|
|
221
226
|
<Portal
|
|
222
227
|
target={portalTarget}
|
|
223
|
-
class="text-area-portal absolute top-0
|
|
228
|
+
class="text-area-portal absolute top-0 start-0 w-full h-full"
|
|
224
229
|
>
|
|
225
230
|
<!-- Blocking overlay -->
|
|
226
231
|
<div
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
<script lang="ts">
|
|
17
17
|
// Import Shadcn UI components from Widgetic design-system package
|
|
18
18
|
import { Input } from '@widgetic/design-system/components';
|
|
19
|
+
import { useTextDirection } from '@widgetic/design-system/components/textDirection';
|
|
20
|
+
import { directionOfFieldValue } from '../../utils/fieldDirection';
|
|
19
21
|
import { InputControllerHeader } from './index';
|
|
20
22
|
|
|
21
23
|
// Component props
|
|
@@ -35,6 +37,18 @@
|
|
|
35
37
|
export let description: TextInputControllerProps['description'] = undefined;
|
|
36
38
|
export let onFocus: TextInputControllerProps['onFocus'] = () => {};
|
|
37
39
|
|
|
40
|
+
// The way the screen is read. What a visitor types reads by its own first letter on a right to left screen; an address
|
|
41
|
+
// (an email, a link) is read left to right on every screen. A left to right screen is written no attribute, as it always was.
|
|
42
|
+
// (The `type` is the name of the kind of field in the schema, `textInput` for a plain text field: see fieldDirection.ts.)
|
|
43
|
+
const textDirection = useTextDirection();
|
|
44
|
+
// What is in the field right now: the value the page gave it, then what is typed (only an empty field has no first letter).
|
|
45
|
+
let typedText = '';
|
|
46
|
+
$: typedText = value ?? '';
|
|
47
|
+
$: hasTypedValue = typedText.length > 0;
|
|
48
|
+
let valueDirection: 'auto' | 'ltr' | undefined;
|
|
49
|
+
// (An empty field takes the direction of the screen, so that its hint and its caret start at the start of the line.)
|
|
50
|
+
$: valueDirection = directionOfFieldValue(type, $textDirection, hasTypedValue);
|
|
51
|
+
|
|
38
52
|
/**
|
|
39
53
|
* Methods
|
|
40
54
|
*/
|
|
@@ -56,7 +70,7 @@
|
|
|
56
70
|
<InputControllerHeader {label} />
|
|
57
71
|
</div>
|
|
58
72
|
|
|
59
|
-
<div class="absolute
|
|
73
|
+
<div class="absolute end-0">
|
|
60
74
|
<div
|
|
61
75
|
class="h-[48px] w-[185px] rounded-large border border-grey2 flex items-center bg-grey1 transition-colors duration-200 hover:border-forest-green"
|
|
62
76
|
on:focusin={onFocus}
|
|
@@ -66,9 +80,11 @@
|
|
|
66
80
|
{type}
|
|
67
81
|
{value}
|
|
68
82
|
{placeholder}
|
|
83
|
+
dir={valueDirection}
|
|
69
84
|
onfocus={onFocus}
|
|
70
85
|
oninput={(e) => {
|
|
71
86
|
const newValue = e.currentTarget.value;
|
|
87
|
+
typedText = newValue;
|
|
72
88
|
if (validate(newValue)) {
|
|
73
89
|
onChange(newValue);
|
|
74
90
|
}
|
|
@@ -240,6 +240,7 @@
|
|
|
240
240
|
if (dropdownContainer && timeButton) {
|
|
241
241
|
const rect = timeButton.getBoundingClientRect();
|
|
242
242
|
dropdownContainer.style.top = `${rect.bottom + 4}px`;
|
|
243
|
+
// rtl-ok: the list is as wide as the button it opens from, so the left edge of the button is both of its edges
|
|
243
244
|
dropdownContainer.style.left = `${rect.left}px`;
|
|
244
245
|
isDropdownPositioned = true;
|
|
245
246
|
}
|
|
@@ -339,7 +340,7 @@
|
|
|
339
340
|
<InputControllerHeader {label} {helpText} />
|
|
340
341
|
</div>
|
|
341
342
|
|
|
342
|
-
<div class="absolute
|
|
343
|
+
<div class="absolute end-0 h-[48px] flex items-center">
|
|
343
344
|
<div class="flex items-center justify-end gap-2">
|
|
344
345
|
<!-- Clock Face -->
|
|
345
346
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
@@ -55,6 +55,11 @@
|
|
|
55
55
|
import { SearchInput } from '@widgetic/design-system/components';
|
|
56
56
|
import CategoryTabs from './shared/CategoryTabs.svelte';
|
|
57
57
|
import { RecentItemsSection, ItemGrid } from './shared';
|
|
58
|
+
import { useEditorLanguage } from '../../i18n/language';
|
|
59
|
+
import { categoryLabel } from '../../i18n/labels';
|
|
60
|
+
|
|
61
|
+
// The words of the editor in the language of the page.
|
|
62
|
+
const { t } = useEditorLanguage();
|
|
58
63
|
|
|
59
64
|
// Utils imports
|
|
60
65
|
import {
|
|
@@ -1499,14 +1504,14 @@
|
|
|
1499
1504
|
|
|
1500
1505
|
<div bind:this={controllerElement} class="flex flex-col gap-2 widgetic-colors">
|
|
1501
1506
|
<div class="flex items-center w-full relative">
|
|
1502
|
-
<div class="w-[185px] flex-shrink-0
|
|
1507
|
+
<div class="w-[185px] flex-shrink-0 ms-auto">
|
|
1503
1508
|
<InputControllerHeader
|
|
1504
1509
|
label={label}
|
|
1505
1510
|
helpText={description}
|
|
1506
1511
|
/>
|
|
1507
1512
|
</div>
|
|
1508
1513
|
|
|
1509
|
-
<div class="absolute
|
|
1514
|
+
<div class="absolute end-0">
|
|
1510
1515
|
<div
|
|
1511
1516
|
class="h-[48px] w-[185px] rounded-large border border-grey2 flex items-center bg-grey1 transition-colors duration-200 hover:border-forest-green"
|
|
1512
1517
|
onfocusin={onFocus}
|
|
@@ -1552,7 +1557,7 @@
|
|
|
1552
1557
|
}}
|
|
1553
1558
|
>
|
|
1554
1559
|
<Popover.Trigger
|
|
1555
|
-
class="w-full h-full flex items-center border-0 bg-transparent hover:bg-transparent focus:bg-transparent
|
|
1560
|
+
class="w-full h-full flex items-center border-0 bg-transparent hover:bg-transparent focus:bg-transparent ps-3 pe-[16px] group"
|
|
1556
1561
|
onclick={handleButtonClick}
|
|
1557
1562
|
onkeydown={handleKeyDown}
|
|
1558
1563
|
onmouseenter={() => {
|
|
@@ -1577,8 +1582,8 @@
|
|
|
1577
1582
|
<path d="M12 17h.01"></path>
|
|
1578
1583
|
</svg>
|
|
1579
1584
|
</div>
|
|
1580
|
-
<span class="truncate text-
|
|
1581
|
-
{value?.name ||
|
|
1585
|
+
<span class="truncate text-start capitalize subtext text-grey7">
|
|
1586
|
+
{value?.name || $t('editor.vector.select')}
|
|
1582
1587
|
</span>
|
|
1583
1588
|
{:else}
|
|
1584
1589
|
<!-- Use a simple img tag with Svelte action for error handling -->
|
|
@@ -1588,13 +1593,13 @@
|
|
|
1588
1593
|
class="w-5 h-5 object-contain"
|
|
1589
1594
|
use:handleImageError={value.name}
|
|
1590
1595
|
/>
|
|
1591
|
-
<span class="truncate text-
|
|
1592
|
-
{value?.name ||
|
|
1596
|
+
<span class="truncate text-start capitalize subtext text-grey7">
|
|
1597
|
+
{value?.name || $t('editor.vector.select')}
|
|
1593
1598
|
</span>
|
|
1594
1599
|
{/if}
|
|
1595
1600
|
{:else}
|
|
1596
|
-
<span class="truncate text-
|
|
1597
|
-
|
|
1601
|
+
<span class="truncate text-start subtext text-grey7">
|
|
1602
|
+
{$t('editor.vector.select')}
|
|
1598
1603
|
</span>
|
|
1599
1604
|
{/if}
|
|
1600
1605
|
</div>
|
|
@@ -1605,10 +1610,10 @@
|
|
|
1605
1610
|
<div class="flex flex-col h-full vector-popover-content">
|
|
1606
1611
|
<!-- Title and upload icon -->
|
|
1607
1612
|
<div class="relative pt-6 pb-[8px] px-4">
|
|
1608
|
-
<div class="h5 text-grey7 widgetic-colors">
|
|
1613
|
+
<div class="h5 text-grey7 widgetic-colors">{$t('editor.vector.title')}</div>
|
|
1609
1614
|
<button
|
|
1610
1615
|
type="button"
|
|
1611
|
-
class="absolute
|
|
1616
|
+
class="absolute end-6 top-[26px] bg-transparent border-0 cursor-pointer p-0 rounded-full hover:bg-grey3 transition-all duration-300"
|
|
1612
1617
|
onclick={() => {
|
|
1613
1618
|
try {
|
|
1614
1619
|
openFileBrowser();
|
|
@@ -1628,7 +1633,7 @@
|
|
|
1628
1633
|
<!-- Search input -->
|
|
1629
1634
|
<div class="relative px-4">
|
|
1630
1635
|
<SearchInput
|
|
1631
|
-
placeholder=
|
|
1636
|
+
placeholder={$t('editor.vector.search')}
|
|
1632
1637
|
bind:value={searchTerm}
|
|
1633
1638
|
onInput={(e: Event) => {
|
|
1634
1639
|
// Use debounced search for better performance
|
|
@@ -1707,6 +1712,7 @@
|
|
|
1707
1712
|
categories={filterOptions}
|
|
1708
1713
|
activeCategory={currentFilter}
|
|
1709
1714
|
onCategorySelect={handleFilterChange}
|
|
1715
|
+
getCategoryLabel={(category) => categoryLabel(category, $t)}
|
|
1710
1716
|
filterVisible={!searchTerm}
|
|
1711
1717
|
/>
|
|
1712
1718
|
</div>
|
|
@@ -1724,14 +1730,13 @@
|
|
|
1724
1730
|
}}
|
|
1725
1731
|
id={`vector-list-${controllerId}`}
|
|
1726
1732
|
role="listbox"
|
|
1727
|
-
aria-label=
|
|
1733
|
+
aria-label={$t('editor.vector.list')}
|
|
1728
1734
|
>
|
|
1729
1735
|
<div class="space-y-0">
|
|
1730
1736
|
<!-- Recently used section - only show when not searching -->
|
|
1731
1737
|
{#if recentVectors.length > 0 && !searchTerm}
|
|
1732
1738
|
<RecentItemsSection
|
|
1733
1739
|
recentItems={recentVectors}
|
|
1734
|
-
title="Recently used"
|
|
1735
1740
|
onItemSelect={handleVectorSelect}
|
|
1736
1741
|
onItemDelete={handleVectorDelete}
|
|
1737
1742
|
isItemSelected={isVectorSelected}
|
|
@@ -1750,7 +1755,7 @@
|
|
|
1750
1755
|
{#if filteredVectors.length > 0}
|
|
1751
1756
|
<div id="vector-header-{controllerId}" class="sticky top-0 -mt-2 bg-white z-10 pt-2 {isScrolled ? 'border-b border-grey2' : 'border-b border-transparent'} transition-border">
|
|
1752
1757
|
<h3 class="subtext text-grey7 px-4 pb-2">
|
|
1753
|
-
{searchTerm ? '
|
|
1758
|
+
{searchTerm ? $t('editor.list.searchResults') : $t('editor.list.more')}
|
|
1754
1759
|
</h3>
|
|
1755
1760
|
</div>
|
|
1756
1761
|
{/if}
|
|
@@ -1758,7 +1763,7 @@
|
|
|
1758
1763
|
{#if filteredVectors.length === 0}
|
|
1759
1764
|
<div class="empty-state-wrapper">
|
|
1760
1765
|
<EmptyState
|
|
1761
|
-
message={searchTerm ? '
|
|
1766
|
+
message={searchTerm ? $t('editor.vector.noneFound') : $t('editor.vector.noneAvailable')}
|
|
1762
1767
|
height="auto"
|
|
1763
1768
|
/>
|
|
1764
1769
|
</div>
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
<script lang="ts">
|
|
16
16
|
import { Check } from '@widgetic/design-system/icons';
|
|
17
17
|
import { escapeHtml } from '../../../utils/htmlSafety';
|
|
18
|
+
import { useEditorLanguage } from '../../../i18n/language';
|
|
19
|
+
|
|
20
|
+
// The words of the editor in the language of the page.
|
|
21
|
+
const { t } = useEditorLanguage();
|
|
18
22
|
|
|
19
23
|
export let items: any[] = [];
|
|
20
24
|
export let containerRef: (node: HTMLElement) => void;
|
|
@@ -117,7 +121,7 @@
|
|
|
117
121
|
data-item-type="font"
|
|
118
122
|
>
|
|
119
123
|
<div class="flex items-center flex-1 min-w-0 group">
|
|
120
|
-
<Check class="
|
|
124
|
+
<Check class="me-2 h-6 w-6 flex-shrink-0 {isItemSelected(item) ? 'text-white' : 'text-sage-green opacity-0'}"/>
|
|
121
125
|
<div class="flex-1 overflow-hidden">
|
|
122
126
|
{@html renderContent(item)}
|
|
123
127
|
</div>
|
|
@@ -136,7 +140,7 @@
|
|
|
136
140
|
data-item-type="font"
|
|
137
141
|
>
|
|
138
142
|
<div class="flex items-center flex-1 min-w-0 group">
|
|
139
|
-
<Check class="
|
|
143
|
+
<Check class="me-2 h-6 w-6 flex-shrink-0 {isItemSelected(item) ? 'text-white' : 'text-sage-green opacity-0'}"/>
|
|
140
144
|
<div class="flex-1 overflow-hidden">
|
|
141
145
|
{@html renderContent(item)}
|
|
142
146
|
</div>
|
|
@@ -149,7 +153,7 @@
|
|
|
149
153
|
|
|
150
154
|
{#if loadingMore}
|
|
151
155
|
<div class="py-4 subtext text-center text-grey5">
|
|
152
|
-
|
|
156
|
+
{$t('editor.list.loadingMore')}
|
|
153
157
|
</div>
|
|
154
158
|
{/if}
|
|
155
159
|
</div>
|