@hjmds/design-contracts 0.8.2
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/README.md +208 -0
- package/dist/alert-dialog.d.ts +102 -0
- package/dist/alert-dialog.d.ts.map +1 -0
- package/dist/alert-dialog.js +136 -0
- package/dist/alert-dialog.js.map +1 -0
- package/dist/base-recipes.d.ts +184 -0
- package/dist/base-recipes.d.ts.map +1 -0
- package/dist/base-recipes.js +129 -0
- package/dist/base-recipes.js.map +1 -0
- package/dist/behaviors.d.ts +1254 -0
- package/dist/behaviors.d.ts.map +1 -0
- package/dist/behaviors.js +972 -0
- package/dist/behaviors.js.map +1 -0
- package/dist/bottom-navigation-defaults.d.ts +13 -0
- package/dist/bottom-navigation-defaults.d.ts.map +1 -0
- package/dist/bottom-navigation-defaults.js +10 -0
- package/dist/bottom-navigation-defaults.js.map +1 -0
- package/dist/bottom-navigation.d.ts +114 -0
- package/dist/bottom-navigation.d.ts.map +1 -0
- package/dist/bottom-navigation.js +223 -0
- package/dist/bottom-navigation.js.map +1 -0
- package/dist/breadcrumb.d.ts +97 -0
- package/dist/breadcrumb.d.ts.map +1 -0
- package/dist/breadcrumb.js +99 -0
- package/dist/breadcrumb.js.map +1 -0
- package/dist/calendar.d.ts +285 -0
- package/dist/calendar.d.ts.map +1 -0
- package/dist/calendar.js +297 -0
- package/dist/calendar.js.map +1 -0
- package/dist/card.d.ts +39 -0
- package/dist/card.d.ts.map +1 -0
- package/dist/card.js +50 -0
- package/dist/card.js.map +1 -0
- package/dist/carousel.d.ts +180 -0
- package/dist/carousel.d.ts.map +1 -0
- package/dist/carousel.js +172 -0
- package/dist/carousel.js.map +1 -0
- package/dist/catalog.d.ts +7158 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +220 -0
- package/dist/catalog.js.map +1 -0
- package/dist/collection.d.ts +123 -0
- package/dist/collection.d.ts.map +1 -0
- package/dist/collection.js +211 -0
- package/dist/collection.js.map +1 -0
- package/dist/color-references.d.ts +46 -0
- package/dist/color-references.d.ts.map +1 -0
- package/dist/color-references.js +39 -0
- package/dist/color-references.js.map +1 -0
- package/dist/colors.d.ts +56 -0
- package/dist/colors.d.ts.map +1 -0
- package/dist/colors.js +90 -0
- package/dist/colors.js.map +1 -0
- package/dist/command-palette.d.ts +240 -0
- package/dist/command-palette.d.ts.map +1 -0
- package/dist/command-palette.js +97 -0
- package/dist/command-palette.js.map +1 -0
- package/dist/component-contracts.d.ts +137 -0
- package/dist/component-contracts.d.ts.map +1 -0
- package/dist/component-contracts.js +53 -0
- package/dist/component-contracts.js.map +1 -0
- package/dist/component-definitions.d.ts +135 -0
- package/dist/component-definitions.d.ts.map +1 -0
- package/dist/component-definitions.js +142 -0
- package/dist/component-definitions.js.map +1 -0
- package/dist/component-recipes.d.ts +3583 -0
- package/dist/component-recipes.d.ts.map +1 -0
- package/dist/component-recipes.js +1503 -0
- package/dist/component-recipes.js.map +1 -0
- package/dist/component-references.d.ts +416 -0
- package/dist/component-references.d.ts.map +1 -0
- package/dist/component-references.js +140 -0
- package/dist/component-references.js.map +1 -0
- package/dist/content-state.d.ts +104 -0
- package/dist/content-state.d.ts.map +1 -0
- package/dist/content-state.js +116 -0
- package/dist/content-state.js.map +1 -0
- package/dist/counter-badge-recipe.d.ts +80 -0
- package/dist/counter-badge-recipe.d.ts.map +1 -0
- package/dist/counter-badge-recipe.js +44 -0
- package/dist/counter-badge-recipe.js.map +1 -0
- package/dist/counter-badge.d.ts +12 -0
- package/dist/counter-badge.d.ts.map +1 -0
- package/dist/counter-badge.js +21 -0
- package/dist/counter-badge.js.map +1 -0
- package/dist/data-table.d.ts +203 -0
- package/dist/data-table.d.ts.map +1 -0
- package/dist/data-table.js +182 -0
- package/dist/data-table.js.map +1 -0
- package/dist/date-picker.d.ts +268 -0
- package/dist/date-picker.d.ts.map +1 -0
- package/dist/date-picker.js +168 -0
- package/dist/date-picker.js.map +1 -0
- package/dist/description-list.d.ts +68 -0
- package/dist/description-list.d.ts.map +1 -0
- package/dist/description-list.js +85 -0
- package/dist/description-list.js.map +1 -0
- package/dist/design-system-provider.d.ts +80 -0
- package/dist/design-system-provider.d.ts.map +1 -0
- package/dist/design-system-provider.js +147 -0
- package/dist/design-system-provider.js.map +1 -0
- package/dist/evidence.d.ts +49 -0
- package/dist/evidence.d.ts.map +1 -0
- package/dist/evidence.js +133 -0
- package/dist/evidence.js.map +1 -0
- package/dist/file-picker.d.ts +183 -0
- package/dist/file-picker.d.ts.map +1 -0
- package/dist/file-picker.js +224 -0
- package/dist/file-picker.js.map +1 -0
- package/dist/floating-action-button.d.ts +143 -0
- package/dist/floating-action-button.d.ts.map +1 -0
- package/dist/floating-action-button.js +149 -0
- package/dist/floating-action-button.js.map +1 -0
- package/dist/form.d.ts +143 -0
- package/dist/form.d.ts.map +1 -0
- package/dist/form.js +206 -0
- package/dist/form.js.map +1 -0
- package/dist/foundations.d.ts +300 -0
- package/dist/foundations.d.ts.map +1 -0
- package/dist/foundations.js +238 -0
- package/dist/foundations.js.map +1 -0
- package/dist/grid.d.ts +75 -0
- package/dist/grid.d.ts.map +1 -0
- package/dist/grid.js +133 -0
- package/dist/grid.js.map +1 -0
- package/dist/icon-button-recipe.d.ts +99 -0
- package/dist/icon-button-recipe.d.ts.map +1 -0
- package/dist/icon-button-recipe.js +53 -0
- package/dist/icon-button-recipe.js.map +1 -0
- package/dist/icon.d.ts +41 -0
- package/dist/icon.d.ts.map +1 -0
- package/dist/icon.js +147 -0
- package/dist/icon.js.map +1 -0
- package/dist/image.d.ts +82 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +100 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +66 -0
- package/dist/index.js.map +1 -0
- package/dist/layout.d.ts +118 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +118 -0
- package/dist/layout.js.map +1 -0
- package/dist/link.d.ts +57 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +142 -0
- package/dist/link.js.map +1 -0
- package/dist/load-more.d.ts +64 -0
- package/dist/load-more.d.ts.map +1 -0
- package/dist/load-more.js +124 -0
- package/dist/load-more.js.map +1 -0
- package/dist/mentions.d.ts +67 -0
- package/dist/mentions.d.ts.map +1 -0
- package/dist/mentions.js +107 -0
- package/dist/mentions.js.map +1 -0
- package/dist/number-field.d.ts +208 -0
- package/dist/number-field.d.ts.map +1 -0
- package/dist/number-field.js +247 -0
- package/dist/number-field.js.map +1 -0
- package/dist/otp-field.d.ts +152 -0
- package/dist/otp-field.d.ts.map +1 -0
- package/dist/otp-field.js +117 -0
- package/dist/otp-field.js.map +1 -0
- package/dist/pagination.d.ts +181 -0
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +226 -0
- package/dist/pagination.js.map +1 -0
- package/dist/password-field.d.ts +187 -0
- package/dist/password-field.d.ts.map +1 -0
- package/dist/password-field.js +120 -0
- package/dist/password-field.js.map +1 -0
- package/dist/popover.d.ts +152 -0
- package/dist/popover.d.ts.map +1 -0
- package/dist/popover.js +137 -0
- package/dist/popover.js.map +1 -0
- package/dist/progress-recipe.d.ts +43 -0
- package/dist/progress-recipe.d.ts.map +1 -0
- package/dist/progress-recipe.js +16 -0
- package/dist/progress-recipe.js.map +1 -0
- package/dist/recipes.d.ts +31 -0
- package/dist/recipes.d.ts.map +1 -0
- package/dist/recipes.js +46 -0
- package/dist/recipes.js.map +1 -0
- package/dist/responsive.d.ts +27 -0
- package/dist/responsive.d.ts.map +1 -0
- package/dist/responsive.js +66 -0
- package/dist/responsive.js.map +1 -0
- package/dist/result.d.ts +111 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +97 -0
- package/dist/result.js.map +1 -0
- package/dist/selection-helpers.d.ts +16 -0
- package/dist/selection-helpers.d.ts.map +1 -0
- package/dist/selection-helpers.js +52 -0
- package/dist/selection-helpers.js.map +1 -0
- package/dist/semantic-colors.d.ts +275 -0
- package/dist/semantic-colors.d.ts.map +1 -0
- package/dist/semantic-colors.js +84 -0
- package/dist/semantic-colors.js.map +1 -0
- package/dist/sheet.d.ts +51 -0
- package/dist/sheet.d.ts.map +1 -0
- package/dist/sheet.js +69 -0
- package/dist/sheet.js.map +1 -0
- package/dist/showcase.d.ts +153 -0
- package/dist/showcase.d.ts.map +1 -0
- package/dist/showcase.js +210 -0
- package/dist/showcase.js.map +1 -0
- package/dist/side-panel.d.ts +199 -0
- package/dist/side-panel.d.ts.map +1 -0
- package/dist/side-panel.js +111 -0
- package/dist/side-panel.js.map +1 -0
- package/dist/slider.d.ts +138 -0
- package/dist/slider.d.ts.map +1 -0
- package/dist/slider.js +150 -0
- package/dist/slider.js.map +1 -0
- package/dist/splitter.d.ts +113 -0
- package/dist/splitter.d.ts.map +1 -0
- package/dist/splitter.js +99 -0
- package/dist/splitter.js.map +1 -0
- package/dist/statistic.d.ts +41 -0
- package/dist/statistic.d.ts.map +1 -0
- package/dist/statistic.js +76 -0
- package/dist/statistic.js.map +1 -0
- package/dist/steps.d.ts +196 -0
- package/dist/steps.d.ts.map +1 -0
- package/dist/steps.js +160 -0
- package/dist/steps.js.map +1 -0
- package/dist/tag.d.ts +128 -0
- package/dist/tag.d.ts.map +1 -0
- package/dist/tag.js +93 -0
- package/dist/tag.js.map +1 -0
- package/dist/timeline.d.ts +147 -0
- package/dist/timeline.d.ts.map +1 -0
- package/dist/timeline.js +127 -0
- package/dist/timeline.js.map +1 -0
- package/dist/toast.d.ts +164 -0
- package/dist/toast.d.ts.map +1 -0
- package/dist/toast.js +529 -0
- package/dist/toast.js.map +1 -0
- package/dist/tokens.d.ts +9 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +9 -0
- package/dist/tokens.js.map +1 -0
- package/dist/tooltip.d.ts +44 -0
- package/dist/tooltip.d.ts.map +1 -0
- package/dist/tooltip.js +88 -0
- package/dist/tooltip.js.map +1 -0
- package/dist/tour.d.ts +218 -0
- package/dist/tour.d.ts.map +1 -0
- package/dist/tour.js +211 -0
- package/dist/tour.js.map +1 -0
- package/dist/transfer-list.d.ts +207 -0
- package/dist/transfer-list.d.ts.map +1 -0
- package/dist/transfer-list.js +193 -0
- package/dist/transfer-list.js.map +1 -0
- package/dist/tree-select.d.ts +78 -0
- package/dist/tree-select.d.ts.map +1 -0
- package/dist/tree-select.js +132 -0
- package/dist/tree-select.js.map +1 -0
- package/dist/tree.d.ts +206 -0
- package/dist/tree.d.ts.map +1 -0
- package/dist/tree.js +223 -0
- package/dist/tree.js.map +1 -0
- package/dist/upload-item.d.ts +173 -0
- package/dist/upload-item.d.ts.map +1 -0
- package/dist/upload-item.js +156 -0
- package/dist/upload-item.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/docs/affix.md +63 -0
- package/docs/anchor.md +59 -0
- package/docs/ant-design-coverage.md +119 -0
- package/docs/app-provider.md +50 -0
- package/docs/app-rn-adoption.md +227 -0
- package/docs/architecture.md +320 -0
- package/docs/authoring-brief.md +87 -0
- package/docs/border-beam.md +66 -0
- package/docs/bottom-navigation.md +127 -0
- package/docs/breadcrumb.md +82 -0
- package/docs/calendar.md +154 -0
- package/docs/carousel.md +130 -0
- package/docs/cascader.md +93 -0
- package/docs/catalog-decision-status.md +306 -0
- package/docs/color-picker.md +72 -0
- package/docs/command-palette.md +116 -0
- package/docs/confirm-popover.md +93 -0
- package/docs/consistency-audit.md +383 -0
- package/docs/consumer-release-gate.md +90 -0
- package/docs/content-state.md +156 -0
- package/docs/context-panel.md +88 -0
- package/docs/cross-platform-core-normalization.md +118 -0
- package/docs/data-table.md +79 -0
- package/docs/date-picker.md +84 -0
- package/docs/description-list.md +75 -0
- package/docs/design-system-provider.md +139 -0
- package/docs/dropdown.md +78 -0
- package/docs/expansion-roadmap.md +285 -0
- package/docs/file-picker.md +59 -0
- package/docs/floating-action-button.md +111 -0
- package/docs/form.md +134 -0
- package/docs/generated/component-maturity.md +103 -0
- package/docs/generated/renderer-evidence.json +5597 -0
- package/docs/generated/renderer-evidence.md +132 -0
- package/docs/generated/showcase-manifest.json +3732 -0
- package/docs/icon.md +22 -0
- package/docs/identity.md +121 -0
- package/docs/image.md +84 -0
- package/docs/implementation-0.5.md +85 -0
- package/docs/layout-primitives.md +107 -0
- package/docs/layout.md +83 -0
- package/docs/library-reference-decisions.md +123 -0
- package/docs/link.md +67 -0
- package/docs/load-more.md +31 -0
- package/docs/mentions.md +82 -0
- package/docs/migration-0.2.md +108 -0
- package/docs/migration-0.3.md +72 -0
- package/docs/migration-0.5.md +82 -0
- package/docs/migration-0.6.md +197 -0
- package/docs/notification.md +54 -0
- package/docs/number-field.md +82 -0
- package/docs/otp-field.md +103 -0
- package/docs/pagination.md +139 -0
- package/docs/password-field.md +98 -0
- package/docs/popover.md +124 -0
- package/docs/promotion-candidates.md +169 -0
- package/docs/qr-code.md +69 -0
- package/docs/rating.md +58 -0
- package/docs/responsive-grid.md +90 -0
- package/docs/result.md +96 -0
- package/docs/showcase.md +97 -0
- package/docs/side-panel.md +71 -0
- package/docs/slider.md +79 -0
- package/docs/splitter.md +55 -0
- package/docs/statistic.md +16 -0
- package/docs/steps.md +115 -0
- package/docs/tag.md +57 -0
- package/docs/time-picker.md +88 -0
- package/docs/timeline.md +146 -0
- package/docs/toast.md +127 -0
- package/docs/tooltip.md +58 -0
- package/docs/tour.md +136 -0
- package/docs/transfer-list.md +94 -0
- package/docs/tree-select.md +101 -0
- package/docs/tree.md +123 -0
- package/docs/upload-item.md +68 -0
- package/docs/utility.md +53 -0
- package/docs/virtual-list.md +70 -0
- package/docs/watermark.md +58 -0
- package/package.json +402 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import type { CheckboxState, CollectionKey, SelectItemDescriptor } from "./behaviors.js";
|
|
2
|
+
/**
|
|
3
|
+
* `docs/transfer-list.md`: two lists sharing one item set, split by whether
|
|
4
|
+
* each id is in `targetKeys`. This is the same shape CheckboxGroup already
|
|
5
|
+
* commits to (`ReadonlySet<Id>`, no third "which panel" enum to keep in
|
|
6
|
+
* sync with membership) — one source of truth instead of two arrays that can
|
|
7
|
+
* drift apart when an item is moved.
|
|
8
|
+
*/
|
|
9
|
+
export type TransferListPanel = "source" | "target";
|
|
10
|
+
export type TransferListDescriptor<Id extends CollectionKey = CollectionKey> = Readonly<{
|
|
11
|
+
items: readonly SelectItemDescriptor<Id>[];
|
|
12
|
+
targetKeys: ReadonlySet<Id>;
|
|
13
|
+
}>;
|
|
14
|
+
export type TransferListSelection<Id extends CollectionKey = CollectionKey> = Readonly<{
|
|
15
|
+
source: ReadonlySet<Id>;
|
|
16
|
+
target: ReadonlySet<Id>;
|
|
17
|
+
}>;
|
|
18
|
+
/** An item already sitting entirely inside one panel (including all-in-target
|
|
19
|
+
* or all-in-source) is valid — nothing here requires either panel to be
|
|
20
|
+
* non-empty, and a disabled item is valid in either panel even though it can
|
|
21
|
+
* never be moved by the UI. */
|
|
22
|
+
export declare function validateTransferListDescriptor<Id extends CollectionKey>(descriptor: TransferListDescriptor<Id>): void;
|
|
23
|
+
export type TransferListPanels<Id extends CollectionKey = CollectionKey> = Readonly<{
|
|
24
|
+
source: readonly SelectItemDescriptor<Id>[];
|
|
25
|
+
target: readonly SelectItemDescriptor<Id>[];
|
|
26
|
+
}>;
|
|
27
|
+
/** Splits by `targetKeys` membership, preserving each item's relative order from `items`. */
|
|
28
|
+
export declare function resolveTransferListPanels<Id extends CollectionKey>(descriptor: TransferListDescriptor<Id>): TransferListPanels<Id>;
|
|
29
|
+
export declare function validateTransferListSelection<Id extends CollectionKey>(descriptor: TransferListDescriptor<Id>, selection: TransferListSelection<Id>): void;
|
|
30
|
+
/** Toggles one row's checkbox within its own panel; the other panel's selection is untouched. */
|
|
31
|
+
export declare function toggleTransferListSelection<Id extends CollectionKey>(descriptor: TransferListDescriptor<Id>, selection: TransferListSelection<Id>, panel: TransferListPanel, id: Id): TransferListSelection<Id>;
|
|
32
|
+
/**
|
|
33
|
+
* Same tri-state `CheckboxState` `resolveDataTableSelectAllState` derives for
|
|
34
|
+
* a table header — disabled rows excluded from both the denominator and the
|
|
35
|
+
* count — generalized from "all rows" to "this one panel's rows".
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveTransferListSelectAllState<Id extends CollectionKey>(descriptor: TransferListDescriptor<Id>, selection: TransferListSelection<Id>, panel: TransferListPanel): CheckboxState;
|
|
38
|
+
/** Mixed-defaults-to-checked, matching `getCheckboxNextState`'s existing convention. */
|
|
39
|
+
export declare function toggleTransferListSelectAll<Id extends CollectionKey>(descriptor: TransferListDescriptor<Id>, selection: TransferListSelection<Id>, panel: TransferListPanel): TransferListSelection<Id>;
|
|
40
|
+
export type TransferListMoveDirection = "toTarget" | "toSource";
|
|
41
|
+
export type TransferListMoveResult<Id extends CollectionKey = CollectionKey> = Readonly<{
|
|
42
|
+
targetKeys: ReadonlySet<Id>;
|
|
43
|
+
selection: TransferListSelection<Id>;
|
|
44
|
+
/** In origin-panel order — the product formats the announcement string from this. */
|
|
45
|
+
movedIds: readonly Id[];
|
|
46
|
+
}>;
|
|
47
|
+
/**
|
|
48
|
+
* Moves every enabled, currently-selected id out of its origin panel.
|
|
49
|
+
* Disabled ids are skipped rather than rejected (a disabled row can never
|
|
50
|
+
* enter a selection through `toggleTransferListSelection`, but a product
|
|
51
|
+
* could hand one to `defaultSelectedKeys`, and skipping mirrors
|
|
52
|
+
* `toggleTreeCheckedSelection`'s disabled guard rather than throwing on it).
|
|
53
|
+
* Moved ids are cleared from the origin panel's selection and deliberately
|
|
54
|
+
* left unselected in the destination panel — a move commits a value, it does
|
|
55
|
+
* not also commit a new pending selection the user did not make.
|
|
56
|
+
*/
|
|
57
|
+
export declare function moveTransferListSelection<Id extends CollectionKey>(descriptor: TransferListDescriptor<Id>, selection: TransferListSelection<Id>, direction: TransferListMoveDirection): TransferListMoveResult<Id>;
|
|
58
|
+
/**
|
|
59
|
+
* Where focus lands once the moved rows are gone from a panel: the item that
|
|
60
|
+
* slid into the first removed row's position, so keyboard users keep moving
|
|
61
|
+
* down the list without their focus jumping to the top or vanishing. `null`
|
|
62
|
+
* means the panel is now empty — the renderer falls back to the panel's
|
|
63
|
+
* empty-state message or the move button, never to the document body.
|
|
64
|
+
*/
|
|
65
|
+
export declare function resolveTransferListFocusAfterMove<Id extends CollectionKey>(remainingPanelItems: readonly SelectItemDescriptor<Id>[], removedIndex: number): Id | null;
|
|
66
|
+
/** Drops selected ids that left either panel (item removed from `items` entirely). */
|
|
67
|
+
export declare function reconcileTransferListSelection<Id extends CollectionKey>(descriptor: TransferListDescriptor<Id>, selection: TransferListSelection<Id>): TransferListSelection<Id>;
|
|
68
|
+
export declare const transferListRecipe: {
|
|
69
|
+
readonly slots: readonly ["root", "panel", "panelHeader", "panelTitle", "panelCount", "list", "item", "itemCheckbox", "emptyState", "moveControls", "moveButton"];
|
|
70
|
+
readonly panel: {
|
|
71
|
+
readonly background: Readonly<{
|
|
72
|
+
source: "theme";
|
|
73
|
+
key: "surface";
|
|
74
|
+
alpha?: number;
|
|
75
|
+
}>;
|
|
76
|
+
readonly border: Readonly<{
|
|
77
|
+
source: "theme";
|
|
78
|
+
key: "border";
|
|
79
|
+
alpha?: number;
|
|
80
|
+
}>;
|
|
81
|
+
readonly borderWidth: 1;
|
|
82
|
+
readonly radius: "md";
|
|
83
|
+
};
|
|
84
|
+
readonly panelHeader: {
|
|
85
|
+
readonly minHeight: 44;
|
|
86
|
+
readonly paddingHorizontal: 12;
|
|
87
|
+
readonly title: {
|
|
88
|
+
readonly color: Readonly<{
|
|
89
|
+
source: "theme";
|
|
90
|
+
key: "textBody";
|
|
91
|
+
alpha?: number;
|
|
92
|
+
}>;
|
|
93
|
+
readonly textVariant: "body";
|
|
94
|
+
};
|
|
95
|
+
readonly count: {
|
|
96
|
+
readonly color: Readonly<{
|
|
97
|
+
source: "theme";
|
|
98
|
+
key: "textMuted";
|
|
99
|
+
alpha?: number;
|
|
100
|
+
}>;
|
|
101
|
+
readonly textVariant: "label";
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
readonly item: {
|
|
105
|
+
readonly minHeight: 44;
|
|
106
|
+
readonly paddingHorizontal: 12;
|
|
107
|
+
readonly gap: 12;
|
|
108
|
+
readonly radius: "md";
|
|
109
|
+
readonly label: {
|
|
110
|
+
readonly color: Readonly<{
|
|
111
|
+
source: "theme";
|
|
112
|
+
key: "textBody";
|
|
113
|
+
alpha?: number;
|
|
114
|
+
}>;
|
|
115
|
+
readonly textVariant: "body";
|
|
116
|
+
};
|
|
117
|
+
readonly description: {
|
|
118
|
+
readonly color: Readonly<{
|
|
119
|
+
source: "theme";
|
|
120
|
+
key: "textMuted";
|
|
121
|
+
alpha?: number;
|
|
122
|
+
}>;
|
|
123
|
+
readonly textVariant: "label";
|
|
124
|
+
};
|
|
125
|
+
readonly highlightedBackground: Readonly<{
|
|
126
|
+
source: "theme";
|
|
127
|
+
key: "text";
|
|
128
|
+
alpha?: number;
|
|
129
|
+
}>;
|
|
130
|
+
readonly focus: {
|
|
131
|
+
readonly color: Readonly<{
|
|
132
|
+
source: "theme";
|
|
133
|
+
key: "contentBrand";
|
|
134
|
+
alpha?: number;
|
|
135
|
+
}>;
|
|
136
|
+
readonly width: 2;
|
|
137
|
+
readonly offset: 2;
|
|
138
|
+
};
|
|
139
|
+
readonly selectedBackground: Readonly<{
|
|
140
|
+
source: "theme";
|
|
141
|
+
key: "primary";
|
|
142
|
+
alpha?: number;
|
|
143
|
+
}>;
|
|
144
|
+
readonly selectedIndicator: Readonly<{
|
|
145
|
+
source: "theme";
|
|
146
|
+
key: "contentBrand";
|
|
147
|
+
alpha?: number;
|
|
148
|
+
}>;
|
|
149
|
+
readonly danger: Readonly<{
|
|
150
|
+
source: "theme";
|
|
151
|
+
key: "danger";
|
|
152
|
+
alpha?: number;
|
|
153
|
+
}>;
|
|
154
|
+
};
|
|
155
|
+
readonly emptyState: {
|
|
156
|
+
readonly color: Readonly<{
|
|
157
|
+
source: "theme";
|
|
158
|
+
key: "textMuted";
|
|
159
|
+
alpha?: number;
|
|
160
|
+
}>;
|
|
161
|
+
readonly textVariant: "label";
|
|
162
|
+
};
|
|
163
|
+
readonly moveControls: {
|
|
164
|
+
readonly gap: 12;
|
|
165
|
+
};
|
|
166
|
+
readonly moveButton: {
|
|
167
|
+
readonly minTarget: 44;
|
|
168
|
+
readonly color: Readonly<{
|
|
169
|
+
source: "theme";
|
|
170
|
+
key: "contentBrand";
|
|
171
|
+
alpha?: number;
|
|
172
|
+
}>;
|
|
173
|
+
readonly disabledOpacity: 0.4;
|
|
174
|
+
};
|
|
175
|
+
readonly states: {
|
|
176
|
+
readonly focus: {
|
|
177
|
+
readonly color: Readonly<{
|
|
178
|
+
source: "theme";
|
|
179
|
+
key: "contentBrand";
|
|
180
|
+
alpha?: number;
|
|
181
|
+
}>;
|
|
182
|
+
readonly width: 2;
|
|
183
|
+
readonly offset: 2;
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
export declare const transferListBehavior: {
|
|
188
|
+
readonly controlled: readonly ["targetKeys", "defaultTargetKeys", "onTargetKeysChange"];
|
|
189
|
+
readonly inputs: readonly ["items"];
|
|
190
|
+
readonly events: readonly ["onMove"];
|
|
191
|
+
readonly stateAxes: {
|
|
192
|
+
readonly availability: readonly ["enabled", "disabled"];
|
|
193
|
+
readonly value: readonly ["empty", "filled", "selected", "mixed"];
|
|
194
|
+
};
|
|
195
|
+
readonly web: {
|
|
196
|
+
readonly roles: readonly ["group", "listbox", "option", "checkbox", "button"];
|
|
197
|
+
readonly keyboard: readonly ["Tab", "Space", "Enter", "ArrowUp", "ArrowDown", "Home", "End"];
|
|
198
|
+
readonly focus: "roving";
|
|
199
|
+
};
|
|
200
|
+
readonly native: {
|
|
201
|
+
readonly roles: readonly ["list", "checkbox", "button"];
|
|
202
|
+
readonly states: readonly ["disabled", "selected", "checked"];
|
|
203
|
+
readonly actions: readonly ["toggle", "toggleSelectAll", "moveSelection", "moveItem"];
|
|
204
|
+
};
|
|
205
|
+
readonly scenarios: readonly ["moving-is-reachable-entirely-by-keyboard-select-with-space-then-activate-the-move-button", "moving-a-single-focused-row-does-not-require-first-opening-multi-select", "focus-after-a-move-lands-on-the-item-that-slid-into-the-removed-rows-position", "focus-after-emptying-a-panel-falls-back-to-its-empty-state-never-lost-to-the-document", "every-move-emits-which-ids-moved-so-the-product-can-announce-a-formatted-sentence", "disabled-items-are-never-selectable-and-never-move", "moved-items-are-cleared-from-the-origin-panels-selection-and-left-unselected-at-the-destination", "select-all-in-a-panel-excludes-disabled-items-from-both-the-denominator-and-the-count", "an-item-with-nothing-selected-in-its-panel-still-supports-direct-single-item-move", "search-and-pagination-inside-a-panel-are-product-composition-not-this-contract"];
|
|
206
|
+
};
|
|
207
|
+
//# sourceMappingURL=transfer-list.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transfer-list.d.ts","sourceRoot":"","sources":["../src/transfer-list.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { getCheckboxNextState, reconcileCheckboxSelection, toggleCheckboxSelection, } from "./selection-helpers.js";
|
|
2
|
+
import { collectionItemContract, focusIndicatorContract } from "./component-contracts.js";
|
|
3
|
+
import { validateCollection } from "./collection.js";
|
|
4
|
+
import { control, radius, spacing, stroke } from "./foundations.js";
|
|
5
|
+
import { semanticColors } from "./semantic-colors.js";
|
|
6
|
+
function withPanelSelection(selection, panel, next) {
|
|
7
|
+
return panel === "source"
|
|
8
|
+
? { source: next, target: selection.target }
|
|
9
|
+
: { source: selection.source, target: next };
|
|
10
|
+
}
|
|
11
|
+
function assertKnownIds(label, ids, knownIds) {
|
|
12
|
+
for (const id of ids) {
|
|
13
|
+
if (!knownIds.has(id)) {
|
|
14
|
+
throw new RangeError(`TransferList ${label} references an unknown item id: ${id}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** An item already sitting entirely inside one panel (including all-in-target
|
|
19
|
+
* or all-in-source) is valid — nothing here requires either panel to be
|
|
20
|
+
* non-empty, and a disabled item is valid in either panel even though it can
|
|
21
|
+
* never be moved by the UI. */
|
|
22
|
+
export function validateTransferListDescriptor(descriptor) {
|
|
23
|
+
validateCollection({ items: descriptor.items });
|
|
24
|
+
const knownIds = new Set(descriptor.items.map((item) => item.id));
|
|
25
|
+
assertKnownIds("targetKeys", descriptor.targetKeys, knownIds);
|
|
26
|
+
}
|
|
27
|
+
/** Splits by `targetKeys` membership, preserving each item's relative order from `items`. */
|
|
28
|
+
export function resolveTransferListPanels(descriptor) {
|
|
29
|
+
validateTransferListDescriptor(descriptor);
|
|
30
|
+
const source = [];
|
|
31
|
+
const target = [];
|
|
32
|
+
for (const item of descriptor.items) {
|
|
33
|
+
(descriptor.targetKeys.has(item.id) ? target : source).push(item);
|
|
34
|
+
}
|
|
35
|
+
return { source, target };
|
|
36
|
+
}
|
|
37
|
+
export function validateTransferListSelection(descriptor, selection) {
|
|
38
|
+
const panels = resolveTransferListPanels(descriptor);
|
|
39
|
+
assertKnownIds("selection.source", selection.source, new Set(panels.source.map((i) => i.id)));
|
|
40
|
+
assertKnownIds("selection.target", selection.target, new Set(panels.target.map((i) => i.id)));
|
|
41
|
+
}
|
|
42
|
+
/** Toggles one row's checkbox within its own panel; the other panel's selection is untouched. */
|
|
43
|
+
export function toggleTransferListSelection(descriptor, selection, panel, id) {
|
|
44
|
+
const panels = resolveTransferListPanels(descriptor);
|
|
45
|
+
const next = toggleCheckboxSelection(panels[panel], selection[panel], id);
|
|
46
|
+
return withPanelSelection(selection, panel, next);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Same tri-state `CheckboxState` `resolveDataTableSelectAllState` derives for
|
|
50
|
+
* a table header — disabled rows excluded from both the denominator and the
|
|
51
|
+
* count — generalized from "all rows" to "this one panel's rows".
|
|
52
|
+
*/
|
|
53
|
+
export function resolveTransferListSelectAllState(descriptor, selection, panel) {
|
|
54
|
+
const items = resolveTransferListPanels(descriptor)[panel];
|
|
55
|
+
const selectable = items.filter((item) => !item.disabled);
|
|
56
|
+
if (selectable.length === 0)
|
|
57
|
+
return false;
|
|
58
|
+
const selectedCount = selectable.filter((item) => selection[panel].has(item.id)).length;
|
|
59
|
+
if (selectedCount === 0)
|
|
60
|
+
return false;
|
|
61
|
+
return selectedCount === selectable.length ? true : "mixed";
|
|
62
|
+
}
|
|
63
|
+
/** Mixed-defaults-to-checked, matching `getCheckboxNextState`'s existing convention. */
|
|
64
|
+
export function toggleTransferListSelectAll(descriptor, selection, panel) {
|
|
65
|
+
const current = resolveTransferListSelectAllState(descriptor, selection, panel);
|
|
66
|
+
const checkAll = getCheckboxNextState(current);
|
|
67
|
+
const items = resolveTransferListPanels(descriptor)[panel];
|
|
68
|
+
const next = checkAll
|
|
69
|
+
? new Set(items.filter((item) => !item.disabled).map((item) => item.id))
|
|
70
|
+
: new Set();
|
|
71
|
+
return withPanelSelection(selection, panel, next);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Moves every enabled, currently-selected id out of its origin panel.
|
|
75
|
+
* Disabled ids are skipped rather than rejected (a disabled row can never
|
|
76
|
+
* enter a selection through `toggleTransferListSelection`, but a product
|
|
77
|
+
* could hand one to `defaultSelectedKeys`, and skipping mirrors
|
|
78
|
+
* `toggleTreeCheckedSelection`'s disabled guard rather than throwing on it).
|
|
79
|
+
* Moved ids are cleared from the origin panel's selection and deliberately
|
|
80
|
+
* left unselected in the destination panel — a move commits a value, it does
|
|
81
|
+
* not also commit a new pending selection the user did not make.
|
|
82
|
+
*/
|
|
83
|
+
export function moveTransferListSelection(descriptor, selection, direction) {
|
|
84
|
+
validateTransferListSelection(descriptor, selection);
|
|
85
|
+
const fromPanel = direction === "toTarget" ? "source" : "target";
|
|
86
|
+
const fromItems = resolveTransferListPanels(descriptor)[fromPanel];
|
|
87
|
+
const movedIds = fromItems
|
|
88
|
+
.filter((item) => !item.disabled && selection[fromPanel].has(item.id))
|
|
89
|
+
.map((item) => item.id);
|
|
90
|
+
if (movedIds.length === 0) {
|
|
91
|
+
return { targetKeys: descriptor.targetKeys, selection, movedIds: [] };
|
|
92
|
+
}
|
|
93
|
+
const movedIdSet = new Set(movedIds);
|
|
94
|
+
const targetKeys = direction === "toTarget"
|
|
95
|
+
? new Set([...descriptor.targetKeys, ...movedIds])
|
|
96
|
+
: new Set([...descriptor.targetKeys].filter((id) => !movedIdSet.has(id)));
|
|
97
|
+
const nextFromSelection = new Set([...selection[fromPanel]].filter((id) => !movedIdSet.has(id)));
|
|
98
|
+
return {
|
|
99
|
+
targetKeys,
|
|
100
|
+
selection: withPanelSelection(selection, fromPanel, nextFromSelection),
|
|
101
|
+
movedIds,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Where focus lands once the moved rows are gone from a panel: the item that
|
|
106
|
+
* slid into the first removed row's position, so keyboard users keep moving
|
|
107
|
+
* down the list without their focus jumping to the top or vanishing. `null`
|
|
108
|
+
* means the panel is now empty — the renderer falls back to the panel's
|
|
109
|
+
* empty-state message or the move button, never to the document body.
|
|
110
|
+
*/
|
|
111
|
+
export function resolveTransferListFocusAfterMove(remainingPanelItems, removedIndex) {
|
|
112
|
+
if (remainingPanelItems.length === 0)
|
|
113
|
+
return null;
|
|
114
|
+
const clamped = Math.min(Math.max(removedIndex, 0), remainingPanelItems.length - 1);
|
|
115
|
+
return remainingPanelItems[clamped].id;
|
|
116
|
+
}
|
|
117
|
+
/** Drops selected ids that left either panel (item removed from `items` entirely). */
|
|
118
|
+
export function reconcileTransferListSelection(descriptor, selection) {
|
|
119
|
+
const panels = resolveTransferListPanels(descriptor);
|
|
120
|
+
const source = reconcileCheckboxSelection(panels.source, selection.source);
|
|
121
|
+
const target = reconcileCheckboxSelection(panels.target, selection.target);
|
|
122
|
+
if (source === selection.source && target === selection.target)
|
|
123
|
+
return selection;
|
|
124
|
+
return { source, target };
|
|
125
|
+
}
|
|
126
|
+
export const transferListRecipe = {
|
|
127
|
+
slots: [
|
|
128
|
+
"root",
|
|
129
|
+
"panel",
|
|
130
|
+
"panelHeader",
|
|
131
|
+
"panelTitle",
|
|
132
|
+
"panelCount",
|
|
133
|
+
"list",
|
|
134
|
+
"item",
|
|
135
|
+
"itemCheckbox",
|
|
136
|
+
"emptyState",
|
|
137
|
+
"moveControls",
|
|
138
|
+
"moveButton",
|
|
139
|
+
],
|
|
140
|
+
panel: {
|
|
141
|
+
background: semanticColors.surface.default,
|
|
142
|
+
border: semanticColors.border.default,
|
|
143
|
+
borderWidth: stroke.default,
|
|
144
|
+
radius: "md",
|
|
145
|
+
},
|
|
146
|
+
panelHeader: {
|
|
147
|
+
minHeight: 44,
|
|
148
|
+
paddingHorizontal: spacing.sm,
|
|
149
|
+
title: { color: semanticColors.content.body, textVariant: "body" },
|
|
150
|
+
count: { color: semanticColors.content.secondary, textVariant: "label" },
|
|
151
|
+
},
|
|
152
|
+
item: collectionItemContract,
|
|
153
|
+
emptyState: { color: semanticColors.content.secondary, textVariant: "label" },
|
|
154
|
+
moveControls: { gap: spacing.sm },
|
|
155
|
+
moveButton: {
|
|
156
|
+
minTarget: control.minTouchTarget,
|
|
157
|
+
color: semanticColors.content.brand,
|
|
158
|
+
disabledOpacity: 0.4,
|
|
159
|
+
},
|
|
160
|
+
states: { focus: focusIndicatorContract },
|
|
161
|
+
};
|
|
162
|
+
export const transferListBehavior = {
|
|
163
|
+
controlled: ["targetKeys", "defaultTargetKeys", "onTargetKeysChange"],
|
|
164
|
+
inputs: ["items"],
|
|
165
|
+
events: ["onMove"],
|
|
166
|
+
stateAxes: {
|
|
167
|
+
availability: ["enabled", "disabled"],
|
|
168
|
+
value: ["empty", "filled", "selected", "mixed"],
|
|
169
|
+
},
|
|
170
|
+
web: {
|
|
171
|
+
roles: ["group", "listbox", "option", "checkbox", "button"],
|
|
172
|
+
keyboard: ["Tab", "Space", "Enter", "ArrowUp", "ArrowDown", "Home", "End"],
|
|
173
|
+
focus: "roving",
|
|
174
|
+
},
|
|
175
|
+
native: {
|
|
176
|
+
roles: ["list", "checkbox", "button"],
|
|
177
|
+
states: ["disabled", "selected", "checked"],
|
|
178
|
+
actions: ["toggle", "toggleSelectAll", "moveSelection", "moveItem"],
|
|
179
|
+
},
|
|
180
|
+
scenarios: [
|
|
181
|
+
"moving-is-reachable-entirely-by-keyboard-select-with-space-then-activate-the-move-button",
|
|
182
|
+
"moving-a-single-focused-row-does-not-require-first-opening-multi-select",
|
|
183
|
+
"focus-after-a-move-lands-on-the-item-that-slid-into-the-removed-rows-position",
|
|
184
|
+
"focus-after-emptying-a-panel-falls-back-to-its-empty-state-never-lost-to-the-document",
|
|
185
|
+
"every-move-emits-which-ids-moved-so-the-product-can-announce-a-formatted-sentence",
|
|
186
|
+
"disabled-items-are-never-selectable-and-never-move",
|
|
187
|
+
"moved-items-are-cleared-from-the-origin-panels-selection-and-left-unselected-at-the-destination",
|
|
188
|
+
"select-all-in-a-panel-excludes-disabled-items-from-both-the-denominator-and-the-count",
|
|
189
|
+
"an-item-with-nothing-selected-in-its-panel-still-supports-direct-single-item-move",
|
|
190
|
+
"search-and-pagination-inside-a-panel-are-product-composition-not-this-contract",
|
|
191
|
+
],
|
|
192
|
+
};
|
|
193
|
+
//# sourceMappingURL=transfer-list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transfer-list.js","sourceRoot":"","sources":["../src/transfer-list.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ColorReference } from \"./color-references.js\";\nimport type {\n BehaviorContract,\n CheckboxState,\n CollectionKey,\n SelectItemDescriptor,\n} from \"./behaviors.js\";\nimport {\n getCheckboxNextState,\n reconcileCheckboxSelection,\n toggleCheckboxSelection,\n} from \"./selection-helpers.js\";\nimport { collectionItemContract, focusIndicatorContract } from \"./component-contracts.js\";\nimport { validateCollection } from \"./collection.js\";\nimport { control, radius, spacing, stroke } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\n\n/**\n * `docs/transfer-list.md`: two lists sharing one item set, split by whether\n * each id is in `targetKeys`. This is the same shape CheckboxGroup already\n * commits to (`ReadonlySet<Id>`, no third \"which panel\" enum to keep in\n * sync with membership) — one source of truth instead of two arrays that can\n * drift apart when an item is moved.\n */\nexport type TransferListPanel = \"source\" | \"target\";\n\nexport type TransferListDescriptor<Id extends CollectionKey = CollectionKey> = Readonly<{\n items: readonly SelectItemDescriptor<Id>[];\n targetKeys: ReadonlySet<Id>;\n}>;\n\nexport type TransferListSelection<Id extends CollectionKey = CollectionKey> = Readonly<{\n source: ReadonlySet<Id>;\n target: ReadonlySet<Id>;\n}>;\n\nfunction withPanelSelection<Id extends CollectionKey>(\n selection: TransferListSelection<Id>,\n panel: TransferListPanel,\n next: ReadonlySet<Id>,\n): TransferListSelection<Id> {\n return panel === \"source\"\n ? { source: next, target: selection.target }\n : { source: selection.source, target: next };\n}\n\nfunction assertKnownIds<Id extends CollectionKey>(\n label: string,\n ids: ReadonlySet<Id>,\n knownIds: ReadonlySet<Id>,\n): void {\n for (const id of ids) {\n if (!knownIds.has(id)) {\n throw new RangeError(`TransferList ${label} references an unknown item id: ${id}`);\n }\n }\n}\n\n/** An item already sitting entirely inside one panel (including all-in-target\n * or all-in-source) is valid — nothing here requires either panel to be\n * non-empty, and a disabled item is valid in either panel even though it can\n * never be moved by the UI. */\nexport function validateTransferListDescriptor<Id extends CollectionKey>(\n descriptor: TransferListDescriptor<Id>,\n): void {\n validateCollection({ items: descriptor.items });\n const knownIds = new Set(descriptor.items.map((item) => item.id));\n assertKnownIds(\"targetKeys\", descriptor.targetKeys, knownIds);\n}\n\nexport type TransferListPanels<Id extends CollectionKey = CollectionKey> = Readonly<{\n source: readonly SelectItemDescriptor<Id>[];\n target: readonly SelectItemDescriptor<Id>[];\n}>;\n\n/** Splits by `targetKeys` membership, preserving each item's relative order from `items`. */\nexport function resolveTransferListPanels<Id extends CollectionKey>(\n descriptor: TransferListDescriptor<Id>,\n): TransferListPanels<Id> {\n validateTransferListDescriptor(descriptor);\n const source: SelectItemDescriptor<Id>[] = [];\n const target: SelectItemDescriptor<Id>[] = [];\n for (const item of descriptor.items) {\n (descriptor.targetKeys.has(item.id) ? target : source).push(item);\n }\n return { source, target };\n}\n\nexport function validateTransferListSelection<Id extends CollectionKey>(\n descriptor: TransferListDescriptor<Id>,\n selection: TransferListSelection<Id>,\n): void {\n const panels = resolveTransferListPanels(descriptor);\n assertKnownIds(\"selection.source\", selection.source, new Set(panels.source.map((i) => i.id)));\n assertKnownIds(\"selection.target\", selection.target, new Set(panels.target.map((i) => i.id)));\n}\n\n/** Toggles one row's checkbox within its own panel; the other panel's selection is untouched. */\nexport function toggleTransferListSelection<Id extends CollectionKey>(\n descriptor: TransferListDescriptor<Id>,\n selection: TransferListSelection<Id>,\n panel: TransferListPanel,\n id: Id,\n): TransferListSelection<Id> {\n const panels = resolveTransferListPanels(descriptor);\n const next = toggleCheckboxSelection(panels[panel], selection[panel], id);\n return withPanelSelection(selection, panel, next);\n}\n\n/**\n * Same tri-state `CheckboxState` `resolveDataTableSelectAllState` derives for\n * a table header — disabled rows excluded from both the denominator and the\n * count — generalized from \"all rows\" to \"this one panel's rows\".\n */\nexport function resolveTransferListSelectAllState<Id extends CollectionKey>(\n descriptor: TransferListDescriptor<Id>,\n selection: TransferListSelection<Id>,\n panel: TransferListPanel,\n): CheckboxState {\n const items = resolveTransferListPanels(descriptor)[panel];\n const selectable = items.filter((item) => !item.disabled);\n if (selectable.length === 0) return false;\n const selectedCount = selectable.filter((item) => selection[panel].has(item.id)).length;\n if (selectedCount === 0) return false;\n return selectedCount === selectable.length ? true : \"mixed\";\n}\n\n/** Mixed-defaults-to-checked, matching `getCheckboxNextState`'s existing convention. */\nexport function toggleTransferListSelectAll<Id extends CollectionKey>(\n descriptor: TransferListDescriptor<Id>,\n selection: TransferListSelection<Id>,\n panel: TransferListPanel,\n): TransferListSelection<Id> {\n const current = resolveTransferListSelectAllState(descriptor, selection, panel);\n const checkAll = getCheckboxNextState(current);\n const items = resolveTransferListPanels(descriptor)[panel];\n const next = checkAll\n ? new Set(items.filter((item) => !item.disabled).map((item) => item.id))\n : new Set<Id>();\n return withPanelSelection(selection, panel, next);\n}\n\nexport type TransferListMoveDirection = \"toTarget\" | \"toSource\";\n\nexport type TransferListMoveResult<Id extends CollectionKey = CollectionKey> = Readonly<{\n targetKeys: ReadonlySet<Id>;\n selection: TransferListSelection<Id>;\n /** In origin-panel order — the product formats the announcement string from this. */\n movedIds: readonly Id[];\n}>;\n\n/**\n * Moves every enabled, currently-selected id out of its origin panel.\n * Disabled ids are skipped rather than rejected (a disabled row can never\n * enter a selection through `toggleTransferListSelection`, but a product\n * could hand one to `defaultSelectedKeys`, and skipping mirrors\n * `toggleTreeCheckedSelection`'s disabled guard rather than throwing on it).\n * Moved ids are cleared from the origin panel's selection and deliberately\n * left unselected in the destination panel — a move commits a value, it does\n * not also commit a new pending selection the user did not make.\n */\nexport function moveTransferListSelection<Id extends CollectionKey>(\n descriptor: TransferListDescriptor<Id>,\n selection: TransferListSelection<Id>,\n direction: TransferListMoveDirection,\n): TransferListMoveResult<Id> {\n validateTransferListSelection(descriptor, selection);\n const fromPanel: TransferListPanel = direction === \"toTarget\" ? \"source\" : \"target\";\n const fromItems = resolveTransferListPanels(descriptor)[fromPanel];\n const movedIds = fromItems\n .filter((item) => !item.disabled && selection[fromPanel].has(item.id))\n .map((item) => item.id);\n\n if (movedIds.length === 0) {\n return { targetKeys: descriptor.targetKeys, selection, movedIds: [] };\n }\n\n const movedIdSet = new Set(movedIds);\n const targetKeys =\n direction === \"toTarget\"\n ? new Set([...descriptor.targetKeys, ...movedIds])\n : new Set([...descriptor.targetKeys].filter((id) => !movedIdSet.has(id)));\n\n const nextFromSelection = new Set(\n [...selection[fromPanel]].filter((id) => !movedIdSet.has(id)),\n );\n return {\n targetKeys,\n selection: withPanelSelection(selection, fromPanel, nextFromSelection),\n movedIds,\n };\n}\n\n/**\n * Where focus lands once the moved rows are gone from a panel: the item that\n * slid into the first removed row's position, so keyboard users keep moving\n * down the list without their focus jumping to the top or vanishing. `null`\n * means the panel is now empty — the renderer falls back to the panel's\n * empty-state message or the move button, never to the document body.\n */\nexport function resolveTransferListFocusAfterMove<Id extends CollectionKey>(\n remainingPanelItems: readonly SelectItemDescriptor<Id>[],\n removedIndex: number,\n): Id | null {\n if (remainingPanelItems.length === 0) return null;\n const clamped = Math.min(Math.max(removedIndex, 0), remainingPanelItems.length - 1);\n return remainingPanelItems[clamped]!.id;\n}\n\n/** Drops selected ids that left either panel (item removed from `items` entirely). */\nexport function reconcileTransferListSelection<Id extends CollectionKey>(\n descriptor: TransferListDescriptor<Id>,\n selection: TransferListSelection<Id>,\n): TransferListSelection<Id> {\n const panels = resolveTransferListPanels(descriptor);\n const source = reconcileCheckboxSelection(panels.source, selection.source);\n const target = reconcileCheckboxSelection(panels.target, selection.target);\n if (source === selection.source && target === selection.target) return selection;\n return { source, target };\n}\n\nexport const transferListRecipe = {\n slots: [\n \"root\",\n \"panel\",\n \"panelHeader\",\n \"panelTitle\",\n \"panelCount\",\n \"list\",\n \"item\",\n \"itemCheckbox\",\n \"emptyState\",\n \"moveControls\",\n \"moveButton\",\n ] as const,\n panel: {\n background: semanticColors.surface.default,\n border: semanticColors.border.default,\n borderWidth: stroke.default,\n radius: \"md\" as const,\n },\n panelHeader: {\n minHeight: 44,\n paddingHorizontal: spacing.sm,\n title: { color: semanticColors.content.body, textVariant: \"body\" as const },\n count: { color: semanticColors.content.secondary, textVariant: \"label\" as const },\n },\n item: collectionItemContract,\n emptyState: { color: semanticColors.content.secondary, textVariant: \"label\" as const },\n moveControls: { gap: spacing.sm },\n moveButton: {\n minTarget: control.minTouchTarget,\n color: semanticColors.content.brand,\n disabledOpacity: 0.4,\n },\n states: { focus: focusIndicatorContract },\n} as const satisfies {\n slots: readonly [\n \"root\",\n \"panel\",\n \"panelHeader\",\n \"panelTitle\",\n \"panelCount\",\n \"list\",\n \"item\",\n \"itemCheckbox\",\n \"emptyState\",\n \"moveControls\",\n \"moveButton\",\n ];\n panel: { background: ColorReference; border: ColorReference; borderWidth: number; radius: keyof typeof radius };\n panelHeader: {\n minHeight: number;\n paddingHorizontal: number;\n title: { color: ColorReference; textVariant: \"body\" };\n count: { color: ColorReference; textVariant: \"label\" };\n };\n item: typeof collectionItemContract;\n emptyState: { color: ColorReference; textVariant: \"label\" };\n moveControls: { gap: number };\n moveButton: { minTarget: number; color: ColorReference; disabledOpacity: number };\n states: { focus: typeof focusIndicatorContract };\n};\n\nexport const transferListBehavior = {\n controlled: [\"targetKeys\", \"defaultTargetKeys\", \"onTargetKeysChange\"],\n inputs: [\"items\"],\n events: [\"onMove\"],\n stateAxes: {\n availability: [\"enabled\", \"disabled\"],\n value: [\"empty\", \"filled\", \"selected\", \"mixed\"],\n },\n web: {\n roles: [\"group\", \"listbox\", \"option\", \"checkbox\", \"button\"],\n keyboard: [\"Tab\", \"Space\", \"Enter\", \"ArrowUp\", \"ArrowDown\", \"Home\", \"End\"],\n focus: \"roving\",\n },\n native: {\n roles: [\"list\", \"checkbox\", \"button\"],\n states: [\"disabled\", \"selected\", \"checked\"],\n actions: [\"toggle\", \"toggleSelectAll\", \"moveSelection\", \"moveItem\"],\n },\n scenarios: [\n \"moving-is-reachable-entirely-by-keyboard-select-with-space-then-activate-the-move-button\",\n \"moving-a-single-focused-row-does-not-require-first-opening-multi-select\",\n \"focus-after-a-move-lands-on-the-item-that-slid-into-the-removed-rows-position\",\n \"focus-after-emptying-a-panel-falls-back-to-its-empty-state-never-lost-to-the-document\",\n \"every-move-emits-which-ids-moved-so-the-product-can-announce-a-formatted-sentence\",\n \"disabled-items-are-never-selectable-and-never-move\",\n \"moved-items-are-cleared-from-the-origin-panels-selection-and-left-unselected-at-the-destination\",\n \"select-all-in-a-panel-excludes-disabled-items-from-both-the-denominator-and-the-count\",\n \"an-item-with-nothing-selected-in-its-panel-still-supports-direct-single-item-move\",\n \"search-and-pagination-inside-a-panel-are-product-composition-not-this-contract\",\n ],\n} as const satisfies BehaviorContract;\n"]}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { CheckboxState } from "./behaviors.js";
|
|
2
|
+
import { type TreeNodeDescriptor } from "./tree.js";
|
|
3
|
+
/**
|
|
4
|
+
* `docs/tree.md`'s own "TreeSelect 판정" concludes TreeSelect is Select's
|
|
5
|
+
* surface (trigger + popup/sheet, committed value) plus Tree's collection
|
|
6
|
+
* (depth/sibling announcement, arrow-key expand/collapse, `expandedKeys`
|
|
7
|
+
* reconciliation) — no third primitive. That verdict is right for
|
|
8
|
+
* **single-select** TreeSelect: picking exactly one node needs nothing
|
|
9
|
+
* beyond Select's existing `selectedKey: Id | null` and Tree's flat item
|
|
10
|
+
* list, because there is nothing to aggregate.
|
|
11
|
+
*
|
|
12
|
+
* It is incomplete for **multiple-select** TreeSelect (checkboxes at every
|
|
13
|
+
* depth). Neither file solves "a parent's checkbox must show a third,
|
|
14
|
+
* aggregate state when only some of its descendant leaves are checked" —
|
|
15
|
+
* that is not a new selection model, it is `resolveDataTableSelectAllState`
|
|
16
|
+
* (`src/data-table.ts`) generalized from one level (header ↔ rows) to
|
|
17
|
+
* recursive depth (parent ↔ every descendant leaf), reusing the exact same
|
|
18
|
+
* `CheckboxState` (`boolean | "mixed"`) the header checkbox already renders.
|
|
19
|
+
* This module is that one missing piece — nothing else.
|
|
20
|
+
*/
|
|
21
|
+
export type TreeCheckedKeys<Id extends string = string> = ReadonlySet<Id>;
|
|
22
|
+
/**
|
|
23
|
+
* A parent's own id is never meaningfully "in" the checked set — its
|
|
24
|
+
* checked/unchecked/mixed state is always derived from its descendant
|
|
25
|
+
* leaves, the same way a DataTable header is never itself a row in
|
|
26
|
+
* `selectedKeys`. Storing a parent id here would be ambiguous (does it mean
|
|
27
|
+
* "all children" or "just this category, independent of children"?) with no
|
|
28
|
+
* measured product need to resolve that ambiguity, so it is rejected instead
|
|
29
|
+
* of guessed at.
|
|
30
|
+
*/
|
|
31
|
+
export declare function validateTreeCheckedSelection<Id extends string>(nodes: readonly TreeNodeDescriptor<Id>[], checkedKeys: TreeCheckedKeys<Id>): void;
|
|
32
|
+
/**
|
|
33
|
+
* One depth-first pass computing every node's tri-state at once — the same
|
|
34
|
+
* "resolve the whole tree once, filter afterward" shape `resolveTreeDescriptor`
|
|
35
|
+
* already uses for depth/position, rather than re-walking per node.
|
|
36
|
+
*
|
|
37
|
+
* A parent's aggregate is computed from *enabled descendant leaf coverage*,
|
|
38
|
+
* exactly generalizing `resolveDataTableSelectAllState`'s "disabled rows are
|
|
39
|
+
* excluded from both the denominator and the count." A disabled leaf keeps
|
|
40
|
+
* its own literal `checkedKeys.has(id)` as its individual displayed state
|
|
41
|
+
* (a disabled checkbox can still visibly be checked), but never counts
|
|
42
|
+
* toward any ancestor's true/false/mixed — otherwise a single
|
|
43
|
+
* disabled-and-unchecked leaf would permanently pin every ancestor to
|
|
44
|
+
* "mixed" even once all of its enabled siblings are checked.
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveTreeCheckedStates<Id extends string>(nodes: readonly TreeNodeDescriptor<Id>[], checkedKeys: TreeCheckedKeys<Id>): ReadonlyMap<Id, CheckboxState>;
|
|
47
|
+
/**
|
|
48
|
+
* Checking or unchecking any node (leaf or parent) sets every enabled
|
|
49
|
+
* descendant leaf to the opposite of "fully checked" — the same
|
|
50
|
+
* mixed-defaults-to-checked convention `getCheckboxNextState` already uses
|
|
51
|
+
* for a single aggregate Checkbox (`src/behaviors.ts`). Disabled leaves are
|
|
52
|
+
* left untouched, mirroring `toggleCheckboxSelection`'s own disabled guard.
|
|
53
|
+
* This is "the rule where selecting a parent selects the children together,"
|
|
54
|
+
* made unambiguous by the fact that parents are never independently stored.
|
|
55
|
+
*/
|
|
56
|
+
export declare function toggleTreeCheckedSelection<Id extends string>(nodes: readonly TreeNodeDescriptor<Id>[], checkedKeys: TreeCheckedKeys<Id>, targetId: Id): TreeCheckedKeys<Id>;
|
|
57
|
+
/**
|
|
58
|
+
* Drops leaf ids that left the tree or stopped being leaves — the same
|
|
59
|
+
* shape as `reconcileCheckboxSelection`/`reconcileTreeExpansion`. Returns the
|
|
60
|
+
* same reference when nothing changed.
|
|
61
|
+
*/
|
|
62
|
+
export declare function reconcileTreeCheckedSelection<Id extends string>(nodes: readonly TreeNodeDescriptor<Id>[], checkedKeys: TreeCheckedKeys<Id>): TreeCheckedKeys<Id>;
|
|
63
|
+
/**
|
|
64
|
+
* No new recipe file: the surface composes three *existing* recipes
|
|
65
|
+
* unchanged — Select's trigger/frame (`selectRecipe`), Tree's row anatomy
|
|
66
|
+
* (`treeRecipe.node`/`.toggle`/`.indentPerLevel`) for each popup row, and the
|
|
67
|
+
* existing Checkbox mark for the tri-state indicator this module derives.
|
|
68
|
+
* Declaring a fourth "treeSelectRecipe" would just alias tokens the other
|
|
69
|
+
* three already own.
|
|
70
|
+
*
|
|
71
|
+
* No new behaviorRegistry entry either: TreeSelect's open/dismiss reasons,
|
|
72
|
+
* roles, and keyboard model are `behaviorRegistry.select` verbatim — this
|
|
73
|
+
* module only adds the functions above, which a Select+Tree renderer calls
|
|
74
|
+
* to compute per-row checkbox state and to interpret a row's checkbox
|
|
75
|
+
* activation.
|
|
76
|
+
*/
|
|
77
|
+
export declare const treeSelectBehaviorScenarios: readonly ["single-select-mode-needs-none-of-this-module-selectedkey-and-flat-items-are-enough", "checking-an-unchecked-parent-checks-every-enabled-descendant-leaf", "unchecking-a-fully-checked-parent-unchecks-every-enabled-descendant-leaf", "a-parent-with-some-but-not-all-descendant-leaves-checked-renders-mixed", "disabled-leaves-are-skipped-by-cascade-toggle-but-still-counted-by-derivation", "checkedkeys-containing-a-parent-or-unknown-id-is-rejected-not-silently-ignored", "removing-a-checked-leaf-from-the-tree-reconciles-it-out-without-touching-siblings"];
|
|
78
|
+
//# sourceMappingURL=tree-select.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-select.d.ts","sourceRoot":"","sources":["../src/tree-select.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAuC,KAAK,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAEzF;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,eAAe,CAAC,EAAE,SAAS,MAAM,GAAG,MAAM,IAAI,WAAW,CAAC,EAAE,CAAC,CAAC;AAY1E;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAAC,EAAE,SAAS,MAAM,EAC5D,KAAK,EAAE,SAAS,kBAAkB,CAAC,EAAE,CAAC,EAAE,EACxC,WAAW,EAAE,eAAe,CAAC,EAAE,CAAC,GAC/B,IAAI,CAUN;AAaD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,SAAS,MAAM,EACxD,KAAK,EAAE,SAAS,kBAAkB,CAAC,EAAE,CAAC,EAAE,EACxC,WAAW,EAAE,eAAe,CAAC,EAAE,CAAC,GAC/B,WAAW,CAAC,EAAE,EAAE,aAAa,CAAC,CAoBhC;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,EAAE,SAAS,MAAM,EAC1D,KAAK,EAAE,SAAS,kBAAkB,CAAC,EAAE,CAAC,EAAE,EACxC,WAAW,EAAE,eAAe,CAAC,EAAE,CAAC,EAChC,QAAQ,EAAE,EAAE,GACX,eAAe,CAAC,EAAE,CAAC,CAqBrB;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,EAAE,SAAS,MAAM,EAC7D,KAAK,EAAE,SAAS,kBAAkB,CAAC,EAAE,CAAC,EAAE,EACxC,WAAW,EAAE,eAAe,CAAC,EAAE,CAAC,GAC/B,eAAe,CAAC,EAAE,CAAC,CAKrB;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,2BAA2B,ojBAQ9B,CAAC"}
|