@widgetic/design-system 0.5.15 → 0.5.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/README.md +167 -0
  2. package/dist/components/CopyableHeader.svelte +25 -11
  3. package/dist/components/CopyableHeader.svelte.d.ts +4 -0
  4. package/dist/components/EmptyState.svelte +1 -1
  5. package/dist/components/PasswordInput.svelte +2 -2
  6. package/dist/components/WScrollArea.svelte +2 -2
  7. package/dist/components/ic/browser/BrowserInputController.svelte +2 -2
  8. package/dist/components/index.d.ts +1 -0
  9. package/dist/components/modals/ActionModal.svelte +3 -0
  10. package/dist/components/modals/ActionModal.svelte.d.ts +1 -0
  11. package/dist/components/modals/BaseModal.svelte +4 -2
  12. package/dist/components/modals/BaseModal.svelte.d.ts +1 -0
  13. package/dist/components/modals/NewItemDialog.svelte +48 -30
  14. package/dist/components/modals/NewItemDialog.svelte.d.ts +3 -0
  15. package/dist/components/modals/Portal.svelte +9 -1
  16. package/dist/components/modals/README.md +34 -1
  17. package/dist/components/modals/RenameDialog.svelte +29 -9
  18. package/dist/components/modals/RenameDialog.svelte.d.ts +3 -0
  19. package/dist/components/modals/messages.d.ts +39 -0
  20. package/dist/components/modals/messages.js +10 -0
  21. package/dist/components/navigation/Breadcrumbs.svelte +30 -17
  22. package/dist/components/navigation/Breadcrumbs.svelte.d.ts +4 -0
  23. package/dist/components/navigation/types.d.ts +8 -0
  24. package/dist/components/pdf/PDFViewer.svelte +17 -8
  25. package/dist/components/pdf/PDFViewer.svelte.d.ts +5 -0
  26. package/dist/components/search-input/SearchInput.svelte +3 -3
  27. package/dist/components/shad-editor/colorpicker.svelte +4 -1
  28. package/dist/components/shad-editor/custom/Extentions/ImageExtention.d.ts +20 -1
  29. package/dist/components/shad-editor/custom/Extentions/ImageExtention.js +4 -1
  30. package/dist/components/shad-editor/custom/code-extended.svelte +4 -2
  31. package/dist/components/shad-editor/custom/image-extended-component.svelte +54 -32
  32. package/dist/components/shad-editor/editor.css +5 -5
  33. package/dist/components/shad-editor/i18n/context.d.ts +6 -0
  34. package/dist/components/shad-editor/i18n/context.js +11 -0
  35. package/dist/components/shad-editor/i18n/index.d.ts +8 -0
  36. package/dist/components/shad-editor/i18n/index.js +11 -0
  37. package/dist/components/shad-editor/i18n/messages.d.ts +109 -0
  38. package/dist/components/shad-editor/i18n/messages.js +116 -0
  39. package/dist/components/shad-editor/icons/block-quote.svelte +3 -1
  40. package/dist/components/shad-editor/icons/bold.svelte +3 -1
  41. package/dist/components/shad-editor/icons/buttle-list.svelte +3 -1
  42. package/dist/components/shad-editor/icons/code.svelte +3 -1
  43. package/dist/components/shad-editor/icons/highlighter.svelte +5 -3
  44. package/dist/components/shad-editor/icons/image.svelte +7 -5
  45. package/dist/components/shad-editor/icons/italic.svelte +3 -1
  46. package/dist/components/shad-editor/icons/link.svelte +8 -6
  47. package/dist/components/shad-editor/icons/ordered-list.svelte +3 -1
  48. package/dist/components/shad-editor/icons/quickcolor.svelte +24 -22
  49. package/dist/components/shad-editor/icons/redo.svelte +3 -1
  50. package/dist/components/shad-editor/icons/search-replace.svelte +16 -14
  51. package/dist/components/shad-editor/icons/strikethrough.svelte +3 -1
  52. package/dist/components/shad-editor/icons/subscript.svelte +3 -1
  53. package/dist/components/shad-editor/icons/superscript.svelte +3 -1
  54. package/dist/components/shad-editor/icons/table.svelte +21 -19
  55. package/dist/components/shad-editor/icons/task-list.svelte +3 -1
  56. package/dist/components/shad-editor/icons/text.svelte +13 -11
  57. package/dist/components/shad-editor/icons/textalign.svelte +11 -9
  58. package/dist/components/shad-editor/icons/textcolor.svelte +5 -3
  59. package/dist/components/shad-editor/icons/underline.svelte +3 -1
  60. package/dist/components/shad-editor/icons/undo.svelte +3 -1
  61. package/dist/components/shad-editor/index.d.ts +1 -0
  62. package/dist/components/shad-editor/index.js +2 -0
  63. package/dist/components/shad-editor/shad-editor.svelte +32 -3
  64. package/dist/components/shad-editor/shad-editor.svelte.d.ts +12 -0
  65. package/dist/components/textDirection.d.ts +91 -0
  66. package/dist/components/textDirection.js +99 -0
  67. package/dist/components/toast/ToasterModal.svelte +49 -14
  68. package/dist/components/toast/ToasterModal.svelte.d.ts +9 -2
  69. package/dist/components/toast/toasterRoots.d.ts +23 -0
  70. package/dist/components/toast/toasterRoots.js +36 -0
  71. package/dist/components/ui/alert/index.d.ts +2 -2
  72. package/dist/components/ui/alert/index.js +1 -1
  73. package/dist/components/ui/breadcrumb/breadcrumb-separator.svelte +1 -1
  74. package/dist/components/ui/calendar/calendar-next-button.svelte +21 -4
  75. package/dist/components/ui/calendar/calendar-next-button.svelte.d.ts +9 -2
  76. package/dist/components/ui/calendar/calendar-prev-button.svelte +21 -4
  77. package/dist/components/ui/calendar/calendar-prev-button.svelte.d.ts +9 -2
  78. package/dist/components/ui/calendar/calendar.svelte +18 -6
  79. package/dist/components/ui/calendar/calendar.svelte.d.ts +6 -1
  80. package/dist/components/ui/command/command-input.svelte +1 -1
  81. package/dist/components/ui/command/command-shortcut.svelte +1 -1
  82. package/dist/components/ui/dialog/dialog-content.svelte +5 -2
  83. package/dist/components/ui/dialog/dialog-content.svelte.d.ts +1 -0
  84. package/dist/components/ui/dialog/dialog-header.svelte +1 -1
  85. package/dist/components/ui/drawer/drawer-header.svelte +1 -1
  86. package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +2 -2
  87. package/dist/components/ui/dropdown-menu/dropdown-menu-content.svelte +6 -0
  88. package/dist/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +1 -1
  89. package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte +1 -1
  90. package/dist/components/ui/dropdown-menu/dropdown-menu-label.svelte +1 -1
  91. package/dist/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +2 -2
  92. package/dist/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +1 -1
  93. package/dist/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +7 -1
  94. package/dist/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +2 -2
  95. package/dist/components/ui/hover-card/hover-card-content.svelte +6 -0
  96. package/dist/components/ui/input-otp/input-otp-slot.svelte +1 -1
  97. package/dist/components/ui/popover/popover-content.svelte +6 -0
  98. package/dist/components/ui/progress/progress.svelte +2 -2
  99. package/dist/components/ui/range-calendar/range-calendar-cell.svelte +1 -1
  100. package/dist/components/ui/range-calendar/range-calendar-next-button.svelte +21 -4
  101. package/dist/components/ui/range-calendar/range-calendar-next-button.svelte.d.ts +9 -2
  102. package/dist/components/ui/range-calendar/range-calendar-prev-button.svelte +21 -4
  103. package/dist/components/ui/range-calendar/range-calendar-prev-button.svelte.d.ts +9 -2
  104. package/dist/components/ui/range-calendar/range-calendar.svelte +18 -6
  105. package/dist/components/ui/range-calendar/range-calendar.svelte.d.ts +7 -2
  106. package/dist/components/ui/scroll-area/scroll-area-scrollbar.svelte +1 -1
  107. package/dist/components/ui/scroll-area/scroll-area.svelte +6 -1
  108. package/dist/components/ui/select/select-content.svelte +6 -0
  109. package/dist/components/ui/select/select-group-heading.svelte +1 -1
  110. package/dist/components/ui/select/select-item.svelte +2 -2
  111. package/dist/components/ui/slider/slider.svelte +6 -0
  112. package/dist/components/ui/switch/switch.svelte +1 -1
  113. package/dist/components/ui/table/table-cell.svelte +1 -1
  114. package/dist/components/ui/table/table-head.svelte +1 -1
  115. package/dist/components/ui/tooltip/tooltip-content.svelte +6 -0
  116. package/dist/i18n/audit/catalogUsage.d.ts +36 -0
  117. package/dist/i18n/audit/catalogUsage.js +58 -0
  118. package/dist/i18n/audit/designSystemWords.d.ts +47 -0
  119. package/dist/i18n/audit/designSystemWords.js +137 -0
  120. package/dist/i18n/audit/hardCodedText.d.ts +40 -0
  121. package/dist/i18n/audit/hardCodedText.js +27 -0
  122. package/dist/i18n/audit/index.d.ts +19 -0
  123. package/dist/i18n/audit/index.js +19 -0
  124. package/dist/i18n/audit/messageCalls.d.ts +34 -0
  125. package/dist/i18n/audit/messageCalls.js +89 -0
  126. package/dist/i18n/audit/syntaxTree.d.ts +33 -0
  127. package/dist/i18n/audit/syntaxTree.js +90 -0
  128. package/dist/i18n/audit/visibleText.d.ts +57 -0
  129. package/dist/i18n/audit/visibleText.js +252 -0
  130. package/dist/i18n/createI18n.d.ts +63 -0
  131. package/dist/i18n/createI18n.js +72 -0
  132. package/dist/i18n/direction.d.ts +27 -0
  133. package/dist/i18n/direction.js +89 -0
  134. package/dist/i18n/index.d.ts +17 -0
  135. package/dist/i18n/index.js +16 -0
  136. package/dist/i18n/localeDate.d.ts +34 -0
  137. package/dist/i18n/localeDate.js +104 -0
  138. package/dist/i18n/splitMessages.d.ts +21 -0
  139. package/dist/i18n/splitMessages.js +33 -0
  140. package/dist/i18n/translator.d.ts +61 -0
  141. package/dist/i18n/translator.js +337 -0
  142. package/dist/rtl/audit/auditPhysicalPlacement.d.ts +36 -0
  143. package/dist/rtl/audit/auditPhysicalPlacement.js +58 -0
  144. package/dist/rtl/audit/index.d.ts +14 -0
  145. package/dist/rtl/audit/index.js +14 -0
  146. package/dist/rtl/audit/physicalPlacement.d.ts +64 -0
  147. package/dist/rtl/audit/physicalPlacement.js +444 -0
  148. package/dist/shad-editor/index.d.ts +1 -1
  149. package/dist/shad-editor/index.js +1 -1
  150. package/dist/styles/global.css +4 -4
  151. package/dist/utils/fillMessage.d.ts +8 -0
  152. package/dist/utils/fillMessage.js +10 -0
  153. package/dist/utils/itemName.d.ts +17 -0
  154. package/dist/utils/itemName.js +41 -0
  155. package/package.json +18 -1
@@ -0,0 +1,72 @@
1
+ import { getContext, setContext } from 'svelte';
2
+ import { derived, readable, writable } from 'svelte/store';
3
+ import { createTranslator, describeMessageProblem } from "./translator.js";
4
+ /** How many different problems the console is told about, so a page that keeps changing its messages cannot fill memory. */
5
+ const MAX_REPORTED_PROBLEMS = 200;
6
+ /** Two settings are the same language when they have the same locale and the same words. */
7
+ function signatureOf(settings) {
8
+ try {
9
+ return JSON.stringify([settings.locale ?? null, settings.messages ?? null]);
10
+ }
11
+ catch {
12
+ // Not serializable (a cycle, a BigInt): treat it as new every time rather than guess.
13
+ return `${Math.random()}`;
14
+ }
15
+ }
16
+ /**
17
+ * Makes the words of one package: the context its root component provides, the hook its parts use, and the English
18
+ * translator for code with no component around it.
19
+ *
20
+ * Every package makes its own (its catalog, its console label, its own context key), so the File Browser inside the
21
+ * editor and the editor around it can each have their own words, and each is told only about its own messages.
22
+ */
23
+ export function createI18n(options) {
24
+ const { catalog, label } = options;
25
+ const api = options.contextApi ?? { getContext, setContext };
26
+ const contextKey = Symbol(`widgetic-${label}-i18n`);
27
+ const reportedProblems = new Set();
28
+ /** Says a problem with the page's messages in the console, once per page however often the language is set. */
29
+ const reportProblem = (problem) => {
30
+ const text = describeMessageProblem(problem);
31
+ if (reportedProblems.has(text) || reportedProblems.size >= MAX_REPORTED_PROBLEMS)
32
+ return;
33
+ reportedProblems.add(text);
34
+ console.warn(`[${label}] ${text}`);
35
+ };
36
+ const translatorFor = (settings) => createTranslator({
37
+ catalog,
38
+ locale: settings.locale ?? undefined,
39
+ messages: settings.messages,
40
+ onProblem: reportProblem
41
+ });
42
+ /** The English translator that a part uses when nothing above it chose a language. */
43
+ const englishTranslator = translatorFor({});
44
+ const englishContext = Object.freeze({
45
+ t: readable(englishTranslator.t),
46
+ locale: readable(englishTranslator.locale)
47
+ });
48
+ function provideI18n(initial) {
49
+ const settings = writable(initial);
50
+ const translator = derived(settings, ($settings) => translatorFor($settings));
51
+ const t = derived(translator, ($translator) => $translator.t);
52
+ const locale = derived(translator, ($translator) => $translator.locale);
53
+ const context = { t, locale };
54
+ api.setContext(contextKey, context);
55
+ let lastSignature = signatureOf(initial);
56
+ return {
57
+ ...context,
58
+ update(next) {
59
+ // Setting the same language again would rebuild every message for nothing.
60
+ const signature = signatureOf(next);
61
+ if (signature === lastSignature)
62
+ return;
63
+ lastSignature = signature;
64
+ settings.set(next);
65
+ }
66
+ };
67
+ }
68
+ function useI18n() {
69
+ return api.getContext(contextKey) ?? englishContext;
70
+ }
71
+ return { provideI18n, useI18n, translateEnglish: englishTranslator.t };
72
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Which way a language is read, so that a screen that follows its page's language can mirror its layout for the
3
+ * languages that are written right to left (Arabic, Hebrew, Persian, Urdu ...), give its elements the `lang` the same
4
+ * language deserves, and keep the names it puts inside a sentence from being turned around (see `translator.ts`).
5
+ *
6
+ * The direction comes from the SCRIPT a language tag is written in, not from a list of languages:
7
+ * `Intl.Locale(tag).maximize()` fills in the script the language is usually written in (`ar` is Arab, `he` is Hebr,
8
+ * `pa-PK` is Arab while `pa-IN` is Guru), and a tag that names its script is taken at its word (`ar-Latn` is Latin and
9
+ * so left to right, `ku-Arab` is Arabic and so right to left). A short list of languages is only the fallback for a
10
+ * browser without `Intl.Locale` and for a tag it cannot read.
11
+ *
12
+ * Nothing here knows about Svelte or the DOM: the same code runs in a unit test.
13
+ */
14
+ /** The two ways text runs: the values of the `dir` attribute. */
15
+ export type TextDirection = 'ltr' | 'rtl';
16
+ /**
17
+ * Which way `locale` is read. Blank, missing or unreadable is left to right, which is what a screen has always been.
18
+ * Never throws.
19
+ */
20
+ export declare function resolveTextDirection(locale: string | null | undefined): TextDirection;
21
+ /**
22
+ * The `lang` a screen's elements get for `locale`: the language tag in its canonical spelling (`pt_BR` and `PT-br`
23
+ * become `pt-BR`). A blank locale is English, which is what the words are then. A tag a browser cannot read gives
24
+ * `undefined`: the screen leaves `lang` off and its elements take the language of the page around them, which is better
25
+ * than a language that is wrong.
26
+ */
27
+ export declare function resolveLanguageTag(locale: string | null | undefined): string | undefined;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Which way a language is read, so that a screen that follows its page's language can mirror its layout for the
3
+ * languages that are written right to left (Arabic, Hebrew, Persian, Urdu ...), give its elements the `lang` the same
4
+ * language deserves, and keep the names it puts inside a sentence from being turned around (see `translator.ts`).
5
+ *
6
+ * The direction comes from the SCRIPT a language tag is written in, not from a list of languages:
7
+ * `Intl.Locale(tag).maximize()` fills in the script the language is usually written in (`ar` is Arab, `he` is Hebr,
8
+ * `pa-PK` is Arab while `pa-IN` is Guru), and a tag that names its script is taken at its word (`ar-Latn` is Latin and
9
+ * so left to right, `ku-Arab` is Arabic and so right to left). A short list of languages is only the fallback for a
10
+ * browser without `Intl.Locale` and for a tag it cannot read.
11
+ *
12
+ * Nothing here knows about Svelte or the DOM: the same code runs in a unit test.
13
+ */
14
+ /**
15
+ * The scripts that are written right to left and that a language of today is written in (ISO 15924): Arabic, Hebrew,
16
+ * Thaana (Dhivehi), Syriac, N'Ko, Adlam (Fulani), Hanifi Rohingya, Mandaic, Samaritan, Garay and Yezidi.
17
+ */
18
+ const RIGHT_TO_LEFT_SCRIPTS = new Set(['Arab', 'Hebr', 'Thaa', 'Syrc', 'Nkoo', 'Adlm', 'Rohg', 'Mand', 'Samr', 'Gara', 'Yezi']);
19
+ /**
20
+ * The languages that are written right to left when their tag says nothing else, for the browser or the tag that
21
+ * `Intl.Locale` cannot answer for. `iw` and `prs` are the old code of Hebrew and the code of Dari.
22
+ */
23
+ const RIGHT_TO_LEFT_LANGUAGES = new Set([
24
+ 'ar',
25
+ 'arc',
26
+ 'arz',
27
+ 'ary',
28
+ 'apc',
29
+ 'azb',
30
+ 'ckb',
31
+ 'dv',
32
+ 'fa',
33
+ 'he',
34
+ 'iw',
35
+ 'ks',
36
+ 'prs',
37
+ 'ps',
38
+ 'sd',
39
+ 'ug',
40
+ 'ur',
41
+ 'yi'
42
+ ]);
43
+ /** A language tag the way a browser wants it: no blanks around it, and a hyphen where a page wrote an underscore (`pt_BR`). */
44
+ function cleanLanguageTag(locale) {
45
+ return typeof locale === 'string' ? locale.trim().replace(/_/g, '-') : '';
46
+ }
47
+ /** The script a language tag is written in, or `undefined` when this browser cannot say (no `Intl.Locale`, or a tag it refuses). */
48
+ function scriptOfLanguageTag(languageTag) {
49
+ if (typeof Intl === 'undefined' || typeof Intl.Locale !== 'function')
50
+ return undefined;
51
+ try {
52
+ return new Intl.Locale(languageTag).maximize().script;
53
+ }
54
+ catch {
55
+ // A tag that is not a language tag at all (`x-foo`, `1234`): the fallback list decides.
56
+ return undefined;
57
+ }
58
+ }
59
+ /**
60
+ * Which way `locale` is read. Blank, missing or unreadable is left to right, which is what a screen has always been.
61
+ * Never throws.
62
+ */
63
+ export function resolveTextDirection(locale) {
64
+ const languageTag = cleanLanguageTag(locale);
65
+ if (languageTag === '')
66
+ return 'ltr';
67
+ const script = scriptOfLanguageTag(languageTag);
68
+ if (script !== undefined)
69
+ return RIGHT_TO_LEFT_SCRIPTS.has(script) ? 'rtl' : 'ltr';
70
+ const primaryLanguage = (languageTag.split('-')[0] ?? '').toLowerCase();
71
+ return RIGHT_TO_LEFT_LANGUAGES.has(primaryLanguage) ? 'rtl' : 'ltr';
72
+ }
73
+ /**
74
+ * The `lang` a screen's elements get for `locale`: the language tag in its canonical spelling (`pt_BR` and `PT-br`
75
+ * become `pt-BR`). A blank locale is English, which is what the words are then. A tag a browser cannot read gives
76
+ * `undefined`: the screen leaves `lang` off and its elements take the language of the page around them, which is better
77
+ * than a language that is wrong.
78
+ */
79
+ export function resolveLanguageTag(locale) {
80
+ const languageTag = cleanLanguageTag(locale);
81
+ if (languageTag === '')
82
+ return 'en';
83
+ try {
84
+ return Intl.getCanonicalLocales(languageTag)[0];
85
+ }
86
+ catch {
87
+ return undefined;
88
+ }
89
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The words of a Widgetic package in the page's language: the translator that fills in a catalog of English messages, and
3
+ * the Svelte context a package's screens read them from.
4
+ *
5
+ * // in the package: its catalog, once
6
+ * const { provideI18n, useI18n, translateEnglish } = createI18n({ catalog: MY_MESSAGES, label: 'MyPackage' });
7
+ * // in its root component: provideI18n({ locale, messages })
8
+ * // in any part: const { t } = useI18n(); then $t('upload.clear')
9
+ *
10
+ * See `translator.ts` for the format of a message.
11
+ */
12
+ export { createI18n } from './createI18n.ts';
13
+ export { resolveLanguageTag, resolveTextDirection, type TextDirection } from './direction.ts';
14
+ export { splitMessages, type SplitMessages } from './splitMessages.ts';
15
+ export type { I18n, I18nContext, I18nHandle, I18nOptions, I18nSettings, SvelteContextApi, Translate } from './createI18n.ts';
16
+ export { createTranslator, describeMessageProblem, findMessageProblems, valueNamesOfTemplate, type MessageOverrides, type MessageProblem, type MessageValue, type MessageValues, type Translator, type TranslatorOptions } from './translator.ts';
17
+ export { formatCalendarWeekdayLabel, formatLocaleShortDate, parseLocaleShortDate, type NumericDateParts } from './localeDate.ts';
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The words of a Widgetic package in the page's language: the translator that fills in a catalog of English messages, and
3
+ * the Svelte context a package's screens read them from.
4
+ *
5
+ * // in the package: its catalog, once
6
+ * const { provideI18n, useI18n, translateEnglish } = createI18n({ catalog: MY_MESSAGES, label: 'MyPackage' });
7
+ * // in its root component: provideI18n({ locale, messages })
8
+ * // in any part: const { t } = useI18n(); then $t('upload.clear')
9
+ *
10
+ * See `translator.ts` for the format of a message.
11
+ */
12
+ export { createI18n } from "./createI18n.js";
13
+ export { resolveLanguageTag, resolveTextDirection } from "./direction.js";
14
+ export { splitMessages } from "./splitMessages.js";
15
+ export { createTranslator, describeMessageProblem, findMessageProblems, valueNamesOfTemplate } from "./translator.js";
16
+ export { formatCalendarWeekdayLabel, formatLocaleShortDate, parseLocaleShortDate } from "./localeDate.js";
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Locale-aware short dates for calendars and range pickers. Values in compositions stay structured (CalendarDate); only
3
+ * display and typed input use these helpers.
4
+ */
5
+ export type NumericDateParts = {
6
+ year: number;
7
+ month: number;
8
+ day: number;
9
+ };
10
+ /** Formats a calendar day the way `locale` writes short numeric dates (order and separators from Intl). */
11
+ export declare function formatLocaleShortDate(parts: NumericDateParts, locale: string): string;
12
+ /**
13
+ * Parses a typed short date for `locale`, and still accepts legacy `DD/MM/YY` (day/month with `/` separators).
14
+ * Returns null when the text is not a valid date.
15
+ */
16
+ export declare function parseLocaleShortDate(text: string, locale: string): NumericDateParts | null;
17
+ /** What a calendar's column heading says for one weekday. */
18
+ export type CalendarWeekdayHeading = {
19
+ /** The text in the column heading. */
20
+ label: string;
21
+ /** The whole name of the weekday, for a screen reader, when `label` is only its one-letter name. Otherwise undefined. */
22
+ fullName: string | undefined;
23
+ };
24
+ /**
25
+ * Weekday heading for calendar grids: uses Intl `weekday: 'short'` for the column (no code-unit truncation).
26
+ *
27
+ * A language written right to left gets the one-letter (`narrow`) name instead, because its short names are whole words
28
+ * that do not fit a column (Arabic `الأربعاء`), and then the whole name goes with it for a screen reader, which would
29
+ * otherwise read a single letter. Only when the browser has such names for the language: when it has none it answers
30
+ * with English letters (Urdu, Pashto, Yiddish), and the short names stay. Every other language keeps its short names.
31
+ */
32
+ export declare function describeCalendarWeekdayHeading(locale: string, weekdayIndex: number): CalendarWeekdayHeading;
33
+ /** The text of a calendar's weekday heading: see `describeCalendarWeekdayHeading` (which also gives the whole name). */
34
+ export declare function formatCalendarWeekdayLabel(locale: string, weekdayIndex: number): string;
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Locale-aware short dates for calendars and range pickers. Values in compositions stay structured (CalendarDate); only
3
+ * display and typed input use these helpers.
4
+ */
5
+ import { resolveTextDirection } from "./direction.js";
6
+ /** Formats a calendar day the way `locale` writes short numeric dates (order and separators from Intl). */
7
+ export function formatLocaleShortDate(parts, locale) {
8
+ try {
9
+ const jsDate = new Date(parts.year, parts.month - 1, parts.day);
10
+ const formatter = new Intl.DateTimeFormat(locale, { day: '2-digit', month: '2-digit', year: '2-digit' });
11
+ return formatter.format(jsDate);
12
+ }
13
+ catch (error) {
14
+ console.warn('[DesignSystem] formatLocaleShortDate failed:', error);
15
+ return `${String(parts.day).padStart(2, '0')}/${String(parts.month).padStart(2, '0')}/${String(parts.year).slice(-2)}`;
16
+ }
17
+ }
18
+ /**
19
+ * Parses a typed short date for `locale`, and still accepts legacy `DD/MM/YY` (day/month with `/` separators).
20
+ * Returns null when the text is not a valid date.
21
+ */
22
+ export function parseLocaleShortDate(text, locale) {
23
+ const trimmed = text.trim();
24
+ if (!trimmed)
25
+ return null;
26
+ const legacy = /^(\d{1,2})\/(\d{1,2})\/(\d{2}|\d{4})$/.exec(trimmed);
27
+ if (legacy) {
28
+ const day = Number.parseInt(legacy[1], 10);
29
+ const month = Number.parseInt(legacy[2], 10);
30
+ let year = Number.parseInt(legacy[3], 10);
31
+ if (legacy[3].length === 2)
32
+ year += year >= 70 ? 1900 : 2000;
33
+ if (month < 1 || month > 12 || day < 1 || day > 31)
34
+ return null;
35
+ return { year, month, day };
36
+ }
37
+ try {
38
+ const formatter = new Intl.DateTimeFormat(locale, { day: '2-digit', month: '2-digit', year: '2-digit' });
39
+ const sample = new Date(2024, 10, 7);
40
+ const sampleParts = formatter.formatToParts(sample);
41
+ const order = sampleParts.filter((part) => part.type !== 'literal').map((part) => part.type);
42
+ const literals = sampleParts.filter((part) => part.type === 'literal').map((part) => part.value);
43
+ const separator = literals[0] ?? '/';
44
+ const chunks = trimmed.split(separator).map((chunk) => chunk.trim());
45
+ if (chunks.length !== 3)
46
+ return null;
47
+ const values = {};
48
+ for (let i = 0; i < 3; i += 1) {
49
+ const kind = order[i];
50
+ if (!kind || kind === 'literal')
51
+ return null;
52
+ const num = Number.parseInt(chunks[i], 10);
53
+ if (!Number.isFinite(num))
54
+ return null;
55
+ values[kind] = num;
56
+ }
57
+ let year = values.year;
58
+ if (year < 100)
59
+ year += year >= 70 ? 1900 : 2000;
60
+ const month = values.month;
61
+ const day = values.day;
62
+ if (month < 1 || month > 12 || day < 1 || day > 31)
63
+ return null;
64
+ return { year, month, day };
65
+ }
66
+ catch (error) {
67
+ console.warn('[DesignSystem] parseLocaleShortDate failed:', error);
68
+ return null;
69
+ }
70
+ }
71
+ /**
72
+ * Latin letters. A browser that has no one-letter weekday names for a language answers with the English ones
73
+ * (`S M T W T F S`), which must never stand in the calendar of a language written in another script.
74
+ */
75
+ const LATIN_LETTER = /\p{Script=Latin}/u;
76
+ /**
77
+ * Weekday heading for calendar grids: uses Intl `weekday: 'short'` for the column (no code-unit truncation).
78
+ *
79
+ * A language written right to left gets the one-letter (`narrow`) name instead, because its short names are whole words
80
+ * that do not fit a column (Arabic `الأربعاء`), and then the whole name goes with it for a screen reader, which would
81
+ * otherwise read a single letter. Only when the browser has such names for the language: when it has none it answers
82
+ * with English letters (Urdu, Pashto, Yiddish), and the short names stay. Every other language keeps its short names.
83
+ */
84
+ export function describeCalendarWeekdayHeading(locale, weekdayIndex) {
85
+ try {
86
+ const date = new Date(Date.UTC(2024, 0, 7 + weekdayIndex));
87
+ const shortName = new Intl.DateTimeFormat(locale, { weekday: 'short', timeZone: 'UTC' }).format(date);
88
+ if (resolveTextDirection(locale) !== 'rtl')
89
+ return { label: shortName, fullName: undefined };
90
+ const narrowName = new Intl.DateTimeFormat(locale, { weekday: 'narrow', timeZone: 'UTC' }).format(date);
91
+ if (narrowName === '' || LATIN_LETTER.test(narrowName))
92
+ return { label: shortName, fullName: undefined };
93
+ const longName = new Intl.DateTimeFormat(locale, { weekday: 'long', timeZone: 'UTC' }).format(date);
94
+ return { label: narrowName, fullName: longName };
95
+ }
96
+ catch (error) {
97
+ console.warn('[DesignSystem] describeCalendarWeekdayHeading failed:', error);
98
+ return { label: '', fullName: undefined };
99
+ }
100
+ }
101
+ /** The text of a calendar's weekday heading: see `describeCalendarWeekdayHeading` (which also gives the whole name). */
102
+ export function formatCalendarWeekdayLabel(locale, weekdayIndex) {
103
+ return describeCalendarWeekdayHeading(locale, weekdayIndex).label;
104
+ }
@@ -0,0 +1,21 @@
1
+ import type { MessageOverrides } from './translator.ts';
2
+ /**
3
+ * A page gives the words of a package as one flat `messages` object. When that package has others inside it (an editor
4
+ * that has the File Browser and a rich text editor in it), the page should not have to know which package a key belongs
5
+ * to, and each package should only hear about its own keys: a package says "this key does not exist" for every key it does
6
+ * not have, so a flat object handed to all of them would make each one complain about the others' words.
7
+ */
8
+ export interface SplitMessages<Name extends string> {
9
+ /** For each package, the entries of `messages` whose key is in its catalog. Every package has one, empty or not. */
10
+ slices: Record<Name, MessageOverrides>;
11
+ /** The entries whose key is in no catalog: a misspelt key, or a key of a package that is not on the page. */
12
+ unclaimed: MessageOverrides;
13
+ }
14
+ /**
15
+ * Gives each catalog the entries of `messages` it has a key for. An entry goes to every catalog that has its key (a key
16
+ * is meant to belong to one package, so that is nearly always one), and to `unclaimed` when no catalog has it. When
17
+ * `messages` is not an object (nothing, a string, a list) every package gets nothing, as it does on its own.
18
+ * Nothing is checked here: a value that is not text, or a template that does not read, is said by the package that owns
19
+ * the key, once, as it is for a page that gives it directly.
20
+ */
21
+ export declare function splitMessages<Name extends string>(messages: MessageOverrides | null | undefined, catalogs: Readonly<Record<Name, Readonly<Record<string, unknown>>>>): SplitMessages<Name>;
@@ -0,0 +1,33 @@
1
+ /** Writes `key` into `target` as an own property, even when `key` is `__proto__`, which `target[key] = …` would turn into a prototype. */
2
+ function setOwn(target, key, value) {
3
+ Object.defineProperty(target, key, { value, enumerable: true, writable: true, configurable: true });
4
+ }
5
+ /**
6
+ * Gives each catalog the entries of `messages` it has a key for. An entry goes to every catalog that has its key (a key
7
+ * is meant to belong to one package, so that is nearly always one), and to `unclaimed` when no catalog has it. When
8
+ * `messages` is not an object (nothing, a string, a list) every package gets nothing, as it does on its own.
9
+ * Nothing is checked here: a value that is not text, or a template that does not read, is said by the package that owns
10
+ * the key, once, as it is for a page that gives it directly.
11
+ */
12
+ export function splitMessages(messages, catalogs) {
13
+ const names = Object.keys(catalogs);
14
+ const slices = {};
15
+ for (const name of names)
16
+ slices[name] = {};
17
+ const unclaimed = {};
18
+ if (messages === null || messages === undefined || typeof messages !== 'object' || Array.isArray(messages))
19
+ return { slices, unclaimed };
20
+ for (const key of Object.keys(messages)) {
21
+ const value = messages[key];
22
+ let claimed = false;
23
+ for (const name of names) {
24
+ if (Object.prototype.hasOwnProperty.call(catalogs[name], key)) {
25
+ setOwn(slices[name], key, value);
26
+ claimed = true;
27
+ }
28
+ }
29
+ if (!claimed)
30
+ setOwn(unclaimed, key, value);
31
+ }
32
+ return { slices, unclaimed };
33
+ }
@@ -0,0 +1,61 @@
1
+ /** A value a template can show. */
2
+ export type MessageValue = string | number;
3
+ export type MessageValues = Readonly<Record<string, MessageValue>>;
4
+ /** Something wrong in what the page supplied. Reported once, never thrown: a broken message must not break the screen. */
5
+ export type MessageProblem = {
6
+ kind: 'unknown-message';
7
+ key: string;
8
+ suggestion?: string;
9
+ } | {
10
+ kind: 'not-text';
11
+ key: string;
12
+ } | {
13
+ kind: 'broken-message';
14
+ key: string;
15
+ reason: string;
16
+ } | {
17
+ kind: 'unknown-value';
18
+ key: string;
19
+ name: string;
20
+ } | {
21
+ kind: 'unknown-locale';
22
+ locale: string;
23
+ };
24
+ /** One line a developer can read in the console about a {@link MessageProblem}. */
25
+ export declare function describeMessageProblem(problem: MessageProblem): string;
26
+ /** The shape the page supplies for `messages`: any of the catalog's keys, with text. */
27
+ export type MessageOverrides = Readonly<Record<string, unknown>>;
28
+ export interface TranslatorOptions<Key extends string> {
29
+ /** A BCP 47 tag ("de", "pt-BR"). It sets plural rules and number formats. The words come from `messages`. Default "en". */
30
+ locale?: string;
31
+ /** The built-in English messages. Every key the screens ask for is here. */
32
+ catalog: Readonly<Record<Key, string>>;
33
+ /** The page's own words: any of the catalog's keys. */
34
+ messages?: MessageOverrides | null;
35
+ /** Told about each problem once. The default is to say nothing. */
36
+ onProblem?: (problem: MessageProblem) => void;
37
+ /**
38
+ * Whether a text value is put between isolate marks, so that the sentence around it cannot turn it around. The default
39
+ * is yes for a language written right to left, where a name in another script, one that starts with a digit or one that
40
+ * ends with a bracket would otherwise be put in the wrong place, and no for any other language.
41
+ */
42
+ isolateValues?: boolean;
43
+ }
44
+ export interface Translator<Key extends string> {
45
+ /** The message for `key`, with `values` put in. An unknown key comes back as the key itself. */
46
+ t: (key: Key, values?: MessageValues) => string;
47
+ /** The language tag in use, after checking it ("en" when the one given was not a tag). */
48
+ readonly locale: string;
49
+ }
50
+ /**
51
+ * Makes the function that turns keys into text. It never throws for what the page supplied: an unusable message is
52
+ * reported through `onProblem` (once) and the English one is used in its place.
53
+ */
54
+ export declare function createTranslator<Key extends string>(options: TranslatorOptions<Key>): Translator<Key>;
55
+ /**
56
+ * The names of the values a message asks for: each `{name}` in it, and the number a plural looks at, sorted. For a test
57
+ * or a tool that checks the code gives a message everything it needs. Throws when the template does not parse.
58
+ */
59
+ export declare function valueNamesOfTemplate(template: string): string[];
60
+ /** Checks `messages` against a catalog without making a translator: what a test, or a build step, can call. */
61
+ export declare function findMessageProblems<Key extends string>(catalog: Readonly<Record<Key, string>>, messages: MessageOverrides): MessageProblem[];