@proyecto-viviana/solid-stately 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/calendar/IncompleteDate.d.ts +61 -0
  2. package/dist/calendar/createCalendarState.d.ts +20 -10
  3. package/dist/calendar/createDateFieldState.d.ts +65 -20
  4. package/dist/calendar/createRangeCalendarState.d.ts +20 -2
  5. package/dist/calendar/createTimeFieldState.d.ts +38 -67
  6. package/dist/calendar/index.d.ts +1 -1
  7. package/dist/calendar/intl/index.d.ts +10 -0
  8. package/dist/collections/ListCollection.d.ts +8 -0
  9. package/dist/collections/createListState.d.ts +16 -1
  10. package/dist/collections/createSelectionState.d.ts +17 -5
  11. package/dist/collections/getChildNodes.d.ts +19 -0
  12. package/dist/collections/index.d.ts +3 -2
  13. package/dist/collections/types.d.ts +9 -0
  14. package/dist/color/intl/index.d.ts +14 -0
  15. package/dist/combobox/createComboBoxState.d.ts +7 -0
  16. package/dist/dnd/createDroppableCollectionState.d.ts +17 -2
  17. package/dist/dnd/index.d.ts +1 -1
  18. package/dist/dnd/types.d.ts +3 -1
  19. package/dist/grid/types.d.ts +17 -3
  20. package/dist/index.d.ts +6 -6
  21. package/dist/index.js +4372 -1087
  22. package/dist/index.js.map +1 -1
  23. package/dist/select/createSelectState.d.ts +6 -0
  24. package/dist/selection/Selection.d.ts +16 -0
  25. package/dist/selection/SelectionManager.d.ts +119 -0
  26. package/dist/selection/createMultipleSelectionState.d.ts +59 -0
  27. package/dist/selection/index.d.ts +1 -0
  28. package/dist/table/TableCollection.d.ts +30 -0
  29. package/dist/table/createTreeGridState.d.ts +15 -0
  30. package/dist/table/index.d.ts +2 -1
  31. package/dist/table/types.d.ts +58 -0
  32. package/dist/toast/createToastState.d.ts +16 -0
  33. package/dist/toast/index.d.ts +1 -1
  34. package/dist/tree/types.d.ts +14 -6
  35. package/package.json +1 -1
  36. package/src/calendar/IncompleteDate.ts +430 -0
  37. package/src/calendar/createCalendarState.ts +129 -40
  38. package/src/calendar/createDateFieldState.ts +633 -477
  39. package/src/calendar/createRangeCalendarState.ts +343 -30
  40. package/src/calendar/createTimeFieldState.ts +179 -467
  41. package/src/calendar/index.ts +2 -0
  42. package/src/calendar/intl/index.ts +364 -0
  43. package/src/collections/ListCollection.ts +31 -0
  44. package/src/collections/createListState.ts +139 -17
  45. package/src/collections/createMenuState.ts +3 -0
  46. package/src/collections/createSelectionState.ts +38 -28
  47. package/src/collections/getChildNodes.ts +129 -0
  48. package/src/collections/index.ts +12 -0
  49. package/src/collections/types.ts +10 -0
  50. package/src/color/Color.ts +104 -133
  51. package/src/color/intl/index.ts +1409 -0
  52. package/src/combobox/createComboBoxState.ts +38 -13
  53. package/src/dnd/createDroppableCollectionState.ts +125 -6
  54. package/src/dnd/index.d.ts +1 -0
  55. package/src/dnd/index.ts +1 -0
  56. package/src/dnd/types.d.ts +3 -1
  57. package/src/dnd/types.ts +4 -1
  58. package/src/grid/createGridState.ts +36 -2
  59. package/src/grid/types.ts +17 -3
  60. package/src/index.d.ts +1 -0
  61. package/src/index.ts +14 -0
  62. package/src/select/createSelectState.ts +15 -2
  63. package/src/selection/Selection.ts +28 -0
  64. package/src/selection/SelectionManager.ts +541 -0
  65. package/src/selection/createMultipleSelectionState.ts +247 -0
  66. package/src/selection/index.ts +6 -0
  67. package/src/steplist/createStepListState.ts +27 -4
  68. package/src/table/TableCollection.ts +213 -43
  69. package/src/table/createTableState.ts +20 -0
  70. package/src/table/createTreeGridState.ts +146 -0
  71. package/src/table/index.ts +3 -0
  72. package/src/table/types.ts +66 -0
  73. package/src/tabs/createTabListState.ts +11 -9
  74. package/src/toast/createToastState.ts +43 -10
  75. package/src/toast/index.ts +1 -0
  76. package/src/tree/createTreeState.ts +37 -6
  77. package/src/tree/types.ts +14 -6
@@ -2,6 +2,8 @@ export {
2
2
  createCalendarState,
3
3
  type CalendarStateProps,
4
4
  type CalendarState,
5
+ type CalendarSelectionMode,
6
+ type CalendarValueType,
5
7
  type ValidationState,
6
8
  type CalendarPageBehavior,
7
9
  type CalendarSelectionAlignment,
@@ -0,0 +1,364 @@
1
+ // DateField validation-message catalog, ported verbatim from @react-stately/datepicker's
2
+ // intl/*.json (34 locales). Keep in sync with the vendored react-spectrum source on every
3
+ // pin bump. Hand-rolled resolver so solid-stately needs no @internationalized/string dep.
4
+
5
+ export type DateFieldErrorKey =
6
+ | "rangeUnderflow"
7
+ | "rangeOverflow"
8
+ | "rangeReversed"
9
+ | "unavailableDate";
10
+
11
+ type DateFieldErrorArgs = { minValue?: string; maxValue?: string };
12
+ type DateFieldErrorMessage = string | ((args: DateFieldErrorArgs) => string);
13
+
14
+ const errorStrings: Record<string, Record<DateFieldErrorKey, DateFieldErrorMessage>> = {
15
+ "ar-AE": {
16
+ "rangeOverflow": (args)=>`\u{64A}\u{62C}\u{628} \u{623}\u{646} \u{62A}\u{643}\u{648}\u{646} \u{627}\u{644}\u{642}\u{64A}\u{645}\u{629} ${args.maxValue} \u{623}\u{648} \u{642}\u{628}\u{644} \u{630}\u{644}\u{643}.`,
17
+ "rangeReversed": `\u{62A}\u{627}\u{631}\u{64A}\u{62E} \u{627}\u{644}\u{628}\u{62F}\u{621} \u{64A}\u{62C}\u{628} \u{623}\u{646} \u{64A}\u{643}\u{648}\u{646} \u{642}\u{628}\u{644} \u{62A}\u{627}\u{631}\u{64A}\u{62E} \u{627}\u{644}\u{627}\u{646}\u{62A}\u{647}\u{627}\u{621}.`,
18
+ "rangeUnderflow": (args)=>`\u{64A}\u{62C}\u{628} \u{623}\u{646} \u{62A}\u{643}\u{648}\u{646} \u{627}\u{644}\u{642}\u{64A}\u{645}\u{629} ${args.minValue} \u{623}\u{648} \u{628}\u{639}\u{62F} \u{630}\u{644}\u{643}.`,
19
+ "unavailableDate": `\u{627}\u{644}\u{628}\u{64A}\u{627}\u{646}\u{627}\u{62A} \u{627}\u{644}\u{645}\u{62D}\u{62F}\u{62F}\u{629} \u{63A}\u{64A}\u{631} \u{645}\u{62A}\u{627}\u{62D}\u{629}.`
20
+ },
21
+ "bg-BG": {
22
+ "rangeOverflow": (args)=>`\u{421}\u{442}\u{43E}\u{439}\u{43D}\u{43E}\u{441}\u{442}\u{442}\u{430} \u{442}\u{440}\u{44F}\u{431}\u{432}\u{430} \u{434}\u{430} \u{435} ${args.maxValue} \u{438}\u{43B}\u{438} \u{43F}\u{43E}-\u{440}\u{430}\u{43D}\u{43D}\u{430}.`,
23
+ "rangeReversed": `\u{41D}\u{430}\u{447}\u{430}\u{43B}\u{43D}\u{430}\u{442}\u{430} \u{434}\u{430}\u{442}\u{430} \u{442}\u{440}\u{44F}\u{431}\u{432}\u{430} \u{434}\u{430} \u{435} \u{43F}\u{440}\u{435}\u{434}\u{438} \u{43A}\u{440}\u{430}\u{439}\u{43D}\u{430}\u{442}\u{430}.`,
24
+ "rangeUnderflow": (args)=>`\u{421}\u{442}\u{43E}\u{439}\u{43D}\u{43E}\u{441}\u{442}\u{442}\u{430} \u{442}\u{440}\u{44F}\u{431}\u{432}\u{430} \u{434}\u{430} \u{435} ${args.minValue} \u{438}\u{43B}\u{438} \u{43F}\u{43E}-\u{43A}\u{44A}\u{441}\u{43D}\u{43E}.`,
25
+ "unavailableDate": `\u{418}\u{437}\u{431}\u{440}\u{430}\u{43D}\u{430}\u{442}\u{430} \u{434}\u{430}\u{442}\u{430} \u{43D}\u{435} \u{435} \u{43D}\u{430}\u{43B}\u{438}\u{447}\u{43D}\u{430}.`
26
+ },
27
+ "cs-CZ": {
28
+ "rangeOverflow": (args)=>`Hodnota mus\xed b\xfdt ${args.maxValue} nebo d\u{159}\xedv\u{11B}j\u{161}\xed.`,
29
+ "rangeReversed": `Datum zah\xe1jen\xed mus\xed p\u{159}edch\xe1zet datu ukon\u{10D}en\xed.`,
30
+ "rangeUnderflow": (args)=>`Hodnota mus\xed b\xfdt ${args.minValue} nebo pozd\u{11B}j\u{161}\xed.`,
31
+ "unavailableDate": `Vybran\xe9 datum nen\xed k dispozici.`
32
+ },
33
+ "da-DK": {
34
+ "rangeOverflow": (args)=>`V\xe6rdien skal v\xe6re ${args.maxValue} eller tidligere.`,
35
+ "rangeReversed": `Startdatoen skal v\xe6re f\xf8r slutdatoen.`,
36
+ "rangeUnderflow": (args)=>`V\xe6rdien skal v\xe6re ${args.minValue} eller nyere.`,
37
+ "unavailableDate": `Den valgte dato er ikke tilg\xe6ngelig.`
38
+ },
39
+ "de-DE": {
40
+ "rangeOverflow": (args)=>`Der Wert muss ${args.maxValue} oder fr\xfcher sein.`,
41
+ "rangeReversed": `Das Startdatum muss vor dem Enddatum liegen.`,
42
+ "rangeUnderflow": (args)=>`Der Wert muss ${args.minValue} oder sp\xe4ter sein.`,
43
+ "unavailableDate": `Das ausgew\xe4hlte Datum ist nicht verf\xfcgbar.`
44
+ },
45
+ "el-GR": {
46
+ "rangeOverflow": (args)=>`\u{397} \u{3C4}\u{3B9}\u{3BC}\u{3AE} \u{3C0}\u{3C1}\u{3AD}\u{3C0}\u{3B5}\u{3B9} \u{3BD}\u{3B1} \u{3B5}\u{3AF}\u{3BD}\u{3B1}\u{3B9} ${args.maxValue} \u{3AE} \u{3C0}\u{3B1}\u{3BB}\u{3B1}\u{3B9}\u{3CC}\u{3C4}\u{3B5}\u{3C1}\u{3B7}.`,
47
+ "rangeReversed": `\u{397} \u{3B7}\u{3BC}\u{3B5}\u{3C1}\u{3BF}\u{3BC}\u{3B7}\u{3BD}\u{3AF}\u{3B1} \u{3AD}\u{3BD}\u{3B1}\u{3C1}\u{3BE}\u{3B7}\u{3C2} \u{3C0}\u{3C1}\u{3AD}\u{3C0}\u{3B5}\u{3B9} \u{3BD}\u{3B1} \u{3B5}\u{3AF}\u{3BD}\u{3B1}\u{3B9} \u{3C0}\u{3C1}\u{3B9}\u{3BD} \u{3B1}\u{3C0}\u{3CC} \u{3C4}\u{3B7}\u{3BD} \u{3B7}\u{3BC}\u{3B5}\u{3C1}\u{3BF}\u{3BC}\u{3B7}\u{3BD}\u{3AF}\u{3B1} \u{3BB}\u{3AE}\u{3BE}\u{3B7}\u{3C2}.`,
48
+ "rangeUnderflow": (args)=>`\u{397} \u{3C4}\u{3B9}\u{3BC}\u{3AE} \u{3C0}\u{3C1}\u{3AD}\u{3C0}\u{3B5}\u{3B9} \u{3BD}\u{3B1} \u{3B5}\u{3AF}\u{3BD}\u{3B1}\u{3B9} ${args.minValue} \u{3AE} \u{3BC}\u{3B5}\u{3C4}\u{3B1}\u{3B3}\u{3B5}\u{3BD}\u{3AD}\u{3C3}\u{3C4}\u{3B5}\u{3C1}\u{3B7}.`,
49
+ "unavailableDate": `\u{397} \u{3B5}\u{3C0}\u{3B9}\u{3BB}\u{3B5}\u{3B3}\u{3BC}\u{3AD}\u{3BD}\u{3B7} \u{3B7}\u{3BC}\u{3B5}\u{3C1}\u{3BF}\u{3BC}\u{3B7}\u{3BD}\u{3AF}\u{3B1} \u{3B4}\u{3B5}\u{3BD} \u{3B5}\u{3AF}\u{3BD}\u{3B1}\u{3B9} \u{3B4}\u{3B9}\u{3B1}\u{3B8}\u{3AD}\u{3C3}\u{3B9}\u{3BC}\u{3B7}.`
50
+ },
51
+ "en-US": {
52
+ "rangeUnderflow": (args)=>`Value must be ${args.minValue} or later.`,
53
+ "rangeOverflow": (args)=>`Value must be ${args.maxValue} or earlier.`,
54
+ "rangeReversed": `Start date must be before end date.`,
55
+ "unavailableDate": `Selected date unavailable.`
56
+ },
57
+ "es-ES": {
58
+ "rangeOverflow": (args)=>`El valor debe ser ${args.maxValue} o anterior.`,
59
+ "rangeReversed": `La fecha de inicio debe ser anterior a la fecha de finalizaci\xf3n.`,
60
+ "rangeUnderflow": (args)=>`El valor debe ser ${args.minValue} o posterior.`,
61
+ "unavailableDate": `Fecha seleccionada no disponible.`
62
+ },
63
+ "et-EE": {
64
+ "rangeOverflow": (args)=>`V\xe4\xe4rtus peab olema ${args.maxValue} v\xf5i varasem.`,
65
+ "rangeReversed": `Alguskuup\xe4ev peab olema enne l\xf5ppkuup\xe4eva.`,
66
+ "rangeUnderflow": (args)=>`V\xe4\xe4rtus peab olema ${args.minValue} v\xf5i hilisem.`,
67
+ "unavailableDate": `Valitud kuup\xe4ev pole saadaval.`
68
+ },
69
+ "fi-FI": {
70
+ "rangeOverflow": (args)=>`Arvon on oltava ${args.maxValue} tai sit\xe4 aikaisempi.`,
71
+ "rangeReversed": `Aloitusp\xe4iv\xe4n on oltava ennen lopetusp\xe4iv\xe4\xe4.`,
72
+ "rangeUnderflow": (args)=>`Arvon on oltava ${args.minValue} tai sit\xe4 my\xf6h\xe4isempi.`,
73
+ "unavailableDate": `Valittu p\xe4iv\xe4m\xe4\xe4r\xe4 ei ole k\xe4ytett\xe4viss\xe4.`
74
+ },
75
+ "fr-FR": {
76
+ "rangeOverflow": (args)=>`La valeur doit \xeatre ${args.maxValue} ou ant\xe9rieure.`,
77
+ "rangeReversed": `La date de d\xe9but doit \xeatre ant\xe9rieure \xe0 la date de fin.`,
78
+ "rangeUnderflow": (args)=>`La valeur doit \xeatre ${args.minValue} ou ult\xe9rieure.`,
79
+ "unavailableDate": `La date s\xe9lectionn\xe9e n\u{2019}est pas disponible.`
80
+ },
81
+ "he-IL": {
82
+ "rangeOverflow": (args)=>`\u{5D4}\u{5E2}\u{5E8}\u{5DA} \u{5D7}\u{5D9}\u{5D9}\u{5D1} \u{5DC}\u{5D4}\u{5D9}\u{5D5}\u{5EA} ${args.maxValue} \u{5D0}\u{5D5} \u{5DE}\u{5D5}\u{5E7}\u{5D3}\u{5DD} \u{5D9}\u{5D5}\u{5EA}\u{5E8}.`,
83
+ "rangeReversed": `\u{5EA}\u{5D0}\u{5E8}\u{5D9}\u{5DA} \u{5D4}\u{5D4}\u{5EA}\u{5D7}\u{5DC}\u{5D4} \u{5D7}\u{5D9}\u{5D9}\u{5D1} \u{5DC}\u{5D4}\u{5D9}\u{5D5}\u{5EA} \u{5DC}\u{5E4}\u{5E0}\u{5D9} \u{5EA}\u{5D0}\u{5E8}\u{5D9}\u{5DA} \u{5D4}\u{5E1}\u{5D9}\u{5D5}\u{5DD}.`,
84
+ "rangeUnderflow": (args)=>`\u{5D4}\u{5E2}\u{5E8}\u{5DA} \u{5D7}\u{5D9}\u{5D9}\u{5D1} \u{5DC}\u{5D4}\u{5D9}\u{5D5}\u{5EA} ${args.minValue} \u{5D0}\u{5D5} \u{5DE}\u{5D0}\u{5D5}\u{5D7}\u{5E8} \u{5D9}\u{5D5}\u{5EA}\u{5E8}.`,
85
+ "unavailableDate": `\u{5D4}\u{5EA}\u{5D0}\u{5E8}\u{5D9}\u{5DA} \u{5D4}\u{5E0}\u{5D1}\u{5D7}\u{5E8} \u{5D0}\u{5D9}\u{5E0}\u{5D5} \u{5D6}\u{5DE}\u{5D9}\u{5DF}.`
86
+ },
87
+ "hr-HR": {
88
+ "rangeOverflow": (args)=>`Vrijednost mora biti ${args.maxValue} ili ranije.`,
89
+ "rangeReversed": `Datum po\u{10D}etka mora biti prije datuma zavr\u{161}etka.`,
90
+ "rangeUnderflow": (args)=>`Vrijednost mora biti ${args.minValue} ili kasnije.`,
91
+ "unavailableDate": `Odabrani datum nije dostupan.`
92
+ },
93
+ "hu-HU": {
94
+ "rangeOverflow": (args)=>`Az \xe9rt\xe9knek ${args.maxValue} vagy kor\xe1bbinak kell lennie.`,
95
+ "rangeReversed": `A kezd\u{151} d\xe1tumnak a befejez\u{151} d\xe1tumn\xe1l kor\xe1bbinak kell lennie.`,
96
+ "rangeUnderflow": (args)=>`Az \xe9rt\xe9knek ${args.minValue} vagy k\xe9s\u{151}bbinek kell lennie.`,
97
+ "unavailableDate": `A kiv\xe1lasztott d\xe1tum nem \xe9rhet\u{151} el.`
98
+ },
99
+ "it-IT": {
100
+ "rangeOverflow": (args)=>`Il valore deve essere ${args.maxValue} o precedente.`,
101
+ "rangeReversed": `La data di inizio deve essere antecedente alla data di fine.`,
102
+ "rangeUnderflow": (args)=>`Il valore deve essere ${args.minValue} o successivo.`,
103
+ "unavailableDate": `Data selezionata non disponibile.`
104
+ },
105
+ "ja-JP": {
106
+ "rangeOverflow": (args)=>`\u{5024}\u{306F} ${args.maxValue} \u{4EE5}\u{4E0B}\u{306B}\u{3059}\u{308B}\u{5FC5}\u{8981}\u{304C}\u{3042}\u{308A}\u{307E}\u{3059}\u{3002}`,
107
+ "rangeReversed": `\u{958B}\u{59CB}\u{65E5}\u{306F}\u{7D42}\u{4E86}\u{65E5}\u{3088}\u{308A}\u{524D}\u{306B}\u{3059}\u{308B}\u{5FC5}\u{8981}\u{304C}\u{3042}\u{308A}\u{307E}\u{3059}\u{3002}`,
108
+ "rangeUnderflow": (args)=>`\u{5024}\u{306F} ${args.minValue} \u{4EE5}\u{4E0A}\u{306B}\u{3059}\u{308B}\u{5FC5}\u{8981}\u{304C}\u{3042}\u{308A}\u{307E}\u{3059}\u{3002}`,
109
+ "unavailableDate": `\u{9078}\u{629E}\u{3057}\u{305F}\u{65E5}\u{4ED8}\u{306F}\u{4F7F}\u{7528}\u{3067}\u{304D}\u{307E}\u{305B}\u{3093}\u{3002}`
110
+ },
111
+ "ko-KR": {
112
+ "rangeOverflow": (args)=>`\u{AC12}\u{C740} ${args.maxValue} \u{C774}\u{C804}\u{C774}\u{C5B4}\u{C57C} \u{D569}\u{B2C8}\u{B2E4}.`,
113
+ "rangeReversed": `\u{C2DC}\u{C791}\u{C77C}\u{C740} \u{C885}\u{B8CC}\u{C77C} \u{C774}\u{C804}\u{C774}\u{C5B4}\u{C57C} \u{D569}\u{B2C8}\u{B2E4}.`,
114
+ "rangeUnderflow": (args)=>`\u{AC12}\u{C740} ${args.minValue} \u{C774}\u{D6C4}\u{C5EC}\u{C57C} \u{D569}\u{B2C8}\u{B2E4}.`,
115
+ "unavailableDate": `\u{C120}\u{D0DD}\u{D55C} \u{B0A0}\u{C9DC}\u{B97C} \u{C0AC}\u{C6A9}\u{D560} \u{C218} \u{C5C6}\u{C2B5}\u{B2C8}\u{B2E4}.`
116
+ },
117
+ "lt-LT": {
118
+ "rangeOverflow": (args)=>`Reik\u{161}m\u{117} turi b\u{16B}ti ${args.maxValue} arba ankstesn\u{117}.`,
119
+ "rangeReversed": `Prad\u{17E}ios data turi b\u{16B}ti ankstesn\u{117} nei pabaigos data.`,
120
+ "rangeUnderflow": (args)=>`Reik\u{161}m\u{117} turi b\u{16B}ti ${args.minValue} arba naujesn\u{117}.`,
121
+ "unavailableDate": `Pasirinkta data nepasiekiama.`
122
+ },
123
+ "lv-LV": {
124
+ "rangeOverflow": (args)=>`V\u{113}rt\u{12B}bai ir j\u{101}b\u{16B}t ${args.maxValue} vai agr\u{101}kai.`,
125
+ "rangeReversed": `S\u{101}kuma datumam ir j\u{101}b\u{16B}t pirms beigu datuma.`,
126
+ "rangeUnderflow": (args)=>`V\u{113}rt\u{12B}bai ir j\u{101}b\u{16B}t ${args.minValue} vai v\u{113}l\u{101}kai.`,
127
+ "unavailableDate": `Atlas\u{12B}tais datums nav pieejams.`
128
+ },
129
+ "nb-NO": {
130
+ "rangeOverflow": (args)=>`Verdien m\xe5 v\xe6re ${args.maxValue} eller tidligere.`,
131
+ "rangeReversed": `Startdatoen m\xe5 v\xe6re f\xf8r sluttdatoen.`,
132
+ "rangeUnderflow": (args)=>`Verdien m\xe5 v\xe6re ${args.minValue} eller senere.`,
133
+ "unavailableDate": `Valgt dato utilgjengelig.`
134
+ },
135
+ "nl-NL": {
136
+ "rangeOverflow": (args)=>`Waarde moet ${args.maxValue} of eerder zijn.`,
137
+ "rangeReversed": `De startdatum moet voor de einddatum liggen.`,
138
+ "rangeUnderflow": (args)=>`Waarde moet ${args.minValue} of later zijn.`,
139
+ "unavailableDate": `Geselecteerde datum niet beschikbaar.`
140
+ },
141
+ "pl-PL": {
142
+ "rangeOverflow": (args)=>`Warto\u{15B}\u{107} musi mie\u{107} warto\u{15B}\u{107} ${args.maxValue} lub wcze\u{15B}niejsz\u{105}.`,
143
+ "rangeReversed": `Data rozpocz\u{119}cia musi by\u{107} wcze\u{15B}niejsza ni\u{17C} data zako\u{144}czenia.`,
144
+ "rangeUnderflow": (args)=>`Warto\u{15B}\u{107} musi mie\u{107} warto\u{15B}\u{107} ${args.minValue} lub p\xf3\u{17A}niejsz\u{105}.`,
145
+ "unavailableDate": `Wybrana data jest niedost\u{119}pna.`
146
+ },
147
+ "pt-BR": {
148
+ "rangeOverflow": (args)=>`O valor deve ser ${args.maxValue} ou anterior.`,
149
+ "rangeReversed": `A data inicial deve ser anterior \xe0 data final.`,
150
+ "rangeUnderflow": (args)=>`O valor deve ser ${args.minValue} ou posterior.`,
151
+ "unavailableDate": `Data selecionada indispon\xedvel.`
152
+ },
153
+ "pt-PT": {
154
+ "rangeOverflow": (args)=>`O valor tem de ser ${args.maxValue} ou anterior.`,
155
+ "rangeReversed": `A data de in\xedcio deve ser anterior \xe0 data de fim.`,
156
+ "rangeUnderflow": (args)=>`O valor tem de ser ${args.minValue} ou posterior.`,
157
+ "unavailableDate": `Data selecionada indispon\xedvel.`
158
+ },
159
+ "ro-RO": {
160
+ "rangeOverflow": (args)=>`Valoarea trebuie s\u{103} fie ${args.maxValue} sau anterioar\u{103}.`,
161
+ "rangeReversed": `Data de \xeenceput trebuie s\u{103} fie anterioar\u{103} datei de sf\xe2r\u{219}it.`,
162
+ "rangeUnderflow": (args)=>`Valoarea trebuie s\u{103} fie ${args.minValue} sau ulterioar\u{103}.`,
163
+ "unavailableDate": `Data selectat\u{103} nu este disponibil\u{103}.`
164
+ },
165
+ "ru-RU": {
166
+ "rangeOverflow": (args)=>`\u{417}\u{43D}\u{430}\u{447}\u{435}\u{43D}\u{438}\u{435} \u{434}\u{43E}\u{43B}\u{436}\u{43D}\u{43E} \u{431}\u{44B}\u{442}\u{44C} \u{43D}\u{435} \u{43F}\u{43E}\u{437}\u{436}\u{435} ${args.maxValue}.`,
167
+ "rangeReversed": `\u{414}\u{430}\u{442}\u{430} \u{43D}\u{430}\u{447}\u{430}\u{43B}\u{430} \u{434}\u{43E}\u{43B}\u{436}\u{43D}\u{430} \u{43F}\u{440}\u{435}\u{434}\u{448}\u{435}\u{441}\u{442}\u{432}\u{43E}\u{432}\u{430}\u{442}\u{44C} \u{434}\u{430}\u{442}\u{435} \u{43E}\u{43A}\u{43E}\u{43D}\u{447}\u{430}\u{43D}\u{438}\u{44F}.`,
168
+ "rangeUnderflow": (args)=>`\u{417}\u{43D}\u{430}\u{447}\u{435}\u{43D}\u{438}\u{435} \u{434}\u{43E}\u{43B}\u{436}\u{43D}\u{43E} \u{431}\u{44B}\u{442}\u{44C} \u{43D}\u{435} \u{440}\u{430}\u{43D}\u{44C}\u{448}\u{435} ${args.minValue}.`,
169
+ "unavailableDate": `\u{412}\u{44B}\u{431}\u{440}\u{430}\u{43D}\u{43D}\u{430}\u{44F} \u{434}\u{430}\u{442}\u{430} \u{43D}\u{435}\u{434}\u{43E}\u{441}\u{442}\u{443}\u{43F}\u{43D}\u{430}.`
170
+ },
171
+ "sk-SK": {
172
+ "rangeOverflow": (args)=>`Hodnota mus\xed by\u{165} ${args.maxValue} alebo skor\u{161}ia.`,
173
+ "rangeReversed": `D\xe1tum za\u{10D}iatku mus\xed by\u{165} skor\u{161}\xed ako d\xe1tum konca.`,
174
+ "rangeUnderflow": (args)=>`Hodnota mus\xed by\u{165} ${args.minValue} alebo neskor\u{161}ia.`,
175
+ "unavailableDate": `Vybrat\xfd d\xe1tum je nedostupn\xfd.`
176
+ },
177
+ "sl-SI": {
178
+ "rangeOverflow": (args)=>`Vrednost mora biti ${args.maxValue} ali starej\u{161}a.`,
179
+ "rangeReversed": `Za\u{10D}etni datum mora biti pred kon\u{10D}nim datumom.`,
180
+ "rangeUnderflow": (args)=>`Vrednost mora biti ${args.minValue} ali novej\u{161}a.`,
181
+ "unavailableDate": `Izbrani datum ni na voljo.`
182
+ },
183
+ "sr-SP": {
184
+ "rangeOverflow": (args)=>`Vrednost mora da bude ${args.maxValue} ili starija.`,
185
+ "rangeReversed": `Datum po\u{10D}etka mora biti pre datuma zavr\u{161}etka.`,
186
+ "rangeUnderflow": (args)=>`Vrednost mora da bude ${args.minValue} ili novija.`,
187
+ "unavailableDate": `Izabrani datum nije dostupan.`
188
+ },
189
+ "sv-SE": {
190
+ "rangeOverflow": (args)=>`V\xe4rdet m\xe5ste vara ${args.maxValue} eller tidigare.`,
191
+ "rangeReversed": `Startdatumet m\xe5ste vara f\xf6re slutdatumet.`,
192
+ "rangeUnderflow": (args)=>`V\xe4rdet m\xe5ste vara ${args.minValue} eller senare.`,
193
+ "unavailableDate": `Det valda datumet \xe4r inte tillg\xe4ngligt.`
194
+ },
195
+ "tr-TR": {
196
+ "rangeOverflow": (args)=>`De\u{11F}er, ${args.maxValue} veya \xf6ncesi olmal\u{131}d\u{131}r.`,
197
+ "rangeReversed": `Ba\u{15F}lang\u{131}\xe7 tarihi biti\u{15F} tarihinden \xf6nce olmal\u{131}d\u{131}r.`,
198
+ "rangeUnderflow": (args)=>`De\u{11F}er, ${args.minValue} veya sonras\u{131} olmal\u{131}d\u{131}r.`,
199
+ "unavailableDate": `Se\xe7ilen tarih kullan\u{131}lam\u{131}yor.`
200
+ },
201
+ "uk-UA": {
202
+ "rangeOverflow": (args)=>`\u{417}\u{43D}\u{430}\u{447}\u{435}\u{43D}\u{43D}\u{44F} \u{43C}\u{430}\u{454} \u{431}\u{443}\u{442}\u{438} \u{43D}\u{435} \u{43F}\u{456}\u{437}\u{43D}\u{456}\u{448}\u{435} ${args.maxValue}.`,
203
+ "rangeReversed": `\u{414}\u{430}\u{442}\u{430} \u{43F}\u{43E}\u{447}\u{430}\u{442}\u{43A}\u{443} \u{43C}\u{430}\u{454} \u{43F}\u{435}\u{440}\u{435}\u{434}\u{443}\u{432}\u{430}\u{442}\u{438} \u{434}\u{430}\u{442}\u{456} \u{437}\u{430}\u{432}\u{435}\u{440}\u{448}\u{435}\u{43D}\u{43D}\u{44F}.`,
204
+ "rangeUnderflow": (args)=>`\u{417}\u{43D}\u{430}\u{447}\u{435}\u{43D}\u{43D}\u{44F} \u{43C}\u{430}\u{454} \u{431}\u{443}\u{442}\u{438} \u{43D}\u{435} \u{440}\u{430}\u{43D}\u{456}\u{448}\u{435} ${args.minValue}.`,
205
+ "unavailableDate": `\u{412}\u{438}\u{431}\u{440}\u{430}\u{43D}\u{430} \u{434}\u{430}\u{442}\u{430} \u{43D}\u{435}\u{434}\u{43E}\u{441}\u{442}\u{443}\u{43F}\u{43D}\u{430}.`
206
+ },
207
+ "zh-CN": {
208
+ "rangeOverflow": (args)=>`\u{503C}\u{5FC5}\u{987B}\u{662F} ${args.maxValue} \u{6216}\u{66F4}\u{65E9}\u{65E5}\u{671F}\u{3002}`,
209
+ "rangeReversed": `\u{5F00}\u{59CB}\u{65E5}\u{671F}\u{5FC5}\u{987B}\u{65E9}\u{4E8E}\u{7ED3}\u{675F}\u{65E5}\u{671F}\u{3002}`,
210
+ "rangeUnderflow": (args)=>`\u{503C}\u{5FC5}\u{987B}\u{662F} ${args.minValue} \u{6216}\u{66F4}\u{665A}\u{65E5}\u{671F}\u{3002}`,
211
+ "unavailableDate": `\u{6240}\u{9009}\u{65E5}\u{671F}\u{4E0D}\u{53EF}\u{7528}\u{3002}`
212
+ },
213
+ "zh-TW": {
214
+ "rangeOverflow": (args)=>`\u{503C}\u{5FC5}\u{9808}\u{662F} ${args.maxValue} \u{6216}\u{66F4}\u{65E9}\u{3002}`,
215
+ "rangeReversed": `\u{958B}\u{59CB}\u{65E5}\u{671F}\u{5FC5}\u{9808}\u{5728}\u{7D50}\u{675F}\u{65E5}\u{671F}\u{4E4B}\u{524D}\u{3002}`,
216
+ "rangeUnderflow": (args)=>`\u{503C}\u{5FC5}\u{9808}\u{662F} ${args.minValue} \u{6216}\u{66F4}\u{665A}\u{3002}`,
217
+ "unavailableDate": `\u{6240}\u{9078}\u{65E5}\u{671F}\u{7121}\u{6CD5}\u{4F7F}\u{7528}\u{3002}`
218
+ },
219
+ };
220
+
221
+ const DEFAULT_LOCALE = "en-US";
222
+
223
+ function getMessageForLocale(
224
+ key: DateFieldErrorKey,
225
+ locale: string,
226
+ ): DateFieldErrorMessage {
227
+ const exact = errorStrings[locale];
228
+ if (exact && exact[key] != null) return exact[key];
229
+
230
+ const language = locale.split("-")[0];
231
+ const match = Object.keys(errorStrings).find((l) => l.split("-")[0] === language);
232
+ if (match && errorStrings[match][key] != null) return errorStrings[match][key];
233
+
234
+ return errorStrings[DEFAULT_LOCALE][key];
235
+ }
236
+
237
+ /** Mirrors LocalizedStringFormatter.format for the datepicker validation messages. */
238
+ export function formatDateFieldError(
239
+ locale: string,
240
+ key: DateFieldErrorKey,
241
+ args?: DateFieldErrorArgs,
242
+ ): string {
243
+ const message = getMessageForLocale(key, locale);
244
+ return typeof message === "function" ? message(args ?? {}) : message;
245
+ }
246
+
247
+ // ---------------------------------------------------------------------------
248
+ // Placeholder-text catalog, ported verbatim from @react-stately/datepicker's
249
+ // placeholders.ts. Based on the <input type="date"> placeholders used by Chrome
250
+ // and Firefox; more languages than React Spectrum's usual translations. Default
251
+ // locale is "en".
252
+ // ---------------------------------------------------------------------------
253
+
254
+ const placeholderStrings: Record<string, { year: string; month: string; day: string }> = {
255
+ ach: { year: "mwaka", month: "dwe", day: "nino" },
256
+ af: { year: "jjjj", month: "mm", day: "dd" },
257
+ am: { year: "ዓዓዓዓ", month: "ሚሜ", day: "ቀቀ" },
258
+ an: { year: "aaaa", month: "mm", day: "dd" },
259
+ ar: { year: "سنة", month: "شهر", day: "يوم" },
260
+ ast: { year: "aaaa", month: "mm", day: "dd" },
261
+ az: { year: "iiii", month: "aa", day: "gg" },
262
+ be: { year: "гггг", month: "мм", day: "дд" },
263
+ bg: { year: "гггг", month: "мм", day: "дд" },
264
+ bn: { year: "yyyy", month: "মিমি", day: "dd" },
265
+ br: { year: "bbbb", month: "mm", day: "dd" },
266
+ bs: { year: "gggg", month: "mm", day: "dd" },
267
+ ca: { year: "aaaa", month: "mm", day: "dd" },
268
+ cak: { year: "jjjj", month: "ii", day: "q'q'" },
269
+ ckb: { year: "ساڵ", month: "مانگ", day: "ڕۆژ" },
270
+ cs: { year: "rrrr", month: "mm", day: "dd" },
271
+ cy: { year: "bbbb", month: "mm", day: "dd" },
272
+ da: { year: "åååå", month: "mm", day: "dd" },
273
+ de: { year: "jjjj", month: "mm", day: "tt" },
274
+ dsb: { year: "llll", month: "mm", day: "źź" },
275
+ el: { year: "εεεε", month: "μμ", day: "ηη" },
276
+ en: { year: "yyyy", month: "mm", day: "dd" },
277
+ eo: { year: "jjjj", month: "mm", day: "tt" },
278
+ es: { year: "aaaa", month: "mm", day: "dd" },
279
+ et: { year: "aaaa", month: "kk", day: "pp" },
280
+ eu: { year: "uuuu", month: "hh", day: "ee" },
281
+ fa: { year: "سال", month: "ماه", day: "روز" },
282
+ ff: { year: "hhhh", month: "ll", day: "ññ" },
283
+ fi: { year: "vvvv", month: "kk", day: "pp" },
284
+ fr: { year: "aaaa", month: "mm", day: "jj" },
285
+ fy: { year: "jjjj", month: "mm", day: "dd" },
286
+ ga: { year: "bbbb", month: "mm", day: "ll" },
287
+ gd: { year: "bbbb", month: "mm", day: "ll" },
288
+ gl: { year: "aaaa", month: "mm", day: "dd" },
289
+ he: { year: "שנה", month: "חודש", day: "יום" },
290
+ hr: { year: "gggg", month: "mm", day: "dd" },
291
+ hsb: { year: "llll", month: "mm", day: "dd" },
292
+ hu: { year: "éééé", month: "hh", day: "nn" },
293
+ ia: { year: "aaaa", month: "mm", day: "dd" },
294
+ id: { year: "tttt", month: "bb", day: "hh" },
295
+ is: { year: "áááá", month: "mm", day: "dd" },
296
+ it: { year: "aaaa", month: "mm", day: "gg" },
297
+ ja: { year: "年", month: "月", day: "日" },
298
+ ka: { year: "წწწწ", month: "თთ", day: "რრ" },
299
+ kk: { year: "жжжж", month: "аа", day: "кк" },
300
+ kn: { year: "ವವವವ", month: "ಮಿಮೀ", day: "ದಿದಿ" },
301
+ ko: { year: "연도", month: "월", day: "일" },
302
+ lb: { year: "jjjj", month: "mm", day: "dd" },
303
+ lo: { year: "ປປປປ", month: "ດດ", day: "ວວ" },
304
+ lt: { year: "mmmm", month: "mm", day: "dd" },
305
+ lv: { year: "gggg", month: "mm", day: "dd" },
306
+ meh: { year: "aaaa", month: "mm", day: "dd" },
307
+ ml: { year: "വർഷം", month: "മാസം", day: "തീയതി" },
308
+ ms: { year: "tttt", month: "mm", day: "hh" },
309
+ nb: { year: "åååå", month: "mm", day: "dd" },
310
+ nl: { year: "jjjj", month: "mm", day: "dd" },
311
+ nn: { year: "åååå", month: "mm", day: "dd" },
312
+ no: { year: "åååå", month: "mm", day: "dd" },
313
+ oc: { year: "aaaa", month: "mm", day: "jj" },
314
+ pl: { year: "rrrr", month: "mm", day: "dd" },
315
+ pt: { year: "aaaa", month: "mm", day: "dd" },
316
+ rm: { year: "oooo", month: "mm", day: "dd" },
317
+ ro: { year: "aaaa", month: "ll", day: "zz" },
318
+ ru: { year: "гггг", month: "мм", day: "дд" },
319
+ sc: { year: "aaaa", month: "mm", day: "dd" },
320
+ scn: { year: "aaaa", month: "mm", day: "jj" },
321
+ sk: { year: "rrrr", month: "mm", day: "dd" },
322
+ sl: { year: "llll", month: "mm", day: "dd" },
323
+ sr: { year: "гггг", month: "мм", day: "дд" },
324
+ "sr-Latn": { year: "gggg", month: "mm", day: "dd" },
325
+ sv: { year: "åååå", month: "mm", day: "dd" },
326
+ szl: { year: "rrrr", month: "mm", day: "dd" },
327
+ tg: { year: "сссс", month: "мм", day: "рр" },
328
+ th: { year: "ปปปป", month: "ดด", day: "วว" },
329
+ tr: { year: "yyyy", month: "aa", day: "gg" },
330
+ uk: { year: "рррр", month: "мм", day: "дд" },
331
+ "zh-CN": { year: "年", month: "月", day: "日" },
332
+ "zh-TW": { year: "年", month: "月", day: "日" },
333
+ };
334
+
335
+ const PLACEHOLDER_DEFAULT_LOCALE = "en";
336
+
337
+ function getPlaceholderStringForLocale(field: "year" | "month" | "day", locale: string): string {
338
+ const exact = placeholderStrings[locale];
339
+ if (exact) return exact[field];
340
+
341
+ const language = locale.split("-")[0];
342
+ const direct = placeholderStrings[language];
343
+ if (direct) return direct[field];
344
+
345
+ const scan = Object.keys(placeholderStrings).find((l) => l.split("-")[0] === language);
346
+ if (scan) return placeholderStrings[scan][field];
347
+
348
+ return placeholderStrings[PLACEHOLDER_DEFAULT_LOCALE][field];
349
+ }
350
+
351
+ /** Mirrors @react-stately/datepicker getPlaceholder. */
352
+ export function getPlaceholder(field: string, value: string, locale: string): string {
353
+ // Use the actual placeholder value for the era and day period fields.
354
+ if (field === "era" || field === "dayPeriod") {
355
+ return value;
356
+ }
357
+
358
+ if (field === "year" || field === "month" || field === "day") {
359
+ return getPlaceholderStringForLocale(field, locale);
360
+ }
361
+
362
+ // For time fields (e.g. hour, minute, etc.), use two dashes as the placeholder.
363
+ return "––";
364
+ }
@@ -90,6 +90,37 @@ export class ListCollection<T = unknown> implements Collection<T> {
90
90
  return this.getItem(key)?.textValue ?? "";
91
91
  }
92
92
 
93
+ /**
94
+ * Filter the collection, returning a new `ListCollection` containing only the
95
+ * item nodes for which `filterFn(node.textValue, node)` returns true. Sections
96
+ * whose children are all filtered out are dropped; surviving sections are
97
+ * rebuilt with their filtered children. Node order is preserved. Mirrors the
98
+ * behaviour of @react-aria/collections `BaseCollection.filter`.
99
+ */
100
+ filter(filterFn: (nodeValue: string, node: CollectionNode<T>) => boolean): ListCollection<T> {
101
+ const filterNodes = (nodes: CollectionNode<T>[]): CollectionNode<T>[] => {
102
+ const result: CollectionNode<T>[] = [];
103
+ for (const node of nodes) {
104
+ if (node.type === "section") {
105
+ const children = filterNodes(node.childNodes ?? []);
106
+ if (children.length > 0) {
107
+ result.push({ ...node, childNodes: children, hasChildNodes: true });
108
+ }
109
+ } else if (node.type === "item") {
110
+ if (filterFn(node.textValue, node)) {
111
+ result.push(node);
112
+ }
113
+ } else {
114
+ // Non-item, non-section nodes (e.g. separators) pass through unfiltered.
115
+ result.push(node);
116
+ }
117
+ }
118
+ return result;
119
+ };
120
+
121
+ return new ListCollection<T>(filterNodes(this.nodes));
122
+ }
123
+
93
124
  /**
94
125
  * Get all items (excluding sections, including items within sections).
95
126
  */
@@ -4,10 +4,12 @@
4
4
  * Based on @react-stately/list.
5
5
  */
6
6
 
7
- import { createSignal, createMemo, type Accessor } from "solid-js";
7
+ import { createMemo, createEffect, untrack, type Accessor } from "solid-js";
8
8
  import { access, type MaybeAccessor } from "../utils";
9
9
  import { ListCollection } from "./ListCollection";
10
- import { createSelectionState, type SelectionState } from "./createSelectionState";
10
+ import type { SelectionState, SelectionPressEvent } from "./createSelectionState";
11
+ import { createMultipleSelectionState } from "../selection/createMultipleSelectionState";
12
+ import { SelectionManager } from "../selection/SelectionManager";
11
13
  import type {
12
14
  Collection,
13
15
  CollectionItemLike,
@@ -51,6 +53,8 @@ export interface ListStateProps<T = unknown> {
51
53
  export interface ListState<T = unknown> extends SelectionState {
52
54
  /** The collection of items. */
53
55
  readonly collection: Accessor<Collection<T>>;
56
+ /** The collection-aware selection manager (single source of truth for selection). */
57
+ readonly selectionManager: SelectionManager<T>;
54
58
  /** Whether the collection is focused. */
55
59
  readonly isFocused: Accessor<boolean>;
56
60
  /** Set the focused state. */
@@ -116,8 +120,9 @@ export function createListState<T = unknown>(
116
120
  return [...new Set([...propsDisabled, ...itemDisabled])];
117
121
  });
118
122
 
119
- // Create selection state
120
- const selectionState = createSelectionState({
123
+ // Raw selection + focus state (the lower layer). This owns the selected keys,
124
+ // focus, selection behavior, and disabled keys, but is not collection-aware.
125
+ const state = createMultipleSelectionState({
121
126
  get selectionMode() {
122
127
  return getProps().selectionMode;
123
128
  },
@@ -147,24 +152,141 @@ export function createListState<T = unknown>(
147
152
  },
148
153
  });
149
154
 
150
- // Focus state
151
- const [isFocused, setIsFocused] = createSignal(false);
152
- const [focusedKey, setFocusedKeyInternal] = createSignal<Key | null>(null);
153
- const [childFocusStrategy, setChildFocusStrategy] = createSignal<FocusStrategy | null>(null);
155
+ // The collection-aware selection manager (the upper layer): the single source
156
+ // of truth for every selection operation. It reads the collection live so it
157
+ // stays reactive as the collection memo recomputes.
158
+ const selectionManager = new SelectionManager<T>(() => collection(), state);
154
159
 
155
- const setFocusedKey = (key: Key | null, childStrategy?: FocusStrategy) => {
156
- setFocusedKeyInternal(key);
157
- setChildFocusStrategy(childStrategy ?? null);
160
+ return {
161
+ collection,
162
+ selectionManager,
163
+
164
+ // Focus — delegate to the raw state (the single source of truth). The
165
+ // setter routes through the manager so it inherits the existence guard and
166
+ // the upstream 'first' child-focus default.
167
+ isFocused: () => state.isFocused,
168
+ setFocused: (isFocused: boolean) => state.setFocused(isFocused),
169
+ focusedKey: () => state.focusedKey,
170
+ setFocusedKey: (key: Key | null, childStrategy?: FocusStrategy) =>
171
+ selectionManager.setFocusedKey(key, childStrategy),
172
+ childFocusStrategy: () => state.childFocusStrategy,
173
+
174
+ // Flattened selection surface — delegates to the manager so the list path
175
+ // shares one faithful engine. extendSelection keeps its legacy second
176
+ // argument for back-compat, but ignores it (the manager reads the
177
+ // collection itself).
178
+ selectionMode: () => selectionManager.selectionMode,
179
+ selectionBehavior: () => selectionManager.selectionBehavior,
180
+ disallowEmptySelection: () => selectionManager.disallowEmptySelection,
181
+ selectedKeys: () => state.selectedKeys,
182
+ disabledKeys: () => selectionManager.disabledKeys,
183
+ disabledBehavior: () => selectionManager.disabledBehavior,
184
+ isEmpty: () => selectionManager.isEmpty,
185
+ isSelectAll: () => selectionManager.isSelectAll,
186
+ isSelected: (key: Key) => selectionManager.isSelected(key),
187
+ isDisabled: (key: Key) => selectionManager.isDisabled(key),
188
+ setSelectionBehavior: (behavior: SelectionBehavior) =>
189
+ selectionManager.setSelectionBehavior(behavior),
190
+ toggleSelection: (key: Key) => selectionManager.toggleSelection(key),
191
+ replaceSelection: (key: Key) => selectionManager.replaceSelection(key),
192
+ setSelectedKeys: (keys: Iterable<Key>) => selectionManager.setSelectedKeys(keys),
193
+ selectAll: () => selectionManager.selectAll(),
194
+ clearSelection: () => selectionManager.clearSelection(),
195
+ toggleSelectAll: () => selectionManager.toggleSelectAll(),
196
+ extendSelection: (toKey: Key, _collection?: Collection) =>
197
+ selectionManager.extendSelection(toKey),
198
+ select: (key: Key, e?: SelectionPressEvent) => selectionManager.select(key, e),
158
199
  };
200
+ }
201
+
202
+ /** A collection filter predicate: `(nodeTextValue, node) => keep`. */
203
+ export type ListFilterFn<T = unknown> = (nodeValue: string, node: CollectionNode<T>) => boolean;
204
+
205
+ /**
206
+ * Wraps a {@link ListState} so its collection is filtered by `filterFn`, returning
207
+ * a new (reactive) ListState. Mirrors @react-stately's `UNSTABLE_useFilteredListState`
208
+ * (used by Autocomplete): the filter re-runs as the predicate — which typically
209
+ * closes over the current input value — or the base collection changes; a single
210
+ * stable, collection-aware manager reads the narrowed collection live and shares
211
+ * the base selection/focus state; and the focused key is reset to the nearest
212
+ * surviving item when the current one is filtered out.
213
+ */
214
+ export function createFilteredListState<T = unknown>(
215
+ state: ListState<T>,
216
+ filterFn: Accessor<ListFilterFn<T> | null | undefined>,
217
+ ): ListState<T> {
218
+ const collection: Accessor<Collection<T>> = createMemo(() => {
219
+ const fn = filterFn();
220
+ const base = state.collection();
221
+ return fn && base.filter ? base.filter(fn) : base;
222
+ });
223
+
224
+ // One stable manager reading the filtered collection live; shares the base
225
+ // manager's underlying selection/focus state and retains the unfiltered
226
+ // collection as `fullCollection` (so select-all still covers filtered-out keys).
227
+ const selectionManager = state.selectionManager.withCollectionAccessor(() => collection());
228
+
229
+ // Reset the focused key if the item it points at was filtered out, walking
230
+ // forward (then backward) through the previous collection to the nearest
231
+ // surviving, enabled item. Mirrors @react-stately's useFocusedKeyReset.
232
+ let cachedCollection: Collection<T> | null = null;
233
+ createEffect(() => {
234
+ const coll = collection();
235
+ const focusedKey = untrack(() => state.focusedKey());
236
+ if (focusedKey != null && !coll.getItem(focusedKey) && cachedCollection) {
237
+ let key = cachedCollection.getKeyAfter(focusedKey);
238
+ let nextFocusedKey: Key | null = null;
239
+ while (key != null) {
240
+ const node = coll.getItem(key);
241
+ if (node && node.type === "item" && !selectionManager.isDisabled(key)) {
242
+ nextFocusedKey = key;
243
+ break;
244
+ }
245
+ key = cachedCollection.getKeyAfter(key);
246
+ }
247
+ if (nextFocusedKey == null) {
248
+ key = cachedCollection.getKeyBefore(focusedKey);
249
+ while (key != null) {
250
+ const node = coll.getItem(key);
251
+ if (node && node.type === "item" && !selectionManager.isDisabled(key)) {
252
+ nextFocusedKey = key;
253
+ break;
254
+ }
255
+ key = cachedCollection.getKeyBefore(key);
256
+ }
257
+ }
258
+ untrack(() => state.setFocusedKey(nextFocusedKey));
259
+ }
260
+ cachedCollection = coll;
261
+ });
159
262
 
263
+ // Spread the base state for type completeness + shared focus fields, then
264
+ // override the collection and every collection-aware operation so navigation,
265
+ // selection, and disabled checks all see the narrowed collection.
160
266
  return {
267
+ ...state,
161
268
  collection,
162
- isFocused,
163
- setFocused: setIsFocused,
164
- focusedKey,
165
- setFocusedKey,
166
- childFocusStrategy,
167
- ...selectionState,
269
+ selectionManager,
270
+ setFocusedKey: (key: Key | null, childStrategy?: FocusStrategy) =>
271
+ selectionManager.setFocusedKey(key, childStrategy),
272
+ selectionMode: () => selectionManager.selectionMode,
273
+ selectionBehavior: () => selectionManager.selectionBehavior,
274
+ disallowEmptySelection: () => selectionManager.disallowEmptySelection,
275
+ disabledKeys: () => selectionManager.disabledKeys,
276
+ isEmpty: () => selectionManager.isEmpty,
277
+ isSelectAll: () => selectionManager.isSelectAll,
278
+ isSelected: (key: Key) => selectionManager.isSelected(key),
279
+ isDisabled: (key: Key) => selectionManager.isDisabled(key),
280
+ setSelectionBehavior: (behavior: SelectionBehavior) =>
281
+ selectionManager.setSelectionBehavior(behavior),
282
+ toggleSelection: (key: Key) => selectionManager.toggleSelection(key),
283
+ replaceSelection: (key: Key) => selectionManager.replaceSelection(key),
284
+ setSelectedKeys: (keys: Iterable<Key>) => selectionManager.setSelectedKeys(keys),
285
+ selectAll: () => selectionManager.selectAll(),
286
+ clearSelection: () => selectionManager.clearSelection(),
287
+ toggleSelectAll: () => selectionManager.toggleSelectAll(),
288
+ extendSelection: (toKey: Key) => selectionManager.extendSelection(toKey),
289
+ select: (key: Key, e?: SelectionPressEvent) => selectionManager.select(key, e),
168
290
  };
169
291
  }
170
292
 
@@ -69,6 +69,9 @@ export function createMenuState<T = unknown>(
69
69
  get selectionBehavior() {
70
70
  return getProps().selectionBehavior;
71
71
  },
72
+ get allowDuplicateSelectionEvents() {
73
+ return getProps().allowDuplicateSelectionEvents;
74
+ },
72
75
  });
73
76
 
74
77
  const close = () => {