@astryxdesign/core 0.4.6 → 0.4.7-canary.29be96d
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/CHANGELOG.md +35 -0
- package/dist/BottomSheet/BottomSheet.d.ts +1 -0
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +54 -15
- package/dist/BottomSheet/BottomSheetEdgeTint.d.ts +6 -0
- package/dist/BottomSheet/BottomSheetEdgeTint.d.ts.map +1 -0
- package/dist/BottomSheet/BottomSheetEdgeTint.js +62 -0
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +1 -0
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts +1 -0
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcher.js +10 -4
- package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
- package/dist/BottomSheet/useSheetGestures.js +23 -5
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +5 -5
- package/dist/Calendar/Calendar.d.ts +3 -1
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +13 -14
- package/dist/Calendar/getInitialFocusDate.d.ts +46 -0
- package/dist/Calendar/getInitialFocusDate.d.ts.map +1 -0
- package/dist/Calendar/getInitialFocusDate.js +62 -0
- package/dist/Chat/ChatMessageList.d.ts +22 -3
- package/dist/Chat/ChatMessageList.d.ts.map +1 -1
- package/dist/Chat/ChatMessageList.js +6 -3
- package/dist/DateInput/TouchDateField.d.ts.map +1 -1
- package/dist/DateInput/TouchDateField.js +34 -1
- package/dist/Dialog/Dialog.d.ts +7 -1
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +48 -22
- package/dist/Heading/Heading.d.ts.map +1 -1
- package/dist/Heading/Heading.js +6 -2
- package/dist/Kbd/Kbd.d.ts.map +1 -1
- package/dist/Kbd/Kbd.js +10 -3
- package/dist/Markdown/index.d.ts +1 -1
- package/dist/Markdown/index.d.ts.map +1 -1
- package/dist/Markdown/parser.d.ts +38 -1
- package/dist/Markdown/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +149 -23
- package/dist/MultiSelector/MultiSelector.d.ts +15 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +17 -6
- package/dist/MultiSelector/index.d.ts +1 -1
- package/dist/MultiSelector/index.d.ts.map +1 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +5 -0
- package/dist/Stepper/Step.d.ts +140 -0
- package/dist/Stepper/Step.d.ts.map +1 -0
- package/dist/Stepper/Step.js +1045 -0
- package/dist/Stepper/StepStatus.d.ts +24 -0
- package/dist/Stepper/StepStatus.d.ts.map +1 -0
- package/dist/Stepper/StepStatus.js +1 -0
- package/dist/Stepper/Stepper.d.ts +93 -0
- package/dist/Stepper/Stepper.d.ts.map +1 -0
- package/dist/Stepper/Stepper.js +184 -0
- package/dist/Stepper/StepperContext.d.ts +41 -0
- package/dist/Stepper/StepperContext.d.ts.map +1 -0
- package/dist/Stepper/StepperContext.js +34 -0
- package/dist/Stepper/index.d.ts +8 -0
- package/dist/Stepper/index.d.ts.map +1 -0
- package/dist/Stepper/index.js +7 -0
- package/dist/Stepper/stepper.stylex.d.ts +15 -0
- package/dist/Stepper/stepper.stylex.d.ts.map +1 -0
- package/dist/Stepper/stepper.stylex.js +20 -0
- package/dist/TabList/Tab.d.ts.map +1 -1
- package/dist/TabList/Tab.js +5 -1
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +4 -1
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +20 -8
- package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts.map +1 -1
- package/dist/Table/plugins/rowStatus/useTableRowStatus.js +10 -3
- package/dist/Table/plugins/selection/useTableSelection.d.ts +16 -0
- package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
- package/dist/Table/plugins/selection/useTableSelection.js +19 -5
- package/dist/Table/types.d.ts +22 -4
- package/dist/Table/types.d.ts.map +1 -1
- package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
- package/dist/Table/useBaseTablePlugins.js +5 -0
- package/dist/Text/Text.d.ts.map +1 -1
- package/dist/Text/Text.js +6 -2
- package/dist/astryx.css +41 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/useHotkeys.d.ts.map +1 -1
- package/dist/hooks/useHotkeys.js +10 -3
- package/dist/hooks/useListFocus.d.ts +5 -2
- package/dist/hooks/useListFocus.d.ts.map +1 -1
- package/dist/hooks/useListFocus.js +12 -6
- package/dist/hooks/useMergedRefs.d.ts +18 -0
- package/dist/hooks/useMergedRefs.d.ts.map +1 -0
- package/dist/hooks/useMergedRefs.js +25 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/utils/timeParser.d.ts +1 -1
- package/dist/utils/timeParser.d.ts.map +1 -1
- package/dist/utils/timeParser.js +26 -12
- package/locales/en.json +14 -6
- package/locales/pseudo.json +6 -0
- package/package.json +8 -3
- package/src/Avatar/Avatar.doc.mjs +2 -1
- package/src/BottomSheet/BottomSheet.test.tsx +123 -0
- package/src/BottomSheet/BottomSheet.tsx +36 -4
- package/src/BottomSheet/BottomSheetEdgeTint.test.tsx +225 -0
- package/src/BottomSheet/BottomSheetEdgeTint.tsx +82 -0
- package/src/BottomSheet/BottomSheetPanel.test.tsx +66 -0
- package/src/BottomSheet/BottomSheetPanel.tsx +19 -0
- package/src/BottomSheet/BottomSheetSwitcher.tsx +13 -0
- package/src/BottomSheet/useSheetGestures.test.ts +27 -0
- package/src/BottomSheet/useSheetGestures.ts +25 -5
- package/src/Breadcrumbs/BreadcrumbItem.tsx +2 -1
- package/src/Button/Button.doc.mjs +22 -0
- package/src/Calendar/Calendar.doc.mjs +4 -3
- package/src/Calendar/Calendar.test.tsx +52 -0
- package/src/Calendar/Calendar.tsx +18 -15
- package/src/Calendar/getInitialFocusDate.test.ts +94 -0
- package/src/Calendar/getInitialFocusDate.ts +93 -0
- package/src/Chat/ChatMessageList.doc.mjs +9 -1
- package/src/Chat/ChatMessageList.test.tsx +46 -0
- package/src/Chat/ChatMessageList.tsx +28 -4
- package/src/CodeBlock/CodeBlock.doc.mjs +6 -0
- package/src/ContextMenu/ContextMenu.doc.mjs +5 -0
- package/src/DateInput/DateInputTouch.test.tsx +36 -0
- package/src/DateInput/TouchDateField.tsx +35 -1
- package/src/Dialog/Dialog.doc.mjs +8 -3
- package/src/Dialog/Dialog.test.tsx +71 -0
- package/src/Dialog/Dialog.tsx +72 -20
- package/src/Field/Field.doc.mjs +11 -0
- package/src/Heading/Heading.test.tsx +23 -1
- package/src/Heading/Heading.tsx +6 -2
- package/src/Kbd/Kbd.test.tsx +17 -0
- package/src/Kbd/Kbd.tsx +10 -3
- package/src/Link/Link.doc.mjs +11 -0
- package/src/Markdown/index.ts +1 -0
- package/src/Markdown/parser.perf.test.ts +71 -1
- package/src/Markdown/parser.test.ts +145 -2
- package/src/Markdown/parser.ts +208 -22
- package/src/MoreMenu/MoreMenu.doc.mjs +7 -1
- package/src/MultiSelector/MultiSelector.doc.mjs +10 -0
- package/src/MultiSelector/MultiSelector.test.tsx +105 -0
- package/src/MultiSelector/MultiSelector.tsx +50 -6
- package/src/MultiSelector/index.ts +1 -0
- package/src/NumberInput/NumberInput.doc.mjs +5 -0
- package/src/Popover/Popover.test.tsx +27 -1
- package/src/PowerSearch/PowerSearch.doc.mjs +10 -0
- package/src/Selector/Selector.doc.mjs +11 -0
- package/src/Selector/Selector.test.tsx +21 -0
- package/src/Selector/Selector.tsx +11 -1
- package/src/Stepper/Step.doc.mjs +98 -0
- package/src/Stepper/Step.tsx +1638 -0
- package/src/Stepper/StepStatus.ts +26 -0
- package/src/Stepper/Stepper.doc.mjs +370 -0
- package/src/Stepper/Stepper.test.tsx +1024 -0
- package/src/Stepper/Stepper.tsx +258 -0
- package/src/Stepper/StepperContext.ts +71 -0
- package/src/Stepper/index.ts +18 -0
- package/src/Stepper/stepper.stylex.ts +19 -0
- package/src/TabList/Tab.tsx +5 -1
- package/src/TabList/TabList.test.tsx +21 -4
- package/src/Table/BaseTable.tsx +6 -3
- package/src/Table/Table.test.tsx +35 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows-perf.test.tsx +112 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows.test.tsx +100 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +17 -8
- package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +13 -7
- package/src/Table/plugins/rowStatus/useTableRowStatus.tsx +12 -3
- package/src/Table/plugins/selection/useTableSelection.test.tsx +76 -0
- package/src/Table/plugins/selection/useTableSelection.tsx +40 -7
- package/src/Table/types.ts +22 -4
- package/src/Table/useBaseTablePlugins.ts +5 -0
- package/src/Table/useTableGroupedRows.doc.mjs +5 -4
- package/src/Table/useTableRowStatus.doc.mjs +1 -1
- package/src/Table/useTableSelection.doc.mjs +31 -0
- package/src/Text/Text.test.tsx +23 -1
- package/src/Text/Text.tsx +6 -2
- package/src/TextInput/TextInput.doc.mjs +10 -0
- package/src/Toast/Toast.doc.mjs +6 -0
- package/src/Tokenizer/Tokenizer.doc.mjs +10 -0
- package/src/Toolbar/Toolbar.doc.mjs +5 -0
- package/src/__tests__/apiContractDrift.test.tsx +80 -0
- package/src/__tests__/fieldContract.test.tsx +24 -0
- package/src/__tests__/structuralComponentContract.test.tsx +39 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useHotkeys.test.ts +18 -0
- package/src/hooks/useHotkeys.ts +10 -3
- package/src/hooks/useListFocus.doc.mjs +2 -2
- package/src/hooks/useListFocus.test.tsx +65 -3
- package/src/hooks/useListFocus.ts +15 -7
- package/src/hooks/useMergedRefs.doc.mjs +79 -0
- package/src/hooks/useMergedRefs.test.tsx +62 -0
- package/src/hooks/useMergedRefs.ts +36 -0
- package/src/index.ts +1 -0
- package/src/theme/MediaTheme.doc.mjs +5 -5
- package/src/utils/timeParser.test.ts +6 -1
- package/src/utils/timeParser.ts +35 -13
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
3
|
import {describe, it, expect} from 'vitest';
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
createIncrementalState,
|
|
6
|
+
parseInline,
|
|
7
|
+
parseMarkdown,
|
|
8
|
+
parseMarkdownIncremental,
|
|
9
|
+
} from './parser';
|
|
10
|
+
import type {BlockNode, InlineNode} from './parser';
|
|
6
11
|
|
|
7
12
|
describe('parseInline', () => {
|
|
8
13
|
it('parses plain text', () => {
|
|
@@ -1209,3 +1214,141 @@ describe('link reference definitions', () => {
|
|
|
1209
1214
|
expect(blocks[0].type).toBe('paragraph');
|
|
1210
1215
|
});
|
|
1211
1216
|
});
|
|
1217
|
+
|
|
1218
|
+
describe('sourceRanges', () => {
|
|
1219
|
+
const slice = (source: string, block: BlockNode) =>
|
|
1220
|
+
block.range == null
|
|
1221
|
+
? null
|
|
1222
|
+
: source.slice(block.range.start, block.range.end);
|
|
1223
|
+
|
|
1224
|
+
it('is off by default', () => {
|
|
1225
|
+
const [block] = parseMarkdown('# Title');
|
|
1226
|
+
expect(block.range).toBeUndefined();
|
|
1227
|
+
});
|
|
1228
|
+
|
|
1229
|
+
it('addresses a block with named start and end offsets', () => {
|
|
1230
|
+
const source = 'One.\n\nTwo.';
|
|
1231
|
+
const [, second] = parseMarkdown(source, {sourceRanges: true});
|
|
1232
|
+
expect(second.range).toEqual({start: 6, end: 10});
|
|
1233
|
+
});
|
|
1234
|
+
|
|
1235
|
+
it('gives every top-level block the source it came from', () => {
|
|
1236
|
+
const source = [
|
|
1237
|
+
'# Title',
|
|
1238
|
+
'',
|
|
1239
|
+
'A paragraph that',
|
|
1240
|
+
'wraps onto two lines.',
|
|
1241
|
+
'',
|
|
1242
|
+
'- one',
|
|
1243
|
+
'- two',
|
|
1244
|
+
'',
|
|
1245
|
+
'```js',
|
|
1246
|
+
'const x = 1;',
|
|
1247
|
+
'```',
|
|
1248
|
+
'',
|
|
1249
|
+
'| a | b |',
|
|
1250
|
+
'| --- | --- |',
|
|
1251
|
+
'| 1 | 2 |',
|
|
1252
|
+
'',
|
|
1253
|
+
'> quoted',
|
|
1254
|
+
'',
|
|
1255
|
+
'---',
|
|
1256
|
+
].join('\n');
|
|
1257
|
+
const blocks = parseMarkdown(source, {sourceRanges: true});
|
|
1258
|
+
expect(blocks.map(b => slice(source, b))).toEqual([
|
|
1259
|
+
'# Title',
|
|
1260
|
+
'A paragraph that\nwraps onto two lines.',
|
|
1261
|
+
'- one\n- two',
|
|
1262
|
+
'```js\nconst x = 1;\n```',
|
|
1263
|
+
'| a | b |\n| --- | --- |\n| 1 | 2 |',
|
|
1264
|
+
'> quoted',
|
|
1265
|
+
'---',
|
|
1266
|
+
]);
|
|
1267
|
+
});
|
|
1268
|
+
|
|
1269
|
+
it('reports offsets into the input, not into the link-definition-stripped text', () => {
|
|
1270
|
+
// The definition lines are removed before the block loop runs, so a naive
|
|
1271
|
+
// offset would drift by their length for everything after them.
|
|
1272
|
+
const source = [
|
|
1273
|
+
'[ref]: https://example.com',
|
|
1274
|
+
'',
|
|
1275
|
+
'See [the docs][ref].',
|
|
1276
|
+
'',
|
|
1277
|
+
'Another paragraph.',
|
|
1278
|
+
].join('\n');
|
|
1279
|
+
const blocks = parseMarkdown(source, {sourceRanges: true});
|
|
1280
|
+
expect(blocks.map(b => slice(source, b))).toEqual([
|
|
1281
|
+
'See [the docs][ref].',
|
|
1282
|
+
'Another paragraph.',
|
|
1283
|
+
]);
|
|
1284
|
+
});
|
|
1285
|
+
|
|
1286
|
+
it('reports absolute offsets when the document is parsed incrementally', () => {
|
|
1287
|
+
const source = ['# Title', '', 'One.', '', 'Two.', '', 'Three.'].join('\n');
|
|
1288
|
+
const state = createIncrementalState();
|
|
1289
|
+
let blocks: BlockNode[] = [];
|
|
1290
|
+
for (let end = 1; end <= source.length; end++) {
|
|
1291
|
+
blocks = parseMarkdownIncremental(source.slice(0, end), state, {
|
|
1292
|
+
sourceRanges: true,
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
expect(blocks.map(b => slice(source, b))).toEqual([
|
|
1296
|
+
'# Title',
|
|
1297
|
+
'One.',
|
|
1298
|
+
'Two.',
|
|
1299
|
+
'Three.',
|
|
1300
|
+
]);
|
|
1301
|
+
});
|
|
1302
|
+
|
|
1303
|
+
it('keeps the blank lines an unterminated fence owns', () => {
|
|
1304
|
+
// Mid-stream the closing fence has not arrived, and the blank lines are
|
|
1305
|
+
// part of the code, not spacing between blocks.
|
|
1306
|
+
const source = '```\ncode\n\n';
|
|
1307
|
+
const [block] = parseMarkdown(source, {sourceRanges: true});
|
|
1308
|
+
// The whole thing: the fence consumed those lines as code.
|
|
1309
|
+
expect(slice(source, block)).toBe(source);
|
|
1310
|
+
});
|
|
1311
|
+
|
|
1312
|
+
it('slices to something that re-parses to the same block', () => {
|
|
1313
|
+
// The property a consumer actually needs, and the one that says the
|
|
1314
|
+
// offsets are right: what the range points at is the block.
|
|
1315
|
+
const check = (source: string) => {
|
|
1316
|
+
for (const block of parseMarkdown(source, {sourceRanges: true})) {
|
|
1317
|
+
const {range: _range, ...node} = block;
|
|
1318
|
+
expect(parseMarkdown(slice(source, block)!)).toEqual([node]);
|
|
1319
|
+
}
|
|
1320
|
+
};
|
|
1321
|
+
check('# Title\n\nA paragraph.\n\n- one\n- two\n\n> quoted');
|
|
1322
|
+
// CRLF: the parser keeps the `\r` in its own content, so the range does
|
|
1323
|
+
// too rather than slicing to text that parses differently.
|
|
1324
|
+
check('# Title\r\n\r\nA paragraph.\r\n');
|
|
1325
|
+
});
|
|
1326
|
+
|
|
1327
|
+
it('re-parses when the caller flips the option on an existing state', () => {
|
|
1328
|
+
const source = 'One.\n\nTwo.\n\nThree.';
|
|
1329
|
+
const state = createIncrementalState();
|
|
1330
|
+
const without = parseMarkdownIncremental(source, state);
|
|
1331
|
+
expect(without.every(b => b.range == null)).toBe(true);
|
|
1332
|
+
const with_ = parseMarkdownIncremental(source, state, {
|
|
1333
|
+
sourceRanges: true,
|
|
1334
|
+
});
|
|
1335
|
+
expect(with_.map(b => slice(source, b))).toEqual([
|
|
1336
|
+
'One.',
|
|
1337
|
+
'Two.',
|
|
1338
|
+
'Three.',
|
|
1339
|
+
]);
|
|
1340
|
+
const back = parseMarkdownIncremental(source, state);
|
|
1341
|
+
expect(back.every(b => b.range == null)).toBe(true);
|
|
1342
|
+
});
|
|
1343
|
+
|
|
1344
|
+
it('spans both halves of a list the incremental parser merged', () => {
|
|
1345
|
+
const source = '1. one\n\n1. two';
|
|
1346
|
+
const state = createIncrementalState();
|
|
1347
|
+
parseMarkdownIncremental('1. one\n\n', state, {sourceRanges: true});
|
|
1348
|
+
const blocks = parseMarkdownIncremental(source, state, {
|
|
1349
|
+
sourceRanges: true,
|
|
1350
|
+
});
|
|
1351
|
+
expect(blocks).toHaveLength(1);
|
|
1352
|
+
expect(slice(source, blocks[0])).toBe(source);
|
|
1353
|
+
});
|
|
1354
|
+
});
|
package/src/Markdown/parser.ts
CHANGED
|
@@ -24,7 +24,15 @@ export type InlineNode =
|
|
|
24
24
|
| {type: 'citation'; sourceId: string}
|
|
25
25
|
| {type: 'break'};
|
|
26
26
|
|
|
27
|
-
export type BlockNode =
|
|
27
|
+
export type BlockNode = BlockNodeKind & {
|
|
28
|
+
/**
|
|
29
|
+
* Where this block came from in the source, when parsed with the
|
|
30
|
+
* `sourceRanges` option. Top-level blocks only.
|
|
31
|
+
*/
|
|
32
|
+
range?: SourceRange;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type BlockNodeKind =
|
|
28
36
|
| {type: 'heading'; level: 1 | 2 | 3 | 4 | 5 | 6; children: InlineNode[]}
|
|
29
37
|
| {type: 'paragraph'; children: InlineNode[]}
|
|
30
38
|
| {type: 'codeblock'; language: string; content: string}
|
|
@@ -47,6 +55,17 @@ export type BlockNode =
|
|
|
47
55
|
| {type: 'hr'}
|
|
48
56
|
| {type: 'image'; src: string; alt: string};
|
|
49
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Where a block sits in the source string handed to `parseMarkdown`:
|
|
60
|
+
* `source.slice(start, end)` is the block, and `end` excludes the block's
|
|
61
|
+
* trailing blank lines.
|
|
62
|
+
*
|
|
63
|
+
* An object rather than a `[start, end]` tuple so a second way of addressing
|
|
64
|
+
* the same block — line numbers, once a consumer needs them — can be added as
|
|
65
|
+
* optional fields without breaking anyone.
|
|
66
|
+
*/
|
|
67
|
+
export type SourceRange = {readonly start: number; readonly end: number};
|
|
68
|
+
|
|
50
69
|
export type ListItemNode = {checked?: boolean; children: BlockNode[]};
|
|
51
70
|
export type TableCellNode = {children: InlineNode[]};
|
|
52
71
|
export type TableAlignment = 'left' | 'center' | 'right' | null;
|
|
@@ -78,11 +97,28 @@ export type ParseOptions = {
|
|
|
78
97
|
* suffix is not rejected (Astryx accepts any plausible TLD shape).
|
|
79
98
|
*/
|
|
80
99
|
autolink?: 'gfm';
|
|
100
|
+
/**
|
|
101
|
+
* When true, every top-level block carries a `range` — the offsets it
|
|
102
|
+
* occupies in the string passed in. Lets a consumer that still holds the
|
|
103
|
+
* source slice the original markdown for a block instead of reconstructing
|
|
104
|
+
* it from the parsed node (or from the rendered DOM). Off by default: the
|
|
105
|
+
* field is absent unless asked for, so nothing that compares nodes changes.
|
|
106
|
+
*
|
|
107
|
+
* Blocks nested inside a list item or a blockquote do not carry one.
|
|
108
|
+
*/
|
|
109
|
+
sourceRanges?: boolean;
|
|
81
110
|
};
|
|
82
111
|
|
|
83
112
|
type ResolvedOptions = {
|
|
84
113
|
readonly sourceIds: ReadonlySet<string> | undefined;
|
|
85
114
|
readonly autolink: 'gfm' | undefined;
|
|
115
|
+
readonly sourceRanges?: boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Offset of this parse's input within the document the ranges are reported
|
|
118
|
+
* against. Internal only — the incremental parser parses slices and needs
|
|
119
|
+
* their blocks' ranges to come out absolute.
|
|
120
|
+
*/
|
|
121
|
+
readonly baseOffset?: number;
|
|
86
122
|
/**
|
|
87
123
|
* Link reference definitions (`[label]: url`) collected from the whole
|
|
88
124
|
* document, keyed by normalized label. Internal only — populated by the
|
|
@@ -109,7 +145,11 @@ function resolveOptions(
|
|
|
109
145
|
return {sourceIds: arg as ReadonlySet<string>, autolink: undefined};
|
|
110
146
|
}
|
|
111
147
|
const opts = arg as ParseOptions;
|
|
112
|
-
return {
|
|
148
|
+
return {
|
|
149
|
+
sourceIds: opts.sourceIds,
|
|
150
|
+
autolink: opts.autolink,
|
|
151
|
+
sourceRanges: opts.sourceRanges,
|
|
152
|
+
};
|
|
113
153
|
}
|
|
114
154
|
|
|
115
155
|
// ---------------------------------------------------------------------------
|
|
@@ -174,6 +214,11 @@ function matchLinkDefinition(
|
|
|
174
214
|
function extractLinkDefinitions(input: string): {
|
|
175
215
|
defs: ReadonlyMap<string, string>;
|
|
176
216
|
cleaned: string;
|
|
217
|
+
/**
|
|
218
|
+
* For each line of `cleaned`, the line of `input` it came from. Undefined
|
|
219
|
+
* when nothing was stripped and the two are the same text.
|
|
220
|
+
*/
|
|
221
|
+
lineMap?: number[];
|
|
177
222
|
} {
|
|
178
223
|
const lines = input.split('\n');
|
|
179
224
|
const defs = new Map<string, string>();
|
|
@@ -235,8 +280,14 @@ function extractLinkDefinitions(input: string): {
|
|
|
235
280
|
if (defs.size === 0) {
|
|
236
281
|
return {defs, cleaned: input};
|
|
237
282
|
}
|
|
238
|
-
const
|
|
239
|
-
|
|
283
|
+
const lineMap: number[] = [];
|
|
284
|
+
for (let index = 0; index < lines.length; index++) {
|
|
285
|
+
if (keep[index]) {
|
|
286
|
+
lineMap.push(index);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const cleaned = lineMap.map(index => lines[index]).join('\n');
|
|
290
|
+
return {defs, cleaned, lineMap};
|
|
240
291
|
}
|
|
241
292
|
|
|
242
293
|
/** Order-independent signature of a link-definition set, for cache checks. */
|
|
@@ -1042,6 +1093,16 @@ function isTableSeparator(line: string): boolean {
|
|
|
1042
1093
|
return nonEmpty.length > 0 && nonEmpty.every(cell => /^:?-+:?$/.test(cell));
|
|
1043
1094
|
}
|
|
1044
1095
|
|
|
1096
|
+
/**
|
|
1097
|
+
* The same options for content parsed out of an enclosing block. Ranges are a
|
|
1098
|
+
* top-level contract: a list item's or a blockquote's children are parsed from
|
|
1099
|
+
* text the caller reassembled (markers and `>` prefixes stripped), so an
|
|
1100
|
+
* offset into it would not address the document.
|
|
1101
|
+
*/
|
|
1102
|
+
function nested(opts: ResolvedOptions): ResolvedOptions {
|
|
1103
|
+
return opts.sourceRanges ? {...opts, sourceRanges: false} : opts;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1045
1106
|
/**
|
|
1046
1107
|
* Returns true when a line could start a new block — used to stop paragraph
|
|
1047
1108
|
* continuation. Every regex here uses bounded or single-class quantifiers
|
|
@@ -1213,7 +1274,7 @@ function parseList(
|
|
|
1213
1274
|
itemText += '\n' + deindented.join('\n');
|
|
1214
1275
|
}
|
|
1215
1276
|
|
|
1216
|
-
items.push({checked, children: parseMarkdownImpl(itemText, opts)});
|
|
1277
|
+
items.push({checked, children: parseMarkdownImpl(itemText, nested(opts))});
|
|
1217
1278
|
|
|
1218
1279
|
// CommonMark loose list: blank line(s) between items of the same style
|
|
1219
1280
|
// and indent still form one list. Skip the blanks and continue if the
|
|
@@ -1274,7 +1335,7 @@ function parseMarkdownImpl(
|
|
|
1274
1335
|
// definitions win on conflict, matching CommonMark's first-definition-wins
|
|
1275
1336
|
// in document order; locally-nested definitions still resolve within this
|
|
1276
1337
|
// parse.
|
|
1277
|
-
const {defs, cleaned} = extractLinkDefinitions(input);
|
|
1338
|
+
const {defs, cleaned, lineMap} = extractLinkDefinitions(input);
|
|
1278
1339
|
const inherited = baseOpts.linkDefs;
|
|
1279
1340
|
let linkDefs: ReadonlyMap<string, string> | undefined;
|
|
1280
1341
|
if (defs.size === 0) {
|
|
@@ -1288,9 +1349,25 @@ function parseMarkdownImpl(
|
|
|
1288
1349
|
linkDefs != null ? {...baseOpts, linkDefs} : baseOpts;
|
|
1289
1350
|
const lines = cleaned.split('\n');
|
|
1290
1351
|
const blocks: BlockNode[] = [];
|
|
1352
|
+
// The line each block started on, parallel to `blocks`. Only collected when
|
|
1353
|
+
// ranges were asked for; a block's end is resolved after the loop, since the
|
|
1354
|
+
// branch that produced it has already moved `index` past whatever it read.
|
|
1355
|
+
const blockStartLines: number[] | null = opts.sourceRanges ? [] : null;
|
|
1356
|
+
// Set only by a block that consumes blank lines as content, where the
|
|
1357
|
+
// positional end derivation would trim them away.
|
|
1358
|
+
const blockEndLines: (number | undefined)[] | null = opts.sourceRanges
|
|
1359
|
+
? []
|
|
1360
|
+
: null;
|
|
1361
|
+
let blockStartLine = 0;
|
|
1362
|
+
const pushBlock = (node: BlockNode, endLine?: number) => {
|
|
1363
|
+
blocks.push(node);
|
|
1364
|
+
blockStartLines?.push(blockStartLine);
|
|
1365
|
+
blockEndLines?.push(endLine);
|
|
1366
|
+
};
|
|
1291
1367
|
let index = 0;
|
|
1292
1368
|
|
|
1293
1369
|
while (index < lines.length) {
|
|
1370
|
+
blockStartLine = index;
|
|
1294
1371
|
const line = lines[index];
|
|
1295
1372
|
if (line.trim() === '') {
|
|
1296
1373
|
index++;
|
|
@@ -1309,14 +1386,20 @@ function parseMarkdownImpl(
|
|
|
1309
1386
|
index++;
|
|
1310
1387
|
}
|
|
1311
1388
|
index++; // skip closing fence
|
|
1312
|
-
|
|
1389
|
+
// A fence owns its blank lines, and an unterminated one (mid-stream)
|
|
1390
|
+
// can end on them, so it states its own end rather than letting the
|
|
1391
|
+
// positional derivation trim them off.
|
|
1392
|
+
pushBlock(
|
|
1393
|
+
{type: 'codeblock', language, content: codeLines.join('\n')},
|
|
1394
|
+
Math.min(index, lines.length) - 1,
|
|
1395
|
+
);
|
|
1313
1396
|
continue;
|
|
1314
1397
|
}
|
|
1315
1398
|
|
|
1316
1399
|
// --- Heading ---
|
|
1317
1400
|
const headingMatch = line.match(/^(#{1,6}) +(.*)/);
|
|
1318
1401
|
if (headingMatch) {
|
|
1319
|
-
|
|
1402
|
+
pushBlock({
|
|
1320
1403
|
type: 'heading',
|
|
1321
1404
|
level: headingMatch[1].length as 1 | 2 | 3 | 4 | 5 | 6,
|
|
1322
1405
|
children: parseInlineEntry(headingMatch[2], opts),
|
|
@@ -1327,7 +1410,7 @@ function parseMarkdownImpl(
|
|
|
1327
1410
|
|
|
1328
1411
|
// --- HR (must precede list check to handle `- - -`, `* * *`, `_ _ _`) ---
|
|
1329
1412
|
if (isHorizontalRule(line)) {
|
|
1330
|
-
|
|
1413
|
+
pushBlock({type: 'hr'});
|
|
1331
1414
|
index++;
|
|
1332
1415
|
continue;
|
|
1333
1416
|
}
|
|
@@ -1335,7 +1418,7 @@ function parseMarkdownImpl(
|
|
|
1335
1418
|
// --- Standalone image ---
|
|
1336
1419
|
const imageMatch = line.match(/^!\[([^\]]*)\]\(([^)]+)\)/);
|
|
1337
1420
|
if (imageMatch && line.trim() === imageMatch[0]) {
|
|
1338
|
-
|
|
1421
|
+
pushBlock({type: 'image', alt: imageMatch[1], src: imageMatch[2]});
|
|
1339
1422
|
index++;
|
|
1340
1423
|
continue;
|
|
1341
1424
|
}
|
|
@@ -1347,7 +1430,7 @@ function parseMarkdownImpl(
|
|
|
1347
1430
|
isTableSeparator(lines[index + 1])
|
|
1348
1431
|
) {
|
|
1349
1432
|
const tableResult = parseTable(lines, index, opts);
|
|
1350
|
-
|
|
1433
|
+
pushBlock(tableResult.node);
|
|
1351
1434
|
index = tableResult.nextIndex;
|
|
1352
1435
|
continue;
|
|
1353
1436
|
}
|
|
@@ -1362,9 +1445,9 @@ function parseMarkdownImpl(
|
|
|
1362
1445
|
quoteLines.push(lines[index].replace(/^> ?/, ''));
|
|
1363
1446
|
index++;
|
|
1364
1447
|
}
|
|
1365
|
-
|
|
1448
|
+
pushBlock({
|
|
1366
1449
|
type: 'blockquote',
|
|
1367
|
-
children: parseMarkdownImpl(quoteLines.join('\n'), opts),
|
|
1450
|
+
children: parseMarkdownImpl(quoteLines.join('\n'), nested(opts)),
|
|
1368
1451
|
});
|
|
1369
1452
|
continue;
|
|
1370
1453
|
}
|
|
@@ -1372,7 +1455,7 @@ function parseMarkdownImpl(
|
|
|
1372
1455
|
// --- Unordered list ---
|
|
1373
1456
|
if (/^ {0,9}[-*+] /.test(line)) {
|
|
1374
1457
|
const listResult = parseList(lines, index, false, opts);
|
|
1375
|
-
|
|
1458
|
+
pushBlock(listResult.node);
|
|
1376
1459
|
index = listResult.nextIndex;
|
|
1377
1460
|
continue;
|
|
1378
1461
|
}
|
|
@@ -1380,7 +1463,7 @@ function parseMarkdownImpl(
|
|
|
1380
1463
|
// --- Ordered list ---
|
|
1381
1464
|
if (/^ {0,9}\d+[.)] /.test(line)) {
|
|
1382
1465
|
const listResult = parseList(lines, index, true, opts);
|
|
1383
|
-
|
|
1466
|
+
pushBlock(listResult.node);
|
|
1384
1467
|
index = listResult.nextIndex;
|
|
1385
1468
|
continue;
|
|
1386
1469
|
}
|
|
@@ -1396,14 +1479,76 @@ function parseMarkdownImpl(
|
|
|
1396
1479
|
paraLines.push(lines[index]);
|
|
1397
1480
|
index++;
|
|
1398
1481
|
}
|
|
1399
|
-
|
|
1482
|
+
pushBlock({
|
|
1400
1483
|
type: 'paragraph',
|
|
1401
1484
|
children: parseInlineEntry(paraLines.join('\n'), opts),
|
|
1402
1485
|
});
|
|
1403
1486
|
}
|
|
1487
|
+
if (blockStartLines != null) {
|
|
1488
|
+
stampSourceRanges(
|
|
1489
|
+
blocks,
|
|
1490
|
+
blockStartLines,
|
|
1491
|
+
blockEndLines ?? [],
|
|
1492
|
+
lines,
|
|
1493
|
+
lineMap,
|
|
1494
|
+
input,
|
|
1495
|
+
opts,
|
|
1496
|
+
);
|
|
1497
|
+
}
|
|
1404
1498
|
return blocks;
|
|
1405
1499
|
}
|
|
1406
1500
|
|
|
1501
|
+
/**
|
|
1502
|
+
* Give each block the offsets it occupies in the original input.
|
|
1503
|
+
*
|
|
1504
|
+
* Blocks are contiguous and in source order, so a block runs from its own
|
|
1505
|
+
* first line to the line before the next block starts, minus the blank lines
|
|
1506
|
+
* between them. Offsets are computed against the *input*, not the text the
|
|
1507
|
+
* block loop saw: link reference definitions are stripped before parsing, and
|
|
1508
|
+
* `lineMap` says which input line each surviving line came from.
|
|
1509
|
+
*/
|
|
1510
|
+
function stampSourceRanges(
|
|
1511
|
+
blocks: BlockNode[],
|
|
1512
|
+
blockStartLines: number[],
|
|
1513
|
+
blockEndLines: (number | undefined)[],
|
|
1514
|
+
lines: string[],
|
|
1515
|
+
lineMap: number[] | undefined,
|
|
1516
|
+
input: string,
|
|
1517
|
+
opts: ResolvedOptions,
|
|
1518
|
+
): void {
|
|
1519
|
+
const base = opts.baseOffset ?? 0;
|
|
1520
|
+
// Offset of the first character of every line of the input.
|
|
1521
|
+
const inputLineStarts = [0];
|
|
1522
|
+
for (let i = 0; i < input.length; i++) {
|
|
1523
|
+
if (input[i] === '\n') {
|
|
1524
|
+
inputLineStarts.push(i + 1);
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
// Stripping removes whole lines and never edits one, so a parsed line's
|
|
1528
|
+
// length is its input line's length.
|
|
1529
|
+
const lineStart = (line: number): number =>
|
|
1530
|
+
base + inputLineStarts[lineMap != null ? lineMap[line] : line];
|
|
1531
|
+
|
|
1532
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
1533
|
+
const startLine = blockStartLines[i];
|
|
1534
|
+
let endLine = blockEndLines[i];
|
|
1535
|
+
if (endLine == null) {
|
|
1536
|
+
const nextStart =
|
|
1537
|
+
i + 1 < blocks.length ? blockStartLines[i + 1] : lines.length;
|
|
1538
|
+
endLine = nextStart - 1;
|
|
1539
|
+
while (endLine > startLine && lines[endLine].trim() === '') {
|
|
1540
|
+
endLine--;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
// Exactly the block's own lines, verbatim — a CRLF document's trailing
|
|
1544
|
+
// `\r` included, since the parser reads it as part of the line too and a
|
|
1545
|
+
// range that dropped it would slice to something that re-parses
|
|
1546
|
+
// differently.
|
|
1547
|
+
const end = lineStart(endLine) + lines[endLine].length;
|
|
1548
|
+
blocks[i] = {...blocks[i], range: {start: lineStart(startLine), end}};
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1407
1552
|
// ---------------------------------------------------------------------------
|
|
1408
1553
|
// Incremental parsing
|
|
1409
1554
|
// ---------------------------------------------------------------------------
|
|
@@ -1420,6 +1565,12 @@ export interface IncrementalState {
|
|
|
1420
1565
|
* with newly-arriving content.
|
|
1421
1566
|
*/
|
|
1422
1567
|
autolink?: 'gfm';
|
|
1568
|
+
/**
|
|
1569
|
+
* The `sourceRanges` option the cached `settledBlocks` were parsed with.
|
|
1570
|
+
* Flipping it invalidates them the same way `autolink` does: they either
|
|
1571
|
+
* lack the ranges the caller now asks for, or carry ones it did not.
|
|
1572
|
+
*/
|
|
1573
|
+
sourceRanges?: boolean;
|
|
1423
1574
|
/**
|
|
1424
1575
|
* Signature of the link reference definitions the cached `settledBlocks`
|
|
1425
1576
|
* were parsed with. Definitions are document-global and typically arrive
|
|
@@ -1689,6 +1840,15 @@ function trimUnsettledStructural(text: string): string {
|
|
|
1689
1840
|
return lines.join('\n');
|
|
1690
1841
|
}
|
|
1691
1842
|
|
|
1843
|
+
/**
|
|
1844
|
+
* The same options, for parsing a slice that starts at `offset` of the
|
|
1845
|
+
* document — so the slice's blocks report ranges into the whole document
|
|
1846
|
+
* rather than into the slice.
|
|
1847
|
+
*/
|
|
1848
|
+
function atOffset(opts: ResolvedOptions, offset: number): ResolvedOptions {
|
|
1849
|
+
return opts.sourceRanges ? {...opts, baseOffset: offset} : opts;
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1692
1852
|
/**
|
|
1693
1853
|
* Concatenate freshly-parsed delta blocks with previously-settled blocks,
|
|
1694
1854
|
* merging adjacent same-style lists into a single loose list. The boundary
|
|
@@ -1718,6 +1878,11 @@ function mergeSettledBlocks(
|
|
|
1718
1878
|
delimiter: prevLast.delimiter,
|
|
1719
1879
|
loose: true,
|
|
1720
1880
|
items: [...prevLast.items, ...deltaFirst.items],
|
|
1881
|
+
// One list now, so one range: from where the first half started to
|
|
1882
|
+
// where the second half ended.
|
|
1883
|
+
...(prevLast.range != null && deltaFirst.range != null
|
|
1884
|
+
? {range: {start: prevLast.range.start, end: deltaFirst.range.end}}
|
|
1885
|
+
: null),
|
|
1721
1886
|
};
|
|
1722
1887
|
return [...prev.slice(0, -1), merged, ...delta.slice(1)];
|
|
1723
1888
|
}
|
|
@@ -1740,15 +1905,19 @@ export function parseMarkdownIncremental(
|
|
|
1740
1905
|
arg?: ReadonlySet<string> | ParseOptions,
|
|
1741
1906
|
): BlockNode[] {
|
|
1742
1907
|
const opts = resolveOptions(arg);
|
|
1743
|
-
// Invalidate cache when the autolink option flips — cached
|
|
1744
|
-
// were parsed with the previous setting and would otherwise
|
|
1745
|
-
// unchanged.
|
|
1746
|
-
if (
|
|
1908
|
+
// Invalidate cache when the autolink or sourceRanges option flips — cached
|
|
1909
|
+
// settled blocks were parsed with the previous setting and would otherwise
|
|
1910
|
+
// be reused unchanged.
|
|
1911
|
+
if (
|
|
1912
|
+
state.autolink !== opts.autolink ||
|
|
1913
|
+
Boolean(state.sourceRanges) !== Boolean(opts.sourceRanges)
|
|
1914
|
+
) {
|
|
1747
1915
|
state.prevInput = '';
|
|
1748
1916
|
state.settledText = '';
|
|
1749
1917
|
state.settledBlocks = [];
|
|
1750
1918
|
state.settledUpTo = 0;
|
|
1751
1919
|
state.autolink = opts.autolink;
|
|
1920
|
+
state.sourceRanges = opts.sourceRanges;
|
|
1752
1921
|
}
|
|
1753
1922
|
if (input === '') {
|
|
1754
1923
|
state.prevInput = '';
|
|
@@ -1799,15 +1968,32 @@ export function parseMarkdownIncremental(
|
|
|
1799
1968
|
) {
|
|
1800
1969
|
// Settled portion grew — parse only the new delta
|
|
1801
1970
|
const delta = settledText.slice(state.settledText.length);
|
|
1802
|
-
const deltaBlocks = parseMarkdownImpl(
|
|
1971
|
+
const deltaBlocks = parseMarkdownImpl(
|
|
1972
|
+
delta,
|
|
1973
|
+
atOffset(parseOpts, state.settledText.length),
|
|
1974
|
+
);
|
|
1803
1975
|
settledBlocks = mergeSettledBlocks(state.settledBlocks, deltaBlocks);
|
|
1804
1976
|
} else {
|
|
1805
1977
|
// Content before the boundary changed — full re-parse of settled portion
|
|
1806
1978
|
settledBlocks = parseMarkdownImpl(settledText, parseOpts);
|
|
1807
1979
|
}
|
|
1808
1980
|
|
|
1981
|
+
// The unsettled tail is trimmed before parsing, so its offset in the
|
|
1982
|
+
// document is where that trimmed text actually starts — not the boundary,
|
|
1983
|
+
// which is a line index. If it somehow can't be located, parse it without
|
|
1984
|
+
// ranges rather than report wrong ones. Only worth searching for when
|
|
1985
|
+
// ranges were asked for: this runs on every streamed chunk.
|
|
1986
|
+
const unsettledStart =
|
|
1987
|
+
unsettledText && opts.sourceRanges
|
|
1988
|
+
? input.indexOf(unsettledText, settledText.length)
|
|
1989
|
+
: -1;
|
|
1809
1990
|
const unsettledBlocks = unsettledText
|
|
1810
|
-
? parseMarkdownImpl(
|
|
1991
|
+
? parseMarkdownImpl(
|
|
1992
|
+
unsettledText,
|
|
1993
|
+
unsettledStart >= 0
|
|
1994
|
+
? atOffset(parseOpts, unsettledStart)
|
|
1995
|
+
: nested(parseOpts),
|
|
1996
|
+
)
|
|
1811
1997
|
: [];
|
|
1812
1998
|
|
|
1813
1999
|
state.settledText = settledText;
|
|
@@ -46,7 +46,8 @@ export const docs = {
|
|
|
46
46
|
type: 'boolean',
|
|
47
47
|
description: 'Whether the menu trigger is disabled.',
|
|
48
48
|
default: 'false',
|
|
49
|
-
},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
50
51
|
name: 'placement',
|
|
51
52
|
type: "'above' | 'below' | 'start' | 'end'",
|
|
52
53
|
description:
|
|
@@ -60,6 +61,11 @@ export const docs = {
|
|
|
60
61
|
"Alignment along the placement axis. Use 'end' to align the menu with the trigger's trailing edge, which is usually what an overflow menu wants.",
|
|
61
62
|
default: "'start'",
|
|
62
63
|
},
|
|
64
|
+
{
|
|
65
|
+
name: 'onOpenChange',
|
|
66
|
+
type: '(isOpen: boolean) => void',
|
|
67
|
+
description: 'Callback fired when the menu opens or closes.',
|
|
68
|
+
},
|
|
63
69
|
{
|
|
64
70
|
name: 'xstyle',
|
|
65
71
|
type: 'StyleXStyles',
|
|
@@ -103,6 +103,12 @@ export const docs = {
|
|
|
103
103
|
description: 'How to display selected items in the trigger.',
|
|
104
104
|
default: "'count'",
|
|
105
105
|
},
|
|
106
|
+
{
|
|
107
|
+
name: 'formatValue',
|
|
108
|
+
type: '(items: {value: string; label: string}[]) => string',
|
|
109
|
+
description:
|
|
110
|
+
'Formats the trigger text when triggerDisplay="count" or "labels". Receives the selected items (value plus resolved label); the count is items.length. Not used by triggerDisplay="badges".',
|
|
111
|
+
},
|
|
106
112
|
{
|
|
107
113
|
name: 'maxBadges',
|
|
108
114
|
type: 'number',
|
|
@@ -284,6 +290,8 @@ export const docsZh = {
|
|
|
284
290
|
placeholder: '未选择值时显示的占位文本。',
|
|
285
291
|
size: '选择器的尺寸变体。',
|
|
286
292
|
triggerDisplay: '在触发器中显示选中项的方式。',
|
|
293
|
+
formatValue:
|
|
294
|
+
'格式化 triggerDisplay="count" 或 "labels" 时的触发器文本。接收选中项(value 及解析后的 label),数量为 items.length。triggerDisplay="badges" 不使用此属性。',
|
|
287
295
|
maxBadges:
|
|
288
296
|
'显示"+N"之前的最大徽章数。仅适用于 triggerDisplay="badges"。',
|
|
289
297
|
hasSelectAll: '是否显示全选复选框。',
|
|
@@ -420,6 +428,8 @@ export const docsDense = {
|
|
|
420
428
|
variant:
|
|
421
429
|
'visual trigger style: input bordered control or ghost toolbar control',
|
|
422
430
|
triggerDisplay: 'how to show selected in trigger',
|
|
431
|
+
formatValue:
|
|
432
|
+
'formats count/labels trigger text; receives selected items',
|
|
423
433
|
maxBadges: 'max badges before "+N"; badges mode only',
|
|
424
434
|
hasSelectAll: 'show select-all checkbox',
|
|
425
435
|
selectAllLabel: 'select-all label',
|