@octanejs/aria 0.0.5 → 0.0.6
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/package.json +3 -3
- package/src/components/Table.ts +2618 -0
- package/src/components/Tree.ts +1450 -0
- package/src/components/TreeDropTargetDelegate.ts +363 -0
- package/src/components/index.ts +57 -0
- package/src/grid/GridKeyboardDelegate.ts +514 -0
- package/src/grid/useGrid.ts +263 -0
- package/src/grid/useGridCell.ts +346 -0
- package/src/grid/useGridRow.ts +108 -0
- package/src/grid/useGridRowGroup.ts +29 -0
- package/src/grid/utils.ts +24 -0
- package/src/index.ts +38 -0
- package/src/intl/autocomplete/index.ts +9 -2
- package/src/intl/breadcrumbs/index.ts +9 -2
- package/src/intl/combobox/index.ts +9 -2
- package/src/intl/compileMessages.ts +356 -0
- package/src/intl/grid/index.ts +9 -2
- package/src/intl/gridlist/index.ts +9 -2
- package/src/intl/menu/index.ts +9 -2
- package/src/intl/numberfield/index.ts +9 -2
- package/src/intl/overlays/index.ts +9 -2
- package/src/intl/searchfield/index.ts +9 -2
- package/src/intl/spinbutton/index.ts +9 -2
- package/src/intl/table/ar-AE.json +13 -0
- package/src/intl/table/bg-BG.json +13 -0
- package/src/intl/table/cs-CZ.json +13 -0
- package/src/intl/table/da-DK.json +13 -0
- package/src/intl/table/de-DE.json +13 -0
- package/src/intl/table/el-GR.json +13 -0
- package/src/intl/table/en-US.json +13 -0
- package/src/intl/table/es-ES.json +13 -0
- package/src/intl/table/et-EE.json +13 -0
- package/src/intl/table/fi-FI.json +13 -0
- package/src/intl/table/fr-FR.json +13 -0
- package/src/intl/table/he-IL.json +13 -0
- package/src/intl/table/hr-HR.json +13 -0
- package/src/intl/table/hu-HU.json +13 -0
- package/src/intl/table/index.ts +82 -0
- package/src/intl/table/it-IT.json +13 -0
- package/src/intl/table/ja-JP.json +13 -0
- package/src/intl/table/ko-KR.json +13 -0
- package/src/intl/table/lt-LT.json +13 -0
- package/src/intl/table/lv-LV.json +13 -0
- package/src/intl/table/nb-NO.json +13 -0
- package/src/intl/table/nl-NL.json +13 -0
- package/src/intl/table/pl-PL.json +13 -0
- package/src/intl/table/pt-BR.json +13 -0
- package/src/intl/table/pt-PT.json +13 -0
- package/src/intl/table/ro-RO.json +13 -0
- package/src/intl/table/ru-RU.json +13 -0
- package/src/intl/table/sk-SK.json +13 -0
- package/src/intl/table/sl-SI.json +13 -0
- package/src/intl/table/sr-SP.json +13 -0
- package/src/intl/table/sv-SE.json +13 -0
- package/src/intl/table/tr-TR.json +13 -0
- package/src/intl/table/uk-UA.json +13 -0
- package/src/intl/table/zh-CN.json +13 -0
- package/src/intl/table/zh-TW.json +13 -0
- package/src/intl/tag/index.ts +9 -2
- package/src/intl/tree/ar-AE.json +4 -0
- package/src/intl/tree/bg-BG.json +4 -0
- package/src/intl/tree/cs-CZ.json +4 -0
- package/src/intl/tree/da-DK.json +4 -0
- package/src/intl/tree/de-DE.json +4 -0
- package/src/intl/tree/el-GR.json +4 -0
- package/src/intl/tree/en-US.json +4 -0
- package/src/intl/tree/es-ES.json +4 -0
- package/src/intl/tree/et-EE.json +4 -0
- package/src/intl/tree/fi-FI.json +4 -0
- package/src/intl/tree/fr-FR.json +4 -0
- package/src/intl/tree/he-IL.json +4 -0
- package/src/intl/tree/hr-HR.json +4 -0
- package/src/intl/tree/hu-HU.json +4 -0
- package/src/intl/tree/index.ts +82 -0
- package/src/intl/tree/it-IT.json +4 -0
- package/src/intl/tree/ja-JP.json +4 -0
- package/src/intl/tree/ko-KR.json +4 -0
- package/src/intl/tree/lt-LT.json +4 -0
- package/src/intl/tree/lv-LV.json +4 -0
- package/src/intl/tree/nb-NO.json +4 -0
- package/src/intl/tree/nl-NL.json +4 -0
- package/src/intl/tree/pl-PL.json +4 -0
- package/src/intl/tree/pt-BR.json +4 -0
- package/src/intl/tree/pt-PT.json +4 -0
- package/src/intl/tree/ro-RO.json +4 -0
- package/src/intl/tree/ru-RU.json +4 -0
- package/src/intl/tree/sk-SK.json +4 -0
- package/src/intl/tree/sl-SI.json +4 -0
- package/src/intl/tree/sr-SP.json +4 -0
- package/src/intl/tree/sv-SE.json +4 -0
- package/src/intl/tree/tr-TR.json +4 -0
- package/src/intl/tree/uk-UA.json +4 -0
- package/src/intl/tree/zh-CN.json +4 -0
- package/src/intl/tree/zh-TW.json +4 -0
- package/src/stately/grid/GridCollection.ts +200 -0
- package/src/stately/grid/useGridState.ts +160 -0
- package/src/stately/index.ts +43 -0
- package/src/stately/table/Cell.ts +49 -0
- package/src/stately/table/Column.ts +121 -0
- package/src/stately/table/Row.ts +145 -0
- package/src/stately/table/TableBody.ts +66 -0
- package/src/stately/table/TableCollection.ts +390 -0
- package/src/stately/table/TableColumnLayout.ts +148 -0
- package/src/stately/table/TableHeader.ts +65 -0
- package/src/stately/table/TableUtils.ts +269 -0
- package/src/stately/table/useTableColumnResizeState.ts +202 -0
- package/src/stately/table/useTableState.ts +235 -0
- package/src/stately/table/useTreeGridState.ts +248 -0
- package/src/table/TableKeyboardDelegate.ts +205 -0
- package/src/table/useTable.ts +175 -0
- package/src/table/useTableCell.ts +81 -0
- package/src/table/useTableColumnHeader.ts +155 -0
- package/src/table/useTableColumnResize.ts +374 -0
- package/src/table/useTableHeaderRow.ts +57 -0
- package/src/table/useTableRow.ts +198 -0
- package/src/table/useTableRowGroup.ts +13 -0
- package/src/table/useTableSelectionCheckbox.ts +105 -0
- package/src/table/utils.ts +42 -0
- package/src/tree/useTree.ts +76 -0
- package/src/tree/useTreeItem.ts +109 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Ported from react-aria (source: .react-spectrum/packages/react-aria/src/grid/useGridRow.ts).
|
|
2
|
+
// octane adaptations:
|
|
3
|
+
// - `GridState`/`IGridCollection`/`GridNode` come from the ported stately grid hooks;
|
|
4
|
+
// `DOMAttributes` is a local structural prop-bag alias.
|
|
5
|
+
// - Public-hook slot threading (splitSlot/subSlot).
|
|
6
|
+
import { chain } from '../utils/chain';
|
|
7
|
+
|
|
8
|
+
import type { FocusableElement, RefObject } from '@react-types/shared';
|
|
9
|
+
import type { IGridCollection as GridCollection, GridNode } from '../stately/grid/GridCollection';
|
|
10
|
+
import { gridMap } from './utils';
|
|
11
|
+
import type { GridState } from '../stately/grid/useGridState';
|
|
12
|
+
import { SelectableItemStates, useSelectableItem } from '../selection/useSelectableItem';
|
|
13
|
+
|
|
14
|
+
import { S, splitSlot, subSlot } from '../internal';
|
|
15
|
+
|
|
16
|
+
// octane adaptation: minimal structural DOMAttributes (upstream's drags React's synthetic
|
|
17
|
+
// handler types along).
|
|
18
|
+
type DOMAttributes = Record<string, any>;
|
|
19
|
+
|
|
20
|
+
export interface GridRowProps<T> {
|
|
21
|
+
/**
|
|
22
|
+
* An object representing the grid row. Contains all the relevant information that makes up the
|
|
23
|
+
* grid row.
|
|
24
|
+
*/
|
|
25
|
+
node: GridNode<T>;
|
|
26
|
+
/** Whether the grid row is contained in a virtual scroller. */
|
|
27
|
+
isVirtualized?: boolean;
|
|
28
|
+
/** Whether selection should occur on press up instead of press down. */
|
|
29
|
+
shouldSelectOnPressUp?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Handler that is called when a user performs an action on the row.
|
|
32
|
+
* Please use onCellAction at the collection level instead.
|
|
33
|
+
*
|
|
34
|
+
* @deprecated
|
|
35
|
+
*/
|
|
36
|
+
onAction?: () => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface GridRowAria extends SelectableItemStates {
|
|
40
|
+
/** Props for the grid row element. */
|
|
41
|
+
rowProps: DOMAttributes;
|
|
42
|
+
/** Whether the row is currently in a pressed state. */
|
|
43
|
+
isPressed: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Provides the behavior and accessibility implementation for a row in a grid.
|
|
48
|
+
*
|
|
49
|
+
* @param props - Props for the row.
|
|
50
|
+
* @param state - State of the parent grid, as returned by `useGridState`.
|
|
51
|
+
*/
|
|
52
|
+
export function useGridRow<T, C extends GridCollection<T>, S extends GridState<T, C>>(
|
|
53
|
+
props: GridRowProps<T>,
|
|
54
|
+
state: S,
|
|
55
|
+
ref: RefObject<FocusableElement | null>,
|
|
56
|
+
): GridRowAria;
|
|
57
|
+
// Slot-threading form: sibling ported hooks pass their derived sub-slot as the trailing arg.
|
|
58
|
+
export function useGridRow<T, C extends GridCollection<T>, S extends GridState<T, C>>(
|
|
59
|
+
props: GridRowProps<T>,
|
|
60
|
+
state: S,
|
|
61
|
+
ref: RefObject<FocusableElement | null>,
|
|
62
|
+
slot: symbol | undefined,
|
|
63
|
+
): GridRowAria;
|
|
64
|
+
export function useGridRow(...args: any[]): GridRowAria {
|
|
65
|
+
const [user, slotArg] = splitSlot(args);
|
|
66
|
+
const slot = slotArg ?? S('useGridRow');
|
|
67
|
+
const props = user[0] as GridRowProps<any>;
|
|
68
|
+
const state = user[1] as GridState<any, GridCollection<any>>;
|
|
69
|
+
const ref = user[2] as RefObject<FocusableElement | null>;
|
|
70
|
+
|
|
71
|
+
let { node, isVirtualized, shouldSelectOnPressUp, onAction } = props;
|
|
72
|
+
|
|
73
|
+
let { actions, shouldSelectOnPressUp: gridShouldSelectOnPressUp } = gridMap.get(state)!;
|
|
74
|
+
let onRowAction = actions.onRowAction ? () => actions.onRowAction?.(node.key) : onAction;
|
|
75
|
+
let { itemProps, ...states } = useSelectableItem(
|
|
76
|
+
{
|
|
77
|
+
selectionManager: state.selectionManager,
|
|
78
|
+
key: node.key,
|
|
79
|
+
ref,
|
|
80
|
+
isVirtualized,
|
|
81
|
+
shouldSelectOnPressUp: gridShouldSelectOnPressUp || shouldSelectOnPressUp,
|
|
82
|
+
onAction:
|
|
83
|
+
onRowAction || node?.props?.onAction
|
|
84
|
+
? chain(node?.props?.onAction, onRowAction)
|
|
85
|
+
: undefined,
|
|
86
|
+
isDisabled: state.collection.size === 0,
|
|
87
|
+
},
|
|
88
|
+
subSlot(slot, 'selectableItem'),
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
let isSelected = state.selectionManager.isSelected(node.key);
|
|
92
|
+
|
|
93
|
+
let rowProps: DOMAttributes = {
|
|
94
|
+
role: 'row',
|
|
95
|
+
'aria-selected': state.selectionManager.selectionMode !== 'none' ? isSelected : undefined,
|
|
96
|
+
'aria-disabled': states.isDisabled || undefined,
|
|
97
|
+
...itemProps,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
if (isVirtualized) {
|
|
101
|
+
rowProps['aria-rowindex'] = node.index + 1; // aria-rowindex is 1 based
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
rowProps,
|
|
106
|
+
...states,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Ported from react-aria (source: .react-spectrum/packages/react-aria/src/grid/useGridRowGroup.ts).
|
|
2
|
+
// octane adaptations: `DOMAttributes` is a local structural prop-bag alias. The hook
|
|
3
|
+
// composes no base hooks, so the optional trailing slot is accepted (uniform calling
|
|
4
|
+
// convention) and unused.
|
|
5
|
+
import { splitSlot } from '../internal';
|
|
6
|
+
|
|
7
|
+
// octane adaptation: minimal structural DOMAttributes (upstream's drags React's synthetic
|
|
8
|
+
// handler types along).
|
|
9
|
+
type DOMAttributes = Record<string, any>;
|
|
10
|
+
|
|
11
|
+
export interface GridRowGroupAria {
|
|
12
|
+
/** Props for the row group element. */
|
|
13
|
+
rowGroupProps: DOMAttributes;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Provides the accessibility implementation for a row group in a grid.
|
|
18
|
+
*/
|
|
19
|
+
export function useGridRowGroup(): GridRowGroupAria;
|
|
20
|
+
// Slot-threading form: sibling ported hooks pass their derived sub-slot as the trailing arg.
|
|
21
|
+
export function useGridRowGroup(slot: symbol | undefined): GridRowGroupAria;
|
|
22
|
+
export function useGridRowGroup(...args: any[]): GridRowGroupAria {
|
|
23
|
+
splitSlot(args);
|
|
24
|
+
return {
|
|
25
|
+
rowGroupProps: {
|
|
26
|
+
role: 'rowgroup',
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Ported from react-aria (source: .react-spectrum/packages/react-aria/src/grid/utils.ts).
|
|
2
|
+
// octane adaptations: `GridState`/`IGridCollection` types come from the ported stately
|
|
3
|
+
// grid hooks.
|
|
4
|
+
import type { IGridCollection as GridCollection } from '../stately/grid/GridCollection';
|
|
5
|
+
|
|
6
|
+
import type { GridState } from '../stately/grid/useGridState';
|
|
7
|
+
import type { Key, KeyboardDelegate } from '@react-types/shared';
|
|
8
|
+
|
|
9
|
+
interface GridMapShared {
|
|
10
|
+
keyboardDelegate: KeyboardDelegate;
|
|
11
|
+
actions: {
|
|
12
|
+
onRowAction?: (key: Key) => void;
|
|
13
|
+
onCellAction?: (key: Key) => void;
|
|
14
|
+
};
|
|
15
|
+
shouldSelectOnPressUp?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Used to share:
|
|
19
|
+
// keyboard delegate between useGrid and useGridCell
|
|
20
|
+
// onRowAction/onCellAction across hooks
|
|
21
|
+
export const gridMap: WeakMap<
|
|
22
|
+
GridState<unknown, GridCollection<unknown>>,
|
|
23
|
+
GridMapShared
|
|
24
|
+
> = new WeakMap<GridState<unknown, GridCollection<unknown>>, GridMapShared>();
|
package/src/index.ts
CHANGED
|
@@ -318,3 +318,41 @@ export type {
|
|
|
318
318
|
// combobox — mirrors the @react-aria/combobox shim surface.
|
|
319
319
|
export { useComboBox } from './combobox/useComboBox';
|
|
320
320
|
export type { AriaComboBoxProps, AriaComboBoxOptions, ComboBoxAria } from './combobox/useComboBox';
|
|
321
|
+
|
|
322
|
+
// tree — mirrors the @react-aria/tree shim surface.
|
|
323
|
+
export { useTree } from './tree/useTree';
|
|
324
|
+
export { useTreeItem } from './tree/useTreeItem';
|
|
325
|
+
export type { AriaTreeOptions, AriaTreeProps, TreeAria, TreeProps } from './tree/useTree';
|
|
326
|
+
export type { AriaTreeItemOptions, TreeItemAria } from './tree/useTreeItem';
|
|
327
|
+
|
|
328
|
+
// table — mirrors the @react-aria/table shim surface.
|
|
329
|
+
export { useTable } from './table/useTable';
|
|
330
|
+
export { useTableColumnHeader } from './table/useTableColumnHeader';
|
|
331
|
+
export { useTableRow } from './table/useTableRow';
|
|
332
|
+
export { useTableHeaderRow } from './table/useTableHeaderRow';
|
|
333
|
+
export { useTableCell } from './table/useTableCell';
|
|
334
|
+
export {
|
|
335
|
+
useTableSelectionCheckbox,
|
|
336
|
+
useTableSelectAllCheckbox,
|
|
337
|
+
} from './table/useTableSelectionCheckbox';
|
|
338
|
+
export { useTableColumnResize } from './table/useTableColumnResize';
|
|
339
|
+
export { useTableRowGroup } from './table/useTableRowGroup';
|
|
340
|
+
export type { AriaTableProps } from './table/useTable';
|
|
341
|
+
export type {
|
|
342
|
+
AriaTableColumnHeaderProps,
|
|
343
|
+
TableColumnHeaderAria,
|
|
344
|
+
} from './table/useTableColumnHeader';
|
|
345
|
+
export type { TableRowAria } from './table/useTableRow';
|
|
346
|
+
export type { TableHeaderRowAria } from './table/useTableHeaderRow';
|
|
347
|
+
export type { AriaTableCellProps, TableCellAria } from './table/useTableCell';
|
|
348
|
+
export type {
|
|
349
|
+
AriaTableSelectionCheckboxProps,
|
|
350
|
+
TableSelectionCheckboxAria,
|
|
351
|
+
TableSelectAllCheckboxAria,
|
|
352
|
+
} from './table/useTableSelectionCheckbox';
|
|
353
|
+
export type {
|
|
354
|
+
AriaTableColumnResizeProps,
|
|
355
|
+
TableColumnResizeAria,
|
|
356
|
+
} from './table/useTableColumnResize';
|
|
357
|
+
export type { GridAria, GridProps } from './grid/useGrid';
|
|
358
|
+
export type { GridRowAria, GridRowProps } from './grid/useGridRow';
|
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
// upstream's Parcel glob import (`import intlMessages from '../../intl/autocomplete/*.json'`).
|
|
3
3
|
// The JSON files are copied VERBATIM from .react-spectrum/packages/react-aria/intl/autocomplete/;
|
|
4
4
|
// refresh them from the pinned checkout on version bumps, never hand-edit.
|
|
5
|
+
//
|
|
6
|
+
// Upstream's Parcel build additionally compiles each message through
|
|
7
|
+
// @internationalized/string-compiler, so `{variable}` placeholders and plural/select
|
|
8
|
+
// ICU arguments become message FUNCTIONS (LocalizedStringFormatter interpolates only
|
|
9
|
+
// function messages — plain strings are returned verbatim). `compileDictionaries`
|
|
10
|
+
// applies the equivalent compile step here at module init.
|
|
11
|
+
import { compileDictionaries } from '../compileMessages';
|
|
5
12
|
import ar_AE from './ar-AE.json';
|
|
6
13
|
import bg_BG from './bg-BG.json';
|
|
7
14
|
import cs_CZ from './cs-CZ.json';
|
|
@@ -37,7 +44,7 @@ import uk_UA from './uk-UA.json';
|
|
|
37
44
|
import zh_CN from './zh-CN.json';
|
|
38
45
|
import zh_TW from './zh-TW.json';
|
|
39
46
|
|
|
40
|
-
export default {
|
|
47
|
+
export default compileDictionaries({
|
|
41
48
|
'ar-AE': ar_AE,
|
|
42
49
|
'bg-BG': bg_BG,
|
|
43
50
|
'cs-CZ': cs_CZ,
|
|
@@ -72,4 +79,4 @@ export default {
|
|
|
72
79
|
'uk-UA': uk_UA,
|
|
73
80
|
'zh-CN': zh_CN,
|
|
74
81
|
'zh-TW': zh_TW,
|
|
75
|
-
}
|
|
82
|
+
});
|
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
// upstream's Parcel glob import (`import intlMessages from '../../intl/breadcrumbs/*.json'`).
|
|
3
3
|
// The JSON files are copied VERBATIM from .react-spectrum/packages/react-aria/intl/breadcrumbs/;
|
|
4
4
|
// refresh them from the pinned checkout on version bumps, never hand-edit.
|
|
5
|
+
//
|
|
6
|
+
// Upstream's Parcel build additionally compiles each message through
|
|
7
|
+
// @internationalized/string-compiler, so `{variable}` placeholders and plural/select
|
|
8
|
+
// ICU arguments become message FUNCTIONS (LocalizedStringFormatter interpolates only
|
|
9
|
+
// function messages — plain strings are returned verbatim). `compileDictionaries`
|
|
10
|
+
// applies the equivalent compile step here at module init.
|
|
11
|
+
import { compileDictionaries } from '../compileMessages';
|
|
5
12
|
import ar_AE from './ar-AE.json';
|
|
6
13
|
import bg_BG from './bg-BG.json';
|
|
7
14
|
import cs_CZ from './cs-CZ.json';
|
|
@@ -37,7 +44,7 @@ import uk_UA from './uk-UA.json';
|
|
|
37
44
|
import zh_CN from './zh-CN.json';
|
|
38
45
|
import zh_TW from './zh-TW.json';
|
|
39
46
|
|
|
40
|
-
export default {
|
|
47
|
+
export default compileDictionaries({
|
|
41
48
|
'ar-AE': ar_AE,
|
|
42
49
|
'bg-BG': bg_BG,
|
|
43
50
|
'cs-CZ': cs_CZ,
|
|
@@ -72,4 +79,4 @@ export default {
|
|
|
72
79
|
'uk-UA': uk_UA,
|
|
73
80
|
'zh-CN': zh_CN,
|
|
74
81
|
'zh-TW': zh_TW,
|
|
75
|
-
}
|
|
82
|
+
});
|
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
// upstream's Parcel glob import (`import intlMessages from '../../intl/combobox/*.json'`).
|
|
3
3
|
// The JSON files are copied VERBATIM from .react-spectrum/packages/react-aria/intl/combobox/;
|
|
4
4
|
// refresh them from the pinned checkout on version bumps, never hand-edit.
|
|
5
|
+
//
|
|
6
|
+
// Upstream's Parcel build additionally compiles each message through
|
|
7
|
+
// @internationalized/string-compiler, so `{variable}` placeholders and plural/select
|
|
8
|
+
// ICU arguments become message FUNCTIONS (LocalizedStringFormatter interpolates only
|
|
9
|
+
// function messages — plain strings are returned verbatim). `compileDictionaries`
|
|
10
|
+
// applies the equivalent compile step here at module init.
|
|
11
|
+
import { compileDictionaries } from '../compileMessages';
|
|
5
12
|
import ar_AE from './ar-AE.json';
|
|
6
13
|
import bg_BG from './bg-BG.json';
|
|
7
14
|
import cs_CZ from './cs-CZ.json';
|
|
@@ -37,7 +44,7 @@ import uk_UA from './uk-UA.json';
|
|
|
37
44
|
import zh_CN from './zh-CN.json';
|
|
38
45
|
import zh_TW from './zh-TW.json';
|
|
39
46
|
|
|
40
|
-
export default {
|
|
47
|
+
export default compileDictionaries({
|
|
41
48
|
'ar-AE': ar_AE,
|
|
42
49
|
'bg-BG': bg_BG,
|
|
43
50
|
'cs-CZ': cs_CZ,
|
|
@@ -72,4 +79,4 @@ export default {
|
|
|
72
79
|
'uk-UA': uk_UA,
|
|
73
80
|
'zh-CN': zh_CN,
|
|
74
81
|
'zh-TW': zh_TW,
|
|
75
|
-
}
|
|
82
|
+
});
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
// Shared runtime ICU message compiler for the generated src/intl/*/index.ts
|
|
2
|
+
// locale-dictionary indexes.
|
|
3
|
+
//
|
|
4
|
+
// Upstream react-aria loads its intl JSON through a Parcel transform that runs every
|
|
5
|
+
// message through @internationalized/string-compiler at BUILD time, turning any message
|
|
6
|
+
// with arguments into a function `(args, formatter) => string`.
|
|
7
|
+
// LocalizedStringFormatter.format only interpolates function messages — plain strings
|
|
8
|
+
// are returned verbatim — so exporting the raw JSON would render `Increase {fieldLabel}`
|
|
9
|
+
// and raw ICU plural source literally. This module is the runtime equivalent of that
|
|
10
|
+
// build step; each generated index applies it once at module init via
|
|
11
|
+
// `compileDictionaries`.
|
|
12
|
+
//
|
|
13
|
+
// It supports the ICU MessageFormat constructs the vendored dictionaries use: literal
|
|
14
|
+
// text (with ICU apostrophe quoting), `{argument}` interpolation, `{arg, number}`,
|
|
15
|
+
// `{arg, plural, …}` / `{arg, selectordinal, …}` (`offset:`, exact `=N` and category
|
|
16
|
+
// selectors, `#`), and `{arg, select, …}`, all arbitrarily nestable. Anything else
|
|
17
|
+
// (date/time arguments, number styles) throws at module init so a dictionary refresh
|
|
18
|
+
// that introduces a new construct fails loudly instead of silently rendering raw ICU
|
|
19
|
+
// source. Evaluation mirrors @internationalized/string-compiler's generated code
|
|
20
|
+
// exactly: `{argument}` coerces like a template literal, plural/select branches without
|
|
21
|
+
// arguments stay plain strings while branches with arguments become thunks over the
|
|
22
|
+
// current args, and `#` formats the nearest enclosing plural's value (select branches
|
|
23
|
+
// inherit it) through `formatter.number`.
|
|
24
|
+
import type {
|
|
25
|
+
LocalizedString,
|
|
26
|
+
LocalizedStringFormatter,
|
|
27
|
+
Variables,
|
|
28
|
+
} from '@internationalized/string';
|
|
29
|
+
|
|
30
|
+
// Structural view of LocalizedStringFormatter's plural/select/number helpers. They are
|
|
31
|
+
// `protected` in the published types, but compiled messages — upstream's and these —
|
|
32
|
+
// call them: format() passes the formatter itself as the message function's second
|
|
33
|
+
// argument for exactly this purpose.
|
|
34
|
+
interface MessageFormatter {
|
|
35
|
+
plural(
|
|
36
|
+
count: number,
|
|
37
|
+
options: Record<string, string | (() => string)>,
|
|
38
|
+
type?: Intl.PluralRuleType,
|
|
39
|
+
): string;
|
|
40
|
+
select(options: Record<string, string | (() => string)>, value: string): string;
|
|
41
|
+
number(value: number): string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type Part =
|
|
45
|
+
| { type: 'literal'; value: string }
|
|
46
|
+
| { type: 'argument'; name: string }
|
|
47
|
+
| { type: 'number'; name: string }
|
|
48
|
+
| { type: 'pound' }
|
|
49
|
+
| {
|
|
50
|
+
type: 'plural';
|
|
51
|
+
name: string;
|
|
52
|
+
offset: number;
|
|
53
|
+
pluralType: Intl.PluralRuleType;
|
|
54
|
+
options: Option[];
|
|
55
|
+
}
|
|
56
|
+
| { type: 'select'; name: string; options: Option[] };
|
|
57
|
+
|
|
58
|
+
// A plural/select branch. Literal-only branches pre-join to a plain string (`text`),
|
|
59
|
+
// matching upstream's compiled string-literal options; branches with arguments keep
|
|
60
|
+
// their parts and are rendered through a per-call thunk that captures the current args.
|
|
61
|
+
type Option = { key: string; text: string | null; parts: Part[] };
|
|
62
|
+
|
|
63
|
+
class MessageParser {
|
|
64
|
+
private pos = 0;
|
|
65
|
+
|
|
66
|
+
constructor(private message: string) {}
|
|
67
|
+
|
|
68
|
+
parse(): Part[] {
|
|
69
|
+
const parts = this.parseParts(false, false);
|
|
70
|
+
if (this.pos < this.message.length) {
|
|
71
|
+
throw this.error('unmatched `}`');
|
|
72
|
+
}
|
|
73
|
+
return parts;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private error(reason: string): Error {
|
|
77
|
+
return new Error(`unsupported ICU message (${reason} at offset ${this.pos}): ${this.message}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Parses parts until end of input, or until an unconsumed `}` when `nested`.
|
|
81
|
+
private parseParts(nested: boolean, inPlural: boolean): Part[] {
|
|
82
|
+
const msg = this.message;
|
|
83
|
+
const parts: Part[] = [];
|
|
84
|
+
let literal = '';
|
|
85
|
+
const flush = () => {
|
|
86
|
+
if (literal !== '') {
|
|
87
|
+
parts.push({ type: 'literal', value: literal });
|
|
88
|
+
literal = '';
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
while (this.pos < msg.length) {
|
|
92
|
+
const ch = msg[this.pos];
|
|
93
|
+
if (ch === '}') {
|
|
94
|
+
if (nested) {
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
throw this.error('unmatched `}`');
|
|
98
|
+
} else if (ch === '{') {
|
|
99
|
+
flush();
|
|
100
|
+
parts.push(this.parseArgument(inPlural));
|
|
101
|
+
} else if (ch === '#' && inPlural) {
|
|
102
|
+
flush();
|
|
103
|
+
parts.push({ type: 'pound' });
|
|
104
|
+
this.pos++;
|
|
105
|
+
} else if (ch === "'") {
|
|
106
|
+
literal += this.parseQuoted(inPlural);
|
|
107
|
+
} else {
|
|
108
|
+
literal += ch;
|
|
109
|
+
this.pos++;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
flush();
|
|
113
|
+
return parts;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ICU apostrophe quoting: `''` is a literal apostrophe; an apostrophe immediately
|
|
117
|
+
// before a syntax character (`{`, `}`, or `#` inside plural) quotes literal text up
|
|
118
|
+
// to the next lone apostrophe (an unterminated quote runs to end of message); any
|
|
119
|
+
// other apostrophe is plain text.
|
|
120
|
+
private parseQuoted(inPlural: boolean): string {
|
|
121
|
+
const msg = this.message;
|
|
122
|
+
const next = msg[this.pos + 1];
|
|
123
|
+
if (next === "'") {
|
|
124
|
+
this.pos += 2;
|
|
125
|
+
return "'";
|
|
126
|
+
}
|
|
127
|
+
if (next === '{' || next === '}' || (inPlural && next === '#')) {
|
|
128
|
+
this.pos += 2;
|
|
129
|
+
let out = next;
|
|
130
|
+
while (this.pos < msg.length) {
|
|
131
|
+
if (msg[this.pos] === "'") {
|
|
132
|
+
if (msg[this.pos + 1] === "'") {
|
|
133
|
+
out += "'";
|
|
134
|
+
this.pos += 2;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
this.pos++;
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
out += msg[this.pos++];
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
this.pos++;
|
|
145
|
+
return "'";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private parseArgument(inPlural: boolean): Part {
|
|
149
|
+
this.pos++; // consume `{`
|
|
150
|
+
const name = this.parseToken();
|
|
151
|
+
if (!/^\w+$/.test(name)) {
|
|
152
|
+
throw this.error('invalid argument name');
|
|
153
|
+
}
|
|
154
|
+
if (this.consume('}')) {
|
|
155
|
+
return { type: 'argument', name };
|
|
156
|
+
}
|
|
157
|
+
this.expect(',');
|
|
158
|
+
const kind = this.parseToken();
|
|
159
|
+
switch (kind) {
|
|
160
|
+
case 'number':
|
|
161
|
+
if (this.consume('}')) {
|
|
162
|
+
return { type: 'number', name };
|
|
163
|
+
}
|
|
164
|
+
throw this.error('number format styles are unsupported');
|
|
165
|
+
case 'plural':
|
|
166
|
+
case 'selectordinal': {
|
|
167
|
+
this.expect(',');
|
|
168
|
+
this.skipWs();
|
|
169
|
+
let offset = 0;
|
|
170
|
+
if (this.message.startsWith('offset:', this.pos)) {
|
|
171
|
+
this.pos += 'offset:'.length;
|
|
172
|
+
this.skipWs();
|
|
173
|
+
let digits = '';
|
|
174
|
+
while (/\d/.test(this.message[this.pos] ?? '')) {
|
|
175
|
+
digits += this.message[this.pos++];
|
|
176
|
+
}
|
|
177
|
+
if (digits === '') {
|
|
178
|
+
throw this.error('invalid plural offset');
|
|
179
|
+
}
|
|
180
|
+
offset = parseInt(digits, 10);
|
|
181
|
+
}
|
|
182
|
+
// `#` binds to this plural inside every branch, including nested selects.
|
|
183
|
+
const options = this.parseOptions(true);
|
|
184
|
+
this.expect('}');
|
|
185
|
+
return {
|
|
186
|
+
type: 'plural',
|
|
187
|
+
name,
|
|
188
|
+
offset,
|
|
189
|
+
pluralType: kind === 'plural' ? 'cardinal' : 'ordinal',
|
|
190
|
+
options,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
case 'select': {
|
|
194
|
+
this.expect(',');
|
|
195
|
+
// Select branches inherit the surrounding plural context for `#`.
|
|
196
|
+
const options = this.parseOptions(inPlural);
|
|
197
|
+
this.expect('}');
|
|
198
|
+
return { type: 'select', name, options };
|
|
199
|
+
}
|
|
200
|
+
default:
|
|
201
|
+
throw this.error(`unsupported argument type \`${kind}\``);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private parseOptions(branchInPlural: boolean): Option[] {
|
|
206
|
+
const options: Option[] = [];
|
|
207
|
+
for (;;) {
|
|
208
|
+
this.skipWs();
|
|
209
|
+
if (this.pos >= this.message.length || this.message[this.pos] === '}') {
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
let key = '';
|
|
213
|
+
while (
|
|
214
|
+
this.pos < this.message.length &&
|
|
215
|
+
!'{}'.includes(this.message[this.pos]) &&
|
|
216
|
+
!/\s/.test(this.message[this.pos])
|
|
217
|
+
) {
|
|
218
|
+
key += this.message[this.pos++];
|
|
219
|
+
}
|
|
220
|
+
if (key === '') {
|
|
221
|
+
throw this.error('invalid option selector');
|
|
222
|
+
}
|
|
223
|
+
this.skipWs();
|
|
224
|
+
this.expect('{');
|
|
225
|
+
const parts = this.parseParts(true, branchInPlural);
|
|
226
|
+
this.expect('}');
|
|
227
|
+
options.push(
|
|
228
|
+
parts.every((p) => p.type === 'literal')
|
|
229
|
+
? { key, text: parts.map((p) => (p as { value: string }).value).join(''), parts }
|
|
230
|
+
: { key, text: null, parts },
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
if (options.length === 0) {
|
|
234
|
+
throw this.error('missing option selectors');
|
|
235
|
+
}
|
|
236
|
+
return options;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private parseToken(): string {
|
|
240
|
+
let out = '';
|
|
241
|
+
while (this.pos < this.message.length && !',}'.includes(this.message[this.pos])) {
|
|
242
|
+
out += this.message[this.pos++];
|
|
243
|
+
}
|
|
244
|
+
return out.trim();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private consume(ch: string): boolean {
|
|
248
|
+
if (this.message[this.pos] === ch) {
|
|
249
|
+
this.pos++;
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private expect(ch: string): void {
|
|
256
|
+
if (!this.consume(ch)) {
|
|
257
|
+
throw this.error(`expected \`${ch}\``);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private skipWs(): void {
|
|
262
|
+
while (/\s/.test(this.message[this.pos] ?? '')) {
|
|
263
|
+
this.pos++;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function renderParts(
|
|
269
|
+
parts: Part[],
|
|
270
|
+
args: Variables,
|
|
271
|
+
formatter: MessageFormatter,
|
|
272
|
+
pluralValue: number | undefined,
|
|
273
|
+
): string {
|
|
274
|
+
let res = '';
|
|
275
|
+
for (const part of parts) {
|
|
276
|
+
switch (part.type) {
|
|
277
|
+
case 'literal':
|
|
278
|
+
res += part.value;
|
|
279
|
+
break;
|
|
280
|
+
case 'argument':
|
|
281
|
+
// Matches the compiled `${args.name}` template hole (String coercion).
|
|
282
|
+
res += String(args?.[part.name]);
|
|
283
|
+
break;
|
|
284
|
+
case 'number':
|
|
285
|
+
res += formatter.number(args?.[part.name] as number);
|
|
286
|
+
break;
|
|
287
|
+
case 'pound':
|
|
288
|
+
res += formatter.number(pluralValue as number);
|
|
289
|
+
break;
|
|
290
|
+
case 'plural': {
|
|
291
|
+
const value =
|
|
292
|
+
part.offset === 0
|
|
293
|
+
? (args?.[part.name] as number)
|
|
294
|
+
: (args?.[part.name] as number) - part.offset;
|
|
295
|
+
res += formatter.plural(
|
|
296
|
+
value,
|
|
297
|
+
buildOptions(part.options, args, formatter, value),
|
|
298
|
+
part.pluralType === 'cardinal' ? undefined : part.pluralType,
|
|
299
|
+
);
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
case 'select':
|
|
303
|
+
res += formatter.select(
|
|
304
|
+
buildOptions(part.options, args, formatter, pluralValue),
|
|
305
|
+
args?.[part.name] as string,
|
|
306
|
+
);
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return res;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function buildOptions(
|
|
314
|
+
options: Option[],
|
|
315
|
+
args: Variables,
|
|
316
|
+
formatter: MessageFormatter,
|
|
317
|
+
pluralValue: number | undefined,
|
|
318
|
+
): Record<string, string | (() => string)> {
|
|
319
|
+
const out: Record<string, string | (() => string)> = {};
|
|
320
|
+
for (const option of options) {
|
|
321
|
+
out[option.key] =
|
|
322
|
+
option.text !== null
|
|
323
|
+
? option.text
|
|
324
|
+
: () => renderParts(option.parts, args, formatter, pluralValue);
|
|
325
|
+
}
|
|
326
|
+
return out;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export function compileString(message: string): LocalizedString {
|
|
330
|
+
const parts = new MessageParser(message).parse();
|
|
331
|
+
if (parts.every((p) => p.type === 'literal')) {
|
|
332
|
+
// No arguments: stays a plain (de-quoted) string, like upstream's compiled output.
|
|
333
|
+
return parts.map((p) => (p as { value: string }).value).join('');
|
|
334
|
+
}
|
|
335
|
+
return (args: Variables, formatter?: LocalizedStringFormatter<any, any>) =>
|
|
336
|
+
renderParts(parts, args, formatter as unknown as MessageFormatter, undefined);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function compileDictionaries(
|
|
340
|
+
dictionaries: Record<string, Record<string, string>>,
|
|
341
|
+
): Record<string, Record<string, LocalizedString>> {
|
|
342
|
+
const compiled: Record<string, Record<string, LocalizedString>> = {};
|
|
343
|
+
for (const locale in dictionaries) {
|
|
344
|
+
const strings = dictionaries[locale];
|
|
345
|
+
const out: Record<string, LocalizedString> = {};
|
|
346
|
+
for (const key in strings) {
|
|
347
|
+
try {
|
|
348
|
+
out[key] = compileString(strings[key]);
|
|
349
|
+
} catch (e) {
|
|
350
|
+
throw new Error(`Failed to compile intl message ${locale}/${key}: ${(e as Error).message}`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
compiled[locale] = out;
|
|
354
|
+
}
|
|
355
|
+
return compiled;
|
|
356
|
+
}
|
package/src/intl/grid/index.ts
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
// upstream's Parcel glob import (`import intlMessages from '../../intl/grid/*.json'`).
|
|
3
3
|
// The JSON files are copied VERBATIM from .react-spectrum/packages/react-aria/intl/grid/;
|
|
4
4
|
// refresh them from the pinned checkout on version bumps, never hand-edit.
|
|
5
|
+
//
|
|
6
|
+
// Upstream's Parcel build additionally compiles each message through
|
|
7
|
+
// @internationalized/string-compiler, so `{variable}` placeholders and plural/select
|
|
8
|
+
// ICU arguments become message FUNCTIONS (LocalizedStringFormatter interpolates only
|
|
9
|
+
// function messages — plain strings are returned verbatim). `compileDictionaries`
|
|
10
|
+
// applies the equivalent compile step here at module init.
|
|
11
|
+
import { compileDictionaries } from '../compileMessages';
|
|
5
12
|
import ar_AE from './ar-AE.json';
|
|
6
13
|
import bg_BG from './bg-BG.json';
|
|
7
14
|
import cs_CZ from './cs-CZ.json';
|
|
@@ -37,7 +44,7 @@ import uk_UA from './uk-UA.json';
|
|
|
37
44
|
import zh_CN from './zh-CN.json';
|
|
38
45
|
import zh_TW from './zh-TW.json';
|
|
39
46
|
|
|
40
|
-
export default {
|
|
47
|
+
export default compileDictionaries({
|
|
41
48
|
'ar-AE': ar_AE,
|
|
42
49
|
'bg-BG': bg_BG,
|
|
43
50
|
'cs-CZ': cs_CZ,
|
|
@@ -72,4 +79,4 @@ export default {
|
|
|
72
79
|
'uk-UA': uk_UA,
|
|
73
80
|
'zh-CN': zh_CN,
|
|
74
81
|
'zh-TW': zh_TW,
|
|
75
|
-
}
|
|
82
|
+
});
|