@astryxdesign/core 0.5.1 → 0.5.2-canary.11abdc7

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 (74) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/Toast/useToastGesture.d.ts.map +1 -1
  3. package/dist/Toast/useToastGesture.js +46 -9
  4. package/dist/i18n/InternationalizationContext.d.ts +2 -0
  5. package/dist/i18n/InternationalizationContext.d.ts.map +1 -1
  6. package/dist/i18n/InternationalizationContext.js +3 -1
  7. package/dist/i18n/InternationalizationProvider.d.ts.map +1 -1
  8. package/dist/i18n/InternationalizationProvider.js +3 -1
  9. package/dist/i18n/resolve.d.ts +1 -1
  10. package/dist/i18n/resolve.d.ts.map +1 -1
  11. package/dist/i18n/resolve.js +37 -38
  12. package/dist/i18n/useTranslator.d.ts.map +1 -1
  13. package/dist/i18n/useTranslator.js +2 -3
  14. package/locales/af-ZA.json +0 -4
  15. package/locales/ar-SA.json +0 -4
  16. package/locales/ca-ES.json +0 -4
  17. package/locales/cs-CZ.json +0 -4
  18. package/locales/da-DK.json +0 -4
  19. package/locales/de-DE.json +0 -4
  20. package/locales/el-GR.json +0 -4
  21. package/locales/es-ES.json +0 -4
  22. package/locales/fi-FI.json +0 -4
  23. package/locales/fr-FR.json +0 -4
  24. package/locales/he-IL.json +0 -4
  25. package/locales/hu-HU.json +0 -4
  26. package/locales/it-IT.json +0 -4
  27. package/locales/ja-JP.json +0 -4
  28. package/locales/ko-KR.json +0 -4
  29. package/locales/nl-NL.json +0 -4
  30. package/locales/no-NO.json +0 -4
  31. package/locales/pl-PL.json +0 -4
  32. package/locales/pt-BR.json +0 -4
  33. package/locales/pt-PT.json +0 -4
  34. package/locales/ro-RO.json +0 -4
  35. package/locales/ru-RU.json +0 -4
  36. package/locales/sr-SP.json +0 -4
  37. package/locales/sv-SE.json +0 -4
  38. package/locales/tr-TR.json +0 -4
  39. package/locales/uk-UA.json +0 -4
  40. package/locales/vi-VN.json +0 -4
  41. package/locales/zh-CN.json +0 -4
  42. package/locales/zh-TW.json +0 -4
  43. package/package.json +3 -3
  44. package/src/Calendar/Calendar.doc.mjs +1 -1
  45. package/src/CheckboxInput/CheckboxInput.doc.mjs +1 -1
  46. package/src/CheckboxList/CheckboxList.doc.mjs +1 -1
  47. package/src/ComplexSelector/ComplexSelector.doc.mjs +2 -2
  48. package/src/DateInput/DateInput.doc.mjs +1 -1
  49. package/src/DateRangeInput/DateRangeInput.doc.mjs +1 -1
  50. package/src/DateTimeInput/DateTimeInput.doc.mjs +1 -1
  51. package/src/Field/Field.doc.mjs +1 -1
  52. package/src/FieldStatus/FieldStatus.doc.mjs +1 -1
  53. package/src/FileInput/FileInput.doc.mjs +1 -1
  54. package/src/Indicator/Indicator.doc.mjs +1 -1
  55. package/src/InputGroup/InputGroup.doc.mjs +1 -1
  56. package/src/MultiSelector/MultiSelector.doc.mjs +1 -1
  57. package/src/NumberInput/NumberInput.doc.mjs +1 -1
  58. package/src/PowerSearch/PowerSearch.doc.mjs +1 -1
  59. package/src/RadioList/RadioList.doc.mjs +1 -1
  60. package/src/Selector/Selector.doc.mjs +1 -1
  61. package/src/Slider/Slider.doc.mjs +2 -2
  62. package/src/Switch/Switch.doc.mjs +1 -1
  63. package/src/TextArea/TextArea.doc.mjs +1 -1
  64. package/src/TextInput/TextInput.doc.mjs +1 -1
  65. package/src/TimeInput/TimeInput.doc.mjs +1 -1
  66. package/src/Toast/ToastViewport.test.tsx +118 -4
  67. package/src/Toast/useToastGesture.ts +61 -8
  68. package/src/Tokenizer/Tokenizer.doc.mjs +1 -1
  69. package/src/Typeahead/Typeahead.doc.mjs +1 -1
  70. package/src/i18n/InternationalizationContext.ts +3 -0
  71. package/src/i18n/InternationalizationProvider.tsx +8 -1
  72. package/src/i18n/__tests__/resolve.test.ts +23 -1
  73. package/src/i18n/resolve.ts +46 -49
  74. package/src/i18n/useTranslator.ts +2 -7
@@ -25,12 +25,14 @@ const SWIPE_ACTIVE_SCALE_MAX = 0.02;
25
25
  export type ToastGestureDirection = 1 | -1;
26
26
 
27
27
  interface GesturePoint {
28
+ source: 'touch' | 'pen';
28
29
  pointerId: number;
29
30
  clientX: number;
30
31
  clientY: number;
31
32
  }
32
33
 
33
34
  interface GestureState {
35
+ source: GesturePoint['source'];
34
36
  pointerId: number;
35
37
  startX: number;
36
38
  startY: number;
@@ -126,6 +128,7 @@ export function useToastGesture({
126
128
  }
127
129
  const surfaceSize = Math.max(root.getBoundingClientRect().height, 1);
128
130
  gestureRef.current = {
131
+ source: point.source,
129
132
  pointerId: point.pointerId,
130
133
  startX: point.clientX,
131
134
  startY: point.clientY,
@@ -159,7 +162,12 @@ export function useToastGesture({
159
162
  ) => {
160
163
  const state = gestureRef.current;
161
164
  const root = rootRef.current;
162
- if (!state || !root || point.pointerId !== state.pointerId) {
165
+ if (
166
+ !state ||
167
+ !root ||
168
+ point.source !== state.source ||
169
+ point.pointerId !== state.pointerId
170
+ ) {
163
171
  return;
164
172
  }
165
173
  const deltaX = point.clientX - state.startX;
@@ -209,7 +217,12 @@ export function useToastGesture({
209
217
  (point: GesturePoint) => {
210
218
  const state = gestureRef.current;
211
219
  const root = rootRef.current;
212
- if (!state || !root || point.pointerId !== state.pointerId) {
220
+ if (
221
+ !state ||
222
+ !root ||
223
+ point.source !== state.source ||
224
+ point.pointerId !== state.pointerId
225
+ ) {
213
226
  return;
214
227
  }
215
228
  const travel = Math.max(
@@ -225,6 +238,10 @@ export function useToastGesture({
225
238
  gestureRef.current = null;
226
239
  root.style.removeProperty('transition-duration');
227
240
  if (isDismissed) {
241
+ // The exit animation owns the final translation. Clear the live-drag
242
+ // transform/fade/scale first so a standalone Toast does not remain
243
+ // partially translated or faded after its dismiss callback fires.
244
+ clearTransientStyles(root);
228
245
  root.style.setProperty(
229
246
  '--_toast-swipe-exit-y',
230
247
  state.direction === 1
@@ -247,7 +264,15 @@ export function useToastGesture({
247
264
  if (
248
265
  event.pointerType === 'pen' &&
249
266
  (event.button == null || event.button === 0) &&
250
- beginGesture(event, event.target)
267
+ beginGesture(
268
+ {
269
+ source: 'pen',
270
+ pointerId: event.pointerId,
271
+ clientX: event.clientX,
272
+ clientY: event.clientY,
273
+ },
274
+ event.target,
275
+ )
251
276
  ) {
252
277
  rootRef.current?.setPointerCapture?.(event.pointerId);
253
278
  }
@@ -261,7 +286,12 @@ export function useToastGesture({
261
286
  return;
262
287
  }
263
288
  moveGesture(
264
- event,
289
+ {
290
+ source: 'pen',
291
+ pointerId: event.pointerId,
292
+ clientX: event.clientX,
293
+ clientY: event.clientY,
294
+ },
265
295
  () => event.preventDefault(),
266
296
  () => rootRef.current?.releasePointerCapture?.(event.pointerId),
267
297
  );
@@ -275,7 +305,12 @@ export function useToastGesture({
275
305
  return;
276
306
  }
277
307
  rootRef.current?.releasePointerCapture?.(event.pointerId);
278
- endGesture(event);
308
+ endGesture({
309
+ source: 'pen',
310
+ pointerId: event.pointerId,
311
+ clientX: event.clientX,
312
+ clientY: event.clientY,
313
+ });
279
314
  },
280
315
  [endGesture],
281
316
  );
@@ -284,6 +319,7 @@ export function useToastGesture({
284
319
  (event: ReactPointerEvent<HTMLDivElement>) => {
285
320
  if (
286
321
  event.pointerType === 'pen' &&
322
+ gestureRef.current?.source === 'pen' &&
287
323
  gestureRef.current?.pointerId === event.pointerId
288
324
  ) {
289
325
  resetGesture(true);
@@ -298,12 +334,14 @@ export function useToastGesture({
298
334
  return;
299
335
  }
300
336
  const point = (touch: Touch): GesturePoint => ({
337
+ source: 'touch',
301
338
  pointerId: touch.identifier,
302
339
  clientX: touch.clientX,
303
340
  clientY: touch.clientY,
304
341
  });
305
342
  const changedTouch = (event: TouchEvent) => {
306
- const pointerId = gestureRef.current?.pointerId;
343
+ const state = gestureRef.current;
344
+ const pointerId = state?.source === 'touch' ? state.pointerId : null;
307
345
  return pointerId == null
308
346
  ? undefined
309
347
  : [...event.changedTouches].find(
@@ -311,12 +349,23 @@ export function useToastGesture({
311
349
  );
312
350
  };
313
351
  const handleTouchStart = (event: TouchEvent) => {
314
- const touch = event.touches.length === 1 ? event.changedTouches[0] : null;
315
- if (touch) {
352
+ if (event.touches?.length !== 1) {
353
+ // A second contact means the browser may be handling pinch zoom or
354
+ // two-finger scrolling. Abandon any accepted one-finger swipe before
355
+ // touchmove can suppress that native gesture.
356
+ resetGesture(true);
357
+ return;
358
+ }
359
+ const touch = event.changedTouches[0];
360
+ if (touch != null) {
316
361
  beginGesture(point(touch), event.target);
317
362
  }
318
363
  };
319
364
  const handleTouchMove = (event: TouchEvent) => {
365
+ if (event.touches?.length !== 1) {
366
+ resetGesture(true);
367
+ return;
368
+ }
320
369
  const touch = changedTouch(event);
321
370
  if (touch) {
322
371
  moveGesture(point(touch), () => {
@@ -327,6 +376,10 @@ export function useToastGesture({
327
376
  }
328
377
  };
329
378
  const handleTouchEnd = (event: TouchEvent) => {
379
+ if (event.touches.length > 0) {
380
+ resetGesture(true);
381
+ return;
382
+ }
330
383
  const touch = changedTouch(event);
331
384
  if (touch) {
332
385
  endGesture(point(touch));
@@ -5,7 +5,7 @@
5
5
  export const docs = {
6
6
  name: 'Tokenizer',
7
7
  displayName: 'Tokenizer',
8
- category: 'Data Input',
8
+ category: 'Form Controls',
9
9
  keywords: ["tokenizer","multiselect","multi-select","chips","tags","combobox","autocomplete","taginput","chipinput"],
10
10
  props: [
11
11
  {
@@ -6,7 +6,7 @@ export const docs = {
6
6
  name: 'Typeahead',
7
7
  displayName: 'Typeahead',
8
8
  group: 'Typeahead',
9
- category: 'Data Input',
9
+ category: 'Form Controls',
10
10
  keywords: [
11
11
  'typeahead',
12
12
  'autocomplete',
@@ -23,6 +23,7 @@
23
23
  */
24
24
 
25
25
  import {createContext} from 'react';
26
+ import {getResolve} from './resolve';
26
27
  import type {Locale, MessagesByLocale, Overrides} from './types';
27
28
 
28
29
  export interface InternationalizationContextValue {
@@ -30,6 +31,7 @@ export interface InternationalizationContextValue {
30
31
  direction: 'ltr' | 'rtl';
31
32
  messages: MessagesByLocale;
32
33
  overrides?: Overrides;
34
+ translate: ReturnType<typeof getResolve>;
33
35
  }
34
36
 
35
37
  /**
@@ -41,5 +43,6 @@ export const InternationalizationContext =
41
43
  locale: 'en',
42
44
  direction: 'ltr',
43
45
  messages: {},
46
+ translate: getResolve('en', {}),
44
47
  });
45
48
  InternationalizationContext.displayName = 'InternationalizationContext';
@@ -23,6 +23,7 @@ import {useMemo, type ReactNode} from 'react';
23
23
  import {InternationalizationContext} from './InternationalizationContext';
24
24
  import {getLocaleDirection} from './getLocaleDirection';
25
25
  import type {Locale, MessagesByLocale, Overrides} from './types';
26
+ import {getResolve} from './resolve';
26
27
 
27
28
  export interface InternationalizationProviderProps {
28
29
  /**
@@ -85,7 +86,13 @@ export function InternationalizationProvider({
85
86
  }: InternationalizationProviderProps) {
86
87
  const direction = dir ?? getLocaleDirection(locale);
87
88
  const value = useMemo(
88
- () => ({locale, direction, messages: messages ?? {}, overrides}),
89
+ () => ({
90
+ locale,
91
+ direction,
92
+ messages: messages ?? {},
93
+ overrides,
94
+ translate: getResolve(locale, messages ?? {}, overrides),
95
+ }),
89
96
  [locale, direction, messages, overrides],
90
97
  );
91
98
  return (
@@ -9,9 +9,16 @@
9
9
  */
10
10
 
11
11
  import {describe, expect, test, beforeEach, vi} from 'vitest';
12
- import {__resetForTests, resolve, resolveLocaleChain} from '../resolve';
12
+ import {__resetForTests, getResolve, resolveLocaleChain} from '../resolve';
13
13
  import type {Catalog, MessagesByLocale, Overrides} from '../types';
14
14
 
15
+ const resolve = (
16
+ ...[key, values, locale, messages, overrides]: [
17
+ ...Parameters<ReturnType<typeof getResolve>>,
18
+ ...Parameters<typeof getResolve>,
19
+ ]
20
+ ) => getResolve(locale, messages, overrides)(key, values);
21
+
15
22
  // Reset caches between tests so warn-once and formatter cache don't bleed.
16
23
  beforeEach(() => {
17
24
  __resetForTests();
@@ -209,6 +216,21 @@ describe('resolve — overrides', () => {
209
216
  // Should fall through to en since fr has no messages and es override doesn't apply
210
217
  expect(out).toBe('Go to next page');
211
218
  });
219
+
220
+ test('override `pt` should take precedence over message `pt-BR`', () => {
221
+ const overrides: Overrides = {
222
+ pt: {key: 'override'},
223
+ };
224
+ const messages: MessagesByLocale = {
225
+ 'pt-BR': {
226
+ key: {
227
+ defaultMessage: 'shipped',
228
+ },
229
+ },
230
+ };
231
+ const out = resolve('key', undefined, 'pt-BR', messages, overrides);
232
+ expect(out).toBe('override');
233
+ });
212
234
  });
213
235
 
214
236
  describe('resolve — ICU MessageFormat', () => {
@@ -73,74 +73,71 @@ export function resolveLocaleChain(locale: Locale): Locale[] {
73
73
  return chain;
74
74
  }
75
75
 
76
- function lookup(
77
- key: string,
76
+ function getLookup(
78
77
  locale: Locale,
79
78
  messages: MessagesByLocale,
80
- overrides: Overrides | undefined,
81
- ): string | null {
79
+ overrides?: Overrides,
80
+ ): Record<string, string> {
81
+ const lookup: Record<string, string> = {};
82
82
  const chain = resolveLocaleChain(locale);
83
-
84
83
  // 1 + 2. Overrides (most specific to least specific in the chain)
85
84
  if (overrides !== undefined) {
86
85
  for (const tag of chain) {
87
- const value = overrides[tag]?.[key];
88
- if (value !== undefined) {
89
- return value;
86
+ if (overrides[tag]) {
87
+ for (const [key, value] of Object.entries(overrides[tag])) {
88
+ if (lookup[key] === undefined && value !== null) {
89
+ lookup[key] = value;
90
+ }
91
+ }
90
92
  }
91
93
  }
92
94
  }
93
-
94
- // 3 + 4. Shipped catalogs (most specific to least specific)
95
95
  for (const tag of chain) {
96
- const entry = messages[tag]?.[key];
97
- if (entry !== undefined) {
98
- return entry.defaultMessage;
96
+ if (messages[tag]) {
97
+ for (const [key, value] of Object.entries(messages[tag])) {
98
+ if (lookup[key] === undefined && value?.defaultMessage !== null) {
99
+ lookup[key] = value?.defaultMessage;
100
+ }
101
+ }
99
102
  }
100
103
  }
101
104
 
102
- // 5. Shipped en catalog — always present
103
- const enEntry = EN_CATALOG[key];
104
- if (enEntry !== undefined) {
105
- return enEntry.defaultMessage;
106
- }
107
-
108
- // 6. Nothing found
109
- return null;
105
+ return lookup;
110
106
  }
111
107
 
112
- export function resolve(
113
- key: string,
114
- values: Record<string, unknown> | undefined,
108
+ export function getResolve(
115
109
  locale: Locale,
116
110
  messages: MessagesByLocale,
117
- overrides: Overrides | undefined,
118
- ): string {
119
- const result = lookup(key, locale, messages, overrides);
120
-
121
- if (result === null) {
122
- // Fires ONLY when a key is missing from every source including the
123
- // shipped `en` catalog — a real bug (typo, stale catalog, deleted key).
124
- // Fallback to `en` from a non-en locale is expected and stays silent,
125
- // matching the FormatJS / i18next default.
126
- warnOnce(
127
- `astryx-i18n:${locale}::${key}`,
128
- 'astryx-i18n',
129
- `missing key: ${key} (locale: ${locale})`,
130
- );
131
- return key;
132
- }
111
+ overrides?: Overrides,
112
+ ) {
113
+ const lookup = getLookup(locale, messages, overrides);
114
+
115
+ return (key: string, values: Record<string, unknown> | undefined) => {
116
+ const result = lookup[key] ?? EN_CATALOG[key]?.defaultMessage;
117
+ if (result === undefined) {
118
+ // Fires ONLY when a key is missing from every source including the
119
+ // shipped `en` catalog — a real bug (typo, stale catalog, deleted key).
120
+ // Fallback to `en` from a non-en locale is expected and stays silent,
121
+ // matching the FormatJS / i18next default.
122
+ warnOnce(
123
+ `astryx-i18n:${locale}::${key}`,
124
+ 'astryx-i18n',
125
+ `missing key: ${key} (locale: ${locale})`,
126
+ );
127
+ return key;
128
+ }
133
129
 
134
- if (values === undefined) {
135
- // Static string — skip the parser entirely for the common case
136
- return result;
137
- }
130
+ if (values === undefined) {
131
+ // Static string — skip the parser entirely for the common case
132
+ return result;
133
+ }
138
134
 
139
- const formatted = getFormatter(result, locale).format(values);
140
- // IntlMessageFormat.format returns string | (string | React elements) — we
141
- // only ever pass string values so it will be a string; assert for the type
142
- // system.
143
- return formatted as string;
135
+ const formatted = getFormatter(result, locale).format(values);
136
+ // IntlMessageFormat.format returns string | (string | React elements) — we
137
+ // only ever pass string values so it will be a string; assert for the type
138
+ // system.
139
+ return formatted as string;
140
+ };
144
141
  }
145
142
 
146
143
  /**
@@ -19,9 +19,8 @@
19
19
  * - /packages/core/src/i18n/t.client.ts
20
20
  */
21
21
 
22
- import {use, useCallback} from 'react';
22
+ import {use} from 'react';
23
23
  import {InternationalizationContext} from './InternationalizationContext';
24
- import {resolve} from './resolve';
25
24
 
26
25
  export type TranslatorFn = (
27
26
  key: string,
@@ -42,9 +41,5 @@ export type TranslatorFn = (
42
41
  */
43
42
  export function useTranslator(): TranslatorFn {
44
43
  const ctx = use(InternationalizationContext);
45
- return useCallback(
46
- (key: string, values?: Record<string, unknown>) =>
47
- resolve(key, values, ctx.locale, ctx.messages, ctx.overrides),
48
- [ctx.locale, ctx.messages, ctx.overrides],
49
- );
44
+ return ctx.translate;
50
45
  }