@nanahoshi/mona-ui 0.23.2 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/fesm2022/nanahoshi-mona-ui-auto-complete.mjs +2 -0
  2. package/fesm2022/nanahoshi-mona-ui-breadcrumb.mjs +2 -0
  3. package/fesm2022/nanahoshi-mona-ui-button-group.mjs +2 -0
  4. package/fesm2022/nanahoshi-mona-ui-calendar.mjs +15 -10
  5. package/fesm2022/nanahoshi-mona-ui-card.mjs +2 -0
  6. package/fesm2022/nanahoshi-mona-ui-chart.mjs +4 -2
  7. package/fesm2022/nanahoshi-mona-ui-chip.mjs +2 -0
  8. package/fesm2022/nanahoshi-mona-ui-color-gradient.mjs +8 -3
  9. package/fesm2022/nanahoshi-mona-ui-color-palette.mjs +2 -0
  10. package/fesm2022/nanahoshi-mona-ui-color-picker.mjs +2 -0
  11. package/fesm2022/nanahoshi-mona-ui-combo-box.mjs +2 -0
  12. package/fesm2022/nanahoshi-mona-ui-date-picker.mjs +2 -0
  13. package/fesm2022/nanahoshi-mona-ui-datetime-picker.mjs +2 -0
  14. package/fesm2022/nanahoshi-mona-ui-dialog.mjs +2 -0
  15. package/fesm2022/nanahoshi-mona-ui-dropdown-list.mjs +2 -0
  16. package/fesm2022/nanahoshi-mona-ui-dropdowns.mjs +8 -3
  17. package/fesm2022/nanahoshi-mona-ui-editor.mjs +8 -3
  18. package/fesm2022/nanahoshi-mona-ui-filter.mjs +3 -1
  19. package/fesm2022/{nanahoshi-mona-ui-grid-grid-footer-row.component-CIMXz60d.mjs → nanahoshi-mona-ui-grid-grid-footer-row.component-CnPhVIr4.mjs} +2 -2
  20. package/fesm2022/{nanahoshi-mona-ui-grid-grid-list.component-CpeheGXU.mjs → nanahoshi-mona-ui-grid-grid-list.component-DxY6bfTX.mjs} +4 -4
  21. package/fesm2022/{nanahoshi-mona-ui-grid-grid-row.component-B_5ay_Nz.mjs → nanahoshi-mona-ui-grid-grid-row.component-CaO5Lae6.mjs} +3 -3
  22. package/fesm2022/{nanahoshi-mona-ui-grid-grid-virtual-list.component-BzKywU5b.mjs → nanahoshi-mona-ui-grid-grid-virtual-list.component-jNYtqg5B.mjs} +4 -4
  23. package/fesm2022/{nanahoshi-mona-ui-grid-nanahoshi-mona-ui-grid-IewqeT4m.mjs → nanahoshi-mona-ui-grid-nanahoshi-mona-ui-grid-9FtfCutf.mjs} +18 -13
  24. package/fesm2022/nanahoshi-mona-ui-grid.mjs +1 -1
  25. package/fesm2022/nanahoshi-mona-ui-i18n-icu.mjs +888 -0
  26. package/fesm2022/nanahoshi-mona-ui-i18n.mjs +398 -116
  27. package/fesm2022/nanahoshi-mona-ui-internal-list.mjs +2 -0
  28. package/fesm2022/nanahoshi-mona-ui-internal-tree.mjs +2 -0
  29. package/fesm2022/nanahoshi-mona-ui-list-box.mjs +2 -0
  30. package/fesm2022/nanahoshi-mona-ui-locales-ar-sa.mjs +401 -0
  31. package/fesm2022/nanahoshi-mona-ui-locales-de-de.mjs +399 -0
  32. package/fesm2022/nanahoshi-mona-ui-locales-es-es.mjs +399 -0
  33. package/fesm2022/nanahoshi-mona-ui-locales-fr-fr.mjs +399 -0
  34. package/fesm2022/nanahoshi-mona-ui-locales-id-id.mjs +397 -0
  35. package/fesm2022/nanahoshi-mona-ui-locales-it-it.mjs +399 -0
  36. package/fesm2022/nanahoshi-mona-ui-locales-ja-jp.mjs +397 -0
  37. package/fesm2022/nanahoshi-mona-ui-locales-ko-kr.mjs +397 -0
  38. package/fesm2022/nanahoshi-mona-ui-locales-pt-br.mjs +399 -0
  39. package/fesm2022/nanahoshi-mona-ui-locales-ru-ru.mjs +401 -0
  40. package/fesm2022/nanahoshi-mona-ui-locales-tr-tr.mjs +397 -0
  41. package/fesm2022/nanahoshi-mona-ui-locales-zh-cn.mjs +397 -0
  42. package/fesm2022/nanahoshi-mona-ui-locales-zh-tw.mjs +397 -0
  43. package/fesm2022/nanahoshi-mona-ui-locales.mjs +77 -4988
  44. package/fesm2022/nanahoshi-mona-ui-multi-select.mjs +7 -2
  45. package/fesm2022/nanahoshi-mona-ui-notification.mjs +19 -2
  46. package/fesm2022/nanahoshi-mona-ui-numeric-text-box.mjs +299 -167
  47. package/fesm2022/nanahoshi-mona-ui-otp-input.mjs +2 -0
  48. package/fesm2022/nanahoshi-mona-ui-pager.mjs +13 -8
  49. package/fesm2022/nanahoshi-mona-ui-popup.mjs +15 -1
  50. package/fesm2022/nanahoshi-mona-ui-rating.mjs +7 -2
  51. package/fesm2022/nanahoshi-mona-ui-scroll-view.mjs +9 -4
  52. package/fesm2022/nanahoshi-mona-ui-sheet.mjs +2 -0
  53. package/fesm2022/nanahoshi-mona-ui-slider.mjs +2 -0
  54. package/fesm2022/nanahoshi-mona-ui-spinner.mjs +2 -0
  55. package/fesm2022/nanahoshi-mona-ui-split-button.mjs +2 -0
  56. package/fesm2022/nanahoshi-mona-ui-splitter.mjs +2 -0
  57. package/fesm2022/nanahoshi-mona-ui-stepper.mjs +2 -0
  58. package/fesm2022/nanahoshi-mona-ui-tabs.mjs +2 -0
  59. package/fesm2022/nanahoshi-mona-ui-text-box.mjs +2 -0
  60. package/fesm2022/nanahoshi-mona-ui-time-picker.mjs +2 -0
  61. package/fesm2022/nanahoshi-mona-ui-time-selector.mjs +2 -0
  62. package/fesm2022/nanahoshi-mona-ui-window.mjs +2 -0
  63. package/i18n/catalogs/en-US.messages.json +373 -0
  64. package/i18n/icu/package.json +4 -0
  65. package/locales/ar-sa/ar-sa.messages.json +373 -0
  66. package/locales/ar-sa/package.json +4 -0
  67. package/locales/de-de/de-de.messages.json +373 -0
  68. package/locales/de-de/package.json +4 -0
  69. package/locales/es-es/es-es.messages.json +373 -0
  70. package/locales/es-es/package.json +4 -0
  71. package/locales/fr-fr/fr-fr.messages.json +373 -0
  72. package/locales/fr-fr/package.json +4 -0
  73. package/locales/id-id/id-id.messages.json +373 -0
  74. package/locales/id-id/package.json +4 -0
  75. package/locales/it-it/it-it.messages.json +373 -0
  76. package/locales/it-it/package.json +4 -0
  77. package/locales/ja-jp/ja-jp.messages.json +373 -0
  78. package/locales/ja-jp/package.json +4 -0
  79. package/locales/ko-kr/ko-kr.messages.json +373 -0
  80. package/locales/ko-kr/package.json +4 -0
  81. package/locales/pt-br/package.json +4 -0
  82. package/locales/pt-br/pt-br.messages.json +373 -0
  83. package/locales/ru-ru/package.json +4 -0
  84. package/locales/ru-ru/ru-ru.messages.json +373 -0
  85. package/locales/tr-tr/package.json +4 -0
  86. package/locales/tr-tr/tr-tr.messages.json +373 -0
  87. package/locales/zh-cn/package.json +4 -0
  88. package/locales/zh-cn/zh-cn.messages.json +373 -0
  89. package/locales/zh-tw/package.json +4 -0
  90. package/locales/zh-tw/zh-tw.messages.json +373 -0
  91. package/package.json +111 -53
  92. package/types/nanahoshi-mona-ui-i18n-icu.d.ts +74 -0
  93. package/types/nanahoshi-mona-ui-i18n.d.ts +139 -9
  94. package/types/nanahoshi-mona-ui-locales-ar-sa.d.ts +379 -0
  95. package/types/nanahoshi-mona-ui-locales-de-de.d.ts +379 -0
  96. package/types/nanahoshi-mona-ui-locales-es-es.d.ts +379 -0
  97. package/types/nanahoshi-mona-ui-locales-fr-fr.d.ts +379 -0
  98. package/types/nanahoshi-mona-ui-locales-id-id.d.ts +379 -0
  99. package/types/nanahoshi-mona-ui-locales-it-it.d.ts +379 -0
  100. package/types/nanahoshi-mona-ui-locales-ja-jp.d.ts +379 -0
  101. package/types/nanahoshi-mona-ui-locales-ko-kr.d.ts +379 -0
  102. package/types/nanahoshi-mona-ui-locales-pt-br.d.ts +379 -0
  103. package/types/nanahoshi-mona-ui-locales-ru-ru.d.ts +379 -0
  104. package/types/nanahoshi-mona-ui-locales-tr-tr.d.ts +379 -0
  105. package/types/nanahoshi-mona-ui-locales-zh-cn.d.ts +379 -0
  106. package/types/nanahoshi-mona-ui-locales-zh-tw.d.ts +379 -0
  107. package/types/nanahoshi-mona-ui-locales.d.ts +30 -4915
  108. package/types/nanahoshi-mona-ui-notification.d.ts +1 -0
  109. package/types/nanahoshi-mona-ui-numeric-text-box.d.ts +113 -37
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, signal, computed, Injectable, isDevMode, ElementRef, DestroyRef, makeEnvironmentProviders } from '@angular/core';
2
+ import { InjectionToken, inject, signal, computed, Injectable, isDevMode, ElementRef, DestroyRef, afterNextRender, Injector, makeEnvironmentProviders, provideAppInitializer } from '@angular/core';
3
3
  import { Directionality } from '@angular/cdk/bidi';
4
4
  import { DateTime } from 'luxon';
5
5
 
@@ -83,16 +83,36 @@ function mergeMessages(fallback, localeMessages, overrideMessages) {
83
83
 
84
84
  class MonaI18nService {
85
85
  #config;
86
+ #loadError;
87
+ #loadedLocales;
88
+ #loadingLocaleId;
86
89
  #locale;
90
+ #localeOverrides;
87
91
  #overrides;
92
+ /** Incremented by every `load()` and `use()`, so only the latest request changes the locale. */
93
+ #localeRequest;
88
94
  constructor() {
89
95
  this.#config = inject(MONA_I18N_CONFIG);
96
+ this.#loadError = signal(null, /* @ts-ignore */
97
+ ...(ngDevMode ? [{ debugName: "#loadError" }] : /* istanbul ignore next */ []));
98
+ this.#loadedLocales = new Map();
99
+ this.#loadingLocaleId = signal(null, /* @ts-ignore */
100
+ ...(ngDevMode ? [{ debugName: "#loadingLocaleId" }] : /* istanbul ignore next */ []));
90
101
  this.#locale = signal(this.#config.locale ?? MONA_DEFAULT_LOCALE, /* @ts-ignore */
91
102
  ...(ngDevMode ? [{ debugName: "#locale" }] : /* istanbul ignore next */ []));
103
+ this.#localeOverrides = signal(createLocaleOverrides(this.#config.localeMessages), /* @ts-ignore */
104
+ ...(ngDevMode ? [{ debugName: "#localeOverrides" }] : /* istanbul ignore next */ []));
92
105
  this.#overrides = signal(this.#config.messages ?? {}, /* @ts-ignore */
93
106
  ...(ngDevMode ? [{ debugName: "#overrides" }] : /* istanbul ignore next */ []));
107
+ /** Incremented by every `load()` and `use()`, so only the latest request changes the locale. */
108
+ this.#localeRequest = 0;
94
109
  this.direction = computed(() => this.#locale().direction, /* @ts-ignore */
95
110
  ...(ngDevMode ? [{ debugName: "direction" }] : /* istanbul ignore next */ []));
111
+ /** The error of the latest `load()` when it failed, otherwise `null`. */
112
+ this.loadError = this.#loadError.asReadonly();
113
+ /** Whether a `load()` is in progress and has not been superseded. */
114
+ this.loading = computed(() => this.#loadingLocaleId() != null, /* @ts-ignore */
115
+ ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
96
116
  this.locale = this.#locale.asReadonly();
97
117
  /**
98
118
  * Canonical BCP 47 id of the active locale. POSIX ids such as `en_US` are converted to `en-US`,
@@ -102,23 +122,108 @@ class MonaI18nService {
102
122
  ...(ngDevMode ? [{ debugName: "localeId" }] : /* istanbul ignore next */ []));
103
123
  warnOnInvalidLocaleId(this.#locale().id);
104
124
  }
105
- clearMessages() {
106
- this.#overrides.set({});
125
+ /**
126
+ * Removes overrides. With `options.locale`, only that locale's overrides are removed; without it, every
127
+ * override is removed, global and per locale.
128
+ */
129
+ clearMessages(options) {
130
+ if (options?.locale == null) {
131
+ this.#overrides.set({});
132
+ this.#localeOverrides.set(new Map());
133
+ return;
134
+ }
135
+ const localeId = canonicalizeLocaleId(options.locale);
136
+ this.#localeOverrides.update(current => {
137
+ const next = new Map(current);
138
+ next.delete(localeId);
139
+ return next;
140
+ });
107
141
  }
108
142
  componentMessages(namespace, fallback) {
109
143
  return computed(() => {
110
144
  const localeMessages = this.#locale().messages[namespace];
111
145
  const overrideMessages = this.#overrides()[namespace];
112
- return mergeMessages(fallback, localeMessages, overrideMessages);
146
+ const localeOverrideMessages = this.#localeOverrides().get(this.localeId())?.[namespace];
147
+ const merged = mergeMessages(fallback, localeMessages, overrideMessages);
148
+ return mergeTwo(merged, localeOverrideMessages);
113
149
  });
114
150
  }
115
- patchMessages(messages) {
116
- this.#overrides.update(current => mergeTwo(current, messages));
151
+ /**
152
+ * Loads a locale with the `loadLocale` function configured in `provideMonaI18n` and switches to it.
153
+ *
154
+ * - Ids are canonicalized, and each locale is loaded once: later calls reuse it, and concurrent calls for the
155
+ * same id share one request.
156
+ * - The latest `load()` or `use()` wins. When a newer one starts before this one finishes, this one resolves
157
+ * without switching.
158
+ * - When loading fails, the current locale stays, `loadError` is set and the promise rejects. The failed
159
+ * locale is not cached, so a later call retries it.
160
+ */
161
+ async load(localeId) {
162
+ const loadLocale = this.#config.loadLocale;
163
+ if (loadLocale == null) {
164
+ throw new Error("[mona-ui] MonaI18nService.load() needs a loader: provideMonaI18n({ loadLocale: loadMonaLocale }).");
165
+ }
166
+ const canonicalId = canonicalizeLocaleId(localeId);
167
+ const request = ++this.#localeRequest;
168
+ this.#loadingLocaleId.set(canonicalId);
169
+ this.#loadError.set(null);
170
+ let pending = this.#loadedLocales.get(canonicalId);
171
+ if (pending == null) {
172
+ pending = Promise.resolve().then(() => loadLocale(canonicalId));
173
+ this.#loadedLocales.set(canonicalId, pending);
174
+ }
175
+ try {
176
+ const locale = await pending;
177
+ if (request === this.#localeRequest) {
178
+ this.#applyLocale(locale);
179
+ }
180
+ }
181
+ catch (error) {
182
+ if (this.#loadedLocales.get(canonicalId) === pending) {
183
+ this.#loadedLocales.delete(canonicalId);
184
+ }
185
+ if (request === this.#localeRequest) {
186
+ this.#loadError.set(error);
187
+ }
188
+ throw error;
189
+ }
190
+ finally {
191
+ if (request === this.#localeRequest) {
192
+ this.#loadingLocaleId.set(null);
193
+ }
194
+ }
195
+ }
196
+ /**
197
+ * Merges overrides into the existing ones. With `options.locale`, they apply only while that locale is
198
+ * active, so overrides patched for German do not show after switching to English.
199
+ */
200
+ patchMessages(messages, options) {
201
+ if (options?.locale == null) {
202
+ this.#overrides.update(current => mergeTwo(current, messages));
203
+ return;
204
+ }
205
+ const localeId = canonicalizeLocaleId(options.locale);
206
+ this.#localeOverrides.update(current => new Map(current).set(localeId, mergeTwo(current.get(localeId) ?? {}, messages)));
117
207
  }
118
- setMessages(messages) {
119
- this.#overrides.set(messages);
208
+ /**
209
+ * Replaces the existing overrides. With `options.locale`, only that locale's overrides are replaced.
210
+ */
211
+ setMessages(messages, options) {
212
+ if (options?.locale == null) {
213
+ this.#overrides.set(messages);
214
+ return;
215
+ }
216
+ const localeId = canonicalizeLocaleId(options.locale);
217
+ this.#localeOverrides.update(current => new Map(current).set(localeId, messages));
120
218
  }
219
+ /** Switches to a locale that is already loaded. It supersedes a `load()` still in progress. */
121
220
  use(locale) {
221
+ this.#localeRequest++;
222
+ this.#loadingLocaleId.set(null);
223
+ this.#loadError.set(null);
224
+ this.#applyLocale(locale);
225
+ }
226
+ #applyLocale(locale) {
122
227
  warnOnInvalidLocaleId(locale.id);
123
228
  this.#locale.set(locale);
124
229
  }
@@ -131,6 +236,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
131
236
  providedIn: "root"
132
237
  }]
133
238
  }], ctorParameters: () => [] });
239
+ function createLocaleOverrides(localeMessages) {
240
+ const overrides = new Map();
241
+ for (const [localeId, messages] of Object.entries(localeMessages ?? {})) {
242
+ const canonicalId = canonicalizeLocaleId(localeId);
243
+ overrides.set(canonicalId, mergeTwo(overrides.get(canonicalId) ?? {}, messages));
244
+ }
245
+ return overrides;
246
+ }
134
247
  function warnOnInvalidLocaleId(localeId) {
135
248
  if (isDevMode() && tryCanonicalizeLocaleId(localeId) == null) {
136
249
  console.warn(`[mona-ui] "${localeId}" is not a valid BCP 47 locale id. Falling back to "${MONA_FALLBACK_LOCALE_ID}".`);
@@ -197,39 +310,94 @@ function resolveComponentDirection(hostElement, directionality) {
197
310
  }
198
311
  return "ltr";
199
312
  }
200
- const documentObservers = new WeakMap();
201
- function observeDocumentDirChanges(doc, callback) {
202
- let entry = documentObservers.get(doc);
203
- if (!entry) {
204
- const callbacks = new Set();
205
- const observer = new MutationObserver(() => {
206
- for (const cb of Array.from(callbacks)) {
207
- cb();
313
+ /**
314
+ * One `dir` attribute observer per observed root (the document element, a shadow root, or the top of a detached
315
+ * tree), shared by every direction-aware instance under it. A mutation only invalidates the instances it can affect,
316
+ * so a `dir` written on an overlay pane does not re-resolve every component on the page.
317
+ */
318
+ const directionRegistries = new WeakMap();
319
+ function isShadowRoot(node) {
320
+ return node.nodeType === 11 && "host" in node;
321
+ }
322
+ function getComposedParent(node) {
323
+ if (node.parentNode) {
324
+ return node.parentNode;
325
+ }
326
+ return isShadowRoot(node) ? node.host : null;
327
+ }
328
+ function composedContains(ancestor, node) {
329
+ for (let current = node; current; current = getComposedParent(current)) {
330
+ if (current === ancestor) {
331
+ return true;
332
+ }
333
+ }
334
+ return false;
335
+ }
336
+ /**
337
+ * A `dir` change affects an instance when it lands on the instance's element or one of its ancestors, or anywhere
338
+ * inside the `dir="auto"` root it resolves against (a nested explicit direction changes which text counts).
339
+ */
340
+ function isAffectedByDirChange(subscriber, targets) {
341
+ for (let current = subscriber.element; current; current = getComposedParent(current)) {
342
+ if (targets.has(current)) {
343
+ return true;
344
+ }
345
+ }
346
+ const autoRoot = subscriber.autoRoot;
347
+ if (autoRoot) {
348
+ for (const target of targets) {
349
+ if (composedContains(autoRoot, target)) {
350
+ return true;
208
351
  }
209
- });
210
- if (doc.documentElement) {
211
- observer.observe(doc.documentElement, {
212
- attributes: true,
213
- attributeFilter: ["dir"],
214
- subtree: true
215
- });
216
352
  }
217
- entry = { refCount: 0, observer, callbacks };
218
- documentObservers.set(doc, entry);
219
353
  }
220
- entry.refCount++;
221
- entry.callbacks.add(callback);
222
- let cleanedUp = false;
354
+ return false;
355
+ }
356
+ function getObservedRoots(element) {
357
+ const roots = [];
358
+ let root = element.getRootNode?.() ?? null;
359
+ while (root && isShadowRoot(root)) {
360
+ roots.push(root);
361
+ root = root.host.getRootNode?.() ?? null;
362
+ }
363
+ // The top of a detached tree, so a host that is not attached yet still sees its own subtree change.
364
+ if (root && root !== element && root.nodeType === 1) {
365
+ roots.push(root);
366
+ }
367
+ const documentElement = element.ownerDocument?.documentElement;
368
+ if (documentElement && !roots.includes(documentElement)) {
369
+ roots.push(documentElement);
370
+ }
371
+ return roots;
372
+ }
373
+ function registerDirectionSubscriber(observedRoot, subscriber) {
374
+ let registry = directionRegistries.get(observedRoot);
375
+ if (!registry) {
376
+ const subscribers = new Set();
377
+ const observer = new MutationObserver(records => {
378
+ const targets = new Set();
379
+ for (const record of records) {
380
+ targets.add(record.target);
381
+ }
382
+ for (const candidate of Array.from(subscribers)) {
383
+ if (subscribers.has(candidate) && isAffectedByDirChange(candidate, targets)) {
384
+ candidate.invalidate();
385
+ }
386
+ }
387
+ });
388
+ observer.observe(observedRoot, { attributes: true, attributeFilter: ["dir"], subtree: true });
389
+ registry = { observer, subscribers };
390
+ directionRegistries.set(observedRoot, registry);
391
+ }
392
+ const activeRegistry = registry;
393
+ activeRegistry.subscribers.add(subscriber);
223
394
  return () => {
224
- if (cleanedUp || !entry) {
225
- return;
226
- }
227
- cleanedUp = true;
228
- entry.callbacks.delete(callback);
229
- entry.refCount--;
230
- if (entry.refCount <= 0) {
231
- entry.observer.disconnect();
232
- documentObservers.delete(doc);
395
+ activeRegistry.subscribers.delete(subscriber);
396
+ if (activeRegistry.subscribers.size === 0) {
397
+ activeRegistry.observer.disconnect();
398
+ if (directionRegistries.get(observedRoot) === activeRegistry) {
399
+ directionRegistries.delete(observedRoot);
400
+ }
233
401
  }
234
402
  };
235
403
  }
@@ -276,46 +444,61 @@ function observeAutoDirectionChanges(autoRoot, callback) {
276
444
  }
277
445
  };
278
446
  }
447
+ /** The nearest `dir="auto"` ancestor-or-self, unless an explicit `ltr`/`rtl` comes first. */
448
+ function findAutoRoot(element) {
449
+ for (let current = element; current; current = current.parentElement) {
450
+ const rawDir = current.getAttribute?.("dir")?.trim().toLowerCase();
451
+ if (rawDir === "auto") {
452
+ return current;
453
+ }
454
+ if (rawDir === "rtl" || rawDir === "ltr") {
455
+ return null;
456
+ }
457
+ }
458
+ return null;
459
+ }
279
460
  function observeComponentDirection(hostElement, directionality, onChange) {
280
461
  const element = hostElement instanceof ElementRef ? hostElement.nativeElement : hostElement;
462
+ const canObserve = typeof MutationObserver !== "undefined" && !!element;
281
463
  let currentDir = resolveComponentDirection(element, directionality);
282
464
  let unobserveAuto = null;
283
- let observedAutoElement = null;
465
+ const rootSubscriptions = new Map();
466
+ const subscriber = element
467
+ ? { autoRoot: null, element, invalidate: () => check() }
468
+ : null;
284
469
  const updateAutoContentObserver = () => {
285
- if (typeof MutationObserver === "undefined" || !element) {
470
+ if (!canObserve || !subscriber) {
286
471
  return;
287
472
  }
288
- let autoRoot = null;
289
- let current = element;
290
- while (current) {
291
- const rawDir = current.getAttribute?.("dir")?.trim().toLowerCase();
292
- if (rawDir === "auto") {
293
- autoRoot = current;
294
- break;
295
- }
296
- if (rawDir === "rtl" || rawDir === "ltr") {
297
- break;
298
- }
299
- current = current.parentElement;
473
+ const autoRoot = findAutoRoot(subscriber.element);
474
+ if (autoRoot === subscriber.autoRoot) {
475
+ return;
300
476
  }
301
- if (autoRoot) {
302
- if (observedAutoElement !== autoRoot) {
303
- if (unobserveAuto) {
304
- unobserveAuto();
305
- }
306
- observedAutoElement = autoRoot;
307
- unobserveAuto = observeAutoDirectionChanges(autoRoot, () => check());
477
+ unobserveAuto?.();
478
+ unobserveAuto = autoRoot ? observeAutoDirectionChanges(autoRoot, () => check()) : null;
479
+ subscriber.autoRoot = autoRoot;
480
+ };
481
+ // Re-registers with the roots the host is under now: it may have been created in a detached view and inserted
482
+ // into the document or a shadow root later.
483
+ const syncObservedRoots = () => {
484
+ if (!canObserve || !subscriber) {
485
+ return;
486
+ }
487
+ const roots = getObservedRoots(subscriber.element);
488
+ for (const [root, unsubscribe] of rootSubscriptions) {
489
+ if (!roots.includes(root)) {
490
+ unsubscribe();
491
+ rootSubscriptions.delete(root);
308
492
  }
309
493
  }
310
- else {
311
- if (unobserveAuto) {
312
- unobserveAuto();
313
- unobserveAuto = null;
314
- observedAutoElement = null;
494
+ for (const root of roots) {
495
+ if (!rootSubscriptions.has(root)) {
496
+ rootSubscriptions.set(root, registerDirectionSubscriber(root, subscriber));
315
497
  }
316
498
  }
317
499
  };
318
500
  const check = () => {
501
+ syncObservedRoots();
319
502
  updateAutoContentObserver();
320
503
  const nextDir = resolveComponentDirection(element, directionality);
321
504
  if (nextDir !== currentDir) {
@@ -333,27 +516,24 @@ function observeComponentDirection(hostElement, directionality, onChange) {
333
516
  });
334
517
  cleanupFns.push(() => sub.unsubscribe());
335
518
  }
336
- if (typeof MutationObserver !== "undefined") {
337
- if (element) {
338
- const elObserver = new MutationObserver(() => check());
339
- elObserver.observe(element, { attributes: true, attributeFilter: ["dir"] });
340
- cleanupFns.push(() => elObserver.disconnect());
341
- const root = element.getRootNode?.();
342
- if (root && root !== element && root !== element.ownerDocument) {
343
- const rootObserver = new MutationObserver(() => check());
344
- rootObserver.observe(root, { attributes: true, attributeFilter: ["dir"], subtree: true });
345
- cleanupFns.push(() => rootObserver.disconnect());
346
- }
347
- if (element.ownerDocument) {
348
- cleanupFns.push(observeDocumentDirChanges(element.ownerDocument, check));
349
- }
350
- }
519
+ if (canObserve && element) {
520
+ // The host's own dir, which the shared root observers miss while the host is outside them (detached or moved).
521
+ const hostObserver = new MutationObserver(() => check());
522
+ hostObserver.observe(element, { attributes: true, attributeFilter: ["dir"] });
523
+ cleanupFns.push(() => hostObserver.disconnect());
351
524
  }
525
+ syncObservedRoots();
526
+ cleanupFns.push(() => {
527
+ for (const unsubscribe of rootSubscriptions.values()) {
528
+ unsubscribe();
529
+ }
530
+ rootSubscriptions.clear();
531
+ });
352
532
  cleanupFns.push(() => {
353
- if (unobserveAuto) {
354
- unobserveAuto();
355
- unobserveAuto = null;
356
- observedAutoElement = null;
533
+ unobserveAuto?.();
534
+ unobserveAuto = null;
535
+ if (subscriber) {
536
+ subscriber.autoRoot = null;
357
537
  }
358
538
  });
359
539
  const handle = Object.assign(() => {
@@ -372,17 +552,17 @@ function injectComponentDirection(hostElementRef) {
372
552
  const destroyRef = inject(DestroyRef, { optional: true });
373
553
  const domVersion = signal(0, /* @ts-ignore */
374
554
  ...(ngDevMode ? [{ debugName: "domVersion" }] : /* istanbul ignore next */ []));
375
- let updateAutoObserverFn = () => { };
376
555
  if (destroyRef) {
377
- const cleanup = observeComponentDirection(hostRef, directionality, () => {
556
+ const handle = observeComponentDirection(hostRef, directionality, () => {
378
557
  domVersion.update(v => v + 1);
379
558
  });
380
- updateAutoObserverFn = cleanup.updateAutoObserver;
381
- destroyRef.onDestroy(() => cleanup());
559
+ destroyRef.onDestroy(() => handle());
560
+ // A host can be moved under a different `dir="auto"` root after it is created; re-check once it has rendered.
561
+ afterNextRender(() => handle.check(), { injector: inject(Injector) });
382
562
  }
563
+ // Pure: observers are managed by the handle above, never during signal evaluation.
383
564
  return computed(() => {
384
565
  domVersion();
385
- updateAutoObserverFn();
386
566
  return resolveComponentDirection(hostRef, directionality);
387
567
  });
388
568
  }
@@ -408,11 +588,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
408
588
  }] });
409
589
 
410
590
  function provideMonaI18n(config = {}) {
591
+ const initialLocale = config.initialLocale;
411
592
  return makeEnvironmentProviders([
412
593
  {
413
594
  provide: MONA_I18N_CONFIG,
414
595
  useValue: config
415
- }
596
+ },
597
+ ...(initialLocale == null
598
+ ? []
599
+ : [
600
+ // A failed load must not stop the application from starting: it keeps the configured locale.
601
+ provideAppInitializer(() => inject(MonaI18nService)
602
+ .load(initialLocale)
603
+ .catch((error) => console.error(`[mona-ui] Could not load the initial locale "${initialLocale}".`, error)))
604
+ ])
416
605
  ]);
417
606
  }
418
607
 
@@ -421,6 +610,9 @@ const FULLWIDTH_ASCII_START = 0xff01;
421
610
  const FULLWIDTH_ASCII_END = 0xff5e;
422
611
  const FULLWIDTH_ASCII_OFFSET = 0xfee0;
423
612
  const DECIMAL_DIGIT_REGEX = /^\p{Nd}$/u;
613
+ /** Apostrophe, right single quotation mark and modifier letter apostrophe. */
614
+ const APOSTROPHE_REGEX = /^['\u2019\u02BC]$/;
615
+ const APOSTROPHE_REGEX_GLOBAL = /['\u2019\u02BC]/g;
424
616
  const asciiDigitCache = new Map();
425
617
  const numberSymbolsCache = new Map();
426
618
  const numberFormatterCache = new Map();
@@ -554,7 +746,12 @@ function getNumberFormatter(locale, options) {
554
746
  }
555
747
  return formatter;
556
748
  }
749
+ /**
750
+ * Formats a number for the locale. A decimal string, such as one returned by {@link parseLocalizedDecimal}, is
751
+ * formatted exactly, without converting it to a `number` first.
752
+ */
557
753
  function formatNumber(value, locale, options) {
754
+ // Intl.NumberFormat formats decimal strings exactly (Intl.NumberFormat v3); the ES2022 lib types only declare numbers.
558
755
  return getNumberFormatter(locale, options).format(value);
559
756
  }
560
757
  /**
@@ -629,7 +826,52 @@ function getEditDecimalSeparators(symbols) {
629
826
  }
630
827
  return separators;
631
828
  }
829
+ const MAX_SAFE_INTEGER_DIGITS = String(Number.MAX_SAFE_INTEGER);
830
+ /** The most significant digits a double reproduces exactly when converted from decimal text and back. */
831
+ const MAX_EXACT_SIGNIFICANT_DIGITS = 15;
832
+ /** Whether ASCII integer and fraction digits survive conversion to a `number` unchanged. */
833
+ function isRepresentableAsNumber(intDigits, fracDigits) {
834
+ const integer = intDigits.replace(/^0+/, "");
835
+ const fraction = fracDigits.replace(/0+$/, "");
836
+ if (fraction === "") {
837
+ return (integer.length < MAX_SAFE_INTEGER_DIGITS.length ||
838
+ (integer.length === MAX_SAFE_INTEGER_DIGITS.length && integer <= MAX_SAFE_INTEGER_DIGITS));
839
+ }
840
+ return (integer + fraction).replace(/^0+/, "").length <= MAX_EXACT_SIGNIFICANT_DIGITS;
841
+ }
842
+ function partsToNumber(parts) {
843
+ if (!isRepresentableAsNumber(parts.integer, parts.fraction)) {
844
+ return "precision";
845
+ }
846
+ return Number((parts.negative ? "-" : "") + parts.integer + (parts.fraction ? "." + parts.fraction : ""));
847
+ }
848
+ /** Leading zeros, trailing fraction zeros and the sign of zero are dropped: `"-0012.500"` becomes `"-12.5"`. */
849
+ function partsToDecimal(parts) {
850
+ const integer = parts.integer.replace(/^0+(?=\d)/, "") || "0";
851
+ const fraction = parts.fraction.replace(/0+$/, "");
852
+ const digits = fraction ? `${integer}.${fraction}` : integer;
853
+ return parts.negative && /[1-9]/.test(digits) ? `-${digits}` : digits;
854
+ }
855
+ /**
856
+ * Parses localized text to a number. Returns `null` when the text is not a number in the locale, or when a
857
+ * `number` cannot hold it exactly (see {@link LocalizedNumberInvalidReason}); it never rounds silently.
858
+ * Use {@link parseLocalizedDecimal} for values that need more precision.
859
+ */
632
860
  function parseLocalizedNumber(text, localeId, options) {
861
+ const parts = parseLocalizedNumberParts(text, localeId, options);
862
+ const value = parts === null ? null : partsToNumber(parts);
863
+ return value === "precision" ? null : value;
864
+ }
865
+ /**
866
+ * Parses localized text with the same rules as {@link parseLocalizedNumber}, but returns a canonical decimal string
867
+ * (`"-1234.5"`: ASCII digits, `.` as the decimal separator, no grouping, exponent, leading zeros or trailing
868
+ * fraction zeros) and has no precision limit. Returns `null` when the text is not a number in the locale.
869
+ */
870
+ function parseLocalizedDecimal(text, localeId, options) {
871
+ const parts = parseLocalizedNumberParts(text, localeId, options);
872
+ return parts === null ? null : partsToDecimal(parts);
873
+ }
874
+ function parseLocalizedNumberParts(text, localeId, options) {
633
875
  if (text == null) {
634
876
  return null;
635
877
  }
@@ -662,6 +904,11 @@ function parseLocalizedNumber(text, localeId, options) {
662
904
  const groupSep = grouping.groupSeparator;
663
905
  const isSpaceGroup = /[ \u00A0\u202F]/.test(groupSep);
664
906
  const decimalSep = symbols.decimal;
907
+ // Engines disagree on the apostrophe a locale groups with (de-CH: Node ICU "'", Chrome "’"),
908
+ // and keyboards type "'", so every apostrophe variant counts as that locale's group separator.
909
+ if (APOSTROPHE_REGEX.test(groupSep)) {
910
+ cleaned = cleaned.replace(APOSTROPHE_REGEX_GLOBAL, groupSep);
911
+ }
665
912
  // Reject non-accepted control or whitespace characters (tabs, newlines, formfeeds)
666
913
  if (/[^\S\u0020\u00A0\u202F]/.test(cleaned)) {
667
914
  return null;
@@ -737,11 +984,8 @@ function parseLocalizedNumber(text, localeId, options) {
737
984
  if (intGroups.length < 2) {
738
985
  return null;
739
986
  }
740
- // Canonical strict check: must meet locale's minimum grouping threshold
741
- const totalGroupedDigits = intGroups.join("").length;
742
- if (totalGroupedDigits < grouping.minimumGroupedIntegerDigits) {
743
- return null;
744
- }
987
+ // Grouping below the locale's minimum (es-ES formats 1000, not 1.000) is still accepted:
988
+ // users type and paste it, and it is unambiguous because the group sizes are checked below.
745
989
  // Last (rightmost) group: must match primaryGroupSize digits
746
990
  const lastGroup = intGroups[intGroups.length - 1];
747
991
  if (lastGroup.length !== grouping.primaryGroupSize || !/^\d+$/.test(lastGroup)) {
@@ -770,9 +1014,7 @@ function parseLocalizedNumber(text, localeId, options) {
770
1014
  }
771
1015
  }
772
1016
  }
773
- const normalizedStr = sign + intPart + (fracPart && fracPart.length > 0 ? "." + fracPart : "");
774
- const num = Number(normalizedStr);
775
- return Number.isFinite(num) ? num : null;
1017
+ return { fraction: fracPart ?? "", integer: intPart, negative: sign === "-" };
776
1018
  }
777
1019
  else {
778
1020
  // mode === "edit": editor buffer grammar with exactly one decimal separator (locale primary or alternate)
@@ -826,11 +1068,7 @@ function parseLocalizedNumber(text, localeId, options) {
826
1068
  intPart = cleaned;
827
1069
  fracPart = "";
828
1070
  }
829
- const normalizedStr = sign +
830
- (intPart.length > 0 ? intPart : "0") +
831
- (fracPart.length > 0 ? "." + fracPart : cleaned.endsWith(foundSep ?? "") ? "" : "");
832
- const num = Number(normalizedStr);
833
- return Number.isFinite(num) ? num : null;
1071
+ return { fraction: fracPart, integer: intPart.length > 0 ? intPart : "0", negative: sign === "-" };
834
1072
  }
835
1073
  }
836
1074
  function validateLocalizedNumber(text, localeId, options) {
@@ -842,9 +1080,13 @@ function validateLocalizedNumber(text, localeId, options) {
842
1080
  return { value: null, valid: true, fractionDigits: 0 };
843
1081
  }
844
1082
  const mode = options?.mode ?? "locale";
845
- const parsed = parseLocalizedNumber(trimmed, localeId, { mode });
1083
+ const parts = parseLocalizedNumberParts(trimmed, localeId, { mode });
1084
+ const parsed = parts === null ? null : partsToNumber(parts);
1085
+ if (parsed === "precision") {
1086
+ return { value: null, valid: false, fractionDigits: 0, reason: "precision" };
1087
+ }
846
1088
  if (parsed === null || !Number.isFinite(parsed)) {
847
- return { value: null, valid: false, fractionDigits: 0 };
1089
+ return { value: null, valid: false, fractionDigits: 0, reason: "syntax" };
848
1090
  }
849
1091
  // Determine the number of fraction digits from the source string
850
1092
  const symbols = getNumberSymbols(localeId);
@@ -903,11 +1145,42 @@ function validateLocalizedNumber(text, localeId, options) {
903
1145
  }
904
1146
  }
905
1147
  if (options?.decimals !== undefined && fractionDigits > options.decimals) {
906
- return { value: parsed, valid: false, fractionDigits };
1148
+ return { value: parsed, valid: false, fractionDigits, reason: "decimals" };
907
1149
  }
908
1150
  return { value: parsed, valid: true, fractionDigits };
909
1151
  }
1152
+ /**
1153
+ * Validates localized text like {@link validateLocalizedNumber}, but returns a canonical decimal string and has no
1154
+ * precision limit, so `reason` is never `"precision"`.
1155
+ */
1156
+ function validateLocalizedDecimal(text, localeId, options) {
1157
+ const trimmed = text == null ? "" : String(text).trim();
1158
+ if (trimmed === "" || trimmed === "-" || trimmed === "+") {
1159
+ return { fractionDigits: 0, valid: true, value: null };
1160
+ }
1161
+ const parts = parseLocalizedNumberParts(trimmed, localeId, { mode: options?.mode ?? "locale" });
1162
+ if (parts === null) {
1163
+ return { fractionDigits: 0, reason: "syntax", valid: false, value: null };
1164
+ }
1165
+ const value = partsToDecimal(parts);
1166
+ const fractionDigits = parts.fraction.length;
1167
+ if (options?.decimals !== undefined && fractionDigits > options.decimals) {
1168
+ return { fractionDigits, reason: "decimals", valid: false, value };
1169
+ }
1170
+ return { fractionDigits, valid: true, value };
1171
+ }
1172
+ const EDIT_SYNTAX_ERROR = { complete: false, reason: "syntax", valid: false, value: null };
910
1173
  function validateLocalizedNumberEdit(text, localeId, options) {
1174
+ return validateEditInput(text, localeId, options, partsToNumber);
1175
+ }
1176
+ /**
1177
+ * Validates text while it is typed, like {@link validateLocalizedNumberEdit}, but returns a canonical decimal string
1178
+ * and has no precision limit.
1179
+ */
1180
+ function validateLocalizedDecimalEdit(text, localeId, options) {
1181
+ return validateEditInput(text, localeId, options, partsToDecimal);
1182
+ }
1183
+ function validateEditInput(text, localeId, options, convert) {
911
1184
  if (text == null) {
912
1185
  return { valid: true, complete: false, value: null };
913
1186
  }
@@ -917,7 +1190,7 @@ function validateLocalizedNumberEdit(text, localeId, options) {
917
1190
  }
918
1191
  // Reject control or whitespace characters in interactive typing
919
1192
  if (/[\s\u00A0\u202F]/.test(raw)) {
920
- return { valid: false, complete: false, value: null };
1193
+ return EDIT_SYNTAX_ERROR;
921
1194
  }
922
1195
  const symbols = getNumberSymbols(localeId);
923
1196
  // Normalize minus signs and bidi controls
@@ -928,12 +1201,12 @@ function validateLocalizedNumberEdit(text, localeId, options) {
928
1201
  const allowSign = options?.allowSign ?? true;
929
1202
  if (normalized.startsWith("-") || normalized.startsWith("+")) {
930
1203
  if (!allowSign) {
931
- return { valid: false, complete: false, value: null };
1204
+ return EDIT_SYNTAX_ERROR;
932
1205
  }
933
1206
  normalized = normalized.slice(1);
934
1207
  }
935
1208
  if (normalized.includes("-") || normalized.includes("+")) {
936
- return { valid: false, complete: false, value: null };
1209
+ return EDIT_SYNTAX_ERROR;
937
1210
  }
938
1211
  // If input was just "-" or "+"
939
1212
  if (normalized === "") {
@@ -948,7 +1221,7 @@ function validateLocalizedNumberEdit(text, localeId, options) {
948
1221
  if (normalized.startsWith(sep, i)) {
949
1222
  if (foundSep !== null) {
950
1223
  // More than one separator is rejected in edit mode
951
- return { valid: false, complete: false, value: null };
1224
+ return EDIT_SYNTAX_ERROR;
952
1225
  }
953
1226
  foundSep = sep;
954
1227
  sepIndex = i;
@@ -961,23 +1234,28 @@ function validateLocalizedNumberEdit(text, localeId, options) {
961
1234
  if (foundSep !== null) {
962
1235
  if (decimals === 0) {
963
1236
  // No decimal point permitted when decimals === 0
964
- return { valid: false, complete: false, value: null };
1237
+ return { valid: false, complete: false, value: null, reason: "decimals" };
965
1238
  }
966
1239
  const intPart = normalized.slice(0, sepIndex);
967
1240
  const fracPart = normalized.slice(sepIndex + foundSep.length);
968
1241
  // Integer part before separator must consist purely of ASCII digits (or empty, e.g. ".5")
969
1242
  if (intPart.length > 0 && !/^\d+$/.test(intPart)) {
970
- return { valid: false, complete: false, value: null };
1243
+ return EDIT_SYNTAX_ERROR;
971
1244
  }
972
1245
  // Fractional part after separator must consist purely of ASCII digits (or empty, e.g. "12.")
973
1246
  if (fracPart.length > 0 && !/^\d+$/.test(fracPart)) {
974
- return { valid: false, complete: false, value: null };
1247
+ return EDIT_SYNTAX_ERROR;
975
1248
  }
976
1249
  // Count fraction digits using normalized ASCII characters (safe against UTF-16 astral splitting)
977
1250
  if (fracPart.length > decimals) {
978
- return { valid: false, complete: false, value: null };
1251
+ return { valid: false, complete: false, value: null, reason: "decimals" };
1252
+ }
1253
+ const parts = parseLocalizedNumberParts(raw, localeId, { mode: "edit" });
1254
+ const parsed = parts === null ? null : convert(parts);
1255
+ // Refuse the keystroke that would make the value too precise for the target type, instead of rounding it.
1256
+ if (parsed === "precision") {
1257
+ return { valid: false, complete: false, value: null, reason: "precision" };
979
1258
  }
980
- const parsed = parseLocalizedNumber(raw, localeId, { mode: "edit" });
981
1259
  const complete = fracPart.length > 0 && (intPart.length > 0 || fracPart.length > 0);
982
1260
  return {
983
1261
  valid: true,
@@ -987,9 +1265,13 @@ function validateLocalizedNumberEdit(text, localeId, options) {
987
1265
  }
988
1266
  // No separator found: must be purely ASCII digits
989
1267
  if (!/^\d+$/.test(normalized)) {
990
- return { valid: false, complete: false, value: null };
1268
+ return EDIT_SYNTAX_ERROR;
1269
+ }
1270
+ const parts = parseLocalizedNumberParts(raw, localeId, { mode: "edit" });
1271
+ const parsed = parts === null ? null : convert(parts);
1272
+ if (parsed === "precision") {
1273
+ return { valid: false, complete: false, value: null, reason: "precision" };
991
1274
  }
992
- const parsed = parseLocalizedNumber(raw, localeId, { mode: "edit" });
993
1275
  return {
994
1276
  valid: true,
995
1277
  complete: true,
@@ -4930,5 +5212,5 @@ function generatePseudoLocale(catalogs, options) {
4930
5212
  * Generated bundle index. Do not edit.
4931
5213
  */
4932
5214
 
4933
- export { BIDI_CONTROL_REGEX, MONA_DEFAULT_LOCALE, MONA_I18N_CONFIG, MonaDirectionService, MonaI18nService, formatGregorianDateToLocaleString, formatNumber, generatePseudoLocale, getEditDecimalSeparators, getLocaleDateInputFormat, getLocaleDateTimeInputFormat, getLocaleFirstDayOfWeek, getLocaleTimeInputFormat, getNumberFormatter, getNumberGroupingPattern, getNumberSymbols, gregorianDateTime, gregorianDateTimeFromObject, injectComponentDirection, mergeMessages, mergeTwo, normalizeGregorianLocale, normalizeLocalizedDigits, normalizeLocalizedInput, normalizeLocalizedMinus, observeComponentDirection, parseGregorianDate, parseLocalizedNumber, provideMonaI18n, pseudoLocalize, pseudoLocalizeCatalog, resolveComponentDirection, sanitizeDateText, stripBidiControls, validateLocalizedNumber, validateLocalizedNumberEdit };
5215
+ export { BIDI_CONTROL_REGEX, MONA_DEFAULT_LOCALE, MONA_FALLBACK_LOCALE_ID, MONA_I18N_CONFIG, MonaDirectionService, MonaI18nService, canonicalizeLocaleId, formatGregorianDateToLocaleString, formatNumber, generatePseudoLocale, getEditDecimalSeparators, getLocaleDateInputFormat, getLocaleDateTimeInputFormat, getLocaleFirstDayOfWeek, getLocaleTimeInputFormat, getNumberFormatter, getNumberGroupingPattern, getNumberSymbols, gregorianDateTime, gregorianDateTimeFromObject, injectComponentDirection, mergeMessages, mergeTwo, normalizeGregorianLocale, normalizeLocalizedDigits, normalizeLocalizedInput, normalizeLocalizedMinus, observeComponentDirection, parseGregorianDate, parseLocalizedDecimal, parseLocalizedNumber, provideMonaI18n, pseudoLocalize, pseudoLocalizeCatalog, resolveComponentDirection, sanitizeDateText, stripBidiControls, tryCanonicalizeLocaleId, validateLocalizedDecimal, validateLocalizedDecimalEdit, validateLocalizedNumber, validateLocalizedNumberEdit };
4934
5216
  //# sourceMappingURL=nanahoshi-mona-ui-i18n.mjs.map