@proyecto-viviana/solidaria 0.2.2 → 0.2.4

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 (210) hide show
  1. package/dist/autocomplete/createAutocomplete.d.ts +2 -2
  2. package/dist/autocomplete/createAutocomplete.d.ts.map +1 -1
  3. package/dist/index.js +233 -234
  4. package/dist/index.js.map +2 -2
  5. package/dist/index.ssr.js +233 -234
  6. package/dist/index.ssr.js.map +2 -2
  7. package/dist/interactions/PressEvent.d.ts +13 -10
  8. package/dist/interactions/PressEvent.d.ts.map +1 -1
  9. package/dist/interactions/createPress.d.ts.map +1 -1
  10. package/dist/interactions/index.d.ts +1 -1
  11. package/dist/interactions/index.d.ts.map +1 -1
  12. package/dist/select/createHiddenSelect.d.ts.map +1 -1
  13. package/dist/toolbar/createToolbar.d.ts.map +1 -1
  14. package/dist/tooltip/createTooltipTrigger.d.ts.map +1 -1
  15. package/package.json +9 -7
  16. package/src/autocomplete/createAutocomplete.ts +341 -0
  17. package/src/autocomplete/index.ts +9 -0
  18. package/src/breadcrumbs/createBreadcrumbs.ts +196 -0
  19. package/src/breadcrumbs/index.ts +8 -0
  20. package/src/button/createButton.ts +142 -0
  21. package/src/button/createToggleButton.ts +101 -0
  22. package/src/button/index.ts +4 -0
  23. package/src/button/types.ts +78 -0
  24. package/src/calendar/createCalendar.ts +138 -0
  25. package/src/calendar/createCalendarCell.ts +187 -0
  26. package/src/calendar/createCalendarGrid.ts +140 -0
  27. package/src/calendar/createRangeCalendar.ts +136 -0
  28. package/src/calendar/createRangeCalendarCell.ts +186 -0
  29. package/src/calendar/index.ts +34 -0
  30. package/src/checkbox/createCheckbox.ts +135 -0
  31. package/src/checkbox/createCheckboxGroup.ts +137 -0
  32. package/src/checkbox/createCheckboxGroupItem.ts +117 -0
  33. package/src/checkbox/createCheckboxGroupState.ts +193 -0
  34. package/src/checkbox/index.ts +13 -0
  35. package/src/color/createColorArea.ts +314 -0
  36. package/src/color/createColorField.ts +137 -0
  37. package/src/color/createColorSlider.ts +197 -0
  38. package/src/color/createColorSwatch.ts +40 -0
  39. package/src/color/createColorWheel.ts +208 -0
  40. package/src/color/index.ts +24 -0
  41. package/src/color/types.ts +116 -0
  42. package/src/combobox/createComboBox.ts +647 -0
  43. package/src/combobox/index.ts +6 -0
  44. package/src/combobox/intl/en-US.json +7 -0
  45. package/src/combobox/intl/es-ES.json +7 -0
  46. package/src/combobox/intl/index.ts +23 -0
  47. package/src/datepicker/createDateField.ts +154 -0
  48. package/src/datepicker/createDatePicker.ts +206 -0
  49. package/src/datepicker/createDateSegment.ts +229 -0
  50. package/src/datepicker/createTimeField.ts +154 -0
  51. package/src/datepicker/index.ts +28 -0
  52. package/src/dialog/createDialog.ts +120 -0
  53. package/src/dialog/index.ts +2 -0
  54. package/src/dialog/types.ts +19 -0
  55. package/src/disclosure/createDisclosure.ts +131 -0
  56. package/src/disclosure/createDisclosureGroup.ts +62 -0
  57. package/src/disclosure/index.ts +11 -0
  58. package/src/dnd/createDrag.ts +209 -0
  59. package/src/dnd/createDraggableCollection.ts +63 -0
  60. package/src/dnd/createDraggableItem.ts +243 -0
  61. package/src/dnd/createDrop.ts +321 -0
  62. package/src/dnd/createDroppableCollection.ts +293 -0
  63. package/src/dnd/createDroppableItem.ts +213 -0
  64. package/src/dnd/index.ts +47 -0
  65. package/src/dnd/types.ts +89 -0
  66. package/src/dnd/utils.ts +294 -0
  67. package/src/focus/FocusScope.tsx +408 -0
  68. package/src/focus/createAutoFocus.ts +321 -0
  69. package/src/focus/createFocusRestore.ts +313 -0
  70. package/src/focus/createVirtualFocus.ts +396 -0
  71. package/src/focus/index.ts +35 -0
  72. package/src/form/createFormReset.ts +51 -0
  73. package/src/form/createFormValidation.ts +224 -0
  74. package/src/form/index.ts +11 -0
  75. package/src/grid/GridKeyboardDelegate.ts +429 -0
  76. package/src/grid/createGrid.ts +261 -0
  77. package/src/grid/createGridCell.ts +182 -0
  78. package/src/grid/createGridRow.ts +153 -0
  79. package/src/grid/index.ts +18 -0
  80. package/src/grid/types.ts +133 -0
  81. package/src/gridlist/createGridList.ts +185 -0
  82. package/src/gridlist/createGridListItem.ts +180 -0
  83. package/src/gridlist/createGridListSelectionCheckbox.ts +59 -0
  84. package/src/gridlist/index.ts +16 -0
  85. package/src/gridlist/types.ts +81 -0
  86. package/src/i18n/NumberFormatter.ts +266 -0
  87. package/src/i18n/createCollator.ts +79 -0
  88. package/src/i18n/createDateFormatter.ts +83 -0
  89. package/src/i18n/createFilter.ts +131 -0
  90. package/src/i18n/createNumberFormatter.ts +52 -0
  91. package/src/i18n/createStringFormatter.ts +87 -0
  92. package/src/i18n/index.ts +40 -0
  93. package/src/i18n/locale.tsx +188 -0
  94. package/src/i18n/utils.ts +99 -0
  95. package/src/index.ts +670 -0
  96. package/src/interactions/FocusableProvider.tsx +44 -0
  97. package/src/interactions/PressEvent.ts +126 -0
  98. package/src/interactions/createFocus.ts +163 -0
  99. package/src/interactions/createFocusRing.ts +89 -0
  100. package/src/interactions/createFocusWithin.ts +206 -0
  101. package/src/interactions/createFocusable.ts +168 -0
  102. package/src/interactions/createHover.ts +254 -0
  103. package/src/interactions/createInteractionModality.ts +424 -0
  104. package/src/interactions/createKeyboard.ts +82 -0
  105. package/src/interactions/createLongPress.ts +174 -0
  106. package/src/interactions/createMove.ts +289 -0
  107. package/src/interactions/createPress.ts +834 -0
  108. package/src/interactions/index.ts +78 -0
  109. package/src/label/createField.ts +145 -0
  110. package/src/label/createLabel.ts +117 -0
  111. package/src/label/createLabels.ts +50 -0
  112. package/src/label/index.ts +19 -0
  113. package/src/landmark/createLandmark.ts +377 -0
  114. package/src/landmark/index.ts +8 -0
  115. package/src/link/createLink.ts +182 -0
  116. package/src/link/index.ts +1 -0
  117. package/src/listbox/createListBox.ts +269 -0
  118. package/src/listbox/createOption.ts +151 -0
  119. package/src/listbox/index.ts +12 -0
  120. package/src/live-announcer/announce.ts +322 -0
  121. package/src/live-announcer/index.ts +9 -0
  122. package/src/menu/createMenu.ts +396 -0
  123. package/src/menu/createMenuItem.ts +149 -0
  124. package/src/menu/createMenuTrigger.ts +88 -0
  125. package/src/menu/index.ts +18 -0
  126. package/src/meter/createMeter.ts +75 -0
  127. package/src/meter/index.ts +1 -0
  128. package/src/numberfield/createNumberField.ts +268 -0
  129. package/src/numberfield/index.ts +5 -0
  130. package/src/overlays/ariaHideOutside.ts +219 -0
  131. package/src/overlays/createInteractOutside.ts +149 -0
  132. package/src/overlays/createModal.tsx +202 -0
  133. package/src/overlays/createOverlay.ts +155 -0
  134. package/src/overlays/createOverlayTrigger.ts +85 -0
  135. package/src/overlays/createPreventScroll.ts +266 -0
  136. package/src/overlays/index.ts +44 -0
  137. package/src/popover/calculatePosition.ts +766 -0
  138. package/src/popover/createOverlayPosition.ts +356 -0
  139. package/src/popover/createPopover.ts +170 -0
  140. package/src/popover/index.ts +24 -0
  141. package/src/progress/createProgressBar.ts +128 -0
  142. package/src/progress/index.ts +5 -0
  143. package/src/radio/createRadio.ts +287 -0
  144. package/src/radio/createRadioGroup.ts +189 -0
  145. package/src/radio/createRadioGroupState.ts +201 -0
  146. package/src/radio/index.ts +23 -0
  147. package/src/searchfield/createSearchField.ts +186 -0
  148. package/src/searchfield/index.ts +2 -0
  149. package/src/select/createHiddenSelect.tsx +236 -0
  150. package/src/select/createSelect.ts +395 -0
  151. package/src/select/index.ts +14 -0
  152. package/src/selection/createTypeSelect.ts +201 -0
  153. package/src/selection/index.ts +6 -0
  154. package/src/separator/createSeparator.ts +82 -0
  155. package/src/separator/index.ts +6 -0
  156. package/src/slider/createSlider.ts +349 -0
  157. package/src/slider/index.ts +2 -0
  158. package/src/ssr/index.tsx +370 -0
  159. package/src/switch/createSwitch.ts +70 -0
  160. package/src/switch/index.ts +1 -0
  161. package/src/table/createTable.ts +526 -0
  162. package/src/table/createTableCell.ts +147 -0
  163. package/src/table/createTableColumnHeader.ts +115 -0
  164. package/src/table/createTableHeaderRow.ts +40 -0
  165. package/src/table/createTableRow.ts +155 -0
  166. package/src/table/createTableRowGroup.ts +32 -0
  167. package/src/table/createTableSelectAllCheckbox.ts +73 -0
  168. package/src/table/createTableSelectionCheckbox.ts +59 -0
  169. package/src/table/index.ts +30 -0
  170. package/src/table/types.ts +165 -0
  171. package/src/tabs/createTabs.ts +472 -0
  172. package/src/tabs/index.ts +14 -0
  173. package/src/tag/createTag.ts +194 -0
  174. package/src/tag/createTagGroup.ts +154 -0
  175. package/src/tag/index.ts +12 -0
  176. package/src/textfield/createTextField.ts +198 -0
  177. package/src/textfield/index.ts +5 -0
  178. package/src/toast/createToast.ts +118 -0
  179. package/src/toast/createToastRegion.ts +100 -0
  180. package/src/toast/index.ts +11 -0
  181. package/src/toggle/createToggle.ts +223 -0
  182. package/src/toggle/createToggleState.ts +94 -0
  183. package/src/toggle/index.ts +7 -0
  184. package/src/toolbar/createToolbar.ts +369 -0
  185. package/src/toolbar/index.ts +6 -0
  186. package/src/tooltip/createTooltip.ts +79 -0
  187. package/src/tooltip/createTooltipTrigger.ts +222 -0
  188. package/src/tooltip/index.ts +6 -0
  189. package/src/tree/createTree.ts +246 -0
  190. package/src/tree/createTreeItem.ts +233 -0
  191. package/src/tree/createTreeSelectionCheckbox.ts +68 -0
  192. package/src/tree/index.ts +16 -0
  193. package/src/tree/types.ts +87 -0
  194. package/src/utils/createDescription.ts +137 -0
  195. package/src/utils/dom.ts +327 -0
  196. package/src/utils/env.ts +54 -0
  197. package/src/utils/events.ts +106 -0
  198. package/src/utils/filterDOMProps.ts +116 -0
  199. package/src/utils/focus.ts +151 -0
  200. package/src/utils/geometry.ts +115 -0
  201. package/src/utils/globalListeners.ts +142 -0
  202. package/src/utils/index.ts +80 -0
  203. package/src/utils/mergeProps.ts +52 -0
  204. package/src/utils/platform.ts +52 -0
  205. package/src/utils/reactivity.ts +36 -0
  206. package/src/utils/textSelection.ts +114 -0
  207. package/src/visually-hidden/createVisuallyHidden.ts +124 -0
  208. package/src/visually-hidden/index.ts +6 -0
  209. package/dist/index.jsx +0 -15845
  210. package/dist/index.jsx.map +0 -7
@@ -0,0 +1,83 @@
1
+ /**
2
+ * createDateFormatter hook for solidaria
3
+ *
4
+ * Provides localized date formatting with caching.
5
+ *
6
+ * Port of @react-aria/i18n useDateFormatter.
7
+ */
8
+
9
+ import { createMemo } from 'solid-js';
10
+ import { useLocale } from './locale';
11
+ import { createCacheKey } from './utils';
12
+
13
+ // ============================================
14
+ // CACHE
15
+ // ============================================
16
+
17
+ const dateFormatterCache = new Map<string, Intl.DateTimeFormat>();
18
+
19
+ /**
20
+ * Gets or creates a cached date formatter.
21
+ */
22
+ function getCachedDateFormatter(
23
+ locale: string,
24
+ options?: Intl.DateTimeFormatOptions
25
+ ): Intl.DateTimeFormat {
26
+ const cacheKey = createCacheKey(locale, options as Record<string, unknown>);
27
+
28
+ if (dateFormatterCache.has(cacheKey)) {
29
+ return dateFormatterCache.get(cacheKey)!;
30
+ }
31
+
32
+ const formatter = new Intl.DateTimeFormat(locale, options);
33
+ dateFormatterCache.set(cacheKey, formatter);
34
+ return formatter;
35
+ }
36
+
37
+ // ============================================
38
+ // HOOK
39
+ // ============================================
40
+
41
+ /**
42
+ * Provides localized date and time formatting for the current locale.
43
+ * Automatically updates when the locale changes.
44
+ *
45
+ * @example
46
+ * ```tsx
47
+ * function DateDisplay(props: { date: Date }) {
48
+ * const formatter = createDateFormatter({
49
+ * year: 'numeric',
50
+ * month: 'long',
51
+ * day: 'numeric',
52
+ * });
53
+ *
54
+ * return <span>{formatter().format(props.date)}</span>;
55
+ * }
56
+ * ```
57
+ *
58
+ * @example
59
+ * ```tsx
60
+ * // Short date
61
+ * const shortDate = createDateFormatter({ dateStyle: 'short' });
62
+ * shortDate().format(new Date()); // '1/19/26' (US) or '19/01/26' (UK)
63
+ *
64
+ * // Full date with time
65
+ * const fullDateTime = createDateFormatter({
66
+ * dateStyle: 'full',
67
+ * timeStyle: 'short',
68
+ * });
69
+ *
70
+ * // Time only
71
+ * const timeFormatter = createDateFormatter({
72
+ * hour: 'numeric',
73
+ * minute: '2-digit',
74
+ * });
75
+ * ```
76
+ */
77
+ export function createDateFormatter(
78
+ options?: Intl.DateTimeFormatOptions
79
+ ): () => Intl.DateTimeFormat {
80
+ const locale = useLocale();
81
+
82
+ return createMemo(() => getCachedDateFormatter(locale().locale, options));
83
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * createFilter hook for solidaria
3
+ *
4
+ * Provides localized string filtering with collation support.
5
+ *
6
+ * Port of @react-aria/i18n useFilter.
7
+ */
8
+
9
+ import { createMemo } from 'solid-js';
10
+ import { createCollator } from './createCollator';
11
+
12
+ // ============================================
13
+ // TYPES
14
+ // ============================================
15
+
16
+ export interface Filter {
17
+ /** Returns whether a string starts with a given substring. */
18
+ startsWith(string: string, substring: string): boolean;
19
+ /** Returns whether a string ends with a given substring. */
20
+ endsWith(string: string, substring: string): boolean;
21
+ /** Returns whether a string contains a given substring. */
22
+ contains(string: string, substring: string): boolean;
23
+ }
24
+
25
+ // ============================================
26
+ // HOOK
27
+ // ============================================
28
+
29
+ /**
30
+ * Provides localized string search functionality for filtering or matching items.
31
+ * Respects locale-specific collation rules for case and diacritic sensitivity.
32
+ *
33
+ * @example
34
+ * ```tsx
35
+ * function SearchableList(props: { items: { name: string }[] }) {
36
+ * const [query, setQuery] = createSignal('');
37
+ * const filter = createFilter({ sensitivity: 'base' });
38
+ *
39
+ * const filteredItems = () =>
40
+ * props.items.filter((item) =>
41
+ * filter().contains(item.name, query())
42
+ * );
43
+ *
44
+ * return (
45
+ * <>
46
+ * <input
47
+ * value={query()}
48
+ * onInput={(e) => setQuery(e.target.value)}
49
+ * placeholder="Search..."
50
+ * />
51
+ * <ul>
52
+ * <For each={filteredItems()}>
53
+ * {(item) => <li>{item.name}</li>}
54
+ * </For>
55
+ * </ul>
56
+ * </>
57
+ * );
58
+ * }
59
+ * ```
60
+ *
61
+ * @example
62
+ * ```tsx
63
+ * // Case-insensitive, diacritic-insensitive filtering
64
+ * const filter = createFilter({ sensitivity: 'base' });
65
+ * filter().contains('Café', 'cafe'); // true
66
+ * filter().startsWith('Hello', 'hello'); // true
67
+ * ```
68
+ */
69
+ export function createFilter(options?: Intl.CollatorOptions): () => Filter {
70
+ const collator = createCollator({
71
+ usage: 'search',
72
+ ...options,
73
+ });
74
+
75
+ return createMemo(() => {
76
+ const coll = collator();
77
+
78
+ const startsWith = (str: string, substring: string): boolean => {
79
+ if (substring.length === 0) {
80
+ return true;
81
+ }
82
+
83
+ // Normalize both strings for safe slicing
84
+ const normalizedStr = str.normalize('NFC');
85
+ const normalizedSub = substring.normalize('NFC');
86
+
87
+ return (
88
+ coll.compare(normalizedStr.slice(0, normalizedSub.length), normalizedSub) ===
89
+ 0
90
+ );
91
+ };
92
+
93
+ const endsWith = (str: string, substring: string): boolean => {
94
+ if (substring.length === 0) {
95
+ return true;
96
+ }
97
+
98
+ const normalizedStr = str.normalize('NFC');
99
+ const normalizedSub = substring.normalize('NFC');
100
+
101
+ return (
102
+ coll.compare(normalizedStr.slice(-normalizedSub.length), normalizedSub) === 0
103
+ );
104
+ };
105
+
106
+ const contains = (str: string, substring: string): boolean => {
107
+ if (substring.length === 0) {
108
+ return true;
109
+ }
110
+
111
+ const normalizedStr = str.normalize('NFC');
112
+ const normalizedSub = substring.normalize('NFC');
113
+ const sliceLen = normalizedSub.length;
114
+
115
+ for (let scan = 0; scan + sliceLen <= normalizedStr.length; scan++) {
116
+ const slice = normalizedStr.slice(scan, scan + sliceLen);
117
+ if (coll.compare(normalizedSub, slice) === 0) {
118
+ return true;
119
+ }
120
+ }
121
+
122
+ return false;
123
+ };
124
+
125
+ return {
126
+ startsWith,
127
+ endsWith,
128
+ contains,
129
+ };
130
+ });
131
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * createNumberFormatter hook for solidaria
3
+ *
4
+ * Provides localized number formatting with automatic locale updates.
5
+ *
6
+ * Port of @react-aria/i18n useNumberFormatter.
7
+ */
8
+
9
+ import { createMemo } from 'solid-js';
10
+ import { useLocale } from './locale';
11
+ import { NumberFormatter, type NumberFormatOptions } from './NumberFormatter';
12
+
13
+ /**
14
+ * Provides localized number formatting for the current locale.
15
+ * Automatically updates when the locale changes.
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * function PriceDisplay(props: { value: number }) {
20
+ * const formatter = createNumberFormatter({
21
+ * style: 'currency',
22
+ * currency: 'USD',
23
+ * });
24
+ *
25
+ * return <span>{formatter().format(props.value)}</span>;
26
+ * }
27
+ * ```
28
+ *
29
+ * @example
30
+ * ```tsx
31
+ * // Percent formatting
32
+ * const percentFormatter = createNumberFormatter({
33
+ * style: 'percent',
34
+ * minimumFractionDigits: 1,
35
+ * });
36
+ * percentFormatter().format(0.125); // '12.5%'
37
+ *
38
+ * // Unit formatting
39
+ * const tempFormatter = createNumberFormatter({
40
+ * style: 'unit',
41
+ * unit: 'celsius',
42
+ * });
43
+ * tempFormatter().format(25); // '25°C'
44
+ * ```
45
+ */
46
+ export function createNumberFormatter(
47
+ options: NumberFormatOptions = {}
48
+ ): () => NumberFormatter {
49
+ const locale = useLocale();
50
+
51
+ return createMemo(() => new NumberFormatter(locale().locale, options));
52
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * String formatter for localized strings with ICU MessageFormat support.
3
+ * Port of @react-aria/i18n useLocalizedStringFormatter.
4
+ */
5
+
6
+ import {
7
+ LocalizedString,
8
+ LocalizedStringDictionary,
9
+ LocalizedStringFormatter,
10
+ type LocalizedStrings,
11
+ } from '@internationalized/string';
12
+ import { createMemo, type Accessor } from 'solid-js';
13
+ import { useLocale } from './locale';
14
+
15
+ // Cache for dictionaries to avoid recreating them
16
+ const cache = new WeakMap<LocalizedStrings<string, LocalizedString>, LocalizedStringDictionary<string, LocalizedString>>();
17
+
18
+ function getCachedDictionary<K extends string, T extends LocalizedString>(
19
+ strings: LocalizedStrings<K, T>
20
+ ): LocalizedStringDictionary<K, T> {
21
+ let dictionary = cache.get(strings as LocalizedStrings<string, LocalizedString>);
22
+ if (!dictionary) {
23
+ dictionary = new LocalizedStringDictionary(strings as LocalizedStrings<string, LocalizedString>);
24
+ cache.set(strings as LocalizedStrings<string, LocalizedString>, dictionary);
25
+ }
26
+ return dictionary as LocalizedStringDictionary<K, T>;
27
+ }
28
+
29
+ /**
30
+ * Returns a cached LocalizedStringDictionary for the given strings.
31
+ */
32
+ export function createStringDictionary<K extends string = string, T extends LocalizedString = string>(
33
+ strings: LocalizedStrings<K, T>,
34
+ packageName?: string
35
+ ): LocalizedStringDictionary<K, T> {
36
+ return (
37
+ (packageName && LocalizedStringDictionary.getGlobalDictionaryForPackage(packageName)) ||
38
+ getCachedDictionary(strings)
39
+ );
40
+ }
41
+
42
+ /**
43
+ * Provides localized string formatting for the current locale.
44
+ * Supports interpolating variables, selecting the correct pluralization,
45
+ * and formatting numbers. Automatically updates when the locale changes.
46
+ *
47
+ * @param strings - A mapping of languages to localized strings by key.
48
+ * @param packageName - Optional package name for global dictionary lookup.
49
+ *
50
+ * @example
51
+ * ```tsx
52
+ * const strings = {
53
+ * 'en-US': {
54
+ * greeting: 'Hello, {name}!',
55
+ * count: '{count, plural, one {# item} other {# items}}'
56
+ * }
57
+ * };
58
+ *
59
+ * function MyComponent() {
60
+ * const stringFormatter = createStringFormatter(strings);
61
+ *
62
+ * return (
63
+ * <div>
64
+ * {stringFormatter().format('greeting', { name: 'World' })}
65
+ * {stringFormatter().format('count', { count: 5 })}
66
+ * </div>
67
+ * );
68
+ * }
69
+ * ```
70
+ */
71
+ export function createStringFormatter<K extends string = string, T extends LocalizedString = string>(
72
+ strings: LocalizedStrings<K, T>,
73
+ packageName?: string
74
+ ): Accessor<LocalizedStringFormatter<K, T>> {
75
+ const localeAccessor = useLocale();
76
+ const dictionary = createStringDictionary(strings, packageName);
77
+
78
+ return createMemo(() => new LocalizedStringFormatter(localeAccessor().locale, dictionary));
79
+ }
80
+
81
+ // Re-export types for convenience
82
+ export type {
83
+ LocalizedString,
84
+ LocalizedStringDictionary,
85
+ LocalizedStringFormatter,
86
+ LocalizedStrings,
87
+ };
@@ -0,0 +1,40 @@
1
+ // Locale context and provider
2
+ export {
3
+ I18nProvider,
4
+ useLocale,
5
+ createDefaultLocale,
6
+ getDefaultLocale,
7
+ type Direction,
8
+ type Locale,
9
+ type I18nProviderProps,
10
+ } from './locale';
11
+
12
+ // RTL utilities
13
+ export { isRTL, createCacheKey } from './utils';
14
+
15
+ // Number formatting
16
+ export {
17
+ NumberFormatter,
18
+ type NumberFormatOptions,
19
+ } from './NumberFormatter';
20
+
21
+ export { createNumberFormatter } from './createNumberFormatter';
22
+
23
+ // Date formatting
24
+ export { createDateFormatter } from './createDateFormatter';
25
+
26
+ // String collation
27
+ export { createCollator } from './createCollator';
28
+
29
+ // String filtering
30
+ export { createFilter, type Filter } from './createFilter';
31
+
32
+ // String formatting (ICU MessageFormat)
33
+ export {
34
+ createStringFormatter,
35
+ createStringDictionary,
36
+ type LocalizedString,
37
+ type LocalizedStringDictionary,
38
+ type LocalizedStringFormatter,
39
+ type LocalizedStrings,
40
+ } from './createStringFormatter';
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Locale context and provider for solidaria
3
+ *
4
+ * Provides locale and text direction to the component tree.
5
+ *
6
+ * Port of @react-aria/i18n context and useDefaultLocale.
7
+ */
8
+
9
+ import {
10
+ type Accessor,
11
+ type JSX,
12
+ type ParentProps,
13
+ createContext,
14
+ createEffect,
15
+ createMemo,
16
+ createSignal,
17
+ onCleanup,
18
+ useContext,
19
+ } from 'solid-js';
20
+ import { isRTL } from './utils';
21
+
22
+ // ============================================
23
+ // TYPES
24
+ // ============================================
25
+
26
+ /** Text direction: left-to-right or right-to-left. */
27
+ export type Direction = 'ltr' | 'rtl';
28
+
29
+ /** Locale information including language code and text direction. */
30
+ export interface Locale {
31
+ /** The BCP47 language code for the locale (e.g., 'en-US', 'ar-SA'). */
32
+ locale: string;
33
+ /** The writing direction for the locale. */
34
+ direction: Direction;
35
+ }
36
+
37
+ export interface I18nProviderProps extends ParentProps {
38
+ /** The locale to apply to the children. If not provided, uses browser default. */
39
+ locale?: string;
40
+ }
41
+
42
+ // ============================================
43
+ // GLOBAL STATE
44
+ // ============================================
45
+
46
+ // Symbol for server-provided locale
47
+ const localeSymbol = Symbol.for('solidaria.i18n.locale');
48
+
49
+ let currentLocale: Locale | null = null;
50
+ const listeners = new Set<(locale: Locale) => void>();
51
+
52
+ /**
53
+ * Gets the default locale from the browser/system.
54
+ */
55
+ export function getDefaultLocale(): Locale {
56
+ let locale =
57
+ (typeof window !== 'undefined' &&
58
+ (window as unknown as Record<symbol, string>)[localeSymbol]) ||
59
+ (typeof navigator !== 'undefined' &&
60
+ (navigator.language ||
61
+ (navigator as unknown as { userLanguage?: string }).userLanguage)) ||
62
+ 'en-US';
63
+
64
+ // Validate the locale is supported
65
+ try {
66
+ Intl.DateTimeFormat.supportedLocalesOf([locale]);
67
+ } catch {
68
+ locale = 'en-US';
69
+ }
70
+
71
+ return {
72
+ locale,
73
+ direction: isRTL(locale) ? 'rtl' : 'ltr',
74
+ };
75
+ }
76
+
77
+ function updateLocale(): void {
78
+ currentLocale = getDefaultLocale();
79
+ for (const listener of listeners) {
80
+ listener(currentLocale);
81
+ }
82
+ }
83
+
84
+ // ============================================
85
+ // CONTEXT
86
+ // ============================================
87
+
88
+ const I18nContext = createContext<Accessor<Locale> | null>(null);
89
+
90
+ // ============================================
91
+ // HOOKS
92
+ // ============================================
93
+
94
+ /**
95
+ * Returns the current browser/system locale, and updates when it changes.
96
+ *
97
+ * @example
98
+ * ```tsx
99
+ * const locale = createDefaultLocale();
100
+ * console.log(locale().locale); // 'en-US'
101
+ * console.log(locale().direction); // 'ltr'
102
+ * ```
103
+ */
104
+ export function createDefaultLocale(): Accessor<Locale> {
105
+ if (!currentLocale) {
106
+ currentLocale = getDefaultLocale();
107
+ }
108
+
109
+ const [locale, setLocale] = createSignal<Locale>(currentLocale);
110
+
111
+ createEffect(() => {
112
+ if (typeof window === 'undefined') {
113
+ return;
114
+ }
115
+
116
+ if (listeners.size === 0) {
117
+ window.addEventListener('languagechange', updateLocale);
118
+ }
119
+
120
+ listeners.add(setLocale);
121
+
122
+ onCleanup(() => {
123
+ listeners.delete(setLocale);
124
+ if (listeners.size === 0) {
125
+ window.removeEventListener('languagechange', updateLocale);
126
+ }
127
+ });
128
+ });
129
+
130
+ return locale;
131
+ }
132
+
133
+ /**
134
+ * Returns the current locale and layout direction from context or browser default.
135
+ *
136
+ * @example
137
+ * ```tsx
138
+ * function MyComponent() {
139
+ * const locale = useLocale();
140
+ * return <div dir={locale().direction}>{locale().locale}</div>;
141
+ * }
142
+ * ```
143
+ */
144
+ export function useLocale(): Accessor<Locale> {
145
+ const context = useContext(I18nContext);
146
+ const defaultLocale = createDefaultLocale();
147
+ return context || defaultLocale;
148
+ }
149
+
150
+ // ============================================
151
+ // PROVIDER
152
+ // ============================================
153
+
154
+ /**
155
+ * Provides the locale for the application to all child components.
156
+ *
157
+ * @example
158
+ * ```tsx
159
+ * // Use browser default locale
160
+ * <I18nProvider>
161
+ * <App />
162
+ * </I18nProvider>
163
+ *
164
+ * // Override with specific locale
165
+ * <I18nProvider locale="ar-SA">
166
+ * <App /> // Will have RTL direction
167
+ * </I18nProvider>
168
+ * ```
169
+ */
170
+ export function I18nProvider(props: I18nProviderProps): JSX.Element {
171
+ const defaultLocale = createDefaultLocale();
172
+
173
+ const locale = createMemo<Locale>(() => {
174
+ if (props.locale) {
175
+ return {
176
+ locale: props.locale,
177
+ direction: isRTL(props.locale) ? 'rtl' : 'ltr',
178
+ };
179
+ }
180
+ return defaultLocale();
181
+ });
182
+
183
+ return (
184
+ <I18nContext.Provider value={locale}>
185
+ {props.children}
186
+ </I18nContext.Provider>
187
+ );
188
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * i18n utilities for solidaria
3
+ *
4
+ * RTL detection and locale utilities.
5
+ *
6
+ * Port of @react-aria/i18n utils.
7
+ */
8
+
9
+ // https://en.wikipedia.org/wiki/Right-to-left
10
+ const RTL_SCRIPTS = new Set([
11
+ 'Arab',
12
+ 'Syrc',
13
+ 'Samr',
14
+ 'Mand',
15
+ 'Thaa',
16
+ 'Mend',
17
+ 'Nkoo',
18
+ 'Adlm',
19
+ 'Rohg',
20
+ 'Hebr',
21
+ ]);
22
+
23
+ const RTL_LANGS = new Set([
24
+ 'ae',
25
+ 'ar',
26
+ 'arc',
27
+ 'bcc',
28
+ 'bqi',
29
+ 'ckb',
30
+ 'dv',
31
+ 'fa',
32
+ 'glk',
33
+ 'he',
34
+ 'ku',
35
+ 'mzn',
36
+ 'nqo',
37
+ 'pnb',
38
+ 'ps',
39
+ 'sd',
40
+ 'ug',
41
+ 'ur',
42
+ 'yi',
43
+ ]);
44
+
45
+ /**
46
+ * Determines if a locale is read right to left.
47
+ * Uses Intl.Locale API when available for accurate detection.
48
+ */
49
+ export function isRTL(localeString: string): boolean {
50
+ // If the Intl.Locale API is available, use it to get the locale's text direction.
51
+ if (typeof Intl !== 'undefined' && Intl.Locale) {
52
+ try {
53
+ const locale = new Intl.Locale(localeString).maximize();
54
+
55
+ // Use the text info object to get the direction if possible.
56
+ // getTextInfo() was implemented as a property by some browsers before it was standardized as a function.
57
+ const localeAny = locale as unknown as {
58
+ getTextInfo?: () => { direction: string };
59
+ textInfo?: { direction: string };
60
+ };
61
+ const textInfo = typeof localeAny.getTextInfo === 'function'
62
+ ? localeAny.getTextInfo()
63
+ : localeAny.textInfo;
64
+
65
+ if (textInfo) {
66
+ return textInfo.direction === 'rtl';
67
+ }
68
+
69
+ // Fallback: guess using the script.
70
+ if (locale.script) {
71
+ return RTL_SCRIPTS.has(locale.script);
72
+ }
73
+ } catch {
74
+ // Fall through to language-based detection
75
+ }
76
+ }
77
+
78
+ // If not, just guess by the language (first part of the locale)
79
+ const lang = localeString.split('-')[0];
80
+ return RTL_LANGS.has(lang);
81
+ }
82
+
83
+ /**
84
+ * Creates a cache key for formatter options.
85
+ */
86
+ export function createCacheKey(
87
+ locale: string,
88
+ options?: Record<string, unknown>
89
+ ): string {
90
+ if (!options) {
91
+ return locale;
92
+ }
93
+ return (
94
+ locale +
95
+ Object.entries(options)
96
+ .sort((a, b) => (a[0] < b[0] ? -1 : 1))
97
+ .join()
98
+ );
99
+ }