@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.
- package/README.md +242 -0
- package/dist/components/WidgetPreview.svelte +123 -86
- package/dist/components/WidgetPreview.svelte.d.ts +8 -0
- package/dist/components/color-select/ColorSelect.svelte +4 -4
- package/dist/components/editors/CompositionEditorApp.svelte +562 -0
- package/dist/components/editors/CompositionEditorApp.svelte.d.ts +95 -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 +267 -850
- package/dist/components/editors/PropsEditor.svelte.d.ts +10 -3
- package/dist/components/ic/AngleInputController.svelte +1 -1
- package/dist/components/ic/BrowserInputController.svelte +58 -31
- package/dist/components/ic/ColorPickerInputController.svelte +55 -21
- package/dist/components/ic/DateFieldInputController.svelte +34 -14
- package/dist/components/ic/DatePickerInputController.svelte +37 -21
- package/dist/components/ic/DropdownInputController.svelte +21 -9
- package/dist/components/ic/DropdownInputController.svelte.d.ts +1 -1
- package/dist/components/ic/FixedPositionInputController.svelte +12 -6
- package/dist/components/ic/FontInputController.svelte +100 -76
- package/dist/components/ic/InputControllerHeader.svelte +16 -9
- package/dist/components/ic/NumberControls.svelte +4 -4
- package/dist/components/ic/NumberInputController.svelte +6 -4
- package/dist/components/ic/PositionInputController.svelte +23 -15
- package/dist/components/ic/RangeCalendarInputController.svelte +36 -20
- package/dist/components/ic/RoundedCornersInputController.svelte +39 -26
- 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 +79 -51
- package/dist/components/ic/TextAreaPreviewInputController.svelte +27 -72
- package/dist/components/ic/TextInputController.svelte +17 -1
- package/dist/components/ic/TimeInputController.svelte +11 -4
- package/dist/components/ic/VectorInputController.svelte +101 -70
- package/dist/components/ic/shared/ItemGrid.svelte +12 -4
- package/dist/components/ic/shared/RecentItemsSection.svelte +16 -11
- package/dist/components/ic/shared/RecentItemsSection.svelte.d.ts +1 -1
- package/dist/composition/compositionApi.d.ts +95 -0
- package/dist/composition/compositionApi.js +289 -0
- package/dist/composition/compositionEditorController.d.ts +263 -0
- package/dist/composition/compositionEditorController.js +746 -0
- package/dist/composition/compositionHelpers.d.ts +74 -0
- package/dist/composition/compositionHelpers.js +405 -0
- 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 +4 -0
- package/dist/index.js +8 -0
- package/dist/mediaLibraryContext.d.ts +10 -0
- package/dist/mediaLibraryContext.js +30 -0
- package/dist/utils/FontManager.d.ts +21 -5
- package/dist/utils/FontManager.js +62 -67
- package/dist/utils/VectorManager.d.ts +0 -1
- package/dist/utils/VectorManager.js +10 -12
- package/dist/utils/dockPlacement.d.ts +12 -0
- package/dist/utils/dockPlacement.js +13 -0
- package/dist/utils/editorStorage.d.ts +23 -0
- package/dist/utils/editorStorage.js +95 -0
- package/dist/utils/fieldDirection.d.ts +13 -0
- package/dist/utils/fieldDirection.js +25 -0
- package/dist/utils/fontUtils.js +5 -3
- package/dist/utils/htmlSafety.d.ts +21 -0
- package/dist/utils/htmlSafety.js +38 -0
- package/dist/utils/itemReorder.d.ts +90 -0
- package/dist/utils/itemReorder.js +354 -0
- package/dist/utils/recentColors.js +4 -2
- package/dist/utils/richTextPreview.d.ts +38 -0
- package/dist/utils/richTextPreview.js +207 -0
- package/dist/utils/scrollActivity.d.ts +14 -0
- package/dist/utils/scrollActivity.js +63 -0
- package/dist/utils/shadowDom.d.ts +67 -0
- package/dist/utils/shadowDom.js +211 -0
- package/dist/utils/vectorUtils.js +5 -3
- package/dist/utils/visibilityCondition.d.ts +40 -0
- package/dist/utils/visibilityCondition.js +535 -0
- package/package.json +7 -6
|
@@ -35,6 +35,13 @@
|
|
|
35
35
|
|
|
36
36
|
// Component config
|
|
37
37
|
import type { InputControllerConfig } from './index';
|
|
38
|
+
|
|
39
|
+
// The cursor of a drag is shown on the module's element, not on the body of the customer's page
|
|
40
|
+
import { lockPointerStyle } from '../../utils/shadowDom';
|
|
41
|
+
import { useEditorLanguage } from '../../i18n/language';
|
|
42
|
+
|
|
43
|
+
// The words of the editor in the language of the page.
|
|
44
|
+
const { t } = useEditorLanguage();
|
|
38
45
|
|
|
39
46
|
/**
|
|
40
47
|
* Core state and configuration props
|
|
@@ -58,6 +65,8 @@
|
|
|
58
65
|
let joystickHandle: HTMLDivElement;
|
|
59
66
|
let isDragging = false;
|
|
60
67
|
let isHovering = false;
|
|
68
|
+
// Puts back the cursor the page had before the drag (does nothing when no drag is going on)
|
|
69
|
+
let releasePointerStyle: () => void = () => {};
|
|
61
70
|
|
|
62
71
|
/**
|
|
63
72
|
* Reactive position state
|
|
@@ -125,8 +134,9 @@
|
|
|
125
134
|
updatePosition(event.clientX, event.clientY);
|
|
126
135
|
document.addEventListener('mousemove', handleMouseMove);
|
|
127
136
|
document.addEventListener('mouseup', handleMouseUp);
|
|
128
|
-
|
|
129
|
-
|
|
137
|
+
// A second press before the first one was let go must not leave the first lock behind
|
|
138
|
+
releasePointerStyle();
|
|
139
|
+
releasePointerStyle = lockPointerStyle(joystickContainer, 'grabbing');
|
|
130
140
|
}
|
|
131
141
|
|
|
132
142
|
function handleMouseMove(event: MouseEvent) {
|
|
@@ -139,9 +149,7 @@
|
|
|
139
149
|
onDragStateChange(false);
|
|
140
150
|
document.removeEventListener('mousemove', handleMouseMove);
|
|
141
151
|
document.removeEventListener('mouseup', handleMouseUp);
|
|
142
|
-
|
|
143
|
-
document.body.classList.remove('position-dragging');
|
|
144
|
-
document.body.style.userSelect = '';
|
|
152
|
+
releasePointerStyle();
|
|
145
153
|
}
|
|
146
154
|
|
|
147
155
|
/**
|
|
@@ -177,17 +185,17 @@
|
|
|
177
185
|
});
|
|
178
186
|
|
|
179
187
|
onDestroy(() => {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
document.body.style.userSelect = '';
|
|
188
|
+
// Only undoes what this drag did (it used to clear the cursor and the text selection of the whole page)
|
|
189
|
+
releasePointerStyle();
|
|
183
190
|
});
|
|
184
191
|
</script>
|
|
185
192
|
|
|
186
|
-
<div class="position-input-controller widgetic-colors">
|
|
193
|
+
<div class="position-input-controller widgetic-colors" class:position-input-controller-dragging={isDragging}>
|
|
187
194
|
<!-- Joystick UI -->
|
|
188
195
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
189
196
|
<div
|
|
190
197
|
bind:this={joystickContainer}
|
|
198
|
+
dir="ltr"
|
|
191
199
|
class="relative w-[185px] h-[185px] bg-grey1 rounded-large border border-grey2 joystick-container transition-colors duration-200 hover:border-forest-green"
|
|
192
200
|
class:overflow-hidden={shouldMaskThumb}
|
|
193
201
|
on:mouseenter={handleMouseEnter}
|
|
@@ -205,22 +213,22 @@
|
|
|
205
213
|
<div
|
|
206
214
|
bind:this={joystickHandle}
|
|
207
215
|
class="absolute w-[24px] h-[24px] bg-white rounded-full -translate-x-1/2 -translate-y-1/2 border-2 border-grey5 z-10 {isDragging ? 'border-grey7 scale-110' : 'hover:border-grey7 hover:scale-110 transition-all duration-150'}"
|
|
208
|
-
style="left: {position.x}%; top: {position.y}
|
|
216
|
+
style="left: {position.x}%; top: {position.y}%; /* rtl-ok: x counts from the left edge of the pad, which is a left to right island */"
|
|
209
217
|
/>
|
|
210
218
|
|
|
211
219
|
<!-- Percentage display with dynamic positioning -->
|
|
212
220
|
<div
|
|
213
|
-
class="absolute
|
|
221
|
+
class="absolute start-4 end-4 flex justify-between items-center transition-all duration-300 ease-in-out"
|
|
214
222
|
class:bottom-2={!shouldShowAtTop}
|
|
215
223
|
class:top-2={shouldShowAtTop}
|
|
216
224
|
style="opacity: {isDragging ? 0.5 : 1}"
|
|
217
225
|
>
|
|
218
226
|
<div class="flex items-center gap-2 w-[50px]">
|
|
219
|
-
<span class="caption text-grey7 w-[12px]">
|
|
227
|
+
<span class="caption text-grey7 w-[12px]">{$t('editor.position.x')}</span>
|
|
220
228
|
<span class="subtext text-grey7 w-[32px]">{formattedX}%</span>
|
|
221
229
|
</div>
|
|
222
230
|
<div class="flex items-center gap-2 w-[50px]">
|
|
223
|
-
<span class="caption text-grey7 w-[12px]">
|
|
231
|
+
<span class="caption text-grey7 w-[12px]">{$t('editor.position.y')}</span>
|
|
224
232
|
<span class="subtext text-grey7 w-[32px]">{formattedY}%</span>
|
|
225
233
|
</div>
|
|
226
234
|
</div>
|
|
@@ -228,8 +236,8 @@
|
|
|
228
236
|
</div>
|
|
229
237
|
|
|
230
238
|
<style>
|
|
231
|
-
/*
|
|
232
|
-
|
|
239
|
+
/* While dragging, everything inside the controller shows the grabbing cursor (this used to be a class on the page's body) */
|
|
240
|
+
.position-input-controller-dragging :global(*) {
|
|
233
241
|
cursor: grabbing !important;
|
|
234
242
|
user-select: none !important;
|
|
235
243
|
-webkit-user-select: none !important;
|
|
@@ -46,6 +46,13 @@
|
|
|
46
46
|
<script lang="ts">
|
|
47
47
|
// Svelte imports
|
|
48
48
|
import { onMount, onDestroy } from 'svelte';
|
|
49
|
+
import { listenForScroll } from '../../utils/shadowDom';
|
|
50
|
+
import { useEditorLanguage } from '../../i18n/language';
|
|
51
|
+
import type { EditorMessageKey } from '../../i18n/messages';
|
|
52
|
+
import { formatLocaleShortDate, type Translate } from '@widgetic/design-system/i18n';
|
|
53
|
+
|
|
54
|
+
// The words of the editor in the language of the page, and the language itself (how the calendar writes its months).
|
|
55
|
+
const { t, locale } = useEditorLanguage();
|
|
49
56
|
|
|
50
57
|
// Shadcn UI Components
|
|
51
58
|
import { Button, RangeCalendar } from '@widgetic/design-system/components';
|
|
@@ -109,26 +116,26 @@
|
|
|
109
116
|
* @param range - The date range to format
|
|
110
117
|
* @returns Formatted date range string
|
|
111
118
|
*/
|
|
112
|
-
function formatDateRange(
|
|
113
|
-
|
|
114
|
-
|
|
119
|
+
function formatDateRange(
|
|
120
|
+
range: DateRange | undefined,
|
|
121
|
+
languageTag: string,
|
|
122
|
+
translate: Translate<EditorMessageKey>,
|
|
123
|
+
): string {
|
|
124
|
+
if (!range || !range.start || !range.end) return translate('editor.date.pickRange');
|
|
125
|
+
|
|
115
126
|
try {
|
|
116
127
|
const formatDate = (date: DateValue) => {
|
|
117
128
|
if (!(date instanceof CalendarDate)) {
|
|
118
|
-
console.warn('Invalid date object:', date);
|
|
119
|
-
return '
|
|
129
|
+
console.warn('[Editor] Invalid date object in range control:', date);
|
|
130
|
+
return translate('editor.date.invalid');
|
|
120
131
|
}
|
|
121
|
-
|
|
122
|
-
const day = date.day.toString().padStart(2, '0');
|
|
123
|
-
const month = date.month.toString().padStart(2, '0');
|
|
124
|
-
const year = date.year.toString().slice(-2);
|
|
125
|
-
return `${day}/${month}/${year}`;
|
|
132
|
+
return formatLocaleShortDate({ year: date.year, month: date.month, day: date.day }, languageTag);
|
|
126
133
|
};
|
|
127
134
|
|
|
128
135
|
return `${formatDate(range.start)} - ${formatDate(range.end)}`;
|
|
129
136
|
} catch (error) {
|
|
130
|
-
console.
|
|
131
|
-
return
|
|
137
|
+
console.warn('[Editor] Error formatting date range:', error);
|
|
138
|
+
return translate('editor.date.pickRange');
|
|
132
139
|
}
|
|
133
140
|
}
|
|
134
141
|
|
|
@@ -177,25 +184,30 @@
|
|
|
177
184
|
closeDropdown();
|
|
178
185
|
}
|
|
179
186
|
|
|
180
|
-
//
|
|
187
|
+
// The control's own element, which says which tree (the page or a module's shadow root) to listen for scrolling in
|
|
188
|
+
let controllerElement: HTMLDivElement | undefined;
|
|
189
|
+
let stopListeningForScroll: () => void = () => {};
|
|
190
|
+
|
|
191
|
+
// Setup scroll event listener on mount (the page and, inside a module, the module's own panels: a scroll never
|
|
192
|
+
// leaves a shadow root, so a listener on the window alone does not hear about those)
|
|
181
193
|
onMount(() => {
|
|
182
|
-
|
|
194
|
+
stopListeningForScroll = listenForScroll(controllerElement, handleScroll);
|
|
183
195
|
});
|
|
184
196
|
|
|
185
197
|
// Cleanup scroll event listener on destroy
|
|
186
198
|
onDestroy(() => {
|
|
187
|
-
|
|
199
|
+
stopListeningForScroll();
|
|
188
200
|
});
|
|
189
201
|
</script>
|
|
190
202
|
|
|
191
203
|
<!-- Range Calendar Container -->
|
|
192
|
-
<div class="flex flex-col gap-2 widgetic-colors">
|
|
204
|
+
<div bind:this={controllerElement} class="flex flex-col gap-2 widgetic-colors">
|
|
193
205
|
<div class="flex items-center w-full relative">
|
|
194
206
|
<div class="flex-1 min-w-0">
|
|
195
207
|
<InputControllerHeader {label} helpText={helpText} />
|
|
196
208
|
</div>
|
|
197
209
|
|
|
198
|
-
<div class="absolute
|
|
210
|
+
<div class="absolute end-0">
|
|
199
211
|
<div
|
|
200
212
|
class="h-[48px] w-[185px] rounded-large border border-grey2 flex items-center bg-grey1 transition-colors duration-200 hover:border-forest-green"
|
|
201
213
|
on:focusin={onFocus}
|
|
@@ -206,16 +218,20 @@
|
|
|
206
218
|
<Button
|
|
207
219
|
id="range-calendar"
|
|
208
220
|
variant="ghost"
|
|
209
|
-
class="w-full h-full justify-start text-
|
|
221
|
+
class="w-full h-full justify-start text-start border-0 bg-transparent hover:bg-transparent focus:bg-transparent p-0"
|
|
210
222
|
>
|
|
211
|
-
<span class="subtext text-grey7 {!currentValue ? 'text-grey3' : ''}">{formatDateRange(currentValue)}</span>
|
|
223
|
+
<span class="subtext text-grey7 {!currentValue ? 'text-grey3' : ''}">{formatDateRange(currentValue, $locale, $t)}</span>
|
|
212
224
|
</Button>
|
|
213
225
|
</div>
|
|
214
226
|
</Popover.Trigger>
|
|
215
227
|
|
|
216
228
|
<Popover.Content class="w-auto p-0 z-50" align="start" sideOffset={4}>
|
|
217
229
|
<RangeCalendar
|
|
218
|
-
{
|
|
230
|
+
value={currentValue}
|
|
231
|
+
locale={$locale}
|
|
232
|
+
calendarLabel={$t('editor.date.rangeCalendarLabel')}
|
|
233
|
+
previousLabel={$t('editor.date.previousMonth')}
|
|
234
|
+
nextLabel={$t('editor.date.nextMonth')}
|
|
219
235
|
onValueChange={(v: BitsDateRange) => {
|
|
220
236
|
handleSelect(v)
|
|
221
237
|
}}
|
|
@@ -28,6 +28,11 @@
|
|
|
28
28
|
import Lock from '../icons/locked.svelte';
|
|
29
29
|
import Unlock from '../icons/unlocked.svelte';
|
|
30
30
|
import type { InputControllerConfig } from './index';
|
|
31
|
+
import { activeElementIn } from '../../utils/shadowDom';
|
|
32
|
+
import { useEditorLanguage } from '../../i18n/language';
|
|
33
|
+
|
|
34
|
+
// The words of the editor in the language of the page.
|
|
35
|
+
const { t } = useEditorLanguage();
|
|
31
36
|
|
|
32
37
|
// Constants
|
|
33
38
|
const MIN_RADIUS = 0;
|
|
@@ -63,19 +68,22 @@
|
|
|
63
68
|
|
|
64
69
|
if (isLocked) {
|
|
65
70
|
// When locked, update all corners to the same value
|
|
66
|
-
|
|
71
|
+
value = {
|
|
67
72
|
topLeft: newValue,
|
|
68
73
|
topRight: newValue,
|
|
69
74
|
bottomRight: newValue,
|
|
70
75
|
bottomLeft: newValue
|
|
71
|
-
}
|
|
76
|
+
};
|
|
72
77
|
} else {
|
|
73
78
|
// When unlocked, update only the specified corner
|
|
74
|
-
|
|
79
|
+
value = {
|
|
75
80
|
...value,
|
|
76
81
|
[corner]: newValue
|
|
77
|
-
}
|
|
82
|
+
};
|
|
78
83
|
}
|
|
84
|
+
// The fields show the change at once, in every corner it reached: the parent hands the value back only when it
|
|
85
|
+
// builds this control again, and until then the corners that were changed together kept showing the old number
|
|
86
|
+
onChange(value);
|
|
79
87
|
}
|
|
80
88
|
|
|
81
89
|
// Handler for input changes
|
|
@@ -167,9 +175,12 @@
|
|
|
167
175
|
|
|
168
176
|
// Reset focused corner if we're not focusing another input immediately
|
|
169
177
|
setTimeout(() => {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
178
|
+
// The focused element of the tree this control is in: inside a module `document.activeElement` is the module's
|
|
179
|
+
// host element, which is never one of the corners
|
|
180
|
+
const focusedElement = activeElementIn(input);
|
|
181
|
+
if (!focusedElement ||
|
|
182
|
+
!(focusedElement instanceof HTMLInputElement) ||
|
|
183
|
+
!Object.values(inputRefs).includes(focusedElement)) {
|
|
173
184
|
focusedCorner = null;
|
|
174
185
|
}
|
|
175
186
|
}, 0);
|
|
@@ -182,29 +193,31 @@
|
|
|
182
193
|
// If locking, normalize all values to the first focused corner or top-left
|
|
183
194
|
if (isLocked && focusedCorner) {
|
|
184
195
|
const referenceValue = value[focusedCorner];
|
|
185
|
-
|
|
196
|
+
value = {
|
|
186
197
|
topLeft: referenceValue,
|
|
187
198
|
topRight: referenceValue,
|
|
188
199
|
bottomRight: referenceValue,
|
|
189
200
|
bottomLeft: referenceValue
|
|
190
|
-
}
|
|
201
|
+
};
|
|
202
|
+
onChange(value);
|
|
191
203
|
}
|
|
192
204
|
}
|
|
193
205
|
|
|
206
|
+
// The outer corner of the picture of the box that each of the four fields draws. The corners of a box are named by
|
|
207
|
+
// their place on the screen, whatever way the screen is read, so these stay physical.
|
|
208
|
+
const CORNER_ROUNDING = {
|
|
209
|
+
topLeft: 'rounded-tl-[15px]', // rtl-ok: the corner of the picture of a box
|
|
210
|
+
topRight: 'rounded-tr-[15px]', // rtl-ok: the corner of the picture of a box
|
|
211
|
+
bottomLeft: 'rounded-bl-[15px]', // rtl-ok: the corner of the picture of a box
|
|
212
|
+
bottomRight: 'rounded-br-[15px]' // rtl-ok: the corner of the picture of a box
|
|
213
|
+
} as const;
|
|
214
|
+
|
|
194
215
|
// Get corner-specific CSS classes
|
|
195
216
|
function getCornerClasses(corner: keyof typeof value) {
|
|
196
|
-
const isTopLeft = corner === 'topLeft';
|
|
197
|
-
const isTopRight = corner === 'topRight';
|
|
198
|
-
const isBottomLeft = corner === 'bottomLeft';
|
|
199
|
-
const isBottomRight = corner === 'bottomRight';
|
|
200
|
-
|
|
201
217
|
return {
|
|
202
218
|
container: `
|
|
203
219
|
relative
|
|
204
|
-
${
|
|
205
|
-
${isTopRight ? 'rounded-tr-[15px]' : ''}
|
|
206
|
-
${isBottomLeft ? 'rounded-bl-[15px]' : ''}
|
|
207
|
-
${isBottomRight ? 'rounded-br-[15px]' : ''}
|
|
220
|
+
${CORNER_ROUNDING[corner]}
|
|
208
221
|
${focusedCorner === corner ? 'bg-sage-green' : 'bg-grey1'}
|
|
209
222
|
hover:bg-[${focusedCorner === corner ? 'hsl(var(--sage-green))' : 'hsl(var(--grey2))'}]
|
|
210
223
|
transition-colors
|
|
@@ -282,13 +295,13 @@
|
|
|
282
295
|
</script>
|
|
283
296
|
|
|
284
297
|
<!-- Main corner radius grid component -->
|
|
285
|
-
<div class="corners-container {className}" role="group" aria-label={label || '
|
|
298
|
+
<div class="corners-container {className}" role="group" aria-label={label || $t('editor.corners.group')} title={helpText}
|
|
286
299
|
on:mouseenter={handleMouseEnter}
|
|
287
300
|
on:mouseleave={handleMouseLeave}
|
|
288
301
|
on:focusin={onFocus}>
|
|
289
302
|
|
|
290
|
-
<!-- Corner radius grid -->
|
|
291
|
-
<div class="corners-grid">
|
|
303
|
+
<!-- Corner radius grid: four fields placed like the four corners of a box, top left first, so it is a left to right island -->
|
|
304
|
+
<div class="corners-grid" dir="ltr">
|
|
292
305
|
<!-- Top Left -->
|
|
293
306
|
<div class={getCornerClasses('topLeft').container}>
|
|
294
307
|
<input
|
|
@@ -303,7 +316,7 @@
|
|
|
303
316
|
on:blur={() => handleInputBlur('topLeft')}
|
|
304
317
|
on:keydown={(e) => handleKeyDown('topLeft', e)}
|
|
305
318
|
class={getCornerClasses('topLeft').input}
|
|
306
|
-
aria-label=
|
|
319
|
+
aria-label={$t('editor.corners.topLeft')}
|
|
307
320
|
maxlength="3"
|
|
308
321
|
/>
|
|
309
322
|
</div>
|
|
@@ -322,7 +335,7 @@
|
|
|
322
335
|
on:blur={() => handleInputBlur('topRight')}
|
|
323
336
|
on:keydown={(e) => handleKeyDown('topRight', e)}
|
|
324
337
|
class={getCornerClasses('topRight').input}
|
|
325
|
-
aria-label=
|
|
338
|
+
aria-label={$t('editor.corners.topRight')}
|
|
326
339
|
maxlength="3"
|
|
327
340
|
/>
|
|
328
341
|
</div>
|
|
@@ -341,7 +354,7 @@
|
|
|
341
354
|
on:blur={() => handleInputBlur('bottomLeft')}
|
|
342
355
|
on:keydown={(e) => handleKeyDown('bottomLeft', e)}
|
|
343
356
|
class={getCornerClasses('bottomLeft').input}
|
|
344
|
-
aria-label=
|
|
357
|
+
aria-label={$t('editor.corners.bottomLeft')}
|
|
345
358
|
maxlength="3"
|
|
346
359
|
/>
|
|
347
360
|
</div>
|
|
@@ -360,7 +373,7 @@
|
|
|
360
373
|
on:blur={() => handleInputBlur('bottomRight')}
|
|
361
374
|
on:keydown={(e) => handleKeyDown('bottomRight', e)}
|
|
362
375
|
class={getCornerClasses('bottomRight').input}
|
|
363
|
-
aria-label=
|
|
376
|
+
aria-label={$t('editor.corners.bottomRight')}
|
|
364
377
|
maxlength="3"
|
|
365
378
|
/>
|
|
366
379
|
</div>
|
|
@@ -369,7 +382,7 @@
|
|
|
369
382
|
<button
|
|
370
383
|
class="lock-button"
|
|
371
384
|
on:click={toggleLock}
|
|
372
|
-
aria-label={isLocked ?
|
|
385
|
+
aria-label={isLocked ? $t('editor.corners.unlock') : $t('editor.corners.lock')}
|
|
373
386
|
aria-pressed={isLocked}
|
|
374
387
|
>
|
|
375
388
|
<div class="flex items-center justify-center w-full h-full {isLocked ? 'text-sage-green' : 'text-grey7'}">
|
|
@@ -42,7 +42,11 @@
|
|
|
42
42
|
* Shadcn UI components from Widgetic design-system package
|
|
43
43
|
*/
|
|
44
44
|
import { Slider } from '@widgetic/design-system/components';
|
|
45
|
+
import { useTextDirection } from '@widgetic/design-system/components/textDirection';
|
|
45
46
|
import { InputControllerHeader } from './index';
|
|
47
|
+
|
|
48
|
+
// The way the screen is read: the slider of the design system starts at the right end on a right to left screen.
|
|
49
|
+
const textDirection = useTextDirection();
|
|
46
50
|
|
|
47
51
|
|
|
48
52
|
/**
|
|
@@ -186,7 +190,8 @@
|
|
|
186
190
|
if (!sliderElement) return;
|
|
187
191
|
|
|
188
192
|
const rect = sliderElement.getBoundingClientRect();
|
|
189
|
-
|
|
193
|
+
// The distance from the end where the track starts: its left end, or its right end when the screen is read from the right
|
|
194
|
+
const clickX = $textDirection === 'rtl' ? rect.right - mouseEvent.clientX : mouseEvent.clientX - rect.left;
|
|
190
195
|
const trackWidth = rect.width;
|
|
191
196
|
|
|
192
197
|
// Calculate the percentage position of the click
|
|
@@ -331,7 +336,7 @@
|
|
|
331
336
|
<!-- Main Container with subcomponents -->
|
|
332
337
|
<div
|
|
333
338
|
class="slider-main-container flex items-center justify-between rounded-large transition-colors duration-200"
|
|
334
|
-
style="height: {height}px; padding-
|
|
339
|
+
style="height: {height}px; padding-inline-start: 16px; padding-inline-end: 16px; gap: {elementsGap}px; border: 1px solid hsl(var(--grey2)); background: hsl(var(--grey1));"
|
|
335
340
|
onfocusin={onFocus}
|
|
336
341
|
bind:this={sliderContainer}
|
|
337
342
|
>
|
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
// Shadcn UI Components
|
|
18
18
|
import { Switch } from '@widgetic/design-system/components';
|
|
19
19
|
import { InputControllerHeader } from './index';
|
|
20
|
+
import { useEditorLanguage } from '../../i18n/language';
|
|
21
|
+
|
|
22
|
+
// The words of the editor in the language of the page.
|
|
23
|
+
const { t } = useEditorLanguage();
|
|
20
24
|
|
|
21
25
|
// Component props
|
|
22
26
|
export let label: SwitchInputControllerProps['label'];
|
|
@@ -38,7 +42,7 @@
|
|
|
38
42
|
<InputControllerHeader {label} />
|
|
39
43
|
</div>
|
|
40
44
|
|
|
41
|
-
<div class="absolute
|
|
45
|
+
<div class="absolute end-0">
|
|
42
46
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
43
47
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
44
48
|
<div
|
|
@@ -48,7 +52,7 @@
|
|
|
48
52
|
onclick={(e) => { if (e.target === e.currentTarget) toggle(); }}
|
|
49
53
|
>
|
|
50
54
|
<!-- Off text -->
|
|
51
|
-
<span class="subtext select-none pointer-events-none" style="color: hsl(var({!value ? '--grey7' : '--grey3'}));">
|
|
55
|
+
<span class="subtext select-none pointer-events-none" style="color: hsl(var({!value ? '--grey7' : '--grey3'}));">{$t('editor.switch.off')}</span>
|
|
52
56
|
|
|
53
57
|
<!-- Switch -->
|
|
54
58
|
<div class="flex items-center justify-center pointer-events-none widgetic-colors">
|
|
@@ -62,7 +66,7 @@
|
|
|
62
66
|
</div>
|
|
63
67
|
|
|
64
68
|
<!-- On text -->
|
|
65
|
-
<span class="subtext select-none pointer-events-none" style="color: hsl(var({value ? '--grey7' : '--grey3'}));">
|
|
69
|
+
<span class="subtext select-none pointer-events-none" style="color: hsl(var({value ? '--grey7' : '--grey3'}));">{$t('editor.switch.on')}</span>
|
|
66
70
|
</div>
|
|
67
71
|
</div>
|
|
68
72
|
</div>
|
|
@@ -116,4 +120,9 @@
|
|
|
116
120
|
transform: translateX(20px) !important;
|
|
117
121
|
border: 1px solid hsl(var(--grey6)) !important;
|
|
118
122
|
}
|
|
123
|
+
|
|
124
|
+
/* The thumb starts at the end of the track that the line starts at: on a right to left screen it is at the right, and it travels to the left. */
|
|
125
|
+
:global(.switch-override:dir(rtl) > *[data-state="checked"]) {
|
|
126
|
+
transform: translateX(-20px) !important;
|
|
127
|
+
}
|
|
119
128
|
</style>
|
|
@@ -30,6 +30,14 @@
|
|
|
30
30
|
import { onMount, onDestroy } from 'svelte';
|
|
31
31
|
import InputControllerHeader from './InputControllerHeader.svelte';
|
|
32
32
|
import { IconX } from '@widgetic/design-system/icons';
|
|
33
|
+
import { useEditorLanguage } from '../../i18n/language';
|
|
34
|
+
import { dirForUserText, useTextDirection } from '@widgetic/design-system/components/textDirection';
|
|
35
|
+
|
|
36
|
+
// The words of the editor in the language of the page.
|
|
37
|
+
const { t } = useEditorLanguage();
|
|
38
|
+
|
|
39
|
+
// The way the screen is read: a tag is what a person typed, so on a right to left screen it is read by its own first letter.
|
|
40
|
+
const textDirection = useTextDirection();
|
|
33
41
|
|
|
34
42
|
// Required props
|
|
35
43
|
export let label: TagsInputControllerProps['label'];
|
|
@@ -44,11 +52,15 @@
|
|
|
44
52
|
export { className as class };
|
|
45
53
|
|
|
46
54
|
// Use config values with fallbacks
|
|
47
|
-
|
|
55
|
+
// The hint in the box: the one the widget gave, or the editor's own word in the language of the page.
|
|
56
|
+
$: placeholder = config.placeholder || $t('editor.tags.add');
|
|
48
57
|
|
|
49
58
|
// Component state
|
|
50
59
|
let inputElement: HTMLInputElement;
|
|
51
60
|
let tagInputContainer: HTMLDivElement;
|
|
61
|
+
// What is typed in the box right now. An empty box has no first letter to be read by, and reads left to right by itself
|
|
62
|
+
// even on a right to left screen: so it takes the direction of the screen until something is typed (see fieldDirection.ts).
|
|
63
|
+
let typedTag = '';
|
|
52
64
|
|
|
53
65
|
function handleTagRemove(tagToRemove: string) {
|
|
54
66
|
const newTags = value.filter(tag => tag !== tagToRemove);
|
|
@@ -68,6 +80,7 @@
|
|
|
68
80
|
onChange(newTags);
|
|
69
81
|
}
|
|
70
82
|
input.value = '';
|
|
83
|
+
typedTag = '';
|
|
71
84
|
}
|
|
72
85
|
}
|
|
73
86
|
}
|
|
@@ -98,27 +111,29 @@
|
|
|
98
111
|
on:focusin={onFocus}
|
|
99
112
|
>
|
|
100
113
|
<!-- Input field with specific padding -->
|
|
101
|
-
<div class="pt-[12px]
|
|
114
|
+
<div class="pt-[12px] ps-[16px] pe-[16px]">
|
|
102
115
|
<input
|
|
103
116
|
bind:this={inputElement}
|
|
104
117
|
type="text"
|
|
105
118
|
autocomplete="off"
|
|
119
|
+
dir={typedTag ? dirForUserText($textDirection) : undefined}
|
|
106
120
|
class="w-full outline-none border-none bg-transparent subtext text-grey7 placeholder:text-grey3 placeholder:subtext"
|
|
107
121
|
placeholder={placeholder}
|
|
122
|
+
on:input={(event) => (typedTag = event.currentTarget.value)}
|
|
108
123
|
on:keydown={handleInputKeydown}
|
|
109
124
|
/>
|
|
110
125
|
</div>
|
|
111
126
|
|
|
112
127
|
<!-- Tags with specific padding -->
|
|
113
128
|
{#if value && value.length > 0}
|
|
114
|
-
<div class="flex flex-wrap gap-1
|
|
129
|
+
<div class="flex flex-wrap gap-1 ps-[16px] pe-[16px] pb-[16px] pt-[8px]">
|
|
115
130
|
{#each value as tag}
|
|
116
131
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
117
132
|
<div
|
|
118
133
|
class="tag-element bg-sage-green text-white border-none hover:bg-deep-forest-green cursor-pointer transition-colors flex items-center gap-1"
|
|
119
134
|
on:click|stopPropagation={() => handleTagRemove(tag)}
|
|
120
135
|
>
|
|
121
|
-
<span class="tag uppercase truncate max-w-[115px]">{tag}</span>
|
|
136
|
+
<span class="tag uppercase truncate max-w-[115px]" dir={dirForUserText($textDirection)}>{tag}</span>
|
|
122
137
|
<IconX class="w-4 h-4 text-white flex-shrink-0" />
|
|
123
138
|
</div>
|
|
124
139
|
{/each}
|