@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
|
@@ -74,6 +74,11 @@
|
|
|
74
74
|
import { SearchInput } from '@widgetic/design-system/components';
|
|
75
75
|
import CategoryTabs from './shared/CategoryTabs.svelte';
|
|
76
76
|
import { RecentItemsSection, ItemGrid } from './shared';
|
|
77
|
+
import { useEditorLanguage } from '../../i18n/language';
|
|
78
|
+
import { categoryLabel, fontWeightLabel } from '../../i18n/labels';
|
|
79
|
+
|
|
80
|
+
// The words of the editor in the language of the page.
|
|
81
|
+
const { t } = useEditorLanguage();
|
|
77
82
|
|
|
78
83
|
|
|
79
84
|
const FONT_FILTER_KEY = (id: string) => `font-specific-filter-state-${id}`;
|
|
@@ -1787,7 +1792,7 @@
|
|
|
1787
1792
|
toggleDropdown(controllerId);
|
|
1788
1793
|
}}
|
|
1789
1794
|
>
|
|
1790
|
-
<Popover.Trigger tabindex={-1} class="w-full h-full flex items-center border-0 bg-transparent hover:bg-transparent focus:bg-transparent
|
|
1795
|
+
<Popover.Trigger tabindex={-1} class="w-full h-full flex items-center border-0 bg-transparent hover:bg-transparent focus:bg-transparent ps-3 pe-[16px] group"
|
|
1791
1796
|
onmouseenter={() => {
|
|
1792
1797
|
// Preload fonts when hovering over the button
|
|
1793
1798
|
// This reduces the delay when actually clicking to open
|
|
@@ -1797,10 +1802,10 @@
|
|
|
1797
1802
|
}}
|
|
1798
1803
|
>
|
|
1799
1804
|
<span
|
|
1800
|
-
class="truncate flex-1 text-
|
|
1805
|
+
class="truncate flex-1 text-start subtext text-grey7"
|
|
1801
1806
|
style="font-family: {cssQuotedString(resolvedFont?.family || 'system-ui')}, sans-serif; font-display: swap;"
|
|
1802
1807
|
>
|
|
1803
|
-
{resolvedFont?.family ||
|
|
1808
|
+
{resolvedFont?.family || $t('editor.font.select')}
|
|
1804
1809
|
</span>
|
|
1805
1810
|
<IconCaretDownFilled class="w-5 h-5 shrink-0 text-grey5 group-hover:text-grey7" />
|
|
1806
1811
|
</Popover.Trigger>
|
|
@@ -1816,11 +1821,11 @@
|
|
|
1816
1821
|
<div class="flex flex-col h-full min-h-0 font-popover-content">
|
|
1817
1822
|
<!-- Title and upload icon -->
|
|
1818
1823
|
<div class="font-popover-header shrink-0 relative pt-6 pb-[11px] px-5">
|
|
1819
|
-
<div class="h5 text-grey7 widgetic-colors">
|
|
1824
|
+
<div class="h5 text-grey7 widgetic-colors">{$t('editor.font.title')}</div>
|
|
1820
1825
|
<button
|
|
1821
1826
|
type="button"
|
|
1822
1827
|
tabindex="-1"
|
|
1823
|
-
class="absolute
|
|
1828
|
+
class="absolute end-6 top-[26px] bg-transparent border-0 cursor-pointer p-0 rounded-full hover:bg-grey3 transition-all duration-300"
|
|
1824
1829
|
onclick={() => {
|
|
1825
1830
|
try {
|
|
1826
1831
|
openFileBrowser();
|
|
@@ -1840,7 +1845,7 @@
|
|
|
1840
1845
|
<!-- Search input -->
|
|
1841
1846
|
<div class="font-popover-search shrink-0 relative px-4">
|
|
1842
1847
|
<SearchInput
|
|
1843
|
-
placeholder=
|
|
1848
|
+
placeholder={$t('editor.font.search')}
|
|
1844
1849
|
bind:value={searchTerm}
|
|
1845
1850
|
onInput={(e: Event) => {
|
|
1846
1851
|
// Use debounced search only for typing
|
|
@@ -1868,6 +1873,7 @@
|
|
|
1868
1873
|
categories={filterOptions}
|
|
1869
1874
|
activeCategory={currentFilter}
|
|
1870
1875
|
onCategorySelect={handleFilterChange}
|
|
1876
|
+
getCategoryLabel={(category) => categoryLabel(category, $t)}
|
|
1871
1877
|
filterVisible={!searchTerm}
|
|
1872
1878
|
/>
|
|
1873
1879
|
</div>
|
|
@@ -1884,14 +1890,13 @@
|
|
|
1884
1890
|
}}
|
|
1885
1891
|
id={`font-list-${controllerId}`}
|
|
1886
1892
|
role="listbox"
|
|
1887
|
-
aria-label=
|
|
1893
|
+
aria-label={$t('editor.font.list')}
|
|
1888
1894
|
>
|
|
1889
1895
|
<div class="space-y-0">
|
|
1890
1896
|
<!-- Recently used section - only show when not searching -->
|
|
1891
1897
|
{#if recentFonts.length > 0 && !searchTerm}
|
|
1892
1898
|
<RecentItemsSection
|
|
1893
1899
|
recentItems={recentFonts}
|
|
1894
|
-
title="Recently used"
|
|
1895
1900
|
onItemSelect={handleFontSelect}
|
|
1896
1901
|
onItemDelete={handleFontDelete}
|
|
1897
1902
|
isItemSelected={isFontSelected}
|
|
@@ -1912,9 +1917,9 @@
|
|
|
1912
1917
|
<div id="font-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">
|
|
1913
1918
|
<h3 class="subtext text-grey7 pb-2 flex items-center">
|
|
1914
1919
|
{#if isInitialLoading}
|
|
1915
|
-
<div class="w-4 h-4 border-2 border-t-sage-green border-
|
|
1920
|
+
<div class="w-4 h-4 border-2 border-t-sage-green border-e-sage-green border-b-grey2 border-s-grey2 rounded-full animate-spin me-2"></div>
|
|
1916
1921
|
{/if}
|
|
1917
|
-
{searchTerm ? '
|
|
1922
|
+
{searchTerm ? $t('editor.list.searchResults') : $t('editor.list.more')}
|
|
1918
1923
|
</h3>
|
|
1919
1924
|
</div>
|
|
1920
1925
|
{/if}
|
|
@@ -1924,13 +1929,13 @@
|
|
|
1924
1929
|
{#if isInitialLoading}
|
|
1925
1930
|
<!-- Show compact loading indicator while fonts are loading -->
|
|
1926
1931
|
<div class="flex items-center">
|
|
1927
|
-
<div class="text-grey5 subtext
|
|
1928
|
-
<div class="w-4 h-4 border-2 border-t-sage-green border-
|
|
1932
|
+
<div class="text-grey5 subtext me-2">{$t('editor.font.loading')}</div>
|
|
1933
|
+
<div class="w-4 h-4 border-2 border-t-sage-green border-e-sage-green border-b-grey2 border-s-grey2 rounded-full animate-spin"></div>
|
|
1929
1934
|
</div>
|
|
1930
1935
|
{:else}
|
|
1931
1936
|
<!-- Only show empty state when loading is complete and no fonts were found -->
|
|
1932
1937
|
<EmptyState
|
|
1933
|
-
message={searchTerm ? '
|
|
1938
|
+
message={searchTerm ? $t('editor.font.noneFound') : $t('editor.font.noneAvailable')}
|
|
1934
1939
|
height="auto"
|
|
1935
1940
|
/>
|
|
1936
1941
|
{/if}
|
|
@@ -1953,7 +1958,7 @@
|
|
|
1953
1958
|
style="color: {isFontSelected(font) ? '#ffffff' : 'hsl(var(--grey7))'};"
|
|
1954
1959
|
>
|
|
1955
1960
|
<div class="flex items-center flex-1 min-w-0 group">
|
|
1956
|
-
<Check class="
|
|
1961
|
+
<Check class="me-2 h-6 w-6 flex-shrink-0 {isFontSelected(font) ? 'text-white' : 'text-sage-green opacity-0'}"/>
|
|
1957
1962
|
<span
|
|
1958
1963
|
class="text-[24px] truncate {index === activeSearchIndex && searchTerm ? 'font-medium' : ''}"
|
|
1959
1964
|
style="font-family: {cssQuotedString(font.family)}, sans-serif; font-display: swap; color: inherit; -webkit-text-fill-color: currentColor;"
|
|
@@ -1965,7 +1970,7 @@
|
|
|
1965
1970
|
<div
|
|
1966
1971
|
role="button"
|
|
1967
1972
|
tabindex="-1"
|
|
1968
|
-
class="
|
|
1973
|
+
class="ms-auto transition-opacity {resolvedFont?.family === font.family ? 'opacity-0 pointer-events-none' : 'opacity-0 group-hover:opacity-100'}"
|
|
1969
1974
|
onclick={(e) => {
|
|
1970
1975
|
e.stopPropagation();
|
|
1971
1976
|
e.preventDefault();
|
|
@@ -1974,11 +1979,11 @@
|
|
|
1974
1979
|
>
|
|
1975
1980
|
<img
|
|
1976
1981
|
src={icTrash}
|
|
1977
|
-
alt=
|
|
1982
|
+
alt={$t('editor.font.remove')}
|
|
1978
1983
|
class="w-4 h-4 text-grey5 hover:text-error"
|
|
1979
1984
|
draggable="false"
|
|
1980
1985
|
/>
|
|
1981
|
-
<span class="sr-only">
|
|
1986
|
+
<span class="sr-only">{$t('editor.font.remove')}</span>
|
|
1982
1987
|
</div>
|
|
1983
1988
|
{/if}
|
|
1984
1989
|
</div>
|
|
@@ -1997,7 +2002,7 @@
|
|
|
1997
2002
|
|
|
1998
2003
|
<div class="font-weight-row flex items-center justify-between w-full min-w-0 gap-2" style="min-height: 48px;">
|
|
1999
2004
|
<div class="font-weight-label-ct flex-1 min-w-0">
|
|
2000
|
-
<InputControllerHeader label={label ? '
|
|
2005
|
+
<InputControllerHeader label={label ? $t('editor.font.weight') : ''} />
|
|
2001
2006
|
</div>
|
|
2002
2007
|
<div class="font-weight-input-ct h-12 shrink-0 rounded-large border border-grey2 flex items-center bg-grey1 transition-colors duration-200 hover:border-forest-green" style="width: 185px; max-width: 185px; min-width: 185px;">
|
|
2003
2008
|
<Popover.Root
|
|
@@ -2009,15 +2014,15 @@
|
|
|
2009
2014
|
}
|
|
2010
2015
|
}}
|
|
2011
2016
|
>
|
|
2012
|
-
<Popover.Trigger tabindex={-1} class="w-full h-full flex items-center border-0 bg-transparent hover:bg-transparent focus:bg-transparent
|
|
2013
|
-
<span class="truncate flex-1 text-
|
|
2014
|
-
{displayWeight || 'Regular'}
|
|
2017
|
+
<Popover.Trigger tabindex={-1} class="w-full h-full flex items-center border-0 bg-transparent hover:bg-transparent focus:bg-transparent ps-3 pe-[16px] group">
|
|
2018
|
+
<span class="truncate flex-1 text-start subtext text-grey7">
|
|
2019
|
+
{fontWeightLabel(displayWeight || 'Regular', $t)}
|
|
2015
2020
|
</span>
|
|
2016
2021
|
<IconCaretDownFilled class="w-5 h-5 shrink-0 text-grey5 group-hover:text-grey7" />
|
|
2017
2022
|
</Popover.Trigger>
|
|
2018
2023
|
<Popover.Content class="font-input-popover p-0 w-[150px]" portalProps={{ to: fontPickerPortalHost }}>
|
|
2019
2024
|
<Command.Root>
|
|
2020
|
-
<Command.List>
|
|
2025
|
+
<Command.List aria-label={$t('editor.font.weightList')}>
|
|
2021
2026
|
<Command.Group>
|
|
2022
2027
|
{#each availableWeights as weight}
|
|
2023
2028
|
<Command.Item
|
|
@@ -2026,9 +2031,9 @@
|
|
|
2026
2031
|
class="px-2 py-2"
|
|
2027
2032
|
>
|
|
2028
2033
|
<Check
|
|
2029
|
-
class="
|
|
2034
|
+
class="me-1 h-4 w-4 {displayWeight === weight ? 'opacity-100' : 'opacity-0'}"
|
|
2030
2035
|
/>
|
|
2031
|
-
<span class="truncate">{weight}</span>
|
|
2036
|
+
<span class="truncate">{fontWeightLabel(weight, $t)}</span>
|
|
2032
2037
|
</Command.Item>
|
|
2033
2038
|
{/each}
|
|
2034
2039
|
</Command.Group>
|
|
@@ -2064,7 +2069,7 @@
|
|
|
2064
2069
|
width: 185px !important;
|
|
2065
2070
|
max-width: 185px !important;
|
|
2066
2071
|
min-width: 185px !important;
|
|
2067
|
-
margin-
|
|
2072
|
+
margin-inline-start: auto;
|
|
2068
2073
|
flex-shrink: 0;
|
|
2069
2074
|
position: relative;
|
|
2070
2075
|
}
|
|
@@ -2211,12 +2216,12 @@
|
|
|
2211
2216
|
|
|
2212
2217
|
/* Style for checkmark icon */
|
|
2213
2218
|
:global(.checkmark-icon) {
|
|
2214
|
-
margin-
|
|
2219
|
+
margin-inline-start: 12px !important;
|
|
2215
2220
|
}
|
|
2216
2221
|
|
|
2217
2222
|
/* Style for font item padding */
|
|
2218
2223
|
:global(.font-item) {
|
|
2219
|
-
padding-
|
|
2224
|
+
padding-inline-start: 48px !important;
|
|
2220
2225
|
}
|
|
2221
2226
|
|
|
2222
2227
|
/* Style for font item height */
|
|
@@ -12,6 +12,10 @@ import { Tooltip, Portal } from '@widgetic/design-system/components';
|
|
|
12
12
|
// Lucide Icons from design-system
|
|
13
13
|
import { Info } from '@widgetic/design-system/icons';
|
|
14
14
|
|
|
15
|
+
// The way the screen is read: the tip opens at the end of the line, beside the icon
|
|
16
|
+
import { sideAtEnd, useTextDirection } from '@widgetic/design-system/components/textDirection';
|
|
17
|
+
const textDirection = useTextDirection();
|
|
18
|
+
|
|
15
19
|
// Component props
|
|
16
20
|
let className = '';
|
|
17
21
|
export { className as class };
|
|
@@ -106,7 +110,7 @@ onDestroy(() => {
|
|
|
106
110
|
{#if showHelp && !isScrolling}
|
|
107
111
|
<Portal target="editor-tooltips">
|
|
108
112
|
<Tooltip.Content
|
|
109
|
-
side=
|
|
113
|
+
side={sideAtEnd($textDirection)}
|
|
110
114
|
sideOffset={5}
|
|
111
115
|
align="center"
|
|
112
116
|
alignOffset={-1}
|
|
@@ -232,10 +232,10 @@
|
|
|
232
232
|
oninput={handleInput}
|
|
233
233
|
onblur={handleBlur}
|
|
234
234
|
readonly={isLocked}
|
|
235
|
-
class="w-full h-full border-0 bg-transparent subtext text-grey7 placeholder:text-grey3 focus-visible:ring-0 focus-visible:ring-offset-0
|
|
235
|
+
class="w-full h-full border-0 bg-transparent subtext text-grey7 placeholder:text-grey3 focus-visible:ring-0 focus-visible:ring-offset-0 ps-4 {customInputClass}"
|
|
236
236
|
/>
|
|
237
237
|
{#if suffix}
|
|
238
|
-
<span class="subtext text-grey7 pointer-events-none -
|
|
238
|
+
<span class="subtext text-grey7 pointer-events-none -ms-1">
|
|
239
239
|
{suffix}
|
|
240
240
|
</span>
|
|
241
241
|
{/if}
|
|
@@ -279,10 +279,10 @@
|
|
|
279
279
|
|
|
280
280
|
<style>
|
|
281
281
|
:global([id^="number-input-"]) {
|
|
282
|
-
text-align:
|
|
282
|
+
text-align: start;
|
|
283
283
|
appearance: textfield;
|
|
284
284
|
-moz-appearance: textfield;
|
|
285
|
-
padding-
|
|
285
|
+
padding-inline-end: 0 !important;
|
|
286
286
|
}
|
|
287
287
|
|
|
288
288
|
:global([id^="number-input-"]::-webkit-outer-spin-button),
|
|
@@ -373,7 +373,7 @@
|
|
|
373
373
|
<InputControllerHeader {label} helpText={helpText} />
|
|
374
374
|
</div>
|
|
375
375
|
|
|
376
|
-
<div class="absolute
|
|
376
|
+
<div class="absolute end-0">
|
|
377
377
|
<NumberControls
|
|
378
378
|
{value}
|
|
379
379
|
{onChange}
|
|
@@ -393,7 +393,7 @@
|
|
|
393
393
|
|
|
394
394
|
<style>
|
|
395
395
|
:global(#number-input) {
|
|
396
|
-
text-align:
|
|
396
|
+
text-align: start;
|
|
397
397
|
appearance: textfield;
|
|
398
398
|
-moz-appearance: textfield;
|
|
399
399
|
}
|
|
@@ -38,6 +38,10 @@
|
|
|
38
38
|
|
|
39
39
|
// The cursor of a drag is shown on the module's element, not on the body of the customer's page
|
|
40
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();
|
|
41
45
|
|
|
42
46
|
/**
|
|
43
47
|
* Core state and configuration props
|
|
@@ -191,6 +195,7 @@
|
|
|
191
195
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
192
196
|
<div
|
|
193
197
|
bind:this={joystickContainer}
|
|
198
|
+
dir="ltr"
|
|
194
199
|
class="relative w-[185px] h-[185px] bg-grey1 rounded-large border border-grey2 joystick-container transition-colors duration-200 hover:border-forest-green"
|
|
195
200
|
class:overflow-hidden={shouldMaskThumb}
|
|
196
201
|
on:mouseenter={handleMouseEnter}
|
|
@@ -208,22 +213,22 @@
|
|
|
208
213
|
<div
|
|
209
214
|
bind:this={joystickHandle}
|
|
210
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'}"
|
|
211
|
-
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 */"
|
|
212
217
|
/>
|
|
213
218
|
|
|
214
219
|
<!-- Percentage display with dynamic positioning -->
|
|
215
220
|
<div
|
|
216
|
-
class="absolute
|
|
221
|
+
class="absolute start-4 end-4 flex justify-between items-center transition-all duration-300 ease-in-out"
|
|
217
222
|
class:bottom-2={!shouldShowAtTop}
|
|
218
223
|
class:top-2={shouldShowAtTop}
|
|
219
224
|
style="opacity: {isDragging ? 0.5 : 1}"
|
|
220
225
|
>
|
|
221
226
|
<div class="flex items-center gap-2 w-[50px]">
|
|
222
|
-
<span class="caption text-grey7 w-[12px]">
|
|
227
|
+
<span class="caption text-grey7 w-[12px]">{$t('editor.position.x')}</span>
|
|
223
228
|
<span class="subtext text-grey7 w-[32px]">{formattedX}%</span>
|
|
224
229
|
</div>
|
|
225
230
|
<div class="flex items-center gap-2 w-[50px]">
|
|
226
|
-
<span class="caption text-grey7 w-[12px]">
|
|
231
|
+
<span class="caption text-grey7 w-[12px]">{$t('editor.position.y')}</span>
|
|
227
232
|
<span class="subtext text-grey7 w-[32px]">{formattedY}%</span>
|
|
228
233
|
</div>
|
|
229
234
|
</div>
|
|
@@ -47,6 +47,12 @@
|
|
|
47
47
|
// Svelte imports
|
|
48
48
|
import { onMount, onDestroy } from 'svelte';
|
|
49
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();
|
|
50
56
|
|
|
51
57
|
// Shadcn UI Components
|
|
52
58
|
import { Button, RangeCalendar } from '@widgetic/design-system/components';
|
|
@@ -110,26 +116,26 @@
|
|
|
110
116
|
* @param range - The date range to format
|
|
111
117
|
* @returns Formatted date range string
|
|
112
118
|
*/
|
|
113
|
-
function formatDateRange(
|
|
114
|
-
|
|
115
|
-
|
|
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
|
+
|
|
116
126
|
try {
|
|
117
127
|
const formatDate = (date: DateValue) => {
|
|
118
128
|
if (!(date instanceof CalendarDate)) {
|
|
119
|
-
console.warn('Invalid date object:', date);
|
|
120
|
-
return '
|
|
129
|
+
console.warn('[Editor] Invalid date object in range control:', date);
|
|
130
|
+
return translate('editor.date.invalid');
|
|
121
131
|
}
|
|
122
|
-
|
|
123
|
-
const day = date.day.toString().padStart(2, '0');
|
|
124
|
-
const month = date.month.toString().padStart(2, '0');
|
|
125
|
-
const year = date.year.toString().slice(-2);
|
|
126
|
-
return `${day}/${month}/${year}`;
|
|
132
|
+
return formatLocaleShortDate({ year: date.year, month: date.month, day: date.day }, languageTag);
|
|
127
133
|
};
|
|
128
134
|
|
|
129
135
|
return `${formatDate(range.start)} - ${formatDate(range.end)}`;
|
|
130
136
|
} catch (error) {
|
|
131
|
-
console.
|
|
132
|
-
return
|
|
137
|
+
console.warn('[Editor] Error formatting date range:', error);
|
|
138
|
+
return translate('editor.date.pickRange');
|
|
133
139
|
}
|
|
134
140
|
}
|
|
135
141
|
|
|
@@ -201,7 +207,7 @@
|
|
|
201
207
|
<InputControllerHeader {label} helpText={helpText} />
|
|
202
208
|
</div>
|
|
203
209
|
|
|
204
|
-
<div class="absolute
|
|
210
|
+
<div class="absolute end-0">
|
|
205
211
|
<div
|
|
206
212
|
class="h-[48px] w-[185px] rounded-large border border-grey2 flex items-center bg-grey1 transition-colors duration-200 hover:border-forest-green"
|
|
207
213
|
on:focusin={onFocus}
|
|
@@ -212,16 +218,20 @@
|
|
|
212
218
|
<Button
|
|
213
219
|
id="range-calendar"
|
|
214
220
|
variant="ghost"
|
|
215
|
-
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"
|
|
216
222
|
>
|
|
217
|
-
<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>
|
|
218
224
|
</Button>
|
|
219
225
|
</div>
|
|
220
226
|
</Popover.Trigger>
|
|
221
227
|
|
|
222
228
|
<Popover.Content class="w-auto p-0 z-50" align="start" sideOffset={4}>
|
|
223
229
|
<RangeCalendar
|
|
224
|
-
{
|
|
230
|
+
value={currentValue}
|
|
231
|
+
locale={$locale}
|
|
232
|
+
calendarLabel={$t('editor.date.rangeCalendarLabel')}
|
|
233
|
+
previousLabel={$t('editor.date.previousMonth')}
|
|
234
|
+
nextLabel={$t('editor.date.nextMonth')}
|
|
225
235
|
onValueChange={(v: BitsDateRange) => {
|
|
226
236
|
handleSelect(v)
|
|
227
237
|
}}
|
|
@@ -29,6 +29,10 @@
|
|
|
29
29
|
import Unlock from '../icons/unlocked.svelte';
|
|
30
30
|
import type { InputControllerConfig } from './index';
|
|
31
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();
|
|
32
36
|
|
|
33
37
|
// Constants
|
|
34
38
|
const MIN_RADIUS = 0;
|
|
@@ -199,20 +203,21 @@
|
|
|
199
203
|
}
|
|
200
204
|
}
|
|
201
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
|
+
|
|
202
215
|
// Get corner-specific CSS classes
|
|
203
216
|
function getCornerClasses(corner: keyof typeof value) {
|
|
204
|
-
const isTopLeft = corner === 'topLeft';
|
|
205
|
-
const isTopRight = corner === 'topRight';
|
|
206
|
-
const isBottomLeft = corner === 'bottomLeft';
|
|
207
|
-
const isBottomRight = corner === 'bottomRight';
|
|
208
|
-
|
|
209
217
|
return {
|
|
210
218
|
container: `
|
|
211
219
|
relative
|
|
212
|
-
${
|
|
213
|
-
${isTopRight ? 'rounded-tr-[15px]' : ''}
|
|
214
|
-
${isBottomLeft ? 'rounded-bl-[15px]' : ''}
|
|
215
|
-
${isBottomRight ? 'rounded-br-[15px]' : ''}
|
|
220
|
+
${CORNER_ROUNDING[corner]}
|
|
216
221
|
${focusedCorner === corner ? 'bg-sage-green' : 'bg-grey1'}
|
|
217
222
|
hover:bg-[${focusedCorner === corner ? 'hsl(var(--sage-green))' : 'hsl(var(--grey2))'}]
|
|
218
223
|
transition-colors
|
|
@@ -290,13 +295,13 @@
|
|
|
290
295
|
</script>
|
|
291
296
|
|
|
292
297
|
<!-- Main corner radius grid component -->
|
|
293
|
-
<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}
|
|
294
299
|
on:mouseenter={handleMouseEnter}
|
|
295
300
|
on:mouseleave={handleMouseLeave}
|
|
296
301
|
on:focusin={onFocus}>
|
|
297
302
|
|
|
298
|
-
<!-- Corner radius grid -->
|
|
299
|
-
<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">
|
|
300
305
|
<!-- Top Left -->
|
|
301
306
|
<div class={getCornerClasses('topLeft').container}>
|
|
302
307
|
<input
|
|
@@ -311,7 +316,7 @@
|
|
|
311
316
|
on:blur={() => handleInputBlur('topLeft')}
|
|
312
317
|
on:keydown={(e) => handleKeyDown('topLeft', e)}
|
|
313
318
|
class={getCornerClasses('topLeft').input}
|
|
314
|
-
aria-label=
|
|
319
|
+
aria-label={$t('editor.corners.topLeft')}
|
|
315
320
|
maxlength="3"
|
|
316
321
|
/>
|
|
317
322
|
</div>
|
|
@@ -330,7 +335,7 @@
|
|
|
330
335
|
on:blur={() => handleInputBlur('topRight')}
|
|
331
336
|
on:keydown={(e) => handleKeyDown('topRight', e)}
|
|
332
337
|
class={getCornerClasses('topRight').input}
|
|
333
|
-
aria-label=
|
|
338
|
+
aria-label={$t('editor.corners.topRight')}
|
|
334
339
|
maxlength="3"
|
|
335
340
|
/>
|
|
336
341
|
</div>
|
|
@@ -349,7 +354,7 @@
|
|
|
349
354
|
on:blur={() => handleInputBlur('bottomLeft')}
|
|
350
355
|
on:keydown={(e) => handleKeyDown('bottomLeft', e)}
|
|
351
356
|
class={getCornerClasses('bottomLeft').input}
|
|
352
|
-
aria-label=
|
|
357
|
+
aria-label={$t('editor.corners.bottomLeft')}
|
|
353
358
|
maxlength="3"
|
|
354
359
|
/>
|
|
355
360
|
</div>
|
|
@@ -368,7 +373,7 @@
|
|
|
368
373
|
on:blur={() => handleInputBlur('bottomRight')}
|
|
369
374
|
on:keydown={(e) => handleKeyDown('bottomRight', e)}
|
|
370
375
|
class={getCornerClasses('bottomRight').input}
|
|
371
|
-
aria-label=
|
|
376
|
+
aria-label={$t('editor.corners.bottomRight')}
|
|
372
377
|
maxlength="3"
|
|
373
378
|
/>
|
|
374
379
|
</div>
|
|
@@ -377,7 +382,7 @@
|
|
|
377
382
|
<button
|
|
378
383
|
class="lock-button"
|
|
379
384
|
on:click={toggleLock}
|
|
380
|
-
aria-label={isLocked ?
|
|
385
|
+
aria-label={isLocked ? $t('editor.corners.unlock') : $t('editor.corners.lock')}
|
|
381
386
|
aria-pressed={isLocked}
|
|
382
387
|
>
|
|
383
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}
|