@astryxdesign/core 0.7.1 → 0.7.2-canary.3aca324
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +38 -14
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +10 -2
- package/dist/Markdown/plugins/frontmatter.d.ts.map +1 -1
- package/dist/Markdown/plugins/frontmatter.js +7 -3
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +22 -8
- package/dist/MultiSelector/OptionActions.a11y.chromium.spec.js +100 -2
- package/dist/MultiSelector/hooks.d.ts.map +1 -1
- package/dist/MultiSelector/hooks.js +3 -1
- package/dist/Pagination/Pagination.d.ts +8 -5
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +21 -8
- package/dist/PowerSearch/PowerSearch.js +1 -1
- package/dist/Selector/hooks.d.ts.map +1 -1
- package/dist/Selector/hooks.js +6 -2
- package/dist/Switch/Switch.d.ts +1 -1
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +13 -1
- package/dist/Text/useTruncation.d.ts +7 -5
- package/dist/Text/useTruncation.d.ts.map +1 -1
- package/dist/Text/useTruncation.js +16 -5
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +11 -0
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +26 -0
- package/dist/astryx.css +5 -4
- package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/en.generated.js +112 -1
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +112 -1
- package/dist/utils/focusOutline.stylex.d.ts +4 -0
- package/dist/utils/focusOutline.stylex.d.ts.map +1 -1
- package/dist/utils/focusOutline.stylex.js +4 -0
- package/dist/utils/sharedMutationObserver.d.ts +21 -0
- package/dist/utils/sharedMutationObserver.d.ts.map +1 -0
- package/dist/utils/sharedMutationObserver.js +69 -0
- package/locales/en.json +444 -0
- package/locales/pseudo.json +333 -0
- package/package.json +4 -4
- package/src/Button/Button.doc.mjs +2 -2
- package/src/Button/Button.test.tsx +155 -9
- package/src/Button/Button.tsx +39 -16
- package/src/Button/__tests__/Button.a11y.known-failures.ts +1 -62
- package/src/Button/__tests__/Button.a11y.states.ts +0 -10
- package/src/HoverCard/HoverCard.test.tsx +58 -0
- package/src/HoverCard/useHoverCard.tsx +10 -2
- package/src/IconButton/IconButton.doc.mjs +2 -2
- package/src/IconButton/IconButton.test.tsx +3 -1
- package/src/List/ListItem.doc.mjs +8 -5
- package/src/Markdown/plugins/frontmatter.test.tsx +25 -0
- package/src/Markdown/plugins/frontmatter.ts +7 -3
- package/src/MultiSelector/MultiSelector.test.tsx +159 -0
- package/src/MultiSelector/MultiSelector.tsx +40 -7
- package/src/MultiSelector/OptionActions.a11y.chromium.spec.ts +112 -2
- package/src/MultiSelector/hooks.ts +3 -1
- package/src/MultiSelector/useMultiCombobox.doc.mjs +267 -0
- package/src/Pagination/Pagination.doc.mjs +7 -7
- package/src/Pagination/Pagination.spec.md +9 -9
- package/src/Pagination/Pagination.test.tsx +242 -0
- package/src/Pagination/Pagination.tsx +27 -11
- package/src/PowerSearch/PowerSearch.test.tsx +85 -0
- package/src/PowerSearch/PowerSearch.tsx +2 -2
- package/src/Selector/hooks.ts +6 -2
- package/src/Selector/useCombobox.doc.mjs +237 -0
- package/src/Switch/Switch.test.tsx +49 -0
- package/src/Switch/Switch.tsx +11 -0
- package/src/Text/useTruncation.test.tsx +69 -0
- package/src/Text/useTruncation.ts +16 -5
- package/src/Tokenizer/Tokenizer.test.tsx +54 -1
- package/src/Tooltip/useTooltip.test.tsx +13 -0
- package/src/Tooltip/useTooltip.tsx +11 -0
- package/src/Typeahead/BaseTypeahead.tsx +26 -0
- package/src/Typeahead/Typeahead.test.tsx +260 -2
- package/src/i18n/InternationalizationProvider.doc.mjs +8 -0
- package/src/i18n/InternationalizationProvider.spec.md +252 -0
- package/src/i18n/__tests__/InternationalizationProvider.test.tsx +128 -0
- package/src/i18n/generated-locales/en.generated.ts +121 -0
- package/src/i18n/generated-locales/pseudo.generated.ts +121 -0
- package/src/i18n/useTranslator.test.tsx +37 -0
- package/src/utils/focusOutline.stylex.ts +4 -0
- package/src/utils/sharedMutationObserver.test.ts +53 -0
- package/src/utils/sharedMutationObserver.ts +78 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file InternationalizationProvider.test.tsx
|
|
5
|
+
* @input packages/core/src/i18n/InternationalizationProvider.tsx, useTranslator,
|
|
6
|
+
* useDirection, useLocale
|
|
7
|
+
* @output Unit tests for the provider's update and nesting contract
|
|
8
|
+
* @position Colocated tests; targets re-rendering when messages, overrides, or
|
|
9
|
+
* dir change (locale is covered by useLocale/useTranslator tests) and a nested
|
|
10
|
+
* provider replacing rather than merging its parent's context.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {describe, expect, test} from 'vitest';
|
|
14
|
+
import {render} from '@testing-library/react';
|
|
15
|
+
import {InternationalizationProvider} from '../InternationalizationProvider';
|
|
16
|
+
import {useDirection} from '../useDirection';
|
|
17
|
+
import {useLocale} from '../useLocale';
|
|
18
|
+
import {useTranslator} from '../useTranslator';
|
|
19
|
+
import type {MessagesByLocale, Overrides} from '../types';
|
|
20
|
+
|
|
21
|
+
const NEXT = '@astryx.pagination.next';
|
|
22
|
+
const APP_KEY = '@myapp.save';
|
|
23
|
+
|
|
24
|
+
function Probe() {
|
|
25
|
+
const t = useTranslator();
|
|
26
|
+
return (
|
|
27
|
+
<>
|
|
28
|
+
<span data-testid="next">{t(NEXT)}</span>
|
|
29
|
+
<span data-testid="app">{t(APP_KEY)}</span>
|
|
30
|
+
<span data-testid="locale">{useLocale()}</span>
|
|
31
|
+
<span data-testid="dir">{useDirection()}</span>
|
|
32
|
+
</>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const FR_MESSAGES: MessagesByLocale = {
|
|
37
|
+
fr: {[APP_KEY]: {defaultMessage: 'Enregistrer'}},
|
|
38
|
+
};
|
|
39
|
+
const FR_MESSAGES_V2: MessagesByLocale = {
|
|
40
|
+
fr: {[APP_KEY]: {defaultMessage: 'Sauvegarder'}},
|
|
41
|
+
};
|
|
42
|
+
const FR_OVERRIDES: Overrides = {fr: {[NEXT]: 'Suivant'}};
|
|
43
|
+
const FR_OVERRIDES_V2: Overrides = {fr: {[NEXT]: 'Page suivante'}};
|
|
44
|
+
|
|
45
|
+
describe('InternationalizationProvider — re-rendering', () => {
|
|
46
|
+
test('re-renders consumers when messages change', () => {
|
|
47
|
+
const {getByTestId, rerender} = render(
|
|
48
|
+
<InternationalizationProvider locale="fr" messages={FR_MESSAGES}>
|
|
49
|
+
<Probe />
|
|
50
|
+
</InternationalizationProvider>,
|
|
51
|
+
);
|
|
52
|
+
expect(getByTestId('app').textContent).toBe('Enregistrer');
|
|
53
|
+
|
|
54
|
+
rerender(
|
|
55
|
+
<InternationalizationProvider locale="fr" messages={FR_MESSAGES_V2}>
|
|
56
|
+
<Probe />
|
|
57
|
+
</InternationalizationProvider>,
|
|
58
|
+
);
|
|
59
|
+
expect(getByTestId('app').textContent).toBe('Sauvegarder');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('re-renders consumers when overrides change', () => {
|
|
63
|
+
const {getByTestId, rerender} = render(
|
|
64
|
+
<InternationalizationProvider locale="fr" overrides={FR_OVERRIDES}>
|
|
65
|
+
<Probe />
|
|
66
|
+
</InternationalizationProvider>,
|
|
67
|
+
);
|
|
68
|
+
expect(getByTestId('next').textContent).toBe('Suivant');
|
|
69
|
+
|
|
70
|
+
rerender(
|
|
71
|
+
<InternationalizationProvider locale="fr" overrides={FR_OVERRIDES_V2}>
|
|
72
|
+
<Probe />
|
|
73
|
+
</InternationalizationProvider>,
|
|
74
|
+
);
|
|
75
|
+
expect(getByTestId('next').textContent).toBe('Page suivante');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('re-renders consumers when dir changes', () => {
|
|
79
|
+
const {getByTestId, rerender} = render(
|
|
80
|
+
<InternationalizationProvider locale="en" dir="rtl">
|
|
81
|
+
<Probe />
|
|
82
|
+
</InternationalizationProvider>,
|
|
83
|
+
);
|
|
84
|
+
expect(getByTestId('dir').textContent).toBe('rtl');
|
|
85
|
+
|
|
86
|
+
rerender(
|
|
87
|
+
<InternationalizationProvider locale="en" dir="ltr">
|
|
88
|
+
<Probe />
|
|
89
|
+
</InternationalizationProvider>,
|
|
90
|
+
);
|
|
91
|
+
expect(getByTestId('dir').textContent).toBe('ltr');
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('InternationalizationProvider — nesting', () => {
|
|
96
|
+
test('a nested provider replaces the parent context instead of merging it', () => {
|
|
97
|
+
const {getByTestId} = render(
|
|
98
|
+
<InternationalizationProvider
|
|
99
|
+
locale="fr"
|
|
100
|
+
dir="rtl"
|
|
101
|
+
messages={FR_MESSAGES}
|
|
102
|
+
overrides={FR_OVERRIDES}>
|
|
103
|
+
<InternationalizationProvider locale="fr">
|
|
104
|
+
<Probe />
|
|
105
|
+
</InternationalizationProvider>
|
|
106
|
+
</InternationalizationProvider>,
|
|
107
|
+
);
|
|
108
|
+
// Locale is the child's own; direction derives from it, not the parent's
|
|
109
|
+
// explicit rtl; parent overrides and catalogs do not leak into the child.
|
|
110
|
+
expect(getByTestId('locale').textContent).toBe('fr');
|
|
111
|
+
expect(getByTestId('dir').textContent).toBe('ltr');
|
|
112
|
+
expect(getByTestId('next').textContent).toBe('Go to next page');
|
|
113
|
+
expect(getByTestId('app').textContent).toBe(APP_KEY);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('a nested provider can narrow the locale for one subtree', () => {
|
|
117
|
+
const {getByTestId} = render(
|
|
118
|
+
<InternationalizationProvider locale="fr" overrides={FR_OVERRIDES}>
|
|
119
|
+
<InternationalizationProvider locale="de">
|
|
120
|
+
<Probe />
|
|
121
|
+
</InternationalizationProvider>
|
|
122
|
+
</InternationalizationProvider>,
|
|
123
|
+
);
|
|
124
|
+
expect(getByTestId('locale').textContent).toBe('de');
|
|
125
|
+
// No de override or catalog: shipped English wins.
|
|
126
|
+
expect(getByTestId('next').textContent).toBe('Go to next page');
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -158,6 +158,27 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
158
158
|
'{itemName} dropped at position {position, number} of {total, number}.',
|
|
159
159
|
'@astryx.listInput.announceAlreadyAtBoundary':
|
|
160
160
|
'This {itemName} is already {boundary, select, first {first} last {last} other {}}.',
|
|
161
|
+
'@astryx.diffViewer.label': 'Diff',
|
|
162
|
+
'@astryx.diffViewer.renamedPath': '{oldPath} → {newPath}',
|
|
163
|
+
'@astryx.diffViewer.statusAdded': 'Added',
|
|
164
|
+
'@astryx.diffViewer.statusDeleted': 'Deleted',
|
|
165
|
+
'@astryx.diffViewer.statusRenamed': 'Renamed',
|
|
166
|
+
'@astryx.diffViewer.addedCount': '+{count, number}',
|
|
167
|
+
'@astryx.diffViewer.removedCount': '−{count, number}',
|
|
168
|
+
'@astryx.diffViewer.stats':
|
|
169
|
+
'{added, plural, one {# line added} other {# lines added}}, {removed, plural, one {# line removed} other {# lines removed}}',
|
|
170
|
+
'@astryx.diffViewer.added': 'Added',
|
|
171
|
+
'@astryx.diffViewer.removed': 'Removed',
|
|
172
|
+
'@astryx.diffViewer.lineNumberButton':
|
|
173
|
+
'{side, select, old {Original line {line, number}} other {Changed line {line, number}}}',
|
|
174
|
+
'@astryx.diffViewer.columnOldLine': 'Original line number',
|
|
175
|
+
'@astryx.diffViewer.columnNewLine': 'Changed line number',
|
|
176
|
+
'@astryx.diffViewer.columnChange': 'Change',
|
|
177
|
+
'@astryx.diffViewer.columnCode': 'Code',
|
|
178
|
+
'@astryx.diffViewer.columnOldCode': 'Original code',
|
|
179
|
+
'@astryx.diffViewer.columnNewCode': 'Changed code',
|
|
180
|
+
'@astryx.diffViewer.binary': 'Binary file not shown',
|
|
181
|
+
'@astryx.diffViewer.noChanges': 'No content changes',
|
|
161
182
|
'@astryx.markdownHeadingLinks.copied': 'Link copied',
|
|
162
183
|
'@astryx.markdownHeadingLinks.copy': 'Copy link to {heading}',
|
|
163
184
|
'@astryx.richTextEditor.tabEscapeHint':
|
|
@@ -459,6 +480,106 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
459
480
|
'@astryx.transferListSelector.apply': 'Apply',
|
|
460
481
|
'@astryx.transferListSelector.cancel': 'Cancel',
|
|
461
482
|
'@astryx.transferListSelector.triggerLabel': '{count, number} selected',
|
|
483
|
+
'@astryx.emojiPicker.label': 'Emoji',
|
|
484
|
+
'@astryx.emojiPicker.searchLabel': 'Search emoji',
|
|
485
|
+
'@astryx.emojiPicker.frequentlyUsed': 'Frequently used',
|
|
486
|
+
'@astryx.emojiPicker.searchResults': 'Search results',
|
|
487
|
+
'@astryx.emojiPicker.noResults': 'No emoji match “{query}”',
|
|
488
|
+
'@astryx.emojiPicker.loading': 'Loading emoji',
|
|
489
|
+
'@astryx.emojiPicker.loadFailed': "More emoji couldn't be loaded.",
|
|
490
|
+
'@astryx.emojiPicker.groupSmileysEmotion': 'Smileys & emotion',
|
|
491
|
+
'@astryx.emojiPicker.groupPeopleBody': 'People & body',
|
|
492
|
+
'@astryx.emojiPicker.groupAnimalsNature': 'Animals & nature',
|
|
493
|
+
'@astryx.emojiPicker.groupFoodDrink': 'Food & drink',
|
|
494
|
+
'@astryx.emojiPicker.groupTravelPlaces': 'Travel & places',
|
|
495
|
+
'@astryx.emojiPicker.groupActivities': 'Activities',
|
|
496
|
+
'@astryx.emojiPicker.groupObjects': 'Objects',
|
|
497
|
+
'@astryx.emojiPicker.groupSymbols': 'Symbols',
|
|
498
|
+
'@astryx.emojiPicker.groupFlags': 'Flags',
|
|
499
|
+
'@astryx.comments.retry': 'Retry',
|
|
500
|
+
'@astryx.commentComposer.placeholder': 'Write a comment…',
|
|
501
|
+
'@astryx.commentComposer.noAgents': 'No agents',
|
|
502
|
+
'@astryx.commentComposer.agents': 'Agents',
|
|
503
|
+
'@astryx.commentComposer.uploadFailed': "Images couldn't be added",
|
|
504
|
+
'@astryx.commentComposer.imagesOnly': 'Only image files can be added',
|
|
505
|
+
'@astryx.commentComposer.chooseImages': 'Choose images',
|
|
506
|
+
'@astryx.commentComposer.imageAlt': 'Image attachment',
|
|
507
|
+
'@astryx.commentComposer.removeQuote': 'Remove quoted text',
|
|
508
|
+
'@astryx.commentComposer.attachImages': 'Attach images',
|
|
509
|
+
'@astryx.commentComposer.addEmoji': 'Add emoji',
|
|
510
|
+
'@astryx.commentComposer.cancel': 'Cancel',
|
|
511
|
+
'@astryx.comments.actionsLabel': "Actions for {name}'s comment",
|
|
512
|
+
'@astryx.comments.addReaction': 'Add reaction',
|
|
513
|
+
'@astryx.comments.agentBadge': 'Agent',
|
|
514
|
+
'@astryx.comments.agentLogo': '{name} logo',
|
|
515
|
+
'@astryx.comments.chooseReaction': 'Choose a reaction',
|
|
516
|
+
'@astryx.comments.copyLink': 'Copy comment link',
|
|
517
|
+
'@astryx.comments.currentVersion': 'Current version',
|
|
518
|
+
'@astryx.comments.delete': 'Delete',
|
|
519
|
+
'@astryx.comments.deleteDescription':
|
|
520
|
+
"Delete {name}'s comment? This action cannot be undone.",
|
|
521
|
+
'@astryx.comments.deleteFailed': "This comment couldn't be deleted.",
|
|
522
|
+
'@astryx.comments.deleteFailedRetry':
|
|
523
|
+
"This comment couldn't be deleted. Try again.",
|
|
524
|
+
'@astryx.comments.deleteTitle': 'Delete comment?',
|
|
525
|
+
'@astryx.comments.edit': 'Edit',
|
|
526
|
+
'@astryx.comments.editFailed': "Your edit couldn't be saved.",
|
|
527
|
+
'@astryx.comments.editPlaceholder': 'Edit comment',
|
|
528
|
+
'@astryx.comments.edited': 'Edited',
|
|
529
|
+
'@astryx.comments.editingContext': 'Editing comment',
|
|
530
|
+
'@astryx.comments.hideReplies': 'Hide replies',
|
|
531
|
+
'@astryx.comments.historyEmptyDescription':
|
|
532
|
+
'This comment has no earlier versions.',
|
|
533
|
+
'@astryx.comments.historyEmptyTitle': 'No edit history',
|
|
534
|
+
'@astryx.comments.historyFailed': "Edit history couldn't load",
|
|
535
|
+
'@astryx.comments.historyLoadFailed':
|
|
536
|
+
"The comment's edit history couldn't be loaded. Try again.",
|
|
537
|
+
'@astryx.comments.historyLoading': 'Loading edit history',
|
|
538
|
+
'@astryx.comments.historySubtitle': 'Comment by {name}',
|
|
539
|
+
'@astryx.comments.historyTitle': 'Edit history',
|
|
540
|
+
'@astryx.comments.linkCopied': 'Comment link copied',
|
|
541
|
+
'@astryx.comments.otherReactors':
|
|
542
|
+
'{count, plural, one {# other} other {# others}}',
|
|
543
|
+
'@astryx.comments.outdated': 'Outdated',
|
|
544
|
+
'@astryx.comments.reactionCelebrate': 'Celebrate',
|
|
545
|
+
'@astryx.comments.reactionCount': '{reaction}: {count, number}',
|
|
546
|
+
'@astryx.comments.reactionFailed': "Your reaction couldn't be saved.",
|
|
547
|
+
'@astryx.comments.reactionLaugh': 'Haha',
|
|
548
|
+
'@astryx.comments.reactionLike': 'Like',
|
|
549
|
+
'@astryx.comments.reactionLove': 'Love',
|
|
550
|
+
'@astryx.comments.reactionSad': 'Sad',
|
|
551
|
+
'@astryx.comments.reactionWow': 'Wow',
|
|
552
|
+
'@astryx.comments.reopen': 'Reopen',
|
|
553
|
+
'@astryx.comments.reopenFailed': "This thread couldn't be reopened.",
|
|
554
|
+
'@astryx.comments.reply': 'Reply',
|
|
555
|
+
'@astryx.comments.replyFailed': "Your reply couldn't be posted.",
|
|
556
|
+
'@astryx.comments.replyPlaceholder': 'Reply to {name}…',
|
|
557
|
+
'@astryx.comments.replyingContext': 'Replying to {name}',
|
|
558
|
+
'@astryx.comments.resolve': 'Mark as resolved',
|
|
559
|
+
'@astryx.comments.resolveFailed': "This thread couldn't be resolved.",
|
|
560
|
+
'@astryx.comments.resolved': 'Resolved',
|
|
561
|
+
'@astryx.comments.resolvedBy': 'Resolved by {name}',
|
|
562
|
+
'@astryx.comments.retryFailed': "Retrying didn't work.",
|
|
563
|
+
'@astryx.comments.sendFailed': "Couldn't send this comment.",
|
|
564
|
+
'@astryx.comments.threadLabel': 'Thread by {name}',
|
|
565
|
+
'@astryx.comments.unread': 'New',
|
|
566
|
+
'@astryx.comments.unsavedDraft':
|
|
567
|
+
'Unsaved draft. Dismiss again to discard it.',
|
|
568
|
+
'@astryx.comments.versionAuthor': 'by {name}',
|
|
569
|
+
'@astryx.comments.viewHistory': 'View edit history',
|
|
570
|
+
'@astryx.comments.viewReplies':
|
|
571
|
+
'View {count, plural, one {# reply} other {# replies}}',
|
|
572
|
+
'@astryx.comments.commentsLabel': 'Threads in {label}',
|
|
573
|
+
'@astryx.comments.emptyDescription': 'Start the discussion with a comment.',
|
|
574
|
+
'@astryx.comments.emptyReadOnlyDescription':
|
|
575
|
+
'There is no discussion on this item.',
|
|
576
|
+
'@astryx.comments.emptyTitle': 'No comments yet',
|
|
577
|
+
'@astryx.comments.label': 'Comments',
|
|
578
|
+
'@astryx.comments.loadFailed': "Comments couldn't load",
|
|
579
|
+
'@astryx.comments.loadOlder': 'Load earlier comments',
|
|
580
|
+
'@astryx.comments.loading': 'Loading comments',
|
|
581
|
+
'@astryx.comments.olderFailed': "Earlier comments couldn't load",
|
|
582
|
+
'@astryx.comments.postFailed': "Your comment couldn't be posted.",
|
|
462
583
|
};
|
|
463
584
|
|
|
464
585
|
export default runtimeCatalog;
|
|
@@ -163,6 +163,27 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
163
163
|
'⟦{itemName} ðřóþþéð àţ þóšíţíóñ {position, number} óƒ {total, number}.⟧',
|
|
164
164
|
'@astryx.listInput.announceAlreadyAtBoundary':
|
|
165
165
|
'⟦Ţĥíš {itemName} íš àłřéàðý {boundary, select, first {first} last {last} other {}}.⟧',
|
|
166
|
+
'@astryx.diffViewer.label': '⟦Ð탃⟧',
|
|
167
|
+
'@astryx.diffViewer.renamedPath': '⟦{oldPath} → {newPath}⟧',
|
|
168
|
+
'@astryx.diffViewer.statusAdded': '⟦Àððéð⟧',
|
|
169
|
+
'@astryx.diffViewer.statusDeleted': '⟦Ðéłéţéð⟧',
|
|
170
|
+
'@astryx.diffViewer.statusRenamed': '⟦Řéñàɱéð⟧',
|
|
171
|
+
'@astryx.diffViewer.addedCount': '⟦+{count, number}⟧',
|
|
172
|
+
'@astryx.diffViewer.removedCount': '⟦−{count, number}⟧',
|
|
173
|
+
'@astryx.diffViewer.stats':
|
|
174
|
+
'⟦{added, plural, one {# line added} other {# lines added}}, {removed, plural, one {# line removed} other {# lines removed}}⟧',
|
|
175
|
+
'@astryx.diffViewer.added': '⟦Àððéð⟧',
|
|
176
|
+
'@astryx.diffViewer.removed': '⟦Řéɱóṽéð⟧',
|
|
177
|
+
'@astryx.diffViewer.lineNumberButton':
|
|
178
|
+
'⟦{side, select, old {Original line {line, number}} other {Changed line {line, number}}}⟧',
|
|
179
|
+
'@astryx.diffViewer.columnOldLine': '⟦Óříĝíñàł łíñé ñúɱƀéř⟧',
|
|
180
|
+
'@astryx.diffViewer.columnNewLine': '⟦Çĥàñĝéð łíñé ñúɱƀéř⟧',
|
|
181
|
+
'@astryx.diffViewer.columnChange': '⟦Çĥàñĝé⟧',
|
|
182
|
+
'@astryx.diffViewer.columnCode': '⟦Çóðé⟧',
|
|
183
|
+
'@astryx.diffViewer.columnOldCode': '⟦Óříĝíñàł çóðé⟧',
|
|
184
|
+
'@astryx.diffViewer.columnNewCode': '⟦Çĥàñĝéð çóðé⟧',
|
|
185
|
+
'@astryx.diffViewer.binary': '⟦Ɓíñàřý ƒíłé ñóţ šĥóŵñ⟧',
|
|
186
|
+
'@astryx.diffViewer.noChanges': '⟦Ñó çóñţéñţ çĥàñĝéš⟧',
|
|
166
187
|
'@astryx.markdownHeadingLinks.copied': '⟦Łíñķ çóþíéð⟧',
|
|
167
188
|
'@astryx.markdownHeadingLinks.copy': '⟦Çóþý łíñķ ţó {heading}⟧',
|
|
168
189
|
'@astryx.richTextEditor.tabEscapeHint':
|
|
@@ -468,6 +489,106 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
468
489
|
'@astryx.transferListSelector.apply': '⟦Àþþłý⟧',
|
|
469
490
|
'@astryx.transferListSelector.cancel': '⟦Çàñçéł⟧',
|
|
470
491
|
'@astryx.transferListSelector.triggerLabel': '⟦{count, number} šéłéçţéð⟧',
|
|
492
|
+
'@astryx.emojiPicker.label': '⟦Éɱóĵí⟧',
|
|
493
|
+
'@astryx.emojiPicker.searchLabel': '⟦Šéàřçĥ éɱóĵí⟧',
|
|
494
|
+
'@astryx.emojiPicker.frequentlyUsed': '⟦Ƒřéɋúéñţłý úšéð⟧',
|
|
495
|
+
'@astryx.emojiPicker.searchResults': '⟦Šéàřçĥ řéšúłţš⟧',
|
|
496
|
+
'@astryx.emojiPicker.noResults': '⟦Ñó éɱóĵí ɱàţçĥ “{query}”⟧',
|
|
497
|
+
'@astryx.emojiPicker.loading': '⟦Łóàðíñĝ éɱóĵí⟧',
|
|
498
|
+
'@astryx.emojiPicker.loadFailed': "⟦Ṁóřé éɱóĵí çóúłðñ'ţ ƀé łóàðéð.⟧",
|
|
499
|
+
'@astryx.emojiPicker.groupSmileysEmotion': '⟦Šɱíłéýš & éɱóţíóñ⟧',
|
|
500
|
+
'@astryx.emojiPicker.groupPeopleBody': '⟦Þéóþłé & ƀóðý⟧',
|
|
501
|
+
'@astryx.emojiPicker.groupAnimalsNature': '⟦Àñíɱàłš & ñàţúřé⟧',
|
|
502
|
+
'@astryx.emojiPicker.groupFoodDrink': '⟦Ƒóóð & ðříñķ⟧',
|
|
503
|
+
'@astryx.emojiPicker.groupTravelPlaces': '⟦Ţřàṽéł & þłàçéš⟧',
|
|
504
|
+
'@astryx.emojiPicker.groupActivities': '⟦Àçţíṽíţíéš⟧',
|
|
505
|
+
'@astryx.emojiPicker.groupObjects': '⟦Óƀĵéçţš⟧',
|
|
506
|
+
'@astryx.emojiPicker.groupSymbols': '⟦Šýɱƀółš⟧',
|
|
507
|
+
'@astryx.emojiPicker.groupFlags': '⟦Ƒłàĝš⟧',
|
|
508
|
+
'@astryx.comments.retry': '⟦Řéţřý⟧',
|
|
509
|
+
'@astryx.commentComposer.placeholder': '⟦Ŵříţé à çóɱɱéñţ…⟧',
|
|
510
|
+
'@astryx.commentComposer.noAgents': '⟦Ñó àĝéñţš⟧',
|
|
511
|
+
'@astryx.commentComposer.agents': '⟦Àĝéñţš⟧',
|
|
512
|
+
'@astryx.commentComposer.uploadFailed': "⟦Íɱàĝéš çóúłðñ'ţ ƀé àððéð⟧",
|
|
513
|
+
'@astryx.commentComposer.imagesOnly': '⟦Óñłý íɱàĝé ƒíłéš çàñ ƀé àððéð⟧',
|
|
514
|
+
'@astryx.commentComposer.chooseImages': '⟦Çĥóóšé íɱàĝéš⟧',
|
|
515
|
+
'@astryx.commentComposer.imageAlt': '⟦Íɱàĝé àţţàçĥɱéñţ⟧',
|
|
516
|
+
'@astryx.commentComposer.removeQuote': '⟦Řéɱóṽé ɋúóţéð ţéẋţ⟧',
|
|
517
|
+
'@astryx.commentComposer.attachImages': '⟦Àţţàçĥ íɱàĝéš⟧',
|
|
518
|
+
'@astryx.commentComposer.addEmoji': '⟦Àðð éɱóĵí⟧',
|
|
519
|
+
'@astryx.commentComposer.cancel': '⟦Çàñçéł⟧',
|
|
520
|
+
'@astryx.comments.actionsLabel': "⟦Àçţíóñš ƒóř {name}'š çóɱɱéñţ⟧",
|
|
521
|
+
'@astryx.comments.addReaction': '⟦Àðð řéàçţíóñ⟧',
|
|
522
|
+
'@astryx.comments.agentBadge': '⟦Àĝéñţ⟧',
|
|
523
|
+
'@astryx.comments.agentLogo': '⟦{name} łóĝó⟧',
|
|
524
|
+
'@astryx.comments.chooseReaction': '⟦Çĥóóšé à řéàçţíóñ⟧',
|
|
525
|
+
'@astryx.comments.copyLink': '⟦Çóþý çóɱɱéñţ łíñķ⟧',
|
|
526
|
+
'@astryx.comments.currentVersion': '⟦Çúřřéñţ ṽéřšíóñ⟧',
|
|
527
|
+
'@astryx.comments.delete': '⟦Ðéłéţé⟧',
|
|
528
|
+
'@astryx.comments.deleteDescription':
|
|
529
|
+
"⟦Ðéłéţé {name}'š çóɱɱéñţ? Ţĥíš àçţíóñ çàññóţ ƀé úñðóñé.⟧",
|
|
530
|
+
'@astryx.comments.deleteFailed': "⟦Ţĥíš çóɱɱéñţ çóúłðñ'ţ ƀé ðéłéţéð.⟧",
|
|
531
|
+
'@astryx.comments.deleteFailedRetry':
|
|
532
|
+
"⟦Ţĥíš çóɱɱéñţ çóúłðñ'ţ ƀé ðéłéţéð. Ţřý àĝàíñ.⟧",
|
|
533
|
+
'@astryx.comments.deleteTitle': '⟦Ðéłéţé çóɱɱéñţ?⟧',
|
|
534
|
+
'@astryx.comments.edit': '⟦Éðíţ⟧',
|
|
535
|
+
'@astryx.comments.editFailed': "⟦Ýóúř éðíţ çóúłðñ'ţ ƀé šàṽéð.⟧",
|
|
536
|
+
'@astryx.comments.editPlaceholder': '⟦Éðíţ çóɱɱéñţ⟧',
|
|
537
|
+
'@astryx.comments.edited': '⟦Éðíţéð⟧',
|
|
538
|
+
'@astryx.comments.editingContext': '⟦Éðíţíñĝ çóɱɱéñţ⟧',
|
|
539
|
+
'@astryx.comments.hideReplies': '⟦Ĥíðé řéþłíéš⟧',
|
|
540
|
+
'@astryx.comments.historyEmptyDescription':
|
|
541
|
+
'⟦Ţĥíš çóɱɱéñţ ĥàš ñó éàřłíéř ṽéřšíóñš.⟧',
|
|
542
|
+
'@astryx.comments.historyEmptyTitle': '⟦Ñó éðíţ ĥíšţóřý⟧',
|
|
543
|
+
'@astryx.comments.historyFailed': "⟦Éðíţ ĥíšţóřý çóúłðñ'ţ łóàð⟧",
|
|
544
|
+
'@astryx.comments.historyLoadFailed':
|
|
545
|
+
"⟦Ţĥé çóɱɱéñţ'š éðíţ ĥíšţóřý çóúłðñ'ţ ƀé łóàðéð. Ţřý àĝàíñ.⟧",
|
|
546
|
+
'@astryx.comments.historyLoading': '⟦Łóàðíñĝ éðíţ ĥíšţóřý⟧',
|
|
547
|
+
'@astryx.comments.historySubtitle': '⟦Çóɱɱéñţ ƀý {name}⟧',
|
|
548
|
+
'@astryx.comments.historyTitle': '⟦Éðíţ ĥíšţóřý⟧',
|
|
549
|
+
'@astryx.comments.linkCopied': '⟦Çóɱɱéñţ łíñķ çóþíéð⟧',
|
|
550
|
+
'@astryx.comments.otherReactors':
|
|
551
|
+
'⟦{count, plural, one {# other} other {# others}}⟧',
|
|
552
|
+
'@astryx.comments.outdated': '⟦Óúţðàţéð⟧',
|
|
553
|
+
'@astryx.comments.reactionCelebrate': '⟦Çéłéƀřàţé⟧',
|
|
554
|
+
'@astryx.comments.reactionCount': '⟦{reaction}: {count, number}⟧',
|
|
555
|
+
'@astryx.comments.reactionFailed': "⟦Ýóúř řéàçţíóñ çóúłðñ'ţ ƀé šàṽéð.⟧",
|
|
556
|
+
'@astryx.comments.reactionLaugh': '⟦Ĥàĥà⟧',
|
|
557
|
+
'@astryx.comments.reactionLike': '⟦Łíķé⟧',
|
|
558
|
+
'@astryx.comments.reactionLove': '⟦Łóṽé⟧',
|
|
559
|
+
'@astryx.comments.reactionSad': '⟦Šàð⟧',
|
|
560
|
+
'@astryx.comments.reactionWow': '⟦Ŵóŵ⟧',
|
|
561
|
+
'@astryx.comments.reopen': '⟦Řéóþéñ⟧',
|
|
562
|
+
'@astryx.comments.reopenFailed': "⟦Ţĥíš ţĥřéàð çóúłðñ'ţ ƀé řéóþéñéð.⟧",
|
|
563
|
+
'@astryx.comments.reply': '⟦Řéþłý⟧',
|
|
564
|
+
'@astryx.comments.replyFailed': "⟦Ýóúř řéþłý çóúłðñ'ţ ƀé þóšţéð.⟧",
|
|
565
|
+
'@astryx.comments.replyPlaceholder': '⟦Řéþłý ţó {name}…⟧',
|
|
566
|
+
'@astryx.comments.replyingContext': '⟦Řéþłýíñĝ ţó {name}⟧',
|
|
567
|
+
'@astryx.comments.resolve': '⟦Ṁàřķ àš řéšółṽéð⟧',
|
|
568
|
+
'@astryx.comments.resolveFailed': "⟦Ţĥíš ţĥřéàð çóúłðñ'ţ ƀé řéšółṽéð.⟧",
|
|
569
|
+
'@astryx.comments.resolved': '⟦Řéšółṽéð⟧',
|
|
570
|
+
'@astryx.comments.resolvedBy': '⟦Řéšółṽéð ƀý {name}⟧',
|
|
571
|
+
'@astryx.comments.retryFailed': "⟦Řéţřýíñĝ ðíðñ'ţ ŵóřķ.⟧",
|
|
572
|
+
'@astryx.comments.sendFailed': "⟦Çóúłðñ'ţ šéñð ţĥíš çóɱɱéñţ.⟧",
|
|
573
|
+
'@astryx.comments.threadLabel': '⟦Ţĥřéàð ƀý {name}⟧',
|
|
574
|
+
'@astryx.comments.unread': '⟦Ñéŵ⟧',
|
|
575
|
+
'@astryx.comments.unsavedDraft':
|
|
576
|
+
'⟦Úñšàṽéð ðřàƒţ. Ðíšɱíšš àĝàíñ ţó ðíšçàřð íţ.⟧',
|
|
577
|
+
'@astryx.comments.versionAuthor': '⟦ƀý {name}⟧',
|
|
578
|
+
'@astryx.comments.viewHistory': '⟦Ṽíéŵ éðíţ ĥíšţóřý⟧',
|
|
579
|
+
'@astryx.comments.viewReplies':
|
|
580
|
+
'⟦Ṽíéŵ {count, plural, one {# reply} other {# replies}}⟧',
|
|
581
|
+
'@astryx.comments.commentsLabel': '⟦Ţĥřéàðš íñ {label}⟧',
|
|
582
|
+
'@astryx.comments.emptyDescription': '⟦Šţàřţ ţĥé ðíšçúššíóñ ŵíţĥ à çóɱɱéñţ.⟧',
|
|
583
|
+
'@astryx.comments.emptyReadOnlyDescription':
|
|
584
|
+
'⟦Ţĥéřé íš ñó ðíšçúššíóñ óñ ţĥíš íţéɱ.⟧',
|
|
585
|
+
'@astryx.comments.emptyTitle': '⟦Ñó çóɱɱéñţš ýéţ⟧',
|
|
586
|
+
'@astryx.comments.label': '⟦Çóɱɱéñţš⟧',
|
|
587
|
+
'@astryx.comments.loadFailed': "⟦Çóɱɱéñţš çóúłðñ'ţ łóàð⟧",
|
|
588
|
+
'@astryx.comments.loadOlder': '⟦Łóàð éàřłíéř çóɱɱéñţš⟧',
|
|
589
|
+
'@astryx.comments.loading': '⟦Łóàðíñĝ çóɱɱéñţš⟧',
|
|
590
|
+
'@astryx.comments.olderFailed': "⟦Éàřłíéř çóɱɱéñţš çóúłðñ'ţ łóàð⟧",
|
|
591
|
+
'@astryx.comments.postFailed': "⟦Ýóúř çóɱɱéñţ çóúłðñ'ţ ƀé þóšţéð.⟧",
|
|
471
592
|
};
|
|
472
593
|
|
|
473
594
|
export default runtimeCatalog;
|
|
@@ -32,6 +32,12 @@ const FR_MESSAGES: MessagesByLocale = {
|
|
|
32
32
|
const FR_RUNTIME_MESSAGES: ProviderMessagesByLocale = {
|
|
33
33
|
fr: {'@astryx.pagination.next': 'Page suivante compacte'},
|
|
34
34
|
};
|
|
35
|
+
// One messages value mixing shapes: a generated string map for the active
|
|
36
|
+
// locale and a rich catalog for its parent.
|
|
37
|
+
const MIXED_MESSAGES: ProviderMessagesByLocale = {
|
|
38
|
+
'fr-CA': {'@astryx.pagination.next': 'Page suivante (CA)'},
|
|
39
|
+
fr: {'@astryx.pagination.previous': {defaultMessage: 'Page précédente'}},
|
|
40
|
+
};
|
|
35
41
|
const FR_OVERRIDES: Overrides = {
|
|
36
42
|
fr: {'@astryx.pagination.next': 'Suivant'},
|
|
37
43
|
};
|
|
@@ -101,6 +107,37 @@ describe('useTranslator — resolution', () => {
|
|
|
101
107
|
).toEqual({defaultMessage: 'Page suivante compacte'});
|
|
102
108
|
});
|
|
103
109
|
|
|
110
|
+
it('resolves rich and runtime catalogs mixed in one messages value', () => {
|
|
111
|
+
const {result} = renderHook(
|
|
112
|
+
() => ({
|
|
113
|
+
context: use(InternationalizationContext),
|
|
114
|
+
translate: useTranslator(),
|
|
115
|
+
}),
|
|
116
|
+
{
|
|
117
|
+
wrapper: ({children}) => (
|
|
118
|
+
<InternationalizationProvider
|
|
119
|
+
locale="fr-CA"
|
|
120
|
+
messages={MIXED_MESSAGES}>
|
|
121
|
+
{children}
|
|
122
|
+
</InternationalizationProvider>
|
|
123
|
+
),
|
|
124
|
+
},
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
expect(result.current.translate('@astryx.pagination.next')).toBe(
|
|
128
|
+
'Page suivante (CA)',
|
|
129
|
+
);
|
|
130
|
+
expect(result.current.translate('@astryx.pagination.previous')).toBe(
|
|
131
|
+
'Page précédente',
|
|
132
|
+
);
|
|
133
|
+
expect(result.current.context.messages).toEqual({
|
|
134
|
+
'fr-CA': {
|
|
135
|
+
'@astryx.pagination.next': {defaultMessage: 'Page suivante (CA)'},
|
|
136
|
+
},
|
|
137
|
+
fr: {'@astryx.pagination.previous': {defaultMessage: 'Page précédente'}},
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
104
141
|
it('lets a provider override beat the provider catalog', () => {
|
|
105
142
|
const {result} = renderHook(() => useTranslator(), {
|
|
106
143
|
wrapper: ({children}) => (
|
|
@@ -70,6 +70,10 @@ const focusOutlineLonghands = {
|
|
|
70
70
|
* independently overridable — and every part stays a var, so a theme's
|
|
71
71
|
* `--focus-outline-*` overrides still flow through.
|
|
72
72
|
*
|
|
73
|
+
* NOT usable inside another file's `stylex.create`, for the same reason as
|
|
74
|
+
* {@link FOCUS_OUTLINE}: StyleX compiles each member there to a CSS variable
|
|
75
|
+
* nothing defines. Compose the `focusVars` tokens instead.
|
|
76
|
+
*
|
|
73
77
|
* Keys are camelCase to match `HTMLElement.style`, so this spreads straight
|
|
74
78
|
* onto an element:
|
|
75
79
|
*
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {describe, it, expect, vi} from 'vitest';
|
|
4
|
+
import {observeContent} from './sharedMutationObserver';
|
|
5
|
+
|
|
6
|
+
const flush = async () => new Promise(resolve => setTimeout(resolve, 0));
|
|
7
|
+
|
|
8
|
+
describe('observeContent', () => {
|
|
9
|
+
it('calls a registration once per batch for changes anywhere inside its element', async () => {
|
|
10
|
+
const element = document.createElement('div');
|
|
11
|
+
element.innerHTML = '<span>first</span>';
|
|
12
|
+
const callback = vi.fn();
|
|
13
|
+
const unsubscribe = observeContent(element, callback);
|
|
14
|
+
|
|
15
|
+
element.querySelector('span')!.firstChild!.nodeValue = 'second';
|
|
16
|
+
element.append('third');
|
|
17
|
+
await flush();
|
|
18
|
+
|
|
19
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
20
|
+
unsubscribe();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('removes only its own registration', async () => {
|
|
24
|
+
const element = document.createElement('div');
|
|
25
|
+
const kept = vi.fn();
|
|
26
|
+
const removed = vi.fn();
|
|
27
|
+
const unsubscribeKept = observeContent(element, kept);
|
|
28
|
+
const unsubscribeRemoved = observeContent(element, removed);
|
|
29
|
+
|
|
30
|
+
unsubscribeRemoved();
|
|
31
|
+
element.textContent = 'changed';
|
|
32
|
+
await flush();
|
|
33
|
+
|
|
34
|
+
expect(kept).toHaveBeenCalledTimes(1);
|
|
35
|
+
expect(removed).not.toHaveBeenCalled();
|
|
36
|
+
unsubscribeKept();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('ignores a released element', async () => {
|
|
40
|
+
const released = document.createElement('div');
|
|
41
|
+
const live = document.createElement('div');
|
|
42
|
+
const releasedCallback = vi.fn();
|
|
43
|
+
const unsubscribeReleased = observeContent(released, releasedCallback);
|
|
44
|
+
const unsubscribeLive = observeContent(live, vi.fn());
|
|
45
|
+
|
|
46
|
+
unsubscribeReleased();
|
|
47
|
+
released.textContent = 'changed';
|
|
48
|
+
await flush();
|
|
49
|
+
|
|
50
|
+
expect(releasedCallback).not.toHaveBeenCalled();
|
|
51
|
+
unsubscribeLive();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file sharedMutationObserver.ts
|
|
7
|
+
* @input MutationObserver API
|
|
8
|
+
* @output Exports observeContent for shared content-change observation
|
|
9
|
+
* @position Internal utility; consumed by useTruncation. The content-change
|
|
10
|
+
* counterpart of sharedResizeObserver: one observer for every element, so a
|
|
11
|
+
* repeated cell or row path does not create one observer per instance.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
type ContentCallback = () => void;
|
|
15
|
+
|
|
16
|
+
const OPTIONS: MutationObserverInit = {
|
|
17
|
+
characterData: true,
|
|
18
|
+
childList: true,
|
|
19
|
+
subtree: true,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
let observer: MutationObserver | null = null;
|
|
23
|
+
const callbacks = new Map<Node, Set<ContentCallback>>();
|
|
24
|
+
|
|
25
|
+
function dispatch(records: MutationRecord[]): void {
|
|
26
|
+
// One call per callback per batch, however many records it received. A
|
|
27
|
+
// record's target can be any descendant, so walk up to the observed roots.
|
|
28
|
+
const pending = new Set<ContentCallback>();
|
|
29
|
+
for (const record of records) {
|
|
30
|
+
for (let node: Node | null = record.target; node; node = node.parentNode) {
|
|
31
|
+
callbacks.get(node)?.forEach(callback => pending.add(callback));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
pending.forEach(callback => callback());
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Call `callback` when the element's text or children change (including
|
|
39
|
+
* descendants). Returns an unsubscribe that removes only this registration.
|
|
40
|
+
*
|
|
41
|
+
* MutationObserver has no per-target unobserve, so a released element stays
|
|
42
|
+
* registered with the shared observer until no element remains; its records
|
|
43
|
+
* match no callback and are ignored. The observer disconnects and is released
|
|
44
|
+
* when the last registration goes.
|
|
45
|
+
*/
|
|
46
|
+
export function observeContent(
|
|
47
|
+
element: Element,
|
|
48
|
+
callback: ContentCallback,
|
|
49
|
+
): () => void {
|
|
50
|
+
if (typeof MutationObserver === 'undefined') {
|
|
51
|
+
return () => {};
|
|
52
|
+
}
|
|
53
|
+
observer ??= new MutationObserver(dispatch);
|
|
54
|
+
const existing = callbacks.get(element);
|
|
55
|
+
if (existing) {
|
|
56
|
+
existing.add(callback);
|
|
57
|
+
} else {
|
|
58
|
+
callbacks.set(element, new Set([callback]));
|
|
59
|
+
observer.observe(element, OPTIONS);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let isSubscribed = true;
|
|
63
|
+
return () => {
|
|
64
|
+
if (!isSubscribed) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
isSubscribed = false;
|
|
68
|
+
const targetCallbacks = callbacks.get(element);
|
|
69
|
+
targetCallbacks?.delete(callback);
|
|
70
|
+
if (targetCallbacks?.size === 0) {
|
|
71
|
+
callbacks.delete(element);
|
|
72
|
+
}
|
|
73
|
+
if (callbacks.size === 0 && observer) {
|
|
74
|
+
observer.disconnect();
|
|
75
|
+
observer = null;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|