@nanahoshi/mona-ui 0.23.2 → 0.23.3

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 (21) hide show
  1. package/fesm2022/nanahoshi-mona-ui-calendar.mjs +7 -4
  2. package/fesm2022/nanahoshi-mona-ui-color-gradient.mjs +5 -2
  3. package/fesm2022/nanahoshi-mona-ui-dropdowns.mjs +6 -3
  4. package/fesm2022/nanahoshi-mona-ui-editor.mjs +5 -2
  5. package/fesm2022/{nanahoshi-mona-ui-grid-grid-footer-row.component-CIMXz60d.mjs → nanahoshi-mona-ui-grid-grid-footer-row.component-BSVlcB9_.mjs} +2 -2
  6. package/fesm2022/{nanahoshi-mona-ui-grid-grid-list.component-CpeheGXU.mjs → nanahoshi-mona-ui-grid-grid-list.component-B2pBOuLa.mjs} +4 -4
  7. package/fesm2022/{nanahoshi-mona-ui-grid-grid-row.component-B_5ay_Nz.mjs → nanahoshi-mona-ui-grid-grid-row.component-Buf4evf5.mjs} +2 -2
  8. package/fesm2022/{nanahoshi-mona-ui-grid-grid-virtual-list.component-BzKywU5b.mjs → nanahoshi-mona-ui-grid-grid-virtual-list.component-1RdqLEa9.mjs} +4 -4
  9. package/fesm2022/{nanahoshi-mona-ui-grid-nanahoshi-mona-ui-grid-IewqeT4m.mjs → nanahoshi-mona-ui-grid-nanahoshi-mona-ui-grid-DeRc_QcP.mjs} +15 -12
  10. package/fesm2022/nanahoshi-mona-ui-grid.mjs +1 -1
  11. package/fesm2022/nanahoshi-mona-ui-i18n.mjs +61 -12
  12. package/fesm2022/nanahoshi-mona-ui-locales.mjs +352 -279
  13. package/fesm2022/nanahoshi-mona-ui-multi-select.mjs +5 -2
  14. package/fesm2022/nanahoshi-mona-ui-notification.mjs +17 -2
  15. package/fesm2022/nanahoshi-mona-ui-pager.mjs +10 -7
  16. package/fesm2022/nanahoshi-mona-ui-popup.mjs +15 -1
  17. package/fesm2022/nanahoshi-mona-ui-rating.mjs +5 -2
  18. package/fesm2022/nanahoshi-mona-ui-scroll-view.mjs +6 -3
  19. package/package.json +1 -1
  20. package/types/nanahoshi-mona-ui-i18n.d.ts +32 -4
  21. package/types/nanahoshi-mona-ui-notification.d.ts +1 -0
@@ -6,7 +6,7 @@ import { none } from '@mirei/ts-collections';
6
6
  import { ChipComponent } from '@nanahoshi/mona-ui/chip';
7
7
  import * as i1 from '@nanahoshi/mona-ui/dropdowns';
8
8
  import { DropdownService, DropdownFooterTemplateDirective, DropdownGroupHeaderTemplateDirective, DropdownHeaderTemplateDirective, DropdownItemTemplateDirective, dropdownPopupThemeVariants, DropdownNoDataTemplateDirective, DropdownPrefixTemplateDirective, DropdownLiveRegionDirective, DropdownDataInputToken, DROPDOWN_POPUP_INPUT_TOKEN, DropdownDataHandlerDirective, DropdownListPopupHandlerDirective } from '@nanahoshi/mona-ui/dropdowns';
9
- import { MonaI18nService } from '@nanahoshi/mona-ui/i18n';
9
+ import { formatNumber, MonaI18nService } from '@nanahoshi/mona-ui/i18n';
10
10
  import { themeControlSurfaceClasses, createElementControlId, restoreOverlayScroll, rxFromResize } from '@nanahoshi/mona-ui/internal';
11
11
  import { IndicatorIconComponent } from '@nanahoshi/mona-ui/internal/indicator-icon';
12
12
  import { ListService, ListComponent, ListGroupHeaderTemplateDirective, ListItemTemplateDirective, ListFooterTemplateDirective, ListHeaderTemplateDirective, ListNoDataTemplateDirective } from '@nanahoshi/mona-ui/internal/list';
@@ -25,9 +25,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
25
25
  }]
26
26
  }] });
27
27
 
28
+ function localizeNumber(value) {
29
+ return formatNumber(value, "en-US");
30
+ }
28
31
  const MULTI_SELECT_DEFAULT_MESSAGES = {
29
32
  clear: "Clear",
30
- itemsCount: (count) => `+ ${count} ${count === 1 ? "item" : "items"}`
33
+ itemsCount: (count) => `+ ${localizeNumber(count)} ${count === 1 ? "item" : "items"}`
31
34
  };
32
35
 
33
36
  class MultiSelectService {
@@ -260,9 +260,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
260
260
  class NotificationService {
261
261
  #appRef;
262
262
  #document;
263
+ #pendingTimeouts;
263
264
  constructor() {
264
265
  this.#appRef = inject(ApplicationRef);
265
266
  this.#document = inject(DOCUMENT$1);
267
+ this.#pendingTimeouts = new Set();
266
268
  this.notificationContainerMap = new Map();
267
269
  this.notificationContainerSubscriptions = {
268
270
  bottom: null,
@@ -273,6 +275,12 @@ class NotificationService {
273
275
  topright: null
274
276
  };
275
277
  this.initialize();
278
+ // The ApplicationRef is destroyed with the same injector, so a timeout firing afterwards would detach
279
+ // views from a destroyed ApplicationRef.
280
+ inject(DestroyRef).onDestroy(() => {
281
+ this.#pendingTimeouts.forEach(handle => window.clearTimeout(handle));
282
+ this.#pendingTimeouts.clear();
283
+ });
276
284
  }
277
285
  hide(id) {
278
286
  const notificationData = this.getNotificationDataById(id);
@@ -326,7 +334,7 @@ class NotificationService {
326
334
  host.appendChild(notificationContainerComponent.location.nativeElement);
327
335
  this.#appRef.attachView(notificationContainerComponent.hostView);
328
336
  notificationContainerComponent.changeDetectorRef.detectChanges();
329
- window.setTimeout(() => {
337
+ this.scheduleTimeout(() => {
330
338
  containerSubject.next(notificationContainerComponent);
331
339
  });
332
340
  return containerSubject;
@@ -427,7 +435,7 @@ class NotificationService {
427
435
  container.componentRef = null;
428
436
  container.subscription = undefined;
429
437
  this.notificationContainerSubscriptions[position] = null;
430
- window.setTimeout(() => {
438
+ this.scheduleTimeout(() => {
431
439
  if (componentRefToDestroy) {
432
440
  this.#appRef.detachView(componentRefToDestroy.hostView);
433
441
  componentRefToDestroy.destroy();
@@ -435,6 +443,13 @@ class NotificationService {
435
443
  }, 300);
436
444
  }
437
445
  }
446
+ scheduleTimeout(callback, delay) {
447
+ const handle = window.setTimeout(() => {
448
+ this.#pendingTimeouts.delete(handle);
449
+ callback();
450
+ }, delay);
451
+ this.#pendingTimeouts.add(handle);
452
+ }
438
453
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NotificationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
439
454
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NotificationService, providedIn: "root" }); }
440
455
  }
@@ -10,24 +10,27 @@ import { PreventableEvent, SlicePipe } from '@nanahoshi/mona-ui/common';
10
10
  import { DropdownListComponent, DropdownListValueTemplateDirective } from '@nanahoshi/mona-ui/dropdown-list';
11
11
  import { DropdownVirtualScrollDirective, DropdownItemTemplateDirective } from '@nanahoshi/mona-ui/dropdowns';
12
12
  import { themeRaisedBackdropClasses, NavigationKeys } from '@nanahoshi/mona-ui/internal';
13
- import { injectComponentDirection, MonaI18nService, formatNumber } from '@nanahoshi/mona-ui/i18n';
13
+ import { formatNumber, injectComponentDirection, MonaI18nService } from '@nanahoshi/mona-ui/i18n';
14
14
  import { NumericTextBoxComponent } from '@nanahoshi/mona-ui/numeric-text-box';
15
15
  import { twMerge } from 'tailwind-merge';
16
16
  import { cva } from 'class-variance-authority';
17
17
 
18
+ function localizeNumber(value) {
19
+ return formatNumber(value, "en-US");
20
+ }
18
21
  const PAGER_DEFAULT_MESSAGES = {
19
22
  firstPageLabel: "First page",
20
- jumpBackwardLabel: (pages) => `Jump back ${pages} pages`,
21
- jumpForwardLabel: (pages) => `Jump forward ${pages} pages`,
23
+ jumpBackwardLabel: (pages) => `Jump back ${localizeNumber(pages)} pages`,
24
+ jumpForwardLabel: (pages) => `Jump forward ${localizeNumber(pages)} pages`,
22
25
  lastPageLabel: "Last page",
23
26
  nextPageLabel: "Next page",
24
27
  ofText: "of",
25
- pageLabel: (page) => `Page ${page}`,
26
- pageSizeLabel: (pageSize) => `${pageSize} / page`,
27
- pageStatus: (page, totalPages) => `Page ${page} of ${totalPages}`,
28
+ pageLabel: (page) => `Page ${localizeNumber(page)}`,
29
+ pageSizeLabel: (pageSize) => `${localizeNumber(pageSize)} / page`,
30
+ pageStatus: (page, totalPages) => `Page ${localizeNumber(page)} of ${localizeNumber(totalPages)}`,
28
31
  pageText: "Page",
29
32
  previousPageLabel: "Previous page",
30
- rangeStatus: (start, end, total) => `${start} - ${end} of ${total} items`
33
+ rangeStatus: (start, end, total) => `${localizeNumber(start)} - ${localizeNumber(end)} of ${localizeNumber(total)} items`
31
34
  };
32
35
 
33
36
  class PagerFocusableDirective {
@@ -636,7 +636,7 @@ class PopupService {
636
636
  ? settings.anchor
637
637
  : null;
638
638
  const direction = resolveComponentDirection(anchorElement, this.#directionality);
639
- return this.#overlay.create(new OverlayConfig({
639
+ const overlayRef = this.#overlay.create(new OverlayConfig({
640
640
  positionStrategy,
641
641
  hasBackdrop: settings.hasBackdrop ?? false,
642
642
  height: settings.height,
@@ -650,6 +650,11 @@ class PopupService {
650
650
  direction,
651
651
  scrollStrategy: settings.blockScroll ? this.#overlay.scrollStrategies.block() : undefined
652
652
  }));
653
+ const lang = resolvePopupLanguage(anchorElement, this.#i18n?.localeId());
654
+ if (lang) {
655
+ overlayRef.overlayElement.setAttribute("lang", lang);
656
+ }
657
+ return overlayRef;
653
658
  }
654
659
  createPositionStrategy(settings) {
655
660
  if (settings.positionStrategy === "global") {
@@ -1046,6 +1051,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
1046
1051
  providedIn: "root"
1047
1052
  }]
1048
1053
  }] });
1054
+ /**
1055
+ * Language of the popup content. The overlay container sits outside the app root, so a `lang` set on the root
1056
+ * (or near the anchor) would not reach the popup and screen readers could read it with the wrong voice
1057
+ * (WCAG 3.1.2). The nearest `lang` above the anchor wins; without one, the active Mona locale is used.
1058
+ */
1059
+ function resolvePopupLanguage(anchorElement, localeId) {
1060
+ const inherited = anchorElement?.closest("[lang]")?.getAttribute("lang")?.trim();
1061
+ return inherited || localeId || null;
1062
+ }
1049
1063
 
1050
1064
  class PopupComponent {
1051
1065
  #destroyRef;
@@ -4,13 +4,16 @@ import { inject, TemplateRef, Directive, computed, viewChild, contentChild, sign
4
4
  import { LucideStar, LucideHeart, LucideFlame, LucideDiamond, LucideCircle, LucideDynamicIcon } from '@lucide/angular';
5
5
  import { createElementControlId } from '@nanahoshi/mona-ui/internal';
6
6
  import { range } from '@mirei/ts-collections';
7
- import { injectComponentDirection, MonaI18nService } from '@nanahoshi/mona-ui/i18n';
7
+ import { formatNumber, injectComponentDirection, MonaI18nService } from '@nanahoshi/mona-ui/i18n';
8
8
  import { twMerge } from 'tailwind-merge';
9
9
  import { cva } from 'class-variance-authority';
10
10
 
11
+ function localizeNumber(value) {
12
+ return formatNumber(value, "en-US");
13
+ }
11
14
  const RATING_DEFAULT_MESSAGES = {
12
15
  notRated: "Not rated",
13
- valueText: (value, max) => `${value} out of ${max}`
16
+ valueText: (value, max) => `${localizeNumber(value)} out of ${localizeNumber(max)}`
14
17
  };
15
18
 
16
19
  /**
@@ -6,7 +6,7 @@ import { toCssValue } from '@nanahoshi/mona-ui/internal';
6
6
  import { select } from '@mirei/ts-collections';
7
7
  import { Subject, startWith, pairwise, map, interval, takeUntil, asyncScheduler, fromEvent, filter, tap } from 'rxjs';
8
8
  import { twMerge } from 'tailwind-merge';
9
- import { injectComponentDirection, MonaI18nService } from '@nanahoshi/mona-ui/i18n';
9
+ import { formatNumber, injectComponentDirection, MonaI18nService } from '@nanahoshi/mona-ui/i18n';
10
10
  import { cva } from 'class-variance-authority';
11
11
 
12
12
  const scrollViewBaseThemeVariants = cva(`
@@ -133,11 +133,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
133
133
  }]
134
134
  }], propDecorators: { active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: true }] }], rounded: [{ type: i0.Input, args: [{ isSignal: true, alias: "rounded", required: true }] }] } });
135
135
 
136
+ function localizeNumber(value) {
137
+ return formatNumber(value, "en-US");
138
+ }
136
139
  const SCROLL_VIEW_DEFAULT_MESSAGES = {
137
140
  carousel: "carousel",
138
141
  nextPage: "Next page",
139
- page: (current) => `Page ${current}`,
140
- pageOf: (current, total) => `Page ${current} of ${total}`,
142
+ page: (current) => `Page ${localizeNumber(current)}`,
143
+ pageOf: (current, total) => `Page ${localizeNumber(current)} of ${localizeNumber(total)}`,
141
144
  previousPage: "Previous page",
142
145
  scrollPagerPrevious: "Scroll pager previous",
143
146
  scrollPagerNext: "Scroll pager next",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanahoshi/mona-ui",
3
- "version": "0.23.2",
3
+ "version": "0.23.3",
4
4
  "description": "A very light Angular UI framework",
5
5
  "author": "Nanahoshi",
6
6
  "repository": {
@@ -484,8 +484,21 @@ declare const MONA_DEFAULT_LOCALE: MonaLocale;
484
484
 
485
485
  interface MonaI18nConfig {
486
486
  readonly locale?: MonaLocale;
487
+ /**
488
+ * Overrides applied only while the locale with that id is active, keyed by locale id such as "de-DE".
489
+ * They take precedence over {@link MonaI18nConfig.messages}.
490
+ */
491
+ readonly localeMessages?: Readonly<Record<string, DeepPartial<MonaLocaleMessages>>>;
492
+ /** Overrides applied whatever the active locale is. */
487
493
  readonly messages?: DeepPartial<MonaLocaleMessages>;
488
494
  }
495
+ interface MonaMessageOverrideOptions {
496
+ /**
497
+ * Locale id the overrides apply to. When omitted, the overrides apply to every locale.
498
+ * Ids are canonicalized, so `de_DE` and `de-DE` address the same overrides.
499
+ */
500
+ readonly locale?: string;
501
+ }
489
502
 
490
503
  declare const MONA_I18N_CONFIG: InjectionToken<MonaI18nConfig>;
491
504
 
@@ -499,10 +512,21 @@ declare class MonaI18nService {
499
512
  */
500
513
  readonly localeId: Signal<string>;
501
514
  constructor();
502
- clearMessages(): void;
515
+ /**
516
+ * Removes overrides. With `options.locale`, only that locale's overrides are removed; without it, every
517
+ * override is removed, global and per locale.
518
+ */
519
+ clearMessages(options?: MonaMessageOverrideOptions): void;
503
520
  componentMessages<K extends keyof MonaLocaleMessages>(namespace: K, fallback: MonaLocaleMessages[K]): Signal<MonaLocaleMessages[K]>;
504
- patchMessages(messages: DeepPartial<MonaLocaleMessages>): void;
505
- setMessages(messages: DeepPartial<MonaLocaleMessages>): void;
521
+ /**
522
+ * Merges overrides into the existing ones. With `options.locale`, they apply only while that locale is
523
+ * active, so overrides patched for German do not show after switching to English.
524
+ */
525
+ patchMessages(messages: DeepPartial<MonaLocaleMessages>, options?: MonaMessageOverrideOptions): void;
526
+ /**
527
+ * Replaces the existing overrides. With `options.locale`, only that locale's overrides are replaced.
528
+ */
529
+ setMessages(messages: DeepPartial<MonaLocaleMessages>, options?: MonaMessageOverrideOptions): void;
506
530
  use(locale: MonaLocale): void;
507
531
  static ɵfac: i0.ɵɵFactoryDeclaration<MonaI18nService, never>;
508
532
  static ɵprov: i0.ɵɵInjectableDeclaration<MonaI18nService>;
@@ -540,6 +564,10 @@ interface NumberGroupingPattern {
540
564
  readonly primaryGroupSize: number;
541
565
  readonly secondaryGroupSize: number;
542
566
  readonly groupSeparator: string;
567
+ /**
568
+ * Fewest integer digits the locale groups when formatting (5 in es-ES, which formats 1000 but 10.000).
569
+ * Parsing does not enforce it: "1.000" is still read as 1000 in es-ES.
570
+ */
543
571
  readonly minimumGroupedIntegerDigits: number;
544
572
  }
545
573
  declare function getNumberGroupingPattern(locale: string): NumberGroupingPattern;
@@ -695,4 +723,4 @@ declare function pseudoLocalizeCatalog<T extends object>(catalog: T, options?: P
695
723
  declare function generatePseudoLocale(catalogs: DeepPartial<MonaLocaleMessages>, options?: GeneratePseudoLocaleOptions): MonaLocale;
696
724
 
697
725
  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 };
698
- export type { CalendarMessages, DatePickerMessages, DateTimePickerMessages, DeepPartial, DialogMessages, DirectionObserverHandle, FilterMessages, GeneratePseudoLocaleOptions, LocaleFirstDayOfWeek, LocaleTimeFormatOptions, LocalizedNumberEditValidationResult, LocalizedNumberParseMode, LocalizedNumberValidationResult, MonaAutoCompleteMessages, MonaBreadcrumbMessages, MonaButtonGroupMessages, MonaCalendarMessages, MonaCardMessages, MonaChartMessages, MonaChipMessages, MonaColorGradientMessages, MonaColorPaletteMessages, MonaColorPickerMessages, MonaComboBoxMessages, MonaDatePickerMessages, MonaDateTimePickerMessages, MonaDialogMessages, MonaDropdownListMessages, MonaDropdownsMessages, MonaEditorMessages, MonaFilterMessages, MonaGridMessages, MonaI18nConfig, MonaListBoxMessages, MonaListMessages, MonaLocale, MonaLocaleMessages, MonaMessageNamespace, MonaMultiSelectMessages, MonaNotificationMessages, MonaNumericTextBoxMessages, MonaOtpInputMessages, MonaPagerMessages, MonaRatingMessages, MonaScrollViewMessages, MonaSheetMessages, MonaSliderMessages, MonaSpinnerMessages, MonaSplitButtonMessages, MonaSplitterMessages, MonaStepperMessages, MonaTabsMessages, MonaTextBoxMessages, MonaTextDirection, MonaTimePickerMessages, MonaTimeSelectorMessages, MonaTreeViewMessages, MonaWindowMessages, NotificationMessages, NumberGroupingPattern, NumberSymbols, ParseLocalizedNumberOptions, PseudoLocalizationOptions, SheetMessages, TimePickerMessages, TimeSelectorMessages, ValidateLocalizedNumberEditOptions, WindowMessages };
726
+ export type { CalendarMessages, DatePickerMessages, DateTimePickerMessages, DeepPartial, DialogMessages, DirectionObserverHandle, FilterMessages, GeneratePseudoLocaleOptions, LocaleFirstDayOfWeek, LocaleTimeFormatOptions, LocalizedNumberEditValidationResult, LocalizedNumberParseMode, LocalizedNumberValidationResult, MonaAutoCompleteMessages, MonaBreadcrumbMessages, MonaButtonGroupMessages, MonaCalendarMessages, MonaCardMessages, MonaChartMessages, MonaChipMessages, MonaColorGradientMessages, MonaColorPaletteMessages, MonaColorPickerMessages, MonaComboBoxMessages, MonaDatePickerMessages, MonaDateTimePickerMessages, MonaDialogMessages, MonaDropdownListMessages, MonaDropdownsMessages, MonaEditorMessages, MonaFilterMessages, MonaGridMessages, MonaI18nConfig, MonaListBoxMessages, MonaListMessages, MonaLocale, MonaLocaleMessages, MonaMessageNamespace, MonaMessageOverrideOptions, MonaMultiSelectMessages, MonaNotificationMessages, MonaNumericTextBoxMessages, MonaOtpInputMessages, MonaPagerMessages, MonaRatingMessages, MonaScrollViewMessages, MonaSheetMessages, MonaSliderMessages, MonaSpinnerMessages, MonaSplitButtonMessages, MonaSplitterMessages, MonaStepperMessages, MonaTabsMessages, MonaTextBoxMessages, MonaTextDirection, MonaTimePickerMessages, MonaTimeSelectorMessages, MonaTreeViewMessages, MonaWindowMessages, NotificationMessages, NumberGroupingPattern, NumberSymbols, ParseLocalizedNumberOptions, PseudoLocalizationOptions, SheetMessages, TimePickerMessages, TimeSelectorMessages, ValidateLocalizedNumberEditOptions, WindowMessages };
@@ -96,6 +96,7 @@ declare class NotificationService {
96
96
  private getNotificationDataById;
97
97
  private initialize;
98
98
  private removeNotificationData;
99
+ private scheduleTimeout;
99
100
  static ɵfac: i0.ɵɵFactoryDeclaration<NotificationService, never>;
100
101
  static ɵprov: i0.ɵɵInjectableDeclaration<NotificationService>;
101
102
  }