@mortenbrudvik/waveui 0.9.0 → 0.10.0

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 (105) hide show
  1. package/README.md +425 -104
  2. package/dist/components/input/Calendar.cjs +2 -0
  3. package/dist/components/input/Calendar.cjs.map +1 -0
  4. package/dist/components/input/Calendar.mjs +352 -0
  5. package/dist/components/input/Calendar.mjs.map +1 -0
  6. package/dist/components/input/Calendar.views.cjs +2 -0
  7. package/dist/components/input/Calendar.views.cjs.map +1 -0
  8. package/dist/components/input/Calendar.views.mjs +194 -0
  9. package/dist/components/input/Calendar.views.mjs.map +1 -0
  10. package/dist/components/input/ColorArea.cjs +1 -1
  11. package/dist/components/input/ColorArea.cjs.map +1 -1
  12. package/dist/components/input/ColorArea.mjs +18 -18
  13. package/dist/components/input/ColorArea.mjs.map +1 -1
  14. package/dist/components/input/Combobox.cjs +1 -1
  15. package/dist/components/input/Combobox.cjs.map +1 -1
  16. package/dist/components/input/Combobox.mjs +232 -156
  17. package/dist/components/input/Combobox.mjs.map +1 -1
  18. package/dist/components/input/Combobox.text.cjs +2 -0
  19. package/dist/components/input/Combobox.text.cjs.map +1 -0
  20. package/dist/components/input/Combobox.text.mjs +24 -0
  21. package/dist/components/input/Combobox.text.mjs.map +1 -0
  22. package/dist/components/input/DatePicker.cjs +1 -1
  23. package/dist/components/input/DatePicker.cjs.map +1 -1
  24. package/dist/components/input/DatePicker.mjs +203 -288
  25. package/dist/components/input/DatePicker.mjs.map +1 -1
  26. package/dist/components/input/Dropdown.cjs +1 -1
  27. package/dist/components/input/Dropdown.cjs.map +1 -1
  28. package/dist/components/input/Dropdown.mjs +135 -109
  29. package/dist/components/input/Dropdown.mjs.map +1 -1
  30. package/dist/components/input/Listbox.cjs +2 -0
  31. package/dist/components/input/Listbox.cjs.map +1 -0
  32. package/dist/components/input/Listbox.mjs +134 -0
  33. package/dist/components/input/Listbox.mjs.map +1 -0
  34. package/dist/components/input/Option.cjs +1 -1
  35. package/dist/components/input/Option.cjs.map +1 -1
  36. package/dist/components/input/Option.mjs +158 -97
  37. package/dist/components/input/Option.mjs.map +1 -1
  38. package/dist/components/input/SwatchPicker.cjs +1 -1
  39. package/dist/components/input/SwatchPicker.cjs.map +1 -1
  40. package/dist/components/input/SwatchPicker.context.cjs +2 -0
  41. package/dist/components/input/SwatchPicker.context.cjs.map +1 -0
  42. package/dist/components/input/SwatchPicker.context.mjs +35 -0
  43. package/dist/components/input/SwatchPicker.context.mjs.map +1 -0
  44. package/dist/components/input/SwatchPicker.mjs +131 -103
  45. package/dist/components/input/SwatchPicker.mjs.map +1 -1
  46. package/dist/components/input/SwatchPicker.swatches.cjs +2 -0
  47. package/dist/components/input/SwatchPicker.swatches.cjs.map +1 -0
  48. package/dist/components/input/SwatchPicker.swatches.mjs +207 -0
  49. package/dist/components/input/SwatchPicker.swatches.mjs.map +1 -0
  50. package/dist/components/input/TagPicker.cjs +1 -1
  51. package/dist/components/input/TagPicker.cjs.map +1 -1
  52. package/dist/components/input/TagPicker.mjs +144 -139
  53. package/dist/components/input/TagPicker.mjs.map +1 -1
  54. package/dist/components/input/TimePicker.cjs +1 -1
  55. package/dist/components/input/TimePicker.cjs.map +1 -1
  56. package/dist/components/input/TimePicker.mjs +77 -77
  57. package/dist/components/input/TimePicker.mjs.map +1 -1
  58. package/dist/components/input/dateUtils.cjs +1 -1
  59. package/dist/components/input/dateUtils.cjs.map +1 -1
  60. package/dist/components/input/dateUtils.mjs +114 -96
  61. package/dist/components/input/dateUtils.mjs.map +1 -1
  62. package/dist/components/input/pickerLabels.cjs +2 -0
  63. package/dist/components/input/pickerLabels.cjs.map +1 -0
  64. package/dist/components/input/pickerLabels.mjs +16 -0
  65. package/dist/components/input/pickerLabels.mjs.map +1 -0
  66. package/dist/components/input/pickerValues.cjs +2 -0
  67. package/dist/components/input/pickerValues.cjs.map +1 -0
  68. package/dist/components/input/pickerValues.mjs +24 -0
  69. package/dist/components/input/pickerValues.mjs.map +1 -0
  70. package/dist/components/table/useGridNavigation.cjs +1 -1
  71. package/dist/components/table/useGridNavigation.cjs.map +1 -1
  72. package/dist/components/table/useGridNavigation.mjs +67 -70
  73. package/dist/components/table/useGridNavigation.mjs.map +1 -1
  74. package/dist/hooks/useActiveDescendant.cjs +2 -0
  75. package/dist/hooks/useActiveDescendant.cjs.map +1 -0
  76. package/dist/hooks/useActiveDescendant.mjs +56 -0
  77. package/dist/hooks/useActiveDescendant.mjs.map +1 -0
  78. package/dist/hooks/useListbox.cjs +1 -1
  79. package/dist/hooks/useListbox.cjs.map +1 -1
  80. package/dist/hooks/useListbox.mjs +250 -210
  81. package/dist/hooks/useListbox.mjs.map +1 -1
  82. package/dist/hooks/useRovingGrid.cjs +2 -0
  83. package/dist/hooks/useRovingGrid.cjs.map +1 -0
  84. package/dist/hooks/useRovingGrid.mjs +69 -0
  85. package/dist/hooks/useRovingGrid.mjs.map +1 -0
  86. package/dist/index.cjs +1 -1
  87. package/dist/index.d.cts +2147 -155
  88. package/dist/index.d.ts +2147 -155
  89. package/dist/index.mjs +107 -101
  90. package/dist/lib/classNames.cjs +1 -1
  91. package/dist/lib/classNames.cjs.map +1 -1
  92. package/dist/lib/classNames.mjs +84 -48
  93. package/dist/lib/classNames.mjs.map +1 -1
  94. package/dist/lib/date.cjs +2 -0
  95. package/dist/lib/date.cjs.map +1 -0
  96. package/dist/lib/date.mjs +44 -0
  97. package/dist/lib/date.mjs.map +1 -0
  98. package/dist/lib/focus.cjs +1 -1
  99. package/dist/lib/focus.cjs.map +1 -1
  100. package/dist/lib/focus.mjs +5 -2
  101. package/dist/lib/focus.mjs.map +1 -1
  102. package/dist/lib/layers.cjs.map +1 -1
  103. package/dist/lib/layers.mjs.map +1 -1
  104. package/dist/styles.css +1 -1
  105. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"dateUtils.mjs","names":["makeDate","date","year","monthIndex","day","startOfDay","startOfMonth","daysInMonth","addDays","amount","addMonths","target","isSameDay","a","b","isSameMonth","isDateInRange","min","max","clampDate","getCalendarDays","first","viewMonth","offset","firstDayOfWeek","days","i","pad","value","length","formatISODate","fromFields","month","parseISODate","match","text","isValidLocaleTag","locale","DEFAULT_DATE_OPTIONS","GRID_CALENDARS","calendarCache","getCalendar","key","calendar","resolved","formatterCache","getFormatter","options","formatter","withCalendar","BIDI_MARKS","infoCache","SAMPLE_DATE","toAsciiDigits","out","char","digits","getLocaleInfo","cached","numberFormat","parts","order","literals","pattern","yearOffset","yearLength","part","info","getLocaleDateFormat","formatDate","digitGroups","normalized","groups","parseDate","iso","values","field","roundTrip","group","getWeekdayNames","short","long","formatMonthYear","formatDayLabel","numberFormatCache","formatDayNumber","TIME_24H","TIME_12H","timeToMinutes","twelve","hours","minutes","seconds","pm","minutesToValue","minutesToTime","format","period","normalizeTimeStep","step","generateTimeOptions","increment","minTime","maxTime"],"sources":["../../../src/components/input/dateUtils.ts"],"sourcesContent":["/**\n * Date and time helpers shared by `DatePicker` and `TimePicker` (spec §5.6).\n *\n * - Every date these helpers return is a **local-midnight** `Date` (the start of a calendar day in\n * the runtime's time zone). Comparisons are by calendar day, never by time of day.\n * - The default display format is `Intl.DateTimeFormat(locale, { year: 'numeric', month:\n * '2-digit', day: '2-digit' })`, and {@link parseDate} is its exact inverse for the same\n * `locale` (field order from `formatToParts`), so a formatted date always parses back to the\n * same day. ISO `yyyy-mm-dd` text is accepted too.\n * - Every formatter uses the locale's calendar only when it has the Gregorian months and days of\n * the grid (Gregorian, or the Buddhist calendar of `th-TH`, whose years are offset); any other\n * calendar (the Persian default of `fa-IR`, `-u-ca-islamic`, Japanese eras) is replaced by the\n * Gregorian one, so the text, the heading and the day labels name the day the grid shows.\n * - Pass an explicit `locale` when rendering on the server: the runtime default locale of the\n * server and the browser may differ, which would change the formatted text between them.\n *\n * Internal module (not exported from the package).\n */\n\n/* ------------------------------------------------------------------ */\n/* Day arithmetic */\n/* ------------------------------------------------------------------ */\n\n/**\n * Builds a local-midnight date without the `new Date(y, …)` two-digit-year mapping (years 0–99\n * would otherwise become 1900–1999).\n */\nfunction makeDate(year: number, monthIndex: number, day: number): Date {\n const date = new Date(2000, 0, 1);\n date.setFullYear(year, monthIndex, day);\n date.setHours(0, 0, 0, 0);\n return date;\n}\n\n/** The start (local midnight) of `date`'s calendar day, as a new `Date`. */\nexport function startOfDay(date: Date): Date {\n return makeDate(date.getFullYear(), date.getMonth(), date.getDate());\n}\n\n/** The first day (local midnight) of `date`'s month. */\nexport function startOfMonth(date: Date): Date {\n return makeDate(date.getFullYear(), date.getMonth(), 1);\n}\n\n/** The number of days in the given month (`monthIndex` may be out of range; it is normalized). */\nfunction daysInMonth(year: number, monthIndex: number): number {\n return makeDate(year, monthIndex + 1, 0).getDate();\n}\n\n/** `date` plus `amount` calendar days, at local midnight (month/year boundaries included). */\nexport function addDays(date: Date, amount: number): Date {\n return makeDate(date.getFullYear(), date.getMonth(), date.getDate() + amount);\n}\n\n/**\n * `date` plus `amount` months, at local midnight. The day is clamped to the length of the target\n * month, so Jan 31 + 1 month is Feb 28 (29 in a leap year) and Mar 31 − 1 month is Feb 28/29 —\n * never a day of the following month.\n */\nexport function addMonths(date: Date, amount: number): Date {\n const target = makeDate(date.getFullYear(), date.getMonth() + amount, 1);\n const day = Math.min(date.getDate(), daysInMonth(target.getFullYear(), target.getMonth()));\n return makeDate(target.getFullYear(), target.getMonth(), day);\n}\n\n/** Whether both dates are on the same calendar day. `false` when either is missing. */\nexport function isSameDay(a: Date | null | undefined, b: Date | null | undefined): boolean {\n return (\n !!a &&\n !!b &&\n a.getFullYear() === b.getFullYear() &&\n a.getMonth() === b.getMonth() &&\n a.getDate() === b.getDate()\n );\n}\n\n/** Whether both dates are in the same month of the same year. */\nexport function isSameMonth(a: Date, b: Date): boolean {\n return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth();\n}\n\n/**\n * Whether `date`'s day lies within `[min, max]`, comparing calendar days (a `max` with a time of\n * day still includes that whole day). Missing bounds are open.\n */\nexport function isDateInRange(date: Date, min?: Date | null, max?: Date | null): boolean {\n const day = startOfDay(date).getTime();\n if (min && day < startOfDay(min).getTime()) return false;\n if (max && day > startOfDay(max).getTime()) return false;\n return true;\n}\n\n/** `date`'s day clamped into `[min, max]` (calendar days), at local midnight. */\nexport function clampDate(date: Date, min?: Date | null, max?: Date | null): Date {\n const day = startOfDay(date);\n if (min && day.getTime() < startOfDay(min).getTime()) return startOfDay(min);\n if (max && day.getTime() > startOfDay(max).getTime()) return startOfDay(max);\n return day;\n}\n\n/**\n * The 42 days (6 weeks) of the calendar grid showing `viewMonth`'s month: the first row starts on\n * `firstDayOfWeek` (0 = Sunday) on or before the 1st.\n */\nexport function getCalendarDays(viewMonth: Date, firstDayOfWeek: number): Date[] {\n const first = startOfMonth(viewMonth);\n const offset = (first.getDay() - firstDayOfWeek + 7) % 7;\n const days: Date[] = [];\n for (let i = 0; i < 42; i++) days.push(addDays(first, i - offset));\n return days;\n}\n\n/* ------------------------------------------------------------------ */\n/* ISO dates */\n/* ------------------------------------------------------------------ */\n\nfunction pad(value: number, length: number): string {\n return String(value).padStart(length, '0');\n}\n\n/** `yyyy-mm-dd` from the **local** calendar fields (the `HiddenInput` form value). */\nexport function formatISODate(date: Date): string {\n return `${pad(date.getFullYear(), 4)}-${pad(date.getMonth() + 1, 2)}-${pad(date.getDate(), 2)}`;\n}\n\n/** Builds a date from calendar fields; `null` when they do not name a real day. */\nfunction fromFields(year: number, month: number, day: number): Date | null {\n if (!Number.isInteger(year) || year < 1 || year > 9999) return null;\n if (!Number.isInteger(month) || month < 1 || month > 12) return null;\n if (!Number.isInteger(day) || day < 1 || day > daysInMonth(year, month - 1)) return null;\n return makeDate(year, month - 1, day);\n}\n\n/** Parses `yyyy-mm-dd` (surrounding whitespace allowed) to local midnight; `null` otherwise. */\nexport function parseISODate(text: string): Date | null {\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(text.trim());\n if (!match) return null;\n return fromFields(Number(match[1]), Number(match[2]), Number(match[3]));\n}\n\n/* ------------------------------------------------------------------ */\n/* Locale formats */\n/* ------------------------------------------------------------------ */\n\n/** A numeric date field. */\nexport type DateField = 'year' | 'month' | 'day';\n\n/** How a locale writes the default (numeric) date format. */\nexport interface LocaleDateFormat {\n /** The order of the fields, e.g. `['day', 'month', 'year']` for `en-GB`. */\n order: DateField[];\n /** A readable pattern with the locale's own separators, e.g. `DD/MM/YYYY`. */\n pattern: string;\n}\n\n/**\n * Whether `Intl` accepts `locale` as a language tag. Every helper below falls back to the runtime\n * default locale for a tag it rejects (the POSIX `de_DE`, `''`, a typo), so DatePicker warns about\n * one in development.\n */\nexport function isValidLocaleTag(locale: string): boolean {\n try {\n Intl.getCanonicalLocales(locale);\n return true;\n } catch {\n return false;\n }\n}\n\nconst DEFAULT_DATE_OPTIONS: Intl.DateTimeFormatOptions = {\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n};\n\n/**\n * Calendars with the Gregorian months and days, which the grid shows (`getCalendarDays`): a locale\n * whose calendar is one of these keeps it. The Buddhist calendar (default of `th-TH`) only numbers\n * the years differently.\n */\nconst GRID_CALENDARS = new Set(['gregory', 'buddhist']);\n\nconst calendarCache = new Map<string, string>();\n\n/**\n * The calendar every date helper formats `locale` in: the locale's own (its default or a `-u-ca-`\n * extension) when it has the Gregorian months and days, else `'gregory'`. So a Persian default\n * (`fa-IR`, `fa-AF`, `ps-AF`), an explicit `-u-ca-islamic` or the Japanese eras never label the\n * Gregorian grid with other months or years, and the text stays parseable; the locale's digits\n * stay. An invalid locale tag uses the runtime default's.\n */\nfunction getCalendar(locale: string | undefined): string {\n const key = locale ?? '';\n let calendar = calendarCache.get(key);\n if (calendar === undefined) {\n let resolved: string;\n try {\n resolved = new Intl.DateTimeFormat(locale).resolvedOptions().calendar;\n } catch {\n resolved = new Intl.DateTimeFormat().resolvedOptions().calendar;\n }\n calendar = GRID_CALENDARS.has(resolved) ? resolved : 'gregory';\n calendarCache.set(key, calendar);\n }\n return calendar;\n}\n\nconst formatterCache = new Map<string, Intl.DateTimeFormat>();\n\n/**\n * A cached `Intl.DateTimeFormat` in the calendar of {@link getCalendar}; an invalid locale tag falls\n * back to the runtime default.\n */\nfunction getFormatter(\n locale: string | undefined,\n options: Intl.DateTimeFormatOptions,\n): Intl.DateTimeFormat {\n const key = `${locale ?? ''}|${JSON.stringify(options)}`;\n let formatter = formatterCache.get(key);\n if (!formatter) {\n const withCalendar = { ...options, calendar: getCalendar(locale) };\n try {\n formatter = new Intl.DateTimeFormat(locale, withCalendar);\n } catch {\n formatter = new Intl.DateTimeFormat(undefined, withCalendar);\n }\n formatterCache.set(key, formatter);\n }\n return formatter;\n}\n\n/** Bidirectional control characters Intl inserts around fields (Arabic, Hebrew locales). */\nconst BIDI_MARKS = /[\\u061c\\u200b-\\u200f\\u202a-\\u202e\\u2066-\\u2069]/g;\n\ninterface LocaleInfo extends LocaleDateFormat {\n /** Non-digit characters of the formatted date (allowed between the typed fields). */\n literals: Set<string>;\n /** Locale digit → ASCII digit (for non-Latin numbering systems). */\n digits: Map<string, string>;\n /** Formatted year minus the Gregorian year (e.g. 543 for the Buddhist calendar). */\n yearOffset: number;\n /** Digit count of a formatted year: shorter typed years are rejected (`25` is not 2025). */\n yearLength: number;\n}\n\nconst infoCache = new Map<string, LocaleInfo>();\nconst SAMPLE_DATE = makeDate(2001, 10, 23); // 23 Nov 2001: every field distinct\n\nfunction toAsciiDigits(text: string, digits: Map<string, string>): string {\n let out = '';\n for (const char of text.replace(BIDI_MARKS, '')) out += digits.get(char) ?? char;\n return out;\n}\n\nfunction getLocaleInfo(locale: string | undefined): LocaleInfo {\n const key = locale ?? '';\n const cached = infoCache.get(key);\n if (cached) return cached;\n\n const digits = new Map<string, string>();\n let numberFormat: Intl.NumberFormat;\n try {\n numberFormat = new Intl.NumberFormat(locale, { useGrouping: false });\n } catch {\n numberFormat = new Intl.NumberFormat(undefined, { useGrouping: false });\n }\n for (let i = 0; i <= 9; i++) digits.set(numberFormat.format(i), String(i));\n\n const parts = getFormatter(locale, DEFAULT_DATE_OPTIONS).formatToParts(SAMPLE_DATE);\n const order: DateField[] = [];\n const literals = new Set<string>();\n let pattern = '';\n let yearOffset = 0;\n let yearLength = 4;\n for (const part of parts) {\n if (part.type === 'day' || part.type === 'month' || part.type === 'year') {\n order.push(part.type);\n if (part.type === 'year') {\n const year = toAsciiDigits(part.value, digits);\n yearLength = year.length;\n yearOffset = Number(year) - SAMPLE_DATE.getFullYear();\n }\n pattern += part.type === 'day' ? 'DD' : part.type === 'month' ? 'MM' : 'YYYY';\n } else {\n const text = part.value.replace(BIDI_MARKS, '');\n pattern += text;\n for (const char of text) literals.add(char);\n }\n }\n const info: LocaleInfo = {\n order,\n pattern: pattern.trim(),\n literals,\n digits,\n yearOffset,\n yearLength,\n };\n infoCache.set(key, info);\n return info;\n}\n\n/**\n * The field order and a readable pattern (`DD/MM/YYYY`, `MM/DD/YYYY`, `DD.MM.YYYY`, …) of the\n * default date format for `locale` (runtime default when omitted).\n */\nexport function getLocaleDateFormat(locale?: string): LocaleDateFormat {\n const { order, pattern } = getLocaleInfo(locale);\n return { order: [...order], pattern };\n}\n\n/**\n * The default DatePicker display text: `Intl.DateTimeFormat(locale, { year: 'numeric', month:\n * '2-digit', day: '2-digit' })` in the grid-compatible calendar of {@link getCalendar} (`fa-IR`:\n * `۲۰۲۵/۰۶/۱۵`, not the Persian `۱۴۰۴/۰۳/۲۵`). {@link parseDate} with the same `locale` reverses it.\n */\nexport function formatDate(date: Date, locale?: string): string {\n return getFormatter(locale, DEFAULT_DATE_OPTIONS).format(date);\n}\n\n/** The numeric fields of `text` in order, or `null` when anything but separators surrounds them. */\nfunction digitGroups(text: string, info: LocaleInfo): string[] | null {\n const normalized = toAsciiDigits(text.trim(), info.digits);\n const groups = normalized.match(/\\d+/g);\n if (!groups) return null;\n for (const char of normalized.replace(/\\d+/g, '')) {\n if (/\\s/.test(char) || info.literals.has(char) || '/.-'.includes(char)) continue;\n return null;\n }\n return groups;\n}\n\n/**\n * Parses text typed in the default display format of `locale` — the exact inverse of\n * {@link formatDate} for the same locale — to local midnight. Also accepts single-digit day/month\n * fields, `/`, `.` and `-` separators, and ISO `yyyy-mm-dd`. Returns `null` for anything else:\n * impossible dates (31/02), a year shorter than the locale writes it (`25`), letters or extra\n * fields.\n */\nexport function parseDate(text: string, locale?: string): Date | null {\n if (!text.trim()) return null;\n const iso = parseISODate(text);\n if (iso) return iso;\n\n const info = getLocaleInfo(locale);\n if (info.order.length !== 3) return null;\n const groups = digitGroups(text, info);\n if (!groups || groups.length !== 3) return null;\n\n const values: Partial<Record<DateField, number>> = {};\n for (let i = 0; i < 3; i++) {\n const field = info.order[i];\n if (field === 'year' && groups[i].length < info.yearLength) return null;\n values[field] = Number(groups[i]);\n }\n const date = fromFields(\n (values.year ?? NaN) - info.yearOffset,\n values.month ?? NaN,\n values.day ?? NaN,\n );\n if (!date) return null;\n\n // Exact inverse: formatting the result must give back the typed numbers (guards calendars\n // whose months or days differ from the Gregorian ones).\n const roundTrip = digitGroups(formatDate(date, locale), info);\n if (!roundTrip || roundTrip.some((group, i) => Number(group) !== Number(groups[i]))) return null;\n return date;\n}\n\n/** The 12 long month names of `locale` (January … December). */\nexport function getMonthNames(locale?: string): string[] {\n const formatter = getFormatter(locale, { month: 'long' });\n return Array.from({ length: 12 }, (_, month) => formatter.format(makeDate(2001, month, 1)));\n}\n\n/** A weekday name in both lengths. */\nexport interface WeekdayName {\n /** Short name for the column header (`Mon`). */\n short: string;\n /** Full name for assistive technology (`Monday`). */\n long: string;\n}\n\n/** The seven weekday names of `locale`, starting at `firstDayOfWeek` (0 = Sunday). */\nexport function getWeekdayNames(locale: string | undefined, firstDayOfWeek = 0): WeekdayName[] {\n const short = getFormatter(locale, { weekday: 'short' });\n const long = getFormatter(locale, { weekday: 'long' });\n // 1 January 2023 was a Sunday.\n return Array.from({ length: 7 }, (_, i) => {\n const day = makeDate(2023, 0, 1 + ((firstDayOfWeek + i) % 7));\n return { short: short.format(day), long: long.format(day) };\n });\n}\n\n/** The calendar heading, e.g. `May 2025` / `Mai 2025`. */\nexport function formatMonthYear(date: Date, locale?: string): string {\n return getFormatter(locale, { month: 'long', year: 'numeric' }).format(date);\n}\n\n/** The accessible name of a day cell, e.g. `Monday, May 26, 2025`. */\nexport function formatDayLabel(date: Date, locale?: string): string {\n return getFormatter(locale, {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n }).format(date);\n}\n\nconst numberFormatCache = new Map<string, Intl.NumberFormat>();\n\n/** The day-of-month number in the locale's digits, without suffixes (`3`, not `3日`). */\nexport function formatDayNumber(date: Date, locale?: string): string {\n const key = locale ?? '';\n let formatter = numberFormatCache.get(key);\n if (!formatter) {\n try {\n formatter = new Intl.NumberFormat(locale, { useGrouping: false });\n } catch {\n formatter = new Intl.NumberFormat(undefined, { useGrouping: false });\n }\n numberFormatCache.set(key, formatter);\n }\n return formatter.format(date.getDate());\n}\n\n/* ------------------------------------------------------------------ */\n/* Time */\n/* ------------------------------------------------------------------ */\n\n/** TimePicker label style. */\nexport type TimeFormat = '12h' | '24h';\n\nconst TIME_24H = /^(\\d{1,2}):(\\d{2})(?::(\\d{2}))?$/;\nconst TIME_12H = /^(\\d{1,2}):(\\d{2})(?::(\\d{2}))?\\s*([ap])\\.?\\s*m\\.?$/i;\n\n/**\n * Minutes since midnight of a time string: `HH:mm`, `H:mm`, `HH:mm:ss` (seconds ignored) or a\n * 12-hour time such as `9:00 AM`, `12:30 pm`, `11:59 p.m.`. `NaN` when invalid.\n */\nexport function timeToMinutes(text: string): number {\n const value = text.trim();\n const twelve = TIME_12H.exec(value);\n const match = twelve ?? TIME_24H.exec(value);\n if (!match) return NaN;\n let hours = Number(match[1]);\n const minutes = Number(match[2]);\n const seconds = match[3] === undefined ? 0 : Number(match[3]);\n if (minutes > 59 || seconds > 59) return NaN;\n if (twelve) {\n if (hours < 1 || hours > 12) return NaN;\n const pm = match[4].toLowerCase() === 'p';\n hours = (hours % 12) + (pm ? 12 : 0);\n } else if (hours > 23) {\n return NaN;\n }\n return hours * 60 + minutes;\n}\n\n/** `HH:mm` (24-hour, zero-padded): the TimePicker value format. */\nexport function minutesToValue(minutes: number): string {\n const hours = Math.floor(minutes / 60) % 24;\n return `${pad(hours, 2)}:${pad(minutes % 60, 2)}`;\n}\n\n/** Display text of a time: `13:00` (`'24h'`) or `1:00 PM` (`'12h'`). */\nexport function minutesToTime(minutes: number, format: TimeFormat): string {\n if (format === '24h') return minutesToValue(minutes);\n const hours = Math.floor(minutes / 60) % 24;\n const period = hours >= 12 ? 'PM' : 'AM';\n const hours12 = hours % 12 === 0 ? 12 : hours % 12;\n return `${hours12}:${pad(minutes % 60, 2)} ${period}`;\n}\n\n/** The TimePicker step fallback for invalid values. */\nexport const DEFAULT_TIME_STEP = 30;\n\n/**\n * Guards a TimePicker `step`: a non-finite or non-positive step falls back to\n * {@link DEFAULT_TIME_STEP} (`valid: false`, so the component can report it); fractions are\n * floored to a whole number of minutes, at least 1.\n */\nexport function normalizeTimeStep(step: number): { step: number; valid: boolean } {\n if (!Number.isFinite(step) || step <= 0) return { step: DEFAULT_TIME_STEP, valid: false };\n return { step: Math.max(1, Math.floor(step)), valid: true };\n}\n\n/** One generated TimePicker option. */\nexport interface TimeOption {\n /** `HH:mm`. */\n value: string;\n /** Display text in the requested format. */\n label: string;\n}\n\n/**\n * The options from `minTime` to `maxTime` (inclusive) every `step` minutes. The step is guarded\n * with {@link normalizeTimeStep} (the loop always terminates); invalid or reversed bounds give\n * `[]`.\n */\nexport function generateTimeOptions(\n step: number,\n minTime: string,\n maxTime: string,\n format: TimeFormat,\n): TimeOption[] {\n const increment = normalizeTimeStep(step).step;\n const min = timeToMinutes(minTime);\n const max = timeToMinutes(maxTime);\n if (Number.isNaN(min) || Number.isNaN(max) || min > max) return [];\n const options: TimeOption[] = [];\n for (let minutes = min; minutes <= max; minutes += increment) {\n options.push({ value: minutesToValue(minutes), label: minutesToTime(minutes, format) });\n }\n return options;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"dateUtils.mjs","names":["isSameMonth","a","b","isDateInRange","day","startOfDay","date","min","max","clampDate","getDecadeStart","year","getYearMonths","makeDate","month","getDecadeYears","decadeStart","index","isMonthOutOfRange","first","startOfMonth","last","isYearOutOfRange","getCalendarDays","viewMonth","offset","firstDayOfWeek","days","i","addDays","isDayUnavailable","disabledDates","getSelectionRange","mode","daysInMonth","start","startOfWeek","workWeekDays","weekDay","isUnavailable","candidate","pad","value","length","formatISODate","fromFields","parseISODate","match","text","isValidLocaleTag","locale","DEFAULT_DATE_OPTIONS","GRID_CALENDARS","calendarCache","getCalendar","key","calendar","resolved","formatterCache","getFormatter","options","formatter","withCalendar","BIDI_MARKS","infoCache","SAMPLE_DATE","toAsciiDigits","out","char","digits","getLocaleInfo","cached","numberFormat","parts","order","literals","pattern","yearOffset","yearLength","part","info","getLocaleDateFormat","formatDate","digitGroups","normalized","groups","parseDate","iso","values","field","roundTrip","group","getWeekdayNames","short","long","formatMonthYear","formatMonthShort","formatYear","formatDecade","formatDayLabel","numberFormatCache","formatDayNumber","TIME_24H","TIME_12H","timeToMinutes","twelve","hours","minutes","seconds","pm","minutesToValue","minutesToTime","format","period","normalizeTimeStep","step","generateTimeOptions","increment","minTime","maxTime","addMonths","isSameDay"],"sources":["../../../src/components/input/dateUtils.ts"],"sourcesContent":["/**\n * Date and time helpers shared by `DatePicker` and `TimePicker` (spec §5.6).\n *\n * - Every date these helpers return is a **local-midnight** `Date` (the start of a calendar day in\n * the runtime's time zone). Comparisons are by calendar day, never by time of day.\n * - The default display format is `Intl.DateTimeFormat(locale, { year: 'numeric', month:\n * '2-digit', day: '2-digit' })`, and {@link parseDate} is its exact inverse for the same\n * `locale` (field order from `formatToParts`), so a formatted date always parses back to the\n * same day. ISO `yyyy-mm-dd` text is accepted too.\n * - Every formatter uses the locale's calendar only when it has the Gregorian months and days of\n * the grid (Gregorian, or the Buddhist calendar of `th-TH`, whose years are offset); any other\n * calendar (the Persian default of `fa-IR`, `-u-ca-islamic`, Japanese eras) is replaced by the\n * Gregorian one, so the text, the heading and the day labels name the day the grid shows.\n * - Pass an explicit `locale` when rendering on the server: the runtime default locale of the\n * server and the browser may differ, which would change the formatted text between them.\n *\n * Internal module (not exported from the package).\n */\n\nimport {\n addDays,\n addMonths,\n daysInMonth,\n isSameDay,\n makeDate,\n startOfDay,\n startOfMonth,\n startOfWeek,\n} from '../../lib/date';\nimport type { DayOfWeek } from '../../lib/types';\n\n/* ------------------------------------------------------------------ */\n/* Day arithmetic (the shared helpers live in lib/date) */\n/* ------------------------------------------------------------------ */\n\nexport { addDays, addMonths, isSameDay, startOfDay, startOfMonth };\n\n/** Whether both dates are in the same month of the same year. */\nexport function isSameMonth(a: Date, b: Date): boolean {\n return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth();\n}\n\n/**\n * Whether `date`'s day lies within `[min, max]`, comparing calendar days (a `max` with a time of\n * day still includes that whole day). Missing bounds are open.\n */\nexport function isDateInRange(date: Date, min?: Date | null, max?: Date | null): boolean {\n const day = startOfDay(date).getTime();\n if (min && day < startOfDay(min).getTime()) return false;\n if (max && day > startOfDay(max).getTime()) return false;\n return true;\n}\n\n/** `date`'s day clamped into `[min, max]` (calendar days), at local midnight. */\nexport function clampDate(date: Date, min?: Date | null, max?: Date | null): Date {\n const day = startOfDay(date);\n if (min && day.getTime() < startOfDay(min).getTime()) return startOfDay(min);\n if (max && day.getTime() > startOfDay(max).getTime()) return startOfDay(max);\n return day;\n}\n\n/** The first year of `year`'s decade (2025 gives 2020). */\nexport function getDecadeStart(year: number): number {\n return Math.floor(year / 10) * 10;\n}\n\n/** The 12 months of `year`, as the 1st of each month (the cells of the month view). */\nexport function getYearMonths(year: number): Date[] {\n return Array.from({ length: 12 }, (_, month) => makeDate(year, month, 1));\n}\n\n/**\n * The 12 years of the year view, as January 1 of each: the decade starting at `decadeStart`, with\n * the year before and the year after it.\n */\nexport function getDecadeYears(decadeStart: number): Date[] {\n return Array.from({ length: 12 }, (_, index) => makeDate(decadeStart - 1 + index, 0, 1));\n}\n\n/** Whether every day of `month`'s month lies outside `[min, max]` (calendar days). */\nexport function isMonthOutOfRange(month: Date, min?: Date | null, max?: Date | null): boolean {\n const first = startOfMonth(month);\n const last = makeDate(first.getFullYear(), first.getMonth() + 1, 0);\n return (!!min && last < startOfDay(min)) || (!!max && first > startOfDay(max));\n}\n\n/** Whether every day of `year` lies outside `[min, max]` (calendar days). */\nexport function isYearOutOfRange(year: number, min?: Date | null, max?: Date | null): boolean {\n return (\n (!!min && makeDate(year, 11, 31) < startOfDay(min)) ||\n (!!max && makeDate(year, 0, 1) > startOfDay(max))\n );\n}\n\n/**\n * The 42 days (6 weeks) of the calendar grid showing `viewMonth`'s month: the first row starts on\n * `firstDayOfWeek` (0 = Sunday) on or before the 1st.\n */\nexport function getCalendarDays(viewMonth: Date, firstDayOfWeek: number): Date[] {\n const first = startOfMonth(viewMonth);\n const offset = (first.getDay() - firstDayOfWeek + 7) % 7;\n const days: Date[] = [];\n for (let i = 0; i < 42; i++) days.push(addDays(first, i - offset));\n return days;\n}\n\n/**\n * Whether a day cannot be picked: outside `min`/`max` (whole days) or named by `disabledDates`\n * (called with local midnight). The one rule behind the focusable-but-unavailable look, the\n * drawn range and the emitted range.\n */\nexport function isDayUnavailable(\n date: Date,\n min?: Date | null,\n max?: Date | null,\n disabledDates?: (date: Date) => boolean,\n): boolean {\n return !isDateInRange(date, min, max) || !!disabledDates?.(startOfDay(date));\n}\n\n/** What a pick selects (the Calendar's `selectionRange`). */\nexport type CalendarSelectionRange = 'day' | 'week' | 'work-week' | 'month';\n\n/**\n * The days a pick of `date` selects, in order, at local midnight: the day itself (`'day'`, whatever\n * its availability), its week from `firstDayOfWeek`, the days of that week named by `workWeekDays`\n * (`'work-week'`, in the order of the week), or its whole month. The days of the other modes that\n * `isUnavailable` names are left out.\n */\nexport function getSelectionRange(\n date: Date,\n mode: CalendarSelectionRange,\n firstDayOfWeek: DayOfWeek,\n workWeekDays: readonly DayOfWeek[],\n isUnavailable?: (date: Date) => boolean,\n): Date[] {\n const day = startOfDay(date);\n let days: Date[];\n if (mode === 'day') return [day];\n if (mode === 'month') {\n const first = startOfMonth(day);\n days = Array.from({ length: daysInMonth(day.getFullYear(), day.getMonth()) }, (_, i) =>\n addDays(first, i),\n );\n } else {\n const start = startOfWeek(day, firstDayOfWeek);\n days = Array.from({ length: 7 }, (_, i) => addDays(start, i));\n if (mode === 'work-week') {\n days = days.filter((weekDay) => workWeekDays.includes(weekDay.getDay() as DayOfWeek));\n }\n }\n return isUnavailable ? days.filter((candidate) => !isUnavailable(candidate)) : days;\n}\n\n/* ------------------------------------------------------------------ */\n/* ISO dates */\n/* ------------------------------------------------------------------ */\n\nfunction pad(value: number, length: number): string {\n return String(value).padStart(length, '0');\n}\n\n/** `yyyy-mm-dd` from the **local** calendar fields (the `HiddenInput` form value). */\nexport function formatISODate(date: Date): string {\n return `${pad(date.getFullYear(), 4)}-${pad(date.getMonth() + 1, 2)}-${pad(date.getDate(), 2)}`;\n}\n\n/** Builds a date from calendar fields; `null` when they do not name a real day. */\nfunction fromFields(year: number, month: number, day: number): Date | null {\n if (!Number.isInteger(year) || year < 1 || year > 9999) return null;\n if (!Number.isInteger(month) || month < 1 || month > 12) return null;\n if (!Number.isInteger(day) || day < 1 || day > daysInMonth(year, month - 1)) return null;\n return makeDate(year, month - 1, day);\n}\n\n/** Parses `yyyy-mm-dd` (surrounding whitespace allowed) to local midnight; `null` otherwise. */\nexport function parseISODate(text: string): Date | null {\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(text.trim());\n if (!match) return null;\n return fromFields(Number(match[1]), Number(match[2]), Number(match[3]));\n}\n\n/* ------------------------------------------------------------------ */\n/* Locale formats */\n/* ------------------------------------------------------------------ */\n\n/** A numeric date field. */\nexport type DateField = 'year' | 'month' | 'day';\n\n/** How a locale writes the default (numeric) date format. */\nexport interface LocaleDateFormat {\n /** The order of the fields, e.g. `['day', 'month', 'year']` for `en-GB`. */\n order: DateField[];\n /** A readable pattern with the locale's own separators, e.g. `DD/MM/YYYY`. */\n pattern: string;\n}\n\n/**\n * Whether `Intl` accepts `locale` as a language tag. Every helper below falls back to the runtime\n * default locale for a tag it rejects (the POSIX `de_DE`, `''`, a typo), so DatePicker warns about\n * one in development.\n */\nexport function isValidLocaleTag(locale: string): boolean {\n try {\n Intl.getCanonicalLocales(locale);\n return true;\n } catch {\n return false;\n }\n}\n\nconst DEFAULT_DATE_OPTIONS: Intl.DateTimeFormatOptions = {\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n};\n\n/**\n * Calendars with the Gregorian months and days, which the grid shows (`getCalendarDays`): a locale\n * whose calendar is one of these keeps it. The Buddhist calendar (default of `th-TH`) only numbers\n * the years differently.\n */\nconst GRID_CALENDARS = new Set(['gregory', 'buddhist']);\n\nconst calendarCache = new Map<string, string>();\n\n/**\n * The calendar every date helper formats `locale` in: the locale's own (its default or a `-u-ca-`\n * extension) when it has the Gregorian months and days, else `'gregory'`. So a Persian default\n * (`fa-IR`, `fa-AF`, `ps-AF`), an explicit `-u-ca-islamic` or the Japanese eras never label the\n * Gregorian grid with other months or years, and the text stays parseable; the locale's digits\n * stay. An invalid locale tag uses the runtime default's.\n */\nfunction getCalendar(locale: string | undefined): string {\n const key = locale ?? '';\n let calendar = calendarCache.get(key);\n if (calendar === undefined) {\n let resolved: string;\n try {\n resolved = new Intl.DateTimeFormat(locale).resolvedOptions().calendar;\n } catch {\n resolved = new Intl.DateTimeFormat().resolvedOptions().calendar;\n }\n calendar = GRID_CALENDARS.has(resolved) ? resolved : 'gregory';\n calendarCache.set(key, calendar);\n }\n return calendar;\n}\n\nconst formatterCache = new Map<string, Intl.DateTimeFormat>();\n\n/**\n * A cached `Intl.DateTimeFormat` in the calendar of {@link getCalendar}; an invalid locale tag falls\n * back to the runtime default.\n */\nfunction getFormatter(\n locale: string | undefined,\n options: Intl.DateTimeFormatOptions,\n): Intl.DateTimeFormat {\n const key = `${locale ?? ''}|${JSON.stringify(options)}`;\n let formatter = formatterCache.get(key);\n if (!formatter) {\n const withCalendar = { ...options, calendar: getCalendar(locale) };\n try {\n formatter = new Intl.DateTimeFormat(locale, withCalendar);\n } catch {\n formatter = new Intl.DateTimeFormat(undefined, withCalendar);\n }\n formatterCache.set(key, formatter);\n }\n return formatter;\n}\n\n/** Bidirectional control characters Intl inserts around fields (Arabic, Hebrew locales). */\nconst BIDI_MARKS = /[\\u061c\\u200b-\\u200f\\u202a-\\u202e\\u2066-\\u2069]/g;\n\ninterface LocaleInfo extends LocaleDateFormat {\n /** Non-digit characters of the formatted date (allowed between the typed fields). */\n literals: Set<string>;\n /** Locale digit → ASCII digit (for non-Latin numbering systems). */\n digits: Map<string, string>;\n /** Formatted year minus the Gregorian year (e.g. 543 for the Buddhist calendar). */\n yearOffset: number;\n /** Digit count of a formatted year: shorter typed years are rejected (`25` is not 2025). */\n yearLength: number;\n}\n\nconst infoCache = new Map<string, LocaleInfo>();\nconst SAMPLE_DATE = makeDate(2001, 10, 23); // 23 Nov 2001: every field distinct\n\nfunction toAsciiDigits(text: string, digits: Map<string, string>): string {\n let out = '';\n for (const char of text.replace(BIDI_MARKS, '')) out += digits.get(char) ?? char;\n return out;\n}\n\nfunction getLocaleInfo(locale: string | undefined): LocaleInfo {\n const key = locale ?? '';\n const cached = infoCache.get(key);\n if (cached) return cached;\n\n const digits = new Map<string, string>();\n let numberFormat: Intl.NumberFormat;\n try {\n numberFormat = new Intl.NumberFormat(locale, { useGrouping: false });\n } catch {\n numberFormat = new Intl.NumberFormat(undefined, { useGrouping: false });\n }\n for (let i = 0; i <= 9; i++) digits.set(numberFormat.format(i), String(i));\n\n const parts = getFormatter(locale, DEFAULT_DATE_OPTIONS).formatToParts(SAMPLE_DATE);\n const order: DateField[] = [];\n const literals = new Set<string>();\n let pattern = '';\n let yearOffset = 0;\n let yearLength = 4;\n for (const part of parts) {\n if (part.type === 'day' || part.type === 'month' || part.type === 'year') {\n order.push(part.type);\n if (part.type === 'year') {\n const year = toAsciiDigits(part.value, digits);\n yearLength = year.length;\n yearOffset = Number(year) - SAMPLE_DATE.getFullYear();\n }\n pattern += part.type === 'day' ? 'DD' : part.type === 'month' ? 'MM' : 'YYYY';\n } else {\n const text = part.value.replace(BIDI_MARKS, '');\n pattern += text;\n for (const char of text) literals.add(char);\n }\n }\n const info: LocaleInfo = {\n order,\n pattern: pattern.trim(),\n literals,\n digits,\n yearOffset,\n yearLength,\n };\n infoCache.set(key, info);\n return info;\n}\n\n/**\n * The field order and a readable pattern (`DD/MM/YYYY`, `MM/DD/YYYY`, `DD.MM.YYYY`, …) of the\n * default date format for `locale` (runtime default when omitted).\n */\nexport function getLocaleDateFormat(locale?: string): LocaleDateFormat {\n const { order, pattern } = getLocaleInfo(locale);\n return { order: [...order], pattern };\n}\n\n/**\n * The default DatePicker display text: `Intl.DateTimeFormat(locale, { year: 'numeric', month:\n * '2-digit', day: '2-digit' })` in the grid-compatible calendar of {@link getCalendar} (`fa-IR`:\n * `۲۰۲۵/۰۶/۱۵`, not the Persian `۱۴۰۴/۰۳/۲۵`). {@link parseDate} with the same `locale` reverses it.\n */\nexport function formatDate(date: Date, locale?: string): string {\n return getFormatter(locale, DEFAULT_DATE_OPTIONS).format(date);\n}\n\n/** The numeric fields of `text` in order, or `null` when anything but separators surrounds them. */\nfunction digitGroups(text: string, info: LocaleInfo): string[] | null {\n const normalized = toAsciiDigits(text.trim(), info.digits);\n const groups = normalized.match(/\\d+/g);\n if (!groups) return null;\n for (const char of normalized.replace(/\\d+/g, '')) {\n if (/\\s/.test(char) || info.literals.has(char) || '/.-'.includes(char)) continue;\n return null;\n }\n return groups;\n}\n\n/**\n * Parses text typed in the default display format of `locale` — the exact inverse of\n * {@link formatDate} for the same locale — to local midnight. Also accepts single-digit day/month\n * fields, `/`, `.` and `-` separators, and ISO `yyyy-mm-dd`. Returns `null` for anything else:\n * impossible dates (31/02), a year shorter than the locale writes it (`25`), letters or extra\n * fields.\n */\nexport function parseDate(text: string, locale?: string): Date | null {\n if (!text.trim()) return null;\n const iso = parseISODate(text);\n if (iso) return iso;\n\n const info = getLocaleInfo(locale);\n if (info.order.length !== 3) return null;\n const groups = digitGroups(text, info);\n if (!groups || groups.length !== 3) return null;\n\n const values: Partial<Record<DateField, number>> = {};\n for (let i = 0; i < 3; i++) {\n const field = info.order[i];\n if (field === 'year' && groups[i].length < info.yearLength) return null;\n values[field] = Number(groups[i]);\n }\n const date = fromFields(\n (values.year ?? NaN) - info.yearOffset,\n values.month ?? NaN,\n values.day ?? NaN,\n );\n if (!date) return null;\n\n // Exact inverse: formatting the result must give back the typed numbers (guards calendars\n // whose months or days differ from the Gregorian ones).\n const roundTrip = digitGroups(formatDate(date, locale), info);\n if (!roundTrip || roundTrip.some((group, i) => Number(group) !== Number(groups[i]))) return null;\n return date;\n}\n\n/** The 12 long month names of `locale` (January … December). */\nexport function getMonthNames(locale?: string): string[] {\n const formatter = getFormatter(locale, { month: 'long' });\n return Array.from({ length: 12 }, (_, month) => formatter.format(makeDate(2001, month, 1)));\n}\n\n/** A weekday name in both lengths. */\nexport interface WeekdayName {\n /** Short name for the column header (`Mon`). */\n short: string;\n /** Full name for assistive technology (`Monday`). */\n long: string;\n}\n\n/** The seven weekday names of `locale`, starting at `firstDayOfWeek` (0 = Sunday). */\nexport function getWeekdayNames(locale: string | undefined, firstDayOfWeek = 0): WeekdayName[] {\n const short = getFormatter(locale, { weekday: 'short' });\n const long = getFormatter(locale, { weekday: 'long' });\n // 1 January 2023 was a Sunday.\n return Array.from({ length: 7 }, (_, i) => {\n const day = makeDate(2023, 0, 1 + ((firstDayOfWeek + i) % 7));\n return { short: short.format(day), long: long.format(day) };\n });\n}\n\n/** The calendar heading, e.g. `May 2025` / `Mai 2025`. */\nexport function formatMonthYear(date: Date, locale?: string): string {\n return getFormatter(locale, { month: 'long', year: 'numeric' }).format(date);\n}\n\n/** The short month name of a month cell, e.g. `Mar` / `Mär`. */\nexport function formatMonthShort(date: Date, locale?: string): string {\n return getFormatter(locale, { month: 'short' }).format(date);\n}\n\n/**\n * The year of a year cell and of the month view's heading, in the locale's digits and the\n * calendar of {@link getCalendar}, without an era: `2025`, the Buddhist `2568` of `th-TH`.\n */\nexport function formatYear(date: Date, locale?: string): string {\n const parts = getFormatter(locale, { year: 'numeric' }).formatToParts(date);\n return parts.find((part) => part.type === 'year')?.value ?? String(date.getFullYear());\n}\n\n/**\n * The year view's heading: the decade starting at `decadeStart` as a range of years\n * (`Intl.DateTimeFormat#formatRange`), e.g. `2020 – 2029`.\n */\nexport function formatDecade(decadeStart: number, locale?: string): string {\n return getFormatter(locale, { year: 'numeric' }).formatRange(\n makeDate(decadeStart, 0, 1),\n makeDate(decadeStart + 9, 11, 31),\n );\n}\n\n/** The accessible name of a day cell, e.g. `Monday, May 26, 2025`. */\nexport function formatDayLabel(date: Date, locale?: string): string {\n return getFormatter(locale, {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n }).format(date);\n}\n\nconst numberFormatCache = new Map<string, Intl.NumberFormat>();\n\n/** The day-of-month number in the locale's digits, without suffixes (`3`, not `3日`). */\nexport function formatDayNumber(date: Date, locale?: string): string {\n const key = locale ?? '';\n let formatter = numberFormatCache.get(key);\n if (!formatter) {\n try {\n formatter = new Intl.NumberFormat(locale, { useGrouping: false });\n } catch {\n formatter = new Intl.NumberFormat(undefined, { useGrouping: false });\n }\n numberFormatCache.set(key, formatter);\n }\n return formatter.format(date.getDate());\n}\n\n/* ------------------------------------------------------------------ */\n/* Time */\n/* ------------------------------------------------------------------ */\n\n/** TimePicker label style. */\nexport type TimeFormat = '12h' | '24h';\n\nconst TIME_24H = /^(\\d{1,2}):(\\d{2})(?::(\\d{2}))?$/;\nconst TIME_12H = /^(\\d{1,2}):(\\d{2})(?::(\\d{2}))?\\s*([ap])\\.?\\s*m\\.?$/i;\n\n/**\n * Minutes since midnight of a time string: `HH:mm`, `H:mm`, `HH:mm:ss` (seconds ignored) or a\n * 12-hour time such as `9:00 AM`, `12:30 pm`, `11:59 p.m.`. `NaN` when invalid.\n */\nexport function timeToMinutes(text: string): number {\n const value = text.trim();\n const twelve = TIME_12H.exec(value);\n const match = twelve ?? TIME_24H.exec(value);\n if (!match) return NaN;\n let hours = Number(match[1]);\n const minutes = Number(match[2]);\n const seconds = match[3] === undefined ? 0 : Number(match[3]);\n if (minutes > 59 || seconds > 59) return NaN;\n if (twelve) {\n if (hours < 1 || hours > 12) return NaN;\n const pm = match[4].toLowerCase() === 'p';\n hours = (hours % 12) + (pm ? 12 : 0);\n } else if (hours > 23) {\n return NaN;\n }\n return hours * 60 + minutes;\n}\n\n/** `HH:mm` (24-hour, zero-padded): the TimePicker value format. */\nexport function minutesToValue(minutes: number): string {\n const hours = Math.floor(minutes / 60) % 24;\n return `${pad(hours, 2)}:${pad(minutes % 60, 2)}`;\n}\n\n/** Display text of a time: `13:00` (`'24h'`) or `1:00 PM` (`'12h'`). */\nexport function minutesToTime(minutes: number, format: TimeFormat): string {\n if (format === '24h') return minutesToValue(minutes);\n const hours = Math.floor(minutes / 60) % 24;\n const period = hours >= 12 ? 'PM' : 'AM';\n const hours12 = hours % 12 === 0 ? 12 : hours % 12;\n return `${hours12}:${pad(minutes % 60, 2)} ${period}`;\n}\n\n/** The TimePicker step fallback for invalid values. */\nexport const DEFAULT_TIME_STEP = 30;\n\n/**\n * Guards a TimePicker `step`: a non-finite or non-positive step falls back to\n * {@link DEFAULT_TIME_STEP} (`valid: false`, so the component can report it); fractions are\n * floored to a whole number of minutes, at least 1.\n */\nexport function normalizeTimeStep(step: number): { step: number; valid: boolean } {\n if (!Number.isFinite(step) || step <= 0) return { step: DEFAULT_TIME_STEP, valid: false };\n return { step: Math.max(1, Math.floor(step)), valid: true };\n}\n\n/** One generated TimePicker option. */\nexport interface TimeOption {\n /** `HH:mm`. */\n value: string;\n /** Display text in the requested format. */\n label: string;\n}\n\n/**\n * The options from `minTime` to `maxTime` (inclusive) every `step` minutes. The step is guarded\n * with {@link normalizeTimeStep} (the loop always terminates); invalid or reversed bounds give\n * `[]`.\n */\nexport function generateTimeOptions(\n step: number,\n minTime: string,\n maxTime: string,\n format: TimeFormat,\n): TimeOption[] {\n const increment = normalizeTimeStep(step).step;\n const min = timeToMinutes(minTime);\n const max = timeToMinutes(maxTime);\n if (Number.isNaN(min) || Number.isNaN(max) || min > max) return [];\n const options: TimeOption[] = [];\n for (let minutes = min; minutes <= max; minutes += increment) {\n options.push({ value: minutesToValue(minutes), label: minutesToTime(minutes, format) });\n }\n return options;\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use client";const e=require("../../hooks/useAnnounce.cjs");var t=e=>e.join(`, `),n=(e,t)=>`${e} added, ${t} selected`,r=(e,t)=>`${e} removed, ${t} selected`;function i(t,i,a,o){let s=a?t?.added??n:t?.removed??r;e.announce(s(i,o))}function a(){return e.useAnnounce(),null}a.displayName=`PickerAnnouncer`,exports.PickerAnnouncer=a,exports.announceToggle=i,exports.defaultAddedLabel=n,exports.defaultRemovedLabel=r,exports.defaultSelectionLabel=t;
2
+ //# sourceMappingURL=pickerLabels.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pickerLabels.cjs","names":["require_useAnnounce","defaultSelectionLabel","labels","defaultAddedLabel","label","count","defaultRemovedLabel","announceToggle","text","added","PickerAnnouncer"],"sources":["../../../src/components/input/pickerLabels.ts"],"sourcesContent":["/** Built-in text shared by the listbox pickers: English defaults of their `labels`, and the\n * announcements of their multi-select toggles. */\nimport { announce, useAnnounce } from '../../hooks/useAnnounce';\n\n/** Dropdown/Combobox `labels.selection`: the selected labels joined for display. */\nexport const defaultSelectionLabel = (labels: string[]): string => labels.join(', ');\n\n/** Dropdown/Combobox/Listbox/TagPicker `labels.added`: announced when a multi-select toggle adds\n * a value. */\nexport const defaultAddedLabel = (label: string, count: number): string =>\n `${label} added, ${count} selected`;\n\n/** Dropdown/Combobox/Listbox/TagPicker `labels.removed`: announced when a multi-select toggle\n * removes a value. */\nexport const defaultRemovedLabel = (label: string, count: number): string =>\n `${label} removed, ${count} selected`;\n\n/** The members of a picker's `labels` that word a toggle announcement. */\nexport interface ToggleAnnouncementLabels {\n added?: (label: string, count: number) => string;\n removed?: (label: string, count: number) => string;\n}\n\n/**\n * Announces a multi-select toggle the user made: `labels.added` or `labels.removed` (else their\n * English defaults) for the toggled option's `label`, with the `count` of values selected after it.\n * The picker's announcer regions must be mounted ({@link PickerAnnouncer}, or `useAnnounce` as\n * TagPicker does).\n */\nexport function announceToggle(\n labels: ToggleAnnouncementLabels | undefined,\n label: string,\n added: boolean,\n count: number,\n): void {\n const text = added\n ? (labels?.added ?? defaultAddedLabel)\n : (labels?.removed ?? defaultRemovedLabel);\n announce(text(label, count));\n}\n\n/**\n * Mounts the shared announcer regions (`useAnnounce`) while a multi-select picker is on the page,\n * so a single-select picker's DOM stays as in 0.7, with no `[data-wave-announcer]` added to\n * `document.body`. Renders nothing; {@link announceToggle} writes into the regions it keeps alive.\n */\nexport function PickerAnnouncer(): null {\n useAnnounce();\n return null;\n}\nPickerAnnouncer.displayName = 'PickerAnnouncer';\n"],"mappings":"4DAKA,IAAaC,EAAyB,GAA6BC,EAAO,KAAK,IAAI,EAItEC,GAAqB,EAAe,IAC/C,GAAGC,EAAM,UAAUC,EAAM,WAIdC,GAAuB,EAAe,IACjD,GAAGF,EAAM,YAAYC,EAAM,WAc7B,SAAgBE,EACd,EACA,EACA,EACA,EACM,CACN,IAAMC,EAAOC,EACRP,GAAQ,OAASC,EACjBD,GAAQ,SAAWI,EACxBN,EAAA,SAASQ,EAAKJ,EAAOC,CAAK,CAAC,CAC7B,CAOA,SAAgBK,GAAwB,CAEtC,OADAV,EAAA,YAAY,EACL,IACT,CACA,EAAgB,YAAc"}
@@ -0,0 +1,16 @@
1
+ "use client";
2
+ import { announce as e, useAnnounce as t } from "../../hooks/useAnnounce.mjs";
3
+ //#region src/components/input/pickerLabels.ts
4
+ var n = (e) => e.join(", "), r = (e, t) => `${e} added, ${t} selected`, i = (e, t) => `${e} removed, ${t} selected`;
5
+ function a(t, n, a, o) {
6
+ let s = a ? t?.added ?? r : t?.removed ?? i;
7
+ e(s(n, o));
8
+ }
9
+ function o() {
10
+ return t(), null;
11
+ }
12
+ o.displayName = "PickerAnnouncer";
13
+ //#endregion
14
+ export { o as PickerAnnouncer, a as announceToggle, r as defaultAddedLabel, i as defaultRemovedLabel, n as defaultSelectionLabel };
15
+
16
+ //# sourceMappingURL=pickerLabels.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pickerLabels.mjs","names":["defaultSelectionLabel","labels","defaultAddedLabel","label","count","defaultRemovedLabel","announceToggle","text","added","PickerAnnouncer","useAnnounce"],"sources":["../../../src/components/input/pickerLabels.ts"],"sourcesContent":["/** Built-in text shared by the listbox pickers: English defaults of their `labels`, and the\n * announcements of their multi-select toggles. */\nimport { announce, useAnnounce } from '../../hooks/useAnnounce';\n\n/** Dropdown/Combobox `labels.selection`: the selected labels joined for display. */\nexport const defaultSelectionLabel = (labels: string[]): string => labels.join(', ');\n\n/** Dropdown/Combobox/Listbox/TagPicker `labels.added`: announced when a multi-select toggle adds\n * a value. */\nexport const defaultAddedLabel = (label: string, count: number): string =>\n `${label} added, ${count} selected`;\n\n/** Dropdown/Combobox/Listbox/TagPicker `labels.removed`: announced when a multi-select toggle\n * removes a value. */\nexport const defaultRemovedLabel = (label: string, count: number): string =>\n `${label} removed, ${count} selected`;\n\n/** The members of a picker's `labels` that word a toggle announcement. */\nexport interface ToggleAnnouncementLabels {\n added?: (label: string, count: number) => string;\n removed?: (label: string, count: number) => string;\n}\n\n/**\n * Announces a multi-select toggle the user made: `labels.added` or `labels.removed` (else their\n * English defaults) for the toggled option's `label`, with the `count` of values selected after it.\n * The picker's announcer regions must be mounted ({@link PickerAnnouncer}, or `useAnnounce` as\n * TagPicker does).\n */\nexport function announceToggle(\n labels: ToggleAnnouncementLabels | undefined,\n label: string,\n added: boolean,\n count: number,\n): void {\n const text = added\n ? (labels?.added ?? defaultAddedLabel)\n : (labels?.removed ?? defaultRemovedLabel);\n announce(text(label, count));\n}\n\n/**\n * Mounts the shared announcer regions (`useAnnounce`) while a multi-select picker is on the page,\n * so a single-select picker's DOM stays as in 0.7, with no `[data-wave-announcer]` added to\n * `document.body`. Renders nothing; {@link announceToggle} writes into the regions it keeps alive.\n */\nexport function PickerAnnouncer(): null {\n useAnnounce();\n return null;\n}\nPickerAnnouncer.displayName = 'PickerAnnouncer';\n"],"mappings":";;;AAKA,IAAaA,KAAyB,MAA6BC,EAAO,KAAK,IAAI,GAItEC,KAAqB,GAAe,MAC/C,GAAGC,EAAM,UAAUC,EAAM,YAIdC,KAAuB,GAAe,MACjD,GAAGF,EAAM,YAAYC,EAAM;AAc7B,SAAgBE,EACd,GACA,GACA,GACA,GACM;CACN,IAAMC,IAAOC,IACRP,GAAQ,SAASC,IACjBD,GAAQ,WAAWI;CACxB,EAASE,EAAKJ,GAAOC,CAAK,CAAC;AAC7B;AAOA,SAAgBK,IAAwB;CAEtC,OADAC,EAAY,GACL;AACT;AACA,EAAgB,cAAc"}
@@ -0,0 +1,2 @@
1
+ "use client";var e=[];function t(t){return Array.isArray(t)?t:t?[t]:e}function n(e,t){return e.length===t.length&&e.every((e,n)=>e===t[n])}function r(e,t){let n=!e.includes(t);return{values:n?[...e,t]:e.filter(e=>e!==t),added:n}}function i(t,r){let i=Array.isArray(r)?r:e;return n(Array.isArray(t)?t:e,i)?t:[...i]}exports.EMPTY_VALUES=e,exports.resetValues=i,exports.sameValues=n,exports.toValues=t,exports.toggleValue=r;
2
+ //# sourceMappingURL=pickerValues.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pickerValues.cjs","names":["EMPTY_VALUES","toValues","value","sameValues","a","b","index","toggleValue","added","values","v","resetValues","initial","defaultValue","current"],"sources":["../../../src/components/input/pickerValues.ts"],"sourcesContent":["/** Shared value-array helpers for the multi-select listbox pickers. */\n\n/** The empty multi-select value: a stable array (the default without `defaultValue`, the cleared\n * value). */\nexport const EMPTY_VALUES: readonly string[] = [];\n\n/**\n * The selected values of a picker's value: an array as it is, any other truthy value as its one\n * value (as 0.7 treated a single value), and `''`, or `null` and `undefined` passed from JavaScript\n * past the types, as none.\n */\nexport function toValues(value: string | readonly string[] | null | undefined): readonly string[] {\n if (Array.isArray(value)) return value;\n // `Array.isArray` does not narrow a readonly array out of the union, and a value passed from\n // JavaScript may be any truthy value: it is kept as it is, as 0.7 kept it.\n return value ? [value as string] : EMPTY_VALUES;\n}\n\n/** Whether two selections hold the same values in the same order (a reset compared by content). */\nexport function sameValues(a: readonly string[], b: readonly string[]): boolean {\n return a.length === b.length && a.every((value, index) => value === b[index]);\n}\n\n/**\n * Toggles `value` in `values`, keeping selection order: appends it when absent, removes it when\n * present. `added` says which happened, for the caller's announcement.\n */\nexport function toggleValue(\n values: readonly string[],\n value: string,\n): { values: string[]; added: boolean } {\n const added = !values.includes(value);\n return { values: added ? [...values, value] : values.filter((v) => v !== value), added };\n}\n\n/**\n * The value a multi-select form reset restores (C-FORMS): `current` itself when it already holds\n * the values of `defaultValue` in the same order, so an unchanged reset reports nothing (an inline\n * default array is a new reference on every render), else a copy of them. A default that is not an\n * array (from JavaScript) restores no values.\n */\nexport function resetValues(\n current: string | readonly string[],\n defaultValue: string | readonly string[] | undefined,\n): string | readonly string[] {\n const initial = Array.isArray(defaultValue) ? defaultValue : EMPTY_VALUES;\n const currentValues = Array.isArray(current) ? current : EMPTY_VALUES;\n return sameValues(currentValues, initial) ? current : [...initial];\n}\n"],"mappings":"aAIA,IAAaA,EAAkC,CAAC,EAOhD,SAAgBC,EAAS,EAAyE,CAIhG,OAHI,MAAM,QAAQC,CAAK,EAAUA,EAG1BA,EAAQ,CAACA,CAAe,EAAIF,CACrC,CAGA,SAAgBG,EAAW,EAAsB,EAA+B,CAC9E,OAAOC,EAAE,SAAWC,EAAE,QAAUD,EAAE,OAAO,EAAO,IAAUF,IAAUG,EAAEC,EAAM,CAC9E,CAMA,SAAgBC,EACd,EACA,EACsC,CACtC,IAAMC,EAAQ,CAACC,EAAO,SAASP,CAAK,EACpC,MAAO,CAAE,OAAQM,EAAQ,CAAC,GAAGC,EAAQP,CAAK,EAAIO,EAAO,OAAQ,GAAMC,IAAMR,CAAK,EAAG,OAAM,CACzF,CAQA,SAAgBS,EACd,EACA,EAC4B,CAC5B,IAAMC,EAAU,MAAM,QAAQC,CAAY,EAAIA,EAAeb,EAE7D,OAAOG,EADe,MAAM,QAAQW,CAAO,EAAIA,EAAUd,EACxBY,CAAO,EAAIE,EAAU,CAAC,GAAGF,CAAO,CACnE"}
@@ -0,0 +1,24 @@
1
+ "use client";
2
+ //#region src/components/input/pickerValues.ts
3
+ var e = [];
4
+ function t(t) {
5
+ return Array.isArray(t) ? t : t ? [t] : e;
6
+ }
7
+ function n(e, t) {
8
+ return e.length === t.length && e.every((e, n) => e === t[n]);
9
+ }
10
+ function r(e, t) {
11
+ let n = !e.includes(t);
12
+ return {
13
+ values: n ? [...e, t] : e.filter((e) => e !== t),
14
+ added: n
15
+ };
16
+ }
17
+ function i(t, r) {
18
+ let i = Array.isArray(r) ? r : e;
19
+ return n(Array.isArray(t) ? t : e, i) ? t : [...i];
20
+ }
21
+ //#endregion
22
+ export { e as EMPTY_VALUES, i as resetValues, n as sameValues, t as toValues, r as toggleValue };
23
+
24
+ //# sourceMappingURL=pickerValues.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pickerValues.mjs","names":["EMPTY_VALUES","toValues","value","sameValues","a","b","index","toggleValue","added","values","v","resetValues","initial","defaultValue","current"],"sources":["../../../src/components/input/pickerValues.ts"],"sourcesContent":["/** Shared value-array helpers for the multi-select listbox pickers. */\n\n/** The empty multi-select value: a stable array (the default without `defaultValue`, the cleared\n * value). */\nexport const EMPTY_VALUES: readonly string[] = [];\n\n/**\n * The selected values of a picker's value: an array as it is, any other truthy value as its one\n * value (as 0.7 treated a single value), and `''`, or `null` and `undefined` passed from JavaScript\n * past the types, as none.\n */\nexport function toValues(value: string | readonly string[] | null | undefined): readonly string[] {\n if (Array.isArray(value)) return value;\n // `Array.isArray` does not narrow a readonly array out of the union, and a value passed from\n // JavaScript may be any truthy value: it is kept as it is, as 0.7 kept it.\n return value ? [value as string] : EMPTY_VALUES;\n}\n\n/** Whether two selections hold the same values in the same order (a reset compared by content). */\nexport function sameValues(a: readonly string[], b: readonly string[]): boolean {\n return a.length === b.length && a.every((value, index) => value === b[index]);\n}\n\n/**\n * Toggles `value` in `values`, keeping selection order: appends it when absent, removes it when\n * present. `added` says which happened, for the caller's announcement.\n */\nexport function toggleValue(\n values: readonly string[],\n value: string,\n): { values: string[]; added: boolean } {\n const added = !values.includes(value);\n return { values: added ? [...values, value] : values.filter((v) => v !== value), added };\n}\n\n/**\n * The value a multi-select form reset restores (C-FORMS): `current` itself when it already holds\n * the values of `defaultValue` in the same order, so an unchanged reset reports nothing (an inline\n * default array is a new reference on every render), else a copy of them. A default that is not an\n * array (from JavaScript) restores no values.\n */\nexport function resetValues(\n current: string | readonly string[],\n defaultValue: string | readonly string[] | undefined,\n): string | readonly string[] {\n const initial = Array.isArray(defaultValue) ? defaultValue : EMPTY_VALUES;\n const currentValues = Array.isArray(current) ? current : EMPTY_VALUES;\n return sameValues(currentValues, initial) ? current : [...initial];\n}\n"],"mappings":";;AAIA,IAAaA,IAAkC,CAAC;AAOhD,SAAgBC,EAAS,GAAyE;CAIhG,OAHI,MAAM,QAAQC,CAAK,IAAUA,IAG1BA,IAAQ,CAACA,CAAe,IAAIF;AACrC;AAGA,SAAgBG,EAAW,GAAsB,GAA+B;CAC9E,OAAOC,EAAE,WAAWC,EAAE,UAAUD,EAAE,OAAO,GAAO,MAAUF,MAAUG,EAAEC,EAAM;AAC9E;AAMA,SAAgBC,EACd,GACA,GACsC;CACtC,IAAMC,IAAQ,CAACC,EAAO,SAASP,CAAK;CACpC,OAAO;EAAE,QAAQM,IAAQ,CAAC,GAAGC,GAAQP,CAAK,IAAIO,EAAO,QAAQ,MAAMC,MAAMR,CAAK;EAAG;CAAM;AACzF;AAQA,SAAgBS,EACd,GACA,GAC4B;CAC5B,IAAMC,IAAU,MAAM,QAAQC,CAAY,IAAIA,IAAeb;CAE7D,OAAOG,EADe,MAAM,QAAQW,CAAO,IAAIA,IAAUd,GACxBY,CAAO,IAAIE,IAAU,CAAC,GAAGF,CAAO;AACnE"}
@@ -1,2 +1,2 @@
1
- "use client";const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../lib/direction.cjs"),n=require("../../lib/focus.cjs");let r=require("react");r=e.__toESM(r,1);var i=new Set([`checkbox`,`radio`,`button`,`submit`,`reset`,`image`,`file`,`color`]),a=new Set([`textbox`,`searchbox`,`combobox`,`spinbutton`,`slider`]),o=[`[data-roving-container]`,...[`toolbar`,`radiogroup`,`listbox`,`grid`,`treegrid`,`tablist`,`menu`,`menubar`,`tree`].map(e=>`[role="${e}"]`)].join(`, `),s=`${n.FOCUSABLE_SELECTOR}, ${o}`,c=[`disabled`,`tabindex`,`hidden`,`inert`,`type`,`contenteditable`,`role`,`data-roving-container`,`aria-selected`];function l(e){let t=e.localName;if(t===`textarea`||t===`select`)return!0;if(t===`input`)return!i.has(e.type);let n=e.getAttribute(`contenteditable`);if(n!==null&&n!==`false`)return!0;let r=e.getAttribute(`role`);return r!==null&&a.has(r)}function u(e){return e.matches(o)}function d(e,t){if(e===t)return!1;for(let n=e.parentElement;n&&n!==t;n=n.parentElement)if(u(n))return!0;return!1}function f(e){let t=e.querySelector(`[tabindex="0"]`);return t&&n.isFocusable(t)?t:n.getFirstTabbable(e,{includeContainer:!0})}function p(e){return e.localName===`input`&&e.type===`hidden`||e.matches(`:disabled`)?!1:e.closest(`[hidden], [inert]`)===null}function m(e){if(!e.name)return!1;let t=e.getRootNode();if(typeof t.querySelectorAll!=`function`)return!1;let n=t.querySelectorAll(`input[type="radio"]:checked`);for(let t of Array.from(n))if(t!==e&&t.name===e.name&&t.form===e.form)return!0;return!1}function h(e){return e.localName===`td`||e.localName===`th`}function g(e){return e.getRootNode().activeElement===e}function _(e,t){let n=t instanceof Element?t:null;for(;n&&n!==e;){if(h(n)&&n.closest(`table`)===e)return n;n=n.parentElement}return null}function v(e){return Array.from(e.rows).filter(e=>e.cells.length>0&&!e.hidden)}var y=1e3;function b(e){let t=new Map(e.map((e,t)=>[e,t])),n=e.map(()=>[]),r=(e,t,r,i)=>{for(let a=0;a<r;a+=1)n[e][t+a]=i};return e.forEach((e,i)=>{let a=n[i],o=0;for(let n of Array.from(e.cells)){for(;a[o];)o+=1;let s=Math.min(Math.max(n.colSpan,1),y);r(i,o,s,n);let c=n.rowSpan===0?1/0:n.rowSpan-1;for(let i=e.nextElementSibling;i&&c>0;i=i.nextElementSibling){if(i.localName!==`tr`)continue;--c;let e=t.get(i);e!==void 0&&r(e,o,s,n)}o+=s}}),n}function x(e,t){for(let n=Math.min(t,e.length-1);n>=0;--n){let t=e[n];if(t)return t}return null}function S(e,t,n){if(t instanceof Element){h(t)&&t.closest(`table`)===e&&n.add(t);for(let r of Array.from(t.querySelectorAll(`td, th`)))r.closest(`table`)===e&&n.add(r)}}function C(e={}){let i=Math.max(1,e.pageSize??10),[a,o]=r.useState(null),y=r.useRef(null),C=r.useRef(null),[w]=r.useState(()=>new WeakSet),[T]=r.useState(()=>new WeakMap),[E]=r.useState(()=>new WeakSet),D=r.useRef(i);r.useLayoutEffect(()=>{D.current=i},[i]);let O=r.useCallback((e,t)=>{let r=[],i=null;for(let a of Array.from(e.querySelectorAll(s))){if(i&&i.contains(a))continue;if(u(a)){i=a,(t?f(a)!==null:p(a))&&r.push({element:a,composite:!0});continue}let e=a.getAttribute(`tabindex`);e!==null&&Number(e)<0&&!w.has(a)||(t?n.isFocusable(a):p(a))&&r.push({element:a,composite:!1})}return r},[w]),k=r.useCallback((e,t,n)=>{if(t.length!==1)return e;let[{element:r,composite:i}]=t;return i||l(r)||n&&r instanceof HTMLInputElement&&r.type===`radio`&&!r.checked&&m(r)?e:r},[]),A=r.useCallback((e,t)=>{let n=String(t);e.getAttribute(`tabindex`)!==n&&(T.set(e,n),e.setAttribute(`tabindex`,n)),w.add(e)},[w,T]),j=r.useCallback((e,t,n)=>{let r=O(e,t);if(n){E.delete(e),A(e,-1);for(let e of r)e.composite||A(e.element,0);return}let i=k(e,r,t),a=!1;i===e?(E.delete(e),A(e,t?0:-1)):w.has(e)&&g(e)?(a=!0,E.add(e),A(e,t?0:-1)):w.has(e)&&(E.delete(e),w.delete(e),e.hasAttribute(`tabindex`)&&(T.set(e,null),e.removeAttribute(`tabindex`)));for(let e of r){if(e.composite)continue;let n=e.element===i&&t&&!a;A(e.element,n?0:-1)}},[k,O,E,A,w,T]),M=r.useCallback((e,t)=>{let n=v(e),r=y.current,i=new Set;if((!r||!r.isConnected||!n.includes(r.parentElement))&&(r=n[0]?.cells[0]??null,y.current=r,r&&i.add(r)),C.current!==null&&C.current!==r){let e=C.current;C.current=null,i.add(e)}let a=C.current;if(t===void 0)for(let e of n)for(let t of Array.from(e.cells))i.add(t);else for(let e of t)e&&i.add(e);for(let t of i)t.isConnected&&_(e,t)===t&&j(t,t===r,t===a)},[j]);r.useLayoutEffect(()=>{if(!a||(M(a),typeof MutationObserver>`u`))return;let e=new MutationObserver(e=>{let t=!1,n=!1,r=new Set;for(let i of e){let e=i.target;if(!(e instanceof Element)||d(e,a))continue;if(i.type===`attributes`){if(i.attributeName===`tabindex`&&T.has(e)&&T.get(e)===e.getAttribute(`tabindex`))continue;t=!0;let o=_(a,e);o?r.add(o):e.localName===`tr`?S(a,e,r):n=!0;continue}t=!0;let o=_(a,e);if(o){r.add(o);continue}for(let e of Array.from(i.addedNodes))S(a,e,r)}t&&(y.current&&r.add(y.current),M(a,n?void 0:r))});return e.observe(a,{childList:!0,subtree:!0,attributes:!0,attributeFilter:c}),()=>e.disconnect()},[a,M,T]);let N=r.useCallback((e,t,n)=>{let r=y.current,i=C.current;y.current=t,C.current=n?t:null,M(e,[r,i,t])},[M]),P=r.useCallback((e,t)=>{N(e,t,!1),k(t,O(t,!0),!0).focus()},[N,k,O]),F=r.useCallback(e=>{let n=e.currentTarget;if(e.defaultPrevented)return;let r=_(n,e.target);if(!r)return;let i=O(r,!0),a=k(r,i,!1),o=e.target===r;if(!o&&e.target!==a){e.key===`Escape`&&(e.preventDefault(),P(n,r));return}if(e.altKey||e.metaKey)return;if((e.key===`Enter`||e.key===`F2`)&&o){let t=null;for(let e of i)if(t=e.composite?f(e.element):e.element,t)break;if(!t)return;e.preventDefault(),N(n,r,!0),t.focus();return}let s=v(n),c=s.findIndex(e=>e===r.parentElement);if(c<0)return;let l=Array.from(s[c].cells).indexOf(r),u=s.length-1,d=e.ctrlKey,p=null,m=null;switch(e.key){case`ArrowLeft`:case`ArrowRight`:if(d)break;p=[c,l+(t.getArrowIntent(e.key,{orientation:`horizontal`,dir:t.getDirection(n)})===`next`?1:-1)];break;case`ArrowDown`:d||(m=[c+1,1]);break;case`ArrowUp`:d||(m=[c-1,-1]);break;case`Home`:p=d?[0,0]:[c,0];break;case`End`:p=d?[u,1/0]:[c,1/0];break;case`PageDown`:d||(m=[Math.min(c+D.current,u),1]);break;case`PageUp`:d||(m=[Math.max(c-D.current,0),-1])}if(!p&&!m)return;e.preventDefault();let h=null;if(m){let e=b(s),t=Math.max(e[c].indexOf(r),0);for(let n=m[0];n>=0&&n<=u;n+=m[1]){let i=x(e[n],t);if(i&&i!==r){h=i;break}}}else if(p){let[e,t]=p,n=s[e].cells;h=t===1/0?n[n.length-1]:n.item(t)}h&&h!==r&&P(n,h)},[N,P,k,O]),I=r.useCallback(e=>{let t=e.currentTarget,n=e.target,r=_(t,n);if(!r)return;let i=n!==r&&n!==k(r,O(r,!0),!1);N(t,r,i)},[N,k,O]),L=r.useCallback(e=>{let t=e.currentTarget,n=e.target,r=[];n instanceof Element&&h(n)&&E.has(n)&&r.push(n);let i=e.relatedTarget,a=C.current;a!==null&&!(i instanceof Node&&t.contains(i))&&(C.current=null,r.push(a)),r.length>0&&M(t,r)},[E,M]);return{gridRef:o,gridProps:r.useMemo(()=>({onKeyDown:F,onFocus:I,onBlur:L}),[L,I,F])}}exports.isTextEntryElement=l,exports.useGridNavigation=C;
1
+ "use client";const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../lib/direction.cjs"),n=require("../../lib/focus.cjs");let r=require("react");r=e.__toESM(r,1);var i=new Set([`checkbox`,`radio`,`button`,`submit`,`reset`,`image`,`file`,`color`]),a=new Set([`textbox`,`searchbox`,`combobox`,`spinbutton`,`slider`]),o=[`[data-roving-container]`,...[`toolbar`,`radiogroup`,`listbox`,`grid`,`treegrid`,`tablist`,`menu`,`menubar`,`tree`].map(e=>`[role="${e}"]`)].join(`, `),s=`${n.FOCUSABLE_SELECTOR}, ${o}`,c=[`disabled`,`tabindex`,`hidden`,`inert`,`type`,`contenteditable`,`role`,`data-roving-container`,`aria-selected`];function l(e){let t=e.localName;if(t===`textarea`||t===`select`)return!0;if(t===`input`)return!i.has(e.type);let n=e.getAttribute(`contenteditable`);if(n!==null&&n!==`false`)return!0;let r=e.getAttribute(`role`);return r!==null&&a.has(r)}function u(e){return e.matches(o)}function d(e,t){if(e===t)return!1;for(let n=e.parentElement;n&&n!==t;n=n.parentElement)if(u(n))return!0;return!1}function f(e){let t=e.querySelector(`[tabindex="0"]`);return t&&n.isFocusable(t)?t:n.getFirstTabbable(e,{includeContainer:!0})}function p(e){return e.localName===`input`&&e.type===`hidden`||e.matches(`:disabled`)?!1:e.closest(`[hidden], [inert]`)===null}function m(e){if(!e.name)return!1;let t=e.getRootNode();if(typeof t.querySelectorAll!=`function`)return!1;let n=t.querySelectorAll(`input[type="radio"]:checked`);for(let t of Array.from(n))if(t!==e&&t.name===e.name&&t.form===e.form)return!0;return!1}function h(e){return e.localName===`td`||e.localName===`th`}function g(e,t){let n=t instanceof Element?t:null;for(;n&&n!==e;){if(h(n)&&n.closest(`table`)===e)return n;n=n.parentElement}return null}function _(e){return Array.from(e.rows).filter(e=>e.cells.length>0&&!e.hidden)}var v=1e3;function y(e){let t=new Map(e.map((e,t)=>[e,t])),n=e.map(()=>[]),r=(e,t,r,i)=>{for(let a=0;a<r;a+=1)n[e][t+a]=i};return e.forEach((e,i)=>{let a=n[i],o=0;for(let n of Array.from(e.cells)){for(;a[o];)o+=1;let s=Math.min(Math.max(n.colSpan,1),v);r(i,o,s,n);let c=n.rowSpan===0?1/0:n.rowSpan-1;for(let i=e.nextElementSibling;i&&c>0;i=i.nextElementSibling){if(i.localName!==`tr`)continue;--c;let e=t.get(i);e!==void 0&&r(e,o,s,n)}o+=s}}),n}function b(e,t){for(let n=Math.min(t,e.length-1);n>=0;--n){let t=e[n];if(t)return t}return null}function x(e,t,n){if(t instanceof Element){h(t)&&t.closest(`table`)===e&&n.add(t);for(let r of Array.from(t.querySelectorAll(`td, th`)))r.closest(`table`)===e&&n.add(r)}}function S(e={}){let i=Math.max(1,e.pageSize??10),[a,o]=r.useState(null),v=r.useRef(null),S=r.useRef(null),[C]=r.useState(()=>new WeakSet),[w]=r.useState(()=>new WeakMap),[T]=r.useState(()=>new WeakSet),E=r.useRef(i);r.useLayoutEffect(()=>{E.current=i},[i]);let D=r.useCallback((e,t)=>{let r=[],i=null;for(let a of Array.from(e.querySelectorAll(s))){if(i&&i.contains(a))continue;if(u(a)){i=a,(t?f(a)!==null:p(a))&&r.push({element:a,composite:!0});continue}let e=a.getAttribute(`tabindex`);e!==null&&Number(e)<0&&!C.has(a)||(t?n.isFocusable(a):p(a))&&r.push({element:a,composite:!1})}return r},[C]),O=r.useCallback((e,t,n)=>{if(t.length!==1)return e;let[{element:r,composite:i}]=t;return i||l(r)||n&&r instanceof HTMLInputElement&&r.type===`radio`&&!r.checked&&m(r)?e:r},[]),k=r.useCallback((e,t)=>{let n=String(t);e.getAttribute(`tabindex`)!==n&&(w.set(e,n),e.setAttribute(`tabindex`,n)),C.add(e)},[C,w]),A=r.useCallback((e,t,r)=>{let i=D(e,t);if(r){T.delete(e),k(e,-1);for(let e of i)e.composite||k(e.element,0);return}let a=O(e,i,t),o=!1;a===e?(T.delete(e),k(e,t?0:-1)):C.has(e)&&n.isFocused(e)?(o=!0,T.add(e),k(e,t?0:-1)):C.has(e)&&(T.delete(e),C.delete(e),e.hasAttribute(`tabindex`)&&(w.set(e,null),e.removeAttribute(`tabindex`)));for(let e of i){if(e.composite)continue;let n=e.element===a&&t&&!o;k(e.element,n?0:-1)}},[O,D,T,k,C,w]),j=r.useCallback((e,t)=>{let n=_(e),r=v.current,i=new Set;if((!r||!r.isConnected||!n.includes(r.parentElement))&&(r=n[0]?.cells[0]??null,v.current=r,r&&i.add(r)),S.current!==null&&S.current!==r){let e=S.current;S.current=null,i.add(e)}let a=S.current;if(t===void 0)for(let e of n)for(let t of Array.from(e.cells))i.add(t);else for(let e of t)e&&i.add(e);for(let t of i)t.isConnected&&g(e,t)===t&&A(t,t===r,t===a)},[A]);r.useLayoutEffect(()=>{if(!a||(j(a),typeof MutationObserver>`u`))return;let e=new MutationObserver(e=>{let t=!1,n=!1,r=new Set;for(let i of e){let e=i.target;if(!(e instanceof Element)||d(e,a))continue;if(i.type===`attributes`){if(i.attributeName===`tabindex`&&w.has(e)&&w.get(e)===e.getAttribute(`tabindex`))continue;t=!0;let o=g(a,e);o?r.add(o):e.localName===`tr`?x(a,e,r):n=!0;continue}t=!0;let o=g(a,e);if(o){r.add(o);continue}for(let e of Array.from(i.addedNodes))x(a,e,r)}t&&(v.current&&r.add(v.current),j(a,n?void 0:r))});return e.observe(a,{childList:!0,subtree:!0,attributes:!0,attributeFilter:c}),()=>e.disconnect()},[a,j,w]);let M=r.useCallback((e,t,n)=>{let r=v.current,i=S.current;v.current=t,S.current=n?t:null,j(e,[r,i,t])},[j]),N=r.useCallback((e,t)=>{M(e,t,!1),O(t,D(t,!0),!0).focus()},[M,O,D]),P=r.useCallback(e=>{let n=e.currentTarget;if(e.defaultPrevented)return;let r=g(n,e.target);if(!r)return;let i=D(r,!0),a=O(r,i,!1),o=e.target===r;if(!o&&e.target!==a){e.key===`Escape`&&(e.preventDefault(),N(n,r));return}if(e.altKey||e.metaKey)return;if((e.key===`Enter`||e.key===`F2`)&&o){let t=null;for(let e of i)if(t=e.composite?f(e.element):e.element,t)break;if(!t)return;e.preventDefault(),M(n,r,!0),t.focus();return}let s=_(n),c=s.findIndex(e=>e===r.parentElement);if(c<0)return;let l=Array.from(s[c].cells).indexOf(r),u=s.length-1,d=e.ctrlKey,p=null,m=null;switch(e.key){case`ArrowLeft`:case`ArrowRight`:if(d)break;p=[c,l+(t.getArrowIntent(e.key,{orientation:`horizontal`,dir:t.getDirection(n)})===`next`?1:-1)];break;case`ArrowDown`:d||(m=[c+1,1]);break;case`ArrowUp`:d||(m=[c-1,-1]);break;case`Home`:p=d?[0,0]:[c,0];break;case`End`:p=d?[u,1/0]:[c,1/0];break;case`PageDown`:d||(m=[Math.min(c+E.current,u),1]);break;case`PageUp`:d||(m=[Math.max(c-E.current,0),-1])}if(!p&&!m)return;e.preventDefault();let h=null;if(m){let e=y(s),t=Math.max(e[c].indexOf(r),0);for(let n=m[0];n>=0&&n<=u;n+=m[1]){let i=b(e[n],t);if(i&&i!==r){h=i;break}}}else if(p){let[e,t]=p,n=s[e].cells;h=t===1/0?n[n.length-1]:n.item(t)}h&&h!==r&&N(n,h)},[M,N,O,D]),F=r.useCallback(e=>{let t=e.currentTarget,n=e.target,r=g(t,n);if(!r)return;let i=n!==r&&n!==O(r,D(r,!0),!1);M(t,r,i)},[M,O,D]),I=r.useCallback(e=>{let t=e.currentTarget,n=e.target,r=[];n instanceof Element&&h(n)&&T.has(n)&&r.push(n);let i=e.relatedTarget,a=S.current;a!==null&&!(i instanceof Node&&t.contains(i))&&(S.current=null,r.push(a)),r.length>0&&j(t,r)},[T,j]);return{gridRef:o,gridProps:r.useMemo(()=>({onKeyDown:P,onFocus:F,onBlur:I}),[I,F,P])}}exports.isTextEntryElement=l,exports.useGridNavigation=S;
2
2
  //# sourceMappingURL=useGridNavigation.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"useGridNavigation.cjs","names":["FOCUSABLE_SELECTOR","isFocusable","getFirstTabbable","React","getArrowIntent","getDirection","require_runtime","require_direction","require_focus","react","NON_TEXT_INPUT_TYPES","KEY_CONSUMING_ROLES","COMPOSITE_SELECTOR","role","WIDGET_CANDIDATE_SELECTOR","OBSERVED_ATTRIBUTES","isTextEntryElement","tag","el","editable","isNestedComposite","isInsideNestedComposite","node","grid","getCompositeEntry","stop","composite","isLikelyFocusable","hasCheckedPeer","radio","root","checked","other","isCellElement","isFocused","getCell","getRows","row","MAX_COL_SPAN","getColumnMap","indexOf","rows","index","map","cover","offset","colSpan","rowIndex","column","cell","own","remaining","next","covered","getCellAtColumn","rowMap","collectCells","into","useGridNavigation","pageSize","options","setGrid","activeCellRef","interactionCellRef","stamped","written","retained","pageSizeRef","getWidgets","widgets","precise","tabIndex","getTarget","element","checkRadioGroup","setTabIndex","value","syncCell","isActive","isInteraction","widget","target","keepCell","isStop","sync","table","active","targets","previous","interaction","cells","observer","relevant","all","dirty","record","records","activate","previousActive","previousInteraction","focusCell","onKeyDown","event","widgetTarget","onCell","entry","columnIndex","lastRow","ctrl","vertical","destination","columns","candidate","nextRow","nextColumn","rowCells","onFocus","onBlur","left"],"sources":["../../../src/components/table/useGridNavigation.ts"],"sourcesContent":["import * as React from 'react';\nimport { getArrowIntent, getDirection } from '../../lib/direction';\nimport { FOCUSABLE_SELECTOR, getFirstTabbable, isFocusable } from '../../lib/focus';\n\n/** Options of {@link useGridNavigation}. */\nexport interface UseGridNavigationOptions {\n /**\n * How many rows PageUp and PageDown move focus.\n * @default 10\n */\n pageSize?: number;\n}\n\n/** Handlers {@link useGridNavigation} needs on the grid element (compose them with the consumer's). */\nexport interface GridNavigationHandlers {\n onKeyDown: React.KeyboardEventHandler<HTMLTableElement>;\n onFocus: React.FocusEventHandler<HTMLTableElement>;\n onBlur: React.FocusEventHandler<HTMLTableElement>;\n}\n\n/** Result of {@link useGridNavigation}. */\nexport interface UseGridNavigationResult {\n /** Callback ref for the `<table role=\"grid\">` element. Stable identity. */\n gridRef: React.RefCallback<HTMLTableElement>;\n /** Keyboard and focus handlers for the grid element. Stable identities. */\n gridProps: GridNavigationHandlers;\n}\n\n/** Input types that do not take arrow or character keys, so a lone one can be a cell's focus target. */\nconst NON_TEXT_INPUT_TYPES: ReadonlySet<string> = new Set([\n 'checkbox',\n 'radio',\n 'button',\n 'submit',\n 'reset',\n 'image',\n 'file',\n 'color',\n]);\n\n/** Widget roles that use arrow or character keys themselves (treated like text entry). */\nconst KEY_CONSUMING_ROLES: ReadonlySet<string> = new Set([\n 'textbox',\n 'searchbox',\n 'combobox',\n 'spinbutton',\n 'slider',\n]);\n\n/**\n * Roles of composite widgets that manage their own children (and usually their tab indexes): an\n * element with one of them, or with `data-roving-container`, is a nested composite. Close to the\n * list of `useRovingTabIndex`, which is kept separately and differs on purpose: here a `toolbar`\n * is a composite (a toolbar in a cell is one widget), while `spinbutton` is not (it is one\n * focusable control that uses the arrow keys, handled by `KEY_CONSUMING_ROLES` like a text\n * field). Keep both lists in mind when adding a role.\n */\nconst COMPOSITE_ROLES: readonly string[] = [\n 'toolbar',\n 'radiogroup',\n 'listbox',\n 'grid',\n 'treegrid',\n 'tablist',\n 'menu',\n 'menubar',\n 'tree',\n];\n\nconst COMPOSITE_SELECTOR = [\n '[data-roving-container]',\n ...COMPOSITE_ROLES.map((role) => `[role=\"${role}\"]`),\n].join(', ');\n\n/** Every element that can be a cell widget: a focusable element or a nested composite root. */\nconst WIDGET_CANDIDATE_SELECTOR = `${FOCUSABLE_SELECTOR}, ${COMPOSITE_SELECTOR}`;\n\n/**\n * Attributes whose changes can change a cell's widgets or the grid's rows. `aria-selected` on a row\n * signals a selection change, which can change the active cell's target (radio groups).\n */\nconst OBSERVED_ATTRIBUTES = [\n 'disabled',\n 'tabindex',\n 'hidden',\n 'inert',\n 'type',\n 'contenteditable',\n 'role',\n 'data-roving-container',\n 'aria-selected',\n];\n\n/**\n * Whether `el` is a text-entry control (or another widget that uses arrow keys itself): a text-like\n * `input`, `textarea`, `select`, a `contenteditable` element, or an element with role textbox,\n * searchbox, combobox, spinbutton or slider. Such widgets are never a cell's navigation target: the\n * cell takes focus and Enter/F2 moves into the widget, so the arrow keys keep moving the caret.\n */\nexport function isTextEntryElement(el: Element): boolean {\n const tag = el.localName;\n if (tag === 'textarea' || tag === 'select') return true;\n if (tag === 'input') return !NON_TEXT_INPUT_TYPES.has((el as HTMLInputElement).type);\n const editable = el.getAttribute('contenteditable');\n if (editable !== null && editable !== 'false') return true;\n const role = el.getAttribute('role');\n return role !== null && KEY_CONSUMING_ROLES.has(role);\n}\n\n/** Whether `el` is a nested composite widget (own roving container or a composite role). */\nfunction isNestedComposite(el: Element): boolean {\n return el.matches(COMPOSITE_SELECTOR);\n}\n\n/** Whether `node` is strictly inside a nested composite of `grid` (the composite root excluded). */\nfunction isInsideNestedComposite(node: Element, grid: HTMLTableElement): boolean {\n if (node === grid) return false;\n for (let el = node.parentElement; el && el !== grid; el = el.parentElement) {\n if (isNestedComposite(el)) return true;\n }\n return false;\n}\n\n/**\n * The element a nested composite is entered at: its own tab stop (`tabindex=\"0\"`), else its first\n * tabbable element (the root itself when it is the focusable element).\n */\nfunction getCompositeEntry(composite: HTMLElement): HTMLElement | null {\n const stop = composite.querySelector<HTMLElement>('[tabindex=\"0\"]');\n if (stop && isFocusable(stop)) return stop;\n return getFirstTabbable(composite, { includeContainer: true });\n}\n\n/**\n * A cheap focusability check for cells that do not hold the tab stop: no style reads, only the\n * attributes that take an element out of the focus order. The cell that holds (or receives) the tab\n * stop is checked precisely with `isFocusable`.\n */\nfunction isLikelyFocusable(el: HTMLElement): boolean {\n if (el.localName === 'input' && (el as HTMLInputElement).type === 'hidden') return false;\n if (el.matches(':disabled')) return false;\n return el.closest('[hidden], [inert]') === null;\n}\n\n/** Whether another radio of `radio`'s named group is checked (the browser's tab stop of the group). */\nfunction hasCheckedPeer(radio: HTMLInputElement): boolean {\n if (!radio.name) return false;\n const root = radio.getRootNode() as Document | ShadowRoot;\n if (typeof root.querySelectorAll !== 'function') return false;\n const checked = root.querySelectorAll<HTMLInputElement>('input[type=\"radio\"]:checked');\n for (const other of Array.from(checked)) {\n if (other !== radio && other.name === radio.name && other.form === radio.form) return true;\n }\n return false;\n}\n\nfunction isCellElement(el: Element): el is HTMLTableCellElement {\n return el.localName === 'td' || el.localName === 'th';\n}\n\n/** Whether `el` is the focused element of its document (or shadow root). */\nfunction isFocused(el: Element): boolean {\n return (el.getRootNode() as Partial<DocumentOrShadowRoot>).activeElement === el;\n}\n\n/** The cell of `grid` that contains `node` (a nested table's cells belong to the outer cell). */\nfunction getCell(grid: HTMLTableElement, node: EventTarget | null): HTMLTableCellElement | null {\n let el: Element | null = node instanceof Element ? node : null;\n while (el && el !== grid) {\n if (isCellElement(el) && el.closest('table') === grid) return el;\n el = el.parentElement;\n }\n return null;\n}\n\n/** The rows of `grid` (header, body and footer rows; a nested table's rows are not included). */\nfunction getRows(grid: HTMLTableElement): HTMLTableRowElement[] {\n return Array.from(grid.rows).filter((row) => row.cells.length > 0 && !row.hidden);\n}\n\n/** Browsers clamp `colSpan` to 1..1000 (`rowSpan` 0 covers the rest of its row group). */\nconst MAX_COL_SPAN = 1000;\n\n/** Per row, the cell covering each visual column (see {@link getColumnMap}). */\ntype ColumnMap = ReadonlyArray<ReadonlyArray<HTMLTableCellElement | undefined>>;\n\n/**\n * The HTML table model of `rows`: per row, the cell covering each visual column, with `colSpan` and\n * `rowSpan` counted. A row span covers the next rows of its row group in the DOM (a hidden row\n * counts, but gets no entry). A column no cell covers is empty. O(number of cells).\n */\nfunction getColumnMap(rows: readonly HTMLTableRowElement[]): ColumnMap {\n const indexOf = new Map<Element, number>(rows.map((row, index) => [row, index]));\n const map: Array<Array<HTMLTableCellElement | undefined>> = rows.map(() => []);\n const cover = (rowIndex: number, column: number, colSpan: number, cell: HTMLTableCellElement) => {\n for (let offset = 0; offset < colSpan; offset += 1) map[rowIndex][column + offset] = cell;\n };\n rows.forEach((row, rowIndex) => {\n const own = map[rowIndex];\n let column = 0;\n for (const cell of Array.from(row.cells)) {\n while (own[column]) column += 1;\n const colSpan = Math.min(Math.max(cell.colSpan, 1), MAX_COL_SPAN);\n cover(rowIndex, column, colSpan, cell);\n let remaining = cell.rowSpan === 0 ? Infinity : cell.rowSpan - 1;\n for (\n let next = row.nextElementSibling;\n next && remaining > 0;\n next = next.nextElementSibling\n ) {\n if (next.localName !== 'tr') continue;\n remaining -= 1;\n const covered = indexOf.get(next);\n if (covered !== undefined) cover(covered, column, colSpan, cell);\n }\n column += colSpan;\n }\n });\n return map;\n}\n\n/** The cell of a row (as {@link getColumnMap} maps it) at `column`, else the last one before it. */\nfunction getCellAtColumn(\n rowMap: ReadonlyArray<HTMLTableCellElement | undefined>,\n column: number,\n): HTMLTableCellElement | null {\n for (let index = Math.min(column, rowMap.length - 1); index >= 0; index -= 1) {\n const cell = rowMap[index];\n if (cell) return cell;\n }\n return null;\n}\n\n/** The grid cells in `node` (itself, or its descendants that belong to `grid`). */\nfunction collectCells(grid: HTMLTableElement, node: Node, into: Set<HTMLTableCellElement>): void {\n if (!(node instanceof Element)) return;\n if (isCellElement(node) && node.closest('table') === grid) into.add(node);\n for (const el of Array.from(node.querySelectorAll<HTMLTableCellElement>('td, th'))) {\n if (el.closest('table') === grid) into.add(el);\n }\n}\n\n/** A widget of a cell: a focusable element, or a nested composite counted as one widget. */\ninterface CellWidget {\n /** The focusable element, or the nested composite's root. */\n element: HTMLElement;\n /** A nested composite: the grid never writes `tabindex` on or inside it. */\n composite: boolean;\n}\n\n/**\n * Grid keyboard model of the APG Grid pattern for a `<table role=\"grid\">` (DataGrid).\n *\n * - **One tab stop.** Exactly one cell of the grid is in the tab order (initially the first cell).\n * Its focus target is its only focusable widget when that widget is not a text-entry control\n * (a checkbox, a sort button, a link); otherwise — no widget, several widgets, a text-entry\n * control or a nested composite — the cell itself. An unchecked radio whose named group has a\n * checked radio is not a target either (browsers skip it in the Tab order), so its cell is.\n * Every other cell and every widget inside a cell gets `tabindex=\"-1\"`. Rows are never focusable.\n * When a widget becomes the target of the focused cell (Space on the cell checks its radio), the\n * cell keeps focus and the tab stop (removing its `tabindex` would blur it) while its target stays\n * at `tabindex=\"-1\"`; the target takes the tab stop once the cell is blurred.\n * - **Navigation mode** (focus on a cell or on its target widget): ArrowLeft/ArrowRight (the\n * previous/next cell of the row, mirrored in RTL), ArrowUp/ArrowDown, Home/End (first/last cell\n * of the row), Ctrl+Home/Ctrl+End (first cell of the grid / last cell of the last row) and\n * PageUp/PageDown ({@link UseGridNavigationOptions.pageSize} rows) move focus between cells.\n * ArrowUp/ArrowDown and PageUp/PageDown keep the visual column (`colSpan` and `rowSpan` counted,\n * so a grouped header works), clamped to a shorter row. Space and Enter on a target widget reach\n * the widget.\n * - **Interaction mode**: Enter or F2 on a cell that is its own target moves focus into its first\n * widget; clicking a widget that is not the cell's target (a text input) enters it too. The\n * grid keys are then ignored, so the arrow keys move the caret, and Tab moves between the cell's\n * widgets. Escape returns focus to the cell. Leaving the grid ends interaction mode.\n * - **Nested composites** (an element with `data-roving-container` or role toolbar, radiogroup,\n * listbox, grid, treegrid, tablist, menu, menubar or tree) count as one widget that uses the arrow\n * keys: its cell takes focus, and Enter/F2 enters the composite at its own tab stop. The grid never\n * writes `tabindex` on or inside a composite (the composite manages its own), so a composite\n * keeps its own Tab stop — the same documented limitation as `useRovingTabIndex`.\n *\n * Keys are ignored when the event's default was prevented (a widget that handled the key, a\n * consumer handler), or with Alt/Meta held. The tab indexes are written to the DOM (layout effect\n * plus a `MutationObserver`), so rows and widgets added later are covered without a re-render. Focus\n * moves re-sync only the cells involved, the observer only the cells that changed, and the grid's\n * own `tabindex` writes do not trigger a re-sync.\n *\n * Internal to the table package.\n */\nexport function useGridNavigation(options: UseGridNavigationOptions = {}): UseGridNavigationResult {\n const pageSize = Math.max(1, options.pageSize ?? 10);\n const [grid, setGrid] = React.useState<HTMLTableElement | null>(null);\n /** The cell that holds the tab stop. */\n const activeCellRef = React.useRef<HTMLTableCellElement | null>(null);\n /** The cell whose widgets are being interacted with, or `null` (navigation mode). */\n const interactionCellRef = React.useRef<HTMLTableCellElement | null>(null);\n /** Elements whose `tabindex` this hook wrote. */\n const [stamped] = React.useState(() => new WeakSet<Element>());\n /** The `tabindex` value this hook last wrote per element (`null`: removed), to skip own records. */\n const [written] = React.useState(() => new WeakMap<Element, string | null>());\n /**\n * Cells kept focusable (holding the tab stop, their target at -1) because they were focused when\n * a widget became their target (a radio that got checked, a widget added): they lose the\n * attribute once blurred.\n */\n const [retained] = React.useState(() => new WeakSet<Element>());\n const pageSizeRef = React.useRef(pageSize);\n\n React.useLayoutEffect(() => {\n pageSizeRef.current = pageSize;\n }, [pageSize]);\n\n /**\n * The widgets of `cell` in DOM order. `precise` checks focusability with `isFocusable` (style\n * reads); otherwise only attributes are checked (cells that do not hold the tab stop).\n */\n const getWidgets = React.useCallback(\n (cell: HTMLTableCellElement, precise: boolean): CellWidget[] => {\n const widgets: CellWidget[] = [];\n let composite: HTMLElement | null = null;\n for (const el of Array.from(cell.querySelectorAll<HTMLElement>(WIDGET_CANDIDATE_SELECTOR))) {\n // Descendants of a nested composite belong to it (candidates come in document order).\n if (composite && composite.contains(el)) continue;\n if (isNestedComposite(el)) {\n composite = el;\n const available = precise ? getCompositeEntry(el) !== null : isLikelyFocusable(el);\n if (available) widgets.push({ element: el, composite: true });\n continue;\n }\n const tabIndex = el.getAttribute('tabindex');\n // An author tabindex=\"-1\" (not written by this hook) opts the element out.\n if (tabIndex !== null && Number(tabIndex) < 0 && !stamped.has(el)) continue;\n if (precise ? isFocusable(el) : isLikelyFocusable(el)) {\n widgets.push({ element: el, composite: false });\n }\n }\n return widgets;\n },\n [stamped],\n );\n\n /**\n * The element of `cell` that takes focus in navigation mode. `checkRadioGroup` applies the radio\n * group rule, which only decides where the tab stop goes (the cell that holds it); a focused\n * lone radio is in navigation mode either way.\n */\n const getTarget = React.useCallback(\n (cell: HTMLTableCellElement, widgets: CellWidget[], checkRadioGroup: boolean): HTMLElement => {\n if (widgets.length !== 1) return cell;\n const [{ element, composite }] = widgets;\n if (composite || isTextEntryElement(element)) return cell;\n if (\n checkRadioGroup &&\n element instanceof HTMLInputElement &&\n element.type === 'radio' &&\n !element.checked &&\n hasCheckedPeer(element)\n ) {\n return cell;\n }\n return element;\n },\n [],\n );\n\n const setTabIndex = React.useCallback(\n (el: HTMLElement, value: 0 | -1) => {\n const next = String(value);\n if (el.getAttribute('tabindex') !== next) {\n written.set(el, next);\n el.setAttribute('tabindex', next);\n }\n stamped.add(el);\n },\n [stamped, written],\n );\n\n /** Writes the tab indexes of one cell and its widgets (only attributes that change). */\n const syncCell = React.useCallback(\n (cell: HTMLTableCellElement, isActive: boolean, isInteraction: boolean) => {\n const widgets = getWidgets(cell, isActive);\n if (isInteraction) {\n retained.delete(cell);\n setTabIndex(cell, -1);\n for (const widget of widgets) {\n if (!widget.composite) setTabIndex(widget.element, 0);\n }\n return;\n }\n const target = getTarget(cell, widgets, isActive);\n /** The focused cell stands in for its target until it is blurred (see below). */\n let keepCell = false;\n if (target === cell) {\n retained.delete(cell);\n setTabIndex(cell, isActive ? 0 : -1);\n } else if (stamped.has(cell) && isFocused(cell)) {\n // A widget became the focused cell's target (its radio got checked, a widget was added).\n // Removing the tabindex of the focused element blurs it (browsers move focus to the body,\n // which ends the grid session), so the cell stays focusable and stays the grid's position.\n // It keeps the tab stop (`tabindex=\"0\"`, when active) and its target stays at -1: the grid\n // still has exactly one Tab stop even if focus is lost without a focusout, and Tab and\n // Shift+Tab from the cell leave the grid (nothing after or before it inside is tabbable).\n // Once the cell is blurred, onBlur re-syncs it and the target takes the tab stop.\n keepCell = true;\n retained.add(cell);\n setTabIndex(cell, isActive ? 0 : -1);\n } else if (stamped.has(cell)) {\n // The cell's widget is its target: the cell itself is not focusable.\n retained.delete(cell);\n stamped.delete(cell);\n if (cell.hasAttribute('tabindex')) {\n written.set(cell, null);\n cell.removeAttribute('tabindex');\n }\n }\n for (const widget of widgets) {\n if (widget.composite) continue;\n const isStop = widget.element === target && isActive && !keepCell;\n setTabIndex(widget.element, isStop ? 0 : -1);\n }\n },\n [getTarget, getWidgets, retained, setTabIndex, stamped, written],\n );\n\n /**\n * Re-resolves the active cell (the first cell when it is gone) and syncs `cells` — every cell\n * when omitted. A new active cell is always synced.\n */\n const sync = React.useCallback(\n (table: HTMLTableElement, cells?: Iterable<HTMLTableCellElement | null>) => {\n const rows = getRows(table);\n let active = activeCellRef.current;\n const targets = new Set<HTMLTableCellElement>();\n if (\n !active ||\n !active.isConnected ||\n !rows.includes(active.parentElement as HTMLTableRowElement)\n ) {\n active = rows[0]?.cells[0] ?? null;\n activeCellRef.current = active;\n if (active) targets.add(active);\n }\n if (interactionCellRef.current !== null && interactionCellRef.current !== active) {\n const previous = interactionCellRef.current;\n interactionCellRef.current = null;\n targets.add(previous);\n }\n const interaction = interactionCellRef.current;\n\n if (cells === undefined) {\n for (const row of rows) for (const cell of Array.from(row.cells)) targets.add(cell);\n } else {\n for (const cell of cells) if (cell) targets.add(cell);\n }\n for (const cell of targets) {\n if (!cell.isConnected || getCell(table, cell) !== cell) continue;\n syncCell(cell, cell === active, cell === interaction);\n }\n },\n [syncCell],\n );\n\n React.useLayoutEffect(() => {\n if (!grid) return;\n sync(grid);\n if (typeof MutationObserver === 'undefined') return;\n const observer = new MutationObserver((records) => {\n let relevant = false;\n let all = false;\n const dirty = new Set<HTMLTableCellElement>();\n for (const record of records) {\n const target = record.target;\n if (!(target instanceof Element)) continue;\n // A nested composite manages its own items (and their tab indexes).\n if (isInsideNestedComposite(target, grid)) continue;\n if (record.type === 'attributes') {\n // The record of one of our own writes.\n if (\n record.attributeName === 'tabindex' &&\n written.has(target) &&\n written.get(target) === target.getAttribute('tabindex')\n ) {\n continue;\n }\n relevant = true;\n const cell = getCell(grid, target);\n if (cell) dirty.add(cell);\n else if (target.localName === 'tr') collectCells(grid, target, dirty);\n else all = true;\n continue;\n }\n relevant = true;\n const cell = getCell(grid, target);\n if (cell) {\n dirty.add(cell);\n continue;\n }\n // Rows, sections or cells added (removed ones need no sync; `sync` re-resolves the\n // active cell when it went away).\n for (const node of Array.from(record.addedNodes)) collectCells(grid, node, dirty);\n }\n if (!relevant) return;\n // The active cell's target can depend on other cells (a radio group whose checked radio\n // changed, reflected by the rows' `aria-selected`), so it is always re-checked.\n if (activeCellRef.current) dirty.add(activeCellRef.current);\n sync(grid, all ? undefined : dirty);\n });\n observer.observe(grid, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: OBSERVED_ATTRIBUTES,\n });\n return () => observer.disconnect();\n }, [grid, sync, written]);\n\n /**\n * Makes `cell` the active cell (in interaction mode when `interaction`) and re-syncs only the\n * cells involved: the previous active and interaction cells and `cell`.\n */\n const activate = React.useCallback(\n (table: HTMLTableElement, cell: HTMLTableCellElement, interaction: boolean) => {\n const previousActive = activeCellRef.current;\n const previousInteraction = interactionCellRef.current;\n activeCellRef.current = cell;\n interactionCellRef.current = interaction ? cell : null;\n sync(table, [previousActive, previousInteraction, cell]);\n },\n [sync],\n );\n\n const focusCell = React.useCallback(\n (table: HTMLTableElement, cell: HTMLTableCellElement) => {\n activate(table, cell, false);\n getTarget(cell, getWidgets(cell, true), true).focus();\n },\n [activate, getTarget, getWidgets],\n );\n\n const onKeyDown = React.useCallback<React.KeyboardEventHandler<HTMLTableElement>>(\n (event) => {\n const table = event.currentTarget;\n if (event.defaultPrevented) return;\n const cell = getCell(table, event.target);\n if (!cell) return;\n const widgets = getWidgets(cell, true);\n // A lone non-text widget is in navigation mode when focused, even an unchecked radio that\n // the radio group rule keeps from holding the tab stop.\n const widgetTarget = getTarget(cell, widgets, false);\n const onCell = event.target === cell;\n\n if (!onCell && event.target !== widgetTarget) {\n // Interaction mode: the widget keeps its keys; Escape returns to the cell.\n if (event.key === 'Escape') {\n event.preventDefault();\n focusCell(table, cell);\n }\n return;\n }\n if (event.altKey || event.metaKey) return;\n\n if ((event.key === 'Enter' || event.key === 'F2') && onCell) {\n let entry: HTMLElement | null = null;\n for (const widget of widgets) {\n entry = widget.composite ? getCompositeEntry(widget.element) : widget.element;\n if (entry) break;\n }\n if (!entry) return;\n event.preventDefault();\n activate(table, cell, true);\n entry.focus();\n return;\n }\n\n const rows = getRows(table);\n const rowIndex = rows.findIndex((row) => row === cell.parentElement);\n if (rowIndex < 0) return;\n const columnIndex = Array.from(rows[rowIndex].cells).indexOf(cell);\n const lastRow = rows.length - 1;\n const ctrl = event.ctrlKey;\n /** Left/Right, Home/End: target row and cell index in it (`Infinity`: the row's last cell). */\n let next: [row: number, column: number] | null = null;\n /** Up/Down, PageUp/PageDown: the first row to try and the direction to keep going in. */\n let vertical: [row: number, step: 1 | -1] | null = null;\n\n switch (event.key) {\n case 'ArrowLeft':\n case 'ArrowRight': {\n if (ctrl) break;\n const intent = getArrowIntent(event.key, {\n orientation: 'horizontal',\n dir: getDirection(table),\n });\n next = [rowIndex, columnIndex + (intent === 'next' ? 1 : -1)];\n break;\n }\n case 'ArrowDown':\n if (!ctrl) vertical = [rowIndex + 1, 1];\n break;\n case 'ArrowUp':\n if (!ctrl) vertical = [rowIndex - 1, -1];\n break;\n case 'Home':\n next = ctrl ? [0, 0] : [rowIndex, 0];\n break;\n case 'End':\n next = ctrl ? [lastRow, Infinity] : [rowIndex, Infinity];\n break;\n case 'PageDown':\n if (!ctrl) vertical = [Math.min(rowIndex + pageSizeRef.current, lastRow), 1];\n break;\n case 'PageUp':\n if (!ctrl) vertical = [Math.max(rowIndex - pageSizeRef.current, 0), -1];\n break;\n default:\n break;\n }\n if (!next && !vertical) return;\n // A handled key never scrolls the page or reaches the widget, even at the grid's edge.\n event.preventDefault();\n let destination: HTMLTableCellElement | null = null;\n if (vertical) {\n // Up/Down keep the visual column (spans counted), clamped to a shorter row; the rows a\n // row-spanning cell covers are skipped when moving away from it.\n const columns = getColumnMap(rows);\n const column = Math.max(columns[rowIndex].indexOf(cell), 0);\n for (let row = vertical[0]; row >= 0 && row <= lastRow; row += vertical[1]) {\n const candidate = getCellAtColumn(columns[row], column);\n if (candidate && candidate !== cell) {\n destination = candidate;\n break;\n }\n }\n } else if (next) {\n // Left/Right stop at the row's edges (no cell there).\n const [nextRow, nextColumn] = next;\n const rowCells = rows[nextRow].cells;\n destination =\n nextColumn === Infinity ? rowCells[rowCells.length - 1] : rowCells.item(nextColumn);\n }\n if (destination && destination !== cell) focusCell(table, destination);\n },\n [activate, focusCell, getTarget, getWidgets],\n );\n\n const onFocus = React.useCallback<React.FocusEventHandler<HTMLTableElement>>(\n (event) => {\n const table = event.currentTarget;\n // React types `target` as the handler's element type; it is the focused descendant.\n const target: EventTarget = event.target;\n const cell = getCell(table, target);\n if (!cell) return;\n const interaction =\n target !== cell && target !== getTarget(cell, getWidgets(cell, true), false);\n activate(table, cell, interaction);\n },\n [activate, getTarget, getWidgets],\n );\n\n const onBlur = React.useCallback<React.FocusEventHandler<HTMLTableElement>>(\n (event) => {\n const table = event.currentTarget;\n // React types `target` as the handler's element type; it is the blurred descendant.\n const left: EventTarget = event.target;\n const cells: HTMLTableCellElement[] = [];\n // A cell kept focusable while focused (see syncCell) is re-synced now that it is blurred\n // (during focusout the blurred element is no longer the active element).\n if (left instanceof Element && isCellElement(left) && retained.has(left)) cells.push(left);\n const next = event.relatedTarget;\n const interaction = interactionCellRef.current;\n if (interaction !== null && !(next instanceof Node && table.contains(next))) {\n interactionCellRef.current = null;\n cells.push(interaction);\n }\n if (cells.length > 0) sync(table, cells);\n },\n [retained, sync],\n );\n\n const gridProps = React.useMemo<GridNavigationHandlers>(\n () => ({ onKeyDown, onFocus, onBlur }),\n [onBlur, onFocus, onKeyDown],\n );\n\n return { gridRef: setGrid, gridProps };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useGridNavigation.cjs","names":["FOCUSABLE_SELECTOR","isFocusable","getFirstTabbable","React","isFocused","getArrowIntent","getDirection","require_runtime","require_direction","require_focus","react","NON_TEXT_INPUT_TYPES","KEY_CONSUMING_ROLES","COMPOSITE_SELECTOR","role","WIDGET_CANDIDATE_SELECTOR","OBSERVED_ATTRIBUTES","isTextEntryElement","tag","el","editable","isNestedComposite","isInsideNestedComposite","node","grid","getCompositeEntry","stop","composite","isLikelyFocusable","hasCheckedPeer","radio","root","checked","other","isCellElement","getCell","getRows","row","MAX_COL_SPAN","getColumnMap","indexOf","rows","index","map","cover","offset","colSpan","rowIndex","column","cell","own","remaining","next","covered","getCellAtColumn","rowMap","collectCells","into","useGridNavigation","pageSize","options","setGrid","activeCellRef","interactionCellRef","stamped","written","retained","pageSizeRef","getWidgets","widgets","precise","tabIndex","getTarget","element","checkRadioGroup","setTabIndex","value","syncCell","isActive","isInteraction","widget","target","keepCell","isStop","sync","table","active","targets","previous","interaction","cells","observer","relevant","all","dirty","record","records","activate","previousActive","previousInteraction","focusCell","onKeyDown","event","widgetTarget","onCell","entry","columnIndex","lastRow","ctrl","vertical","destination","columns","candidate","nextRow","nextColumn","rowCells","onFocus","onBlur","left"],"sources":["../../../src/components/table/useGridNavigation.ts"],"sourcesContent":["import * as React from 'react';\nimport { getArrowIntent, getDirection } from '../../lib/direction';\nimport { FOCUSABLE_SELECTOR, getFirstTabbable, isFocusable, isFocused } from '../../lib/focus';\n\n/** Options of {@link useGridNavigation}. */\nexport interface UseGridNavigationOptions {\n /**\n * How many rows PageUp and PageDown move focus.\n * @default 10\n */\n pageSize?: number;\n}\n\n/** Handlers {@link useGridNavigation} needs on the grid element (compose them with the consumer's). */\nexport interface GridNavigationHandlers {\n onKeyDown: React.KeyboardEventHandler<HTMLTableElement>;\n onFocus: React.FocusEventHandler<HTMLTableElement>;\n onBlur: React.FocusEventHandler<HTMLTableElement>;\n}\n\n/** Result of {@link useGridNavigation}. */\nexport interface UseGridNavigationResult {\n /** Callback ref for the `<table role=\"grid\">` element. Stable identity. */\n gridRef: React.RefCallback<HTMLTableElement>;\n /** Keyboard and focus handlers for the grid element. Stable identities. */\n gridProps: GridNavigationHandlers;\n}\n\n/** Input types that do not take arrow or character keys, so a lone one can be a cell's focus target. */\nconst NON_TEXT_INPUT_TYPES: ReadonlySet<string> = new Set([\n 'checkbox',\n 'radio',\n 'button',\n 'submit',\n 'reset',\n 'image',\n 'file',\n 'color',\n]);\n\n/** Widget roles that use arrow or character keys themselves (treated like text entry). */\nconst KEY_CONSUMING_ROLES: ReadonlySet<string> = new Set([\n 'textbox',\n 'searchbox',\n 'combobox',\n 'spinbutton',\n 'slider',\n]);\n\n/**\n * Roles of composite widgets that manage their own children (and usually their tab indexes): an\n * element with one of them, or with `data-roving-container`, is a nested composite. Close to the\n * list of `useRovingTabIndex`, which is kept separately and differs on purpose: here a `toolbar`\n * is a composite (a toolbar in a cell is one widget), while `spinbutton` is not (it is one\n * focusable control that uses the arrow keys, handled by `KEY_CONSUMING_ROLES` like a text\n * field). Keep both lists in mind when adding a role.\n */\nconst COMPOSITE_ROLES: readonly string[] = [\n 'toolbar',\n 'radiogroup',\n 'listbox',\n 'grid',\n 'treegrid',\n 'tablist',\n 'menu',\n 'menubar',\n 'tree',\n];\n\nconst COMPOSITE_SELECTOR = [\n '[data-roving-container]',\n ...COMPOSITE_ROLES.map((role) => `[role=\"${role}\"]`),\n].join(', ');\n\n/** Every element that can be a cell widget: a focusable element or a nested composite root. */\nconst WIDGET_CANDIDATE_SELECTOR = `${FOCUSABLE_SELECTOR}, ${COMPOSITE_SELECTOR}`;\n\n/**\n * Attributes whose changes can change a cell's widgets or the grid's rows. `aria-selected` on a row\n * signals a selection change, which can change the active cell's target (radio groups).\n */\nconst OBSERVED_ATTRIBUTES = [\n 'disabled',\n 'tabindex',\n 'hidden',\n 'inert',\n 'type',\n 'contenteditable',\n 'role',\n 'data-roving-container',\n 'aria-selected',\n];\n\n/**\n * Whether `el` is a text-entry control (or another widget that uses arrow keys itself): a text-like\n * `input`, `textarea`, `select`, a `contenteditable` element, or an element with role textbox,\n * searchbox, combobox, spinbutton or slider. Such widgets are never a cell's navigation target: the\n * cell takes focus and Enter/F2 moves into the widget, so the arrow keys keep moving the caret.\n */\nexport function isTextEntryElement(el: Element): boolean {\n const tag = el.localName;\n if (tag === 'textarea' || tag === 'select') return true;\n if (tag === 'input') return !NON_TEXT_INPUT_TYPES.has((el as HTMLInputElement).type);\n const editable = el.getAttribute('contenteditable');\n if (editable !== null && editable !== 'false') return true;\n const role = el.getAttribute('role');\n return role !== null && KEY_CONSUMING_ROLES.has(role);\n}\n\n/** Whether `el` is a nested composite widget (own roving container or a composite role). */\nfunction isNestedComposite(el: Element): boolean {\n return el.matches(COMPOSITE_SELECTOR);\n}\n\n/** Whether `node` is strictly inside a nested composite of `grid` (the composite root excluded). */\nfunction isInsideNestedComposite(node: Element, grid: HTMLTableElement): boolean {\n if (node === grid) return false;\n for (let el = node.parentElement; el && el !== grid; el = el.parentElement) {\n if (isNestedComposite(el)) return true;\n }\n return false;\n}\n\n/**\n * The element a nested composite is entered at: its own tab stop (`tabindex=\"0\"`), else its first\n * tabbable element (the root itself when it is the focusable element).\n */\nfunction getCompositeEntry(composite: HTMLElement): HTMLElement | null {\n const stop = composite.querySelector<HTMLElement>('[tabindex=\"0\"]');\n if (stop && isFocusable(stop)) return stop;\n return getFirstTabbable(composite, { includeContainer: true });\n}\n\n/**\n * A cheap focusability check for cells that do not hold the tab stop: no style reads, only the\n * attributes that take an element out of the focus order. The cell that holds (or receives) the tab\n * stop is checked precisely with `isFocusable`.\n */\nfunction isLikelyFocusable(el: HTMLElement): boolean {\n if (el.localName === 'input' && (el as HTMLInputElement).type === 'hidden') return false;\n if (el.matches(':disabled')) return false;\n return el.closest('[hidden], [inert]') === null;\n}\n\n/** Whether another radio of `radio`'s named group is checked (the browser's tab stop of the group). */\nfunction hasCheckedPeer(radio: HTMLInputElement): boolean {\n if (!radio.name) return false;\n const root = radio.getRootNode() as Document | ShadowRoot;\n if (typeof root.querySelectorAll !== 'function') return false;\n const checked = root.querySelectorAll<HTMLInputElement>('input[type=\"radio\"]:checked');\n for (const other of Array.from(checked)) {\n if (other !== radio && other.name === radio.name && other.form === radio.form) return true;\n }\n return false;\n}\n\nfunction isCellElement(el: Element): el is HTMLTableCellElement {\n return el.localName === 'td' || el.localName === 'th';\n}\n\n/** The cell of `grid` that contains `node` (a nested table's cells belong to the outer cell). */\nfunction getCell(grid: HTMLTableElement, node: EventTarget | null): HTMLTableCellElement | null {\n let el: Element | null = node instanceof Element ? node : null;\n while (el && el !== grid) {\n if (isCellElement(el) && el.closest('table') === grid) return el;\n el = el.parentElement;\n }\n return null;\n}\n\n/** The rows of `grid` (header, body and footer rows; a nested table's rows are not included). */\nfunction getRows(grid: HTMLTableElement): HTMLTableRowElement[] {\n return Array.from(grid.rows).filter((row) => row.cells.length > 0 && !row.hidden);\n}\n\n/** Browsers clamp `colSpan` to 1..1000 (`rowSpan` 0 covers the rest of its row group). */\nconst MAX_COL_SPAN = 1000;\n\n/** Per row, the cell covering each visual column (see {@link getColumnMap}). */\ntype ColumnMap = ReadonlyArray<ReadonlyArray<HTMLTableCellElement | undefined>>;\n\n/**\n * The HTML table model of `rows`: per row, the cell covering each visual column, with `colSpan` and\n * `rowSpan` counted. A row span covers the next rows of its row group in the DOM (a hidden row\n * counts, but gets no entry). A column no cell covers is empty. O(number of cells).\n */\nfunction getColumnMap(rows: readonly HTMLTableRowElement[]): ColumnMap {\n const indexOf = new Map<Element, number>(rows.map((row, index) => [row, index]));\n const map: Array<Array<HTMLTableCellElement | undefined>> = rows.map(() => []);\n const cover = (rowIndex: number, column: number, colSpan: number, cell: HTMLTableCellElement) => {\n for (let offset = 0; offset < colSpan; offset += 1) map[rowIndex][column + offset] = cell;\n };\n rows.forEach((row, rowIndex) => {\n const own = map[rowIndex];\n let column = 0;\n for (const cell of Array.from(row.cells)) {\n while (own[column]) column += 1;\n const colSpan = Math.min(Math.max(cell.colSpan, 1), MAX_COL_SPAN);\n cover(rowIndex, column, colSpan, cell);\n let remaining = cell.rowSpan === 0 ? Infinity : cell.rowSpan - 1;\n for (\n let next = row.nextElementSibling;\n next && remaining > 0;\n next = next.nextElementSibling\n ) {\n if (next.localName !== 'tr') continue;\n remaining -= 1;\n const covered = indexOf.get(next);\n if (covered !== undefined) cover(covered, column, colSpan, cell);\n }\n column += colSpan;\n }\n });\n return map;\n}\n\n/** The cell of a row (as {@link getColumnMap} maps it) at `column`, else the last one before it. */\nfunction getCellAtColumn(\n rowMap: ReadonlyArray<HTMLTableCellElement | undefined>,\n column: number,\n): HTMLTableCellElement | null {\n for (let index = Math.min(column, rowMap.length - 1); index >= 0; index -= 1) {\n const cell = rowMap[index];\n if (cell) return cell;\n }\n return null;\n}\n\n/** The grid cells in `node` (itself, or its descendants that belong to `grid`). */\nfunction collectCells(grid: HTMLTableElement, node: Node, into: Set<HTMLTableCellElement>): void {\n if (!(node instanceof Element)) return;\n if (isCellElement(node) && node.closest('table') === grid) into.add(node);\n for (const el of Array.from(node.querySelectorAll<HTMLTableCellElement>('td, th'))) {\n if (el.closest('table') === grid) into.add(el);\n }\n}\n\n/** A widget of a cell: a focusable element, or a nested composite counted as one widget. */\ninterface CellWidget {\n /** The focusable element, or the nested composite's root. */\n element: HTMLElement;\n /** A nested composite: the grid never writes `tabindex` on or inside it. */\n composite: boolean;\n}\n\n/**\n * Grid keyboard model of the APG Grid pattern for a `<table role=\"grid\">` (DataGrid).\n *\n * - **One tab stop.** Exactly one cell of the grid is in the tab order (initially the first cell).\n * Its focus target is its only focusable widget when that widget is not a text-entry control\n * (a checkbox, a sort button, a link); otherwise — no widget, several widgets, a text-entry\n * control or a nested composite — the cell itself. An unchecked radio whose named group has a\n * checked radio is not a target either (browsers skip it in the Tab order), so its cell is.\n * Every other cell and every widget inside a cell gets `tabindex=\"-1\"`. Rows are never focusable.\n * When a widget becomes the target of the focused cell (Space on the cell checks its radio), the\n * cell keeps focus and the tab stop (removing its `tabindex` would blur it) while its target stays\n * at `tabindex=\"-1\"`; the target takes the tab stop once the cell is blurred.\n * - **Navigation mode** (focus on a cell or on its target widget): ArrowLeft/ArrowRight (the\n * previous/next cell of the row, mirrored in RTL), ArrowUp/ArrowDown, Home/End (first/last cell\n * of the row), Ctrl+Home/Ctrl+End (first cell of the grid / last cell of the last row) and\n * PageUp/PageDown ({@link UseGridNavigationOptions.pageSize} rows) move focus between cells.\n * ArrowUp/ArrowDown and PageUp/PageDown keep the visual column (`colSpan` and `rowSpan` counted,\n * so a grouped header works), clamped to a shorter row. Space and Enter on a target widget reach\n * the widget.\n * - **Interaction mode**: Enter or F2 on a cell that is its own target moves focus into its first\n * widget; clicking a widget that is not the cell's target (a text input) enters it too. The\n * grid keys are then ignored, so the arrow keys move the caret, and Tab moves between the cell's\n * widgets. Escape returns focus to the cell. Leaving the grid ends interaction mode.\n * - **Nested composites** (an element with `data-roving-container` or role toolbar, radiogroup,\n * listbox, grid, treegrid, tablist, menu, menubar or tree) count as one widget that uses the arrow\n * keys: its cell takes focus, and Enter/F2 enters the composite at its own tab stop. The grid never\n * writes `tabindex` on or inside a composite (the composite manages its own), so a composite\n * keeps its own Tab stop — the same documented limitation as `useRovingTabIndex`.\n *\n * Keys are ignored when the event's default was prevented (a widget that handled the key, a\n * consumer handler), or with Alt/Meta held. The tab indexes are written to the DOM (layout effect\n * plus a `MutationObserver`), so rows and widgets added later are covered without a re-render. Focus\n * moves re-sync only the cells involved, the observer only the cells that changed, and the grid's\n * own `tabindex` writes do not trigger a re-sync.\n *\n * Internal to the table package.\n */\nexport function useGridNavigation(options: UseGridNavigationOptions = {}): UseGridNavigationResult {\n const pageSize = Math.max(1, options.pageSize ?? 10);\n const [grid, setGrid] = React.useState<HTMLTableElement | null>(null);\n /** The cell that holds the tab stop. */\n const activeCellRef = React.useRef<HTMLTableCellElement | null>(null);\n /** The cell whose widgets are being interacted with, or `null` (navigation mode). */\n const interactionCellRef = React.useRef<HTMLTableCellElement | null>(null);\n /** Elements whose `tabindex` this hook wrote. */\n const [stamped] = React.useState(() => new WeakSet<Element>());\n /** The `tabindex` value this hook last wrote per element (`null`: removed), to skip own records. */\n const [written] = React.useState(() => new WeakMap<Element, string | null>());\n /**\n * Cells kept focusable (holding the tab stop, their target at -1) because they were focused when\n * a widget became their target (a radio that got checked, a widget added): they lose the\n * attribute once blurred.\n */\n const [retained] = React.useState(() => new WeakSet<Element>());\n const pageSizeRef = React.useRef(pageSize);\n\n React.useLayoutEffect(() => {\n pageSizeRef.current = pageSize;\n }, [pageSize]);\n\n /**\n * The widgets of `cell` in DOM order. `precise` checks focusability with `isFocusable` (style\n * reads); otherwise only attributes are checked (cells that do not hold the tab stop).\n */\n const getWidgets = React.useCallback(\n (cell: HTMLTableCellElement, precise: boolean): CellWidget[] => {\n const widgets: CellWidget[] = [];\n let composite: HTMLElement | null = null;\n for (const el of Array.from(cell.querySelectorAll<HTMLElement>(WIDGET_CANDIDATE_SELECTOR))) {\n // Descendants of a nested composite belong to it (candidates come in document order).\n if (composite && composite.contains(el)) continue;\n if (isNestedComposite(el)) {\n composite = el;\n const available = precise ? getCompositeEntry(el) !== null : isLikelyFocusable(el);\n if (available) widgets.push({ element: el, composite: true });\n continue;\n }\n const tabIndex = el.getAttribute('tabindex');\n // An author tabindex=\"-1\" (not written by this hook) opts the element out.\n if (tabIndex !== null && Number(tabIndex) < 0 && !stamped.has(el)) continue;\n if (precise ? isFocusable(el) : isLikelyFocusable(el)) {\n widgets.push({ element: el, composite: false });\n }\n }\n return widgets;\n },\n [stamped],\n );\n\n /**\n * The element of `cell` that takes focus in navigation mode. `checkRadioGroup` applies the radio\n * group rule, which only decides where the tab stop goes (the cell that holds it); a focused\n * lone radio is in navigation mode either way.\n */\n const getTarget = React.useCallback(\n (cell: HTMLTableCellElement, widgets: CellWidget[], checkRadioGroup: boolean): HTMLElement => {\n if (widgets.length !== 1) return cell;\n const [{ element, composite }] = widgets;\n if (composite || isTextEntryElement(element)) return cell;\n if (\n checkRadioGroup &&\n element instanceof HTMLInputElement &&\n element.type === 'radio' &&\n !element.checked &&\n hasCheckedPeer(element)\n ) {\n return cell;\n }\n return element;\n },\n [],\n );\n\n const setTabIndex = React.useCallback(\n (el: HTMLElement, value: 0 | -1) => {\n const next = String(value);\n if (el.getAttribute('tabindex') !== next) {\n written.set(el, next);\n el.setAttribute('tabindex', next);\n }\n stamped.add(el);\n },\n [stamped, written],\n );\n\n /** Writes the tab indexes of one cell and its widgets (only attributes that change). */\n const syncCell = React.useCallback(\n (cell: HTMLTableCellElement, isActive: boolean, isInteraction: boolean) => {\n const widgets = getWidgets(cell, isActive);\n if (isInteraction) {\n retained.delete(cell);\n setTabIndex(cell, -1);\n for (const widget of widgets) {\n if (!widget.composite) setTabIndex(widget.element, 0);\n }\n return;\n }\n const target = getTarget(cell, widgets, isActive);\n /** The focused cell stands in for its target until it is blurred (see below). */\n let keepCell = false;\n if (target === cell) {\n retained.delete(cell);\n setTabIndex(cell, isActive ? 0 : -1);\n } else if (stamped.has(cell) && isFocused(cell)) {\n // A widget became the focused cell's target (its radio got checked, a widget was added).\n // Removing the tabindex of the focused element blurs it (browsers move focus to the body,\n // which ends the grid session), so the cell stays focusable and stays the grid's position.\n // It keeps the tab stop (`tabindex=\"0\"`, when active) and its target stays at -1: the grid\n // still has exactly one Tab stop even if focus is lost without a focusout, and Tab and\n // Shift+Tab from the cell leave the grid (nothing after or before it inside is tabbable).\n // Once the cell is blurred, onBlur re-syncs it and the target takes the tab stop.\n keepCell = true;\n retained.add(cell);\n setTabIndex(cell, isActive ? 0 : -1);\n } else if (stamped.has(cell)) {\n // The cell's widget is its target: the cell itself is not focusable.\n retained.delete(cell);\n stamped.delete(cell);\n if (cell.hasAttribute('tabindex')) {\n written.set(cell, null);\n cell.removeAttribute('tabindex');\n }\n }\n for (const widget of widgets) {\n if (widget.composite) continue;\n const isStop = widget.element === target && isActive && !keepCell;\n setTabIndex(widget.element, isStop ? 0 : -1);\n }\n },\n [getTarget, getWidgets, retained, setTabIndex, stamped, written],\n );\n\n /**\n * Re-resolves the active cell (the first cell when it is gone) and syncs `cells` — every cell\n * when omitted. A new active cell is always synced.\n */\n const sync = React.useCallback(\n (table: HTMLTableElement, cells?: Iterable<HTMLTableCellElement | null>) => {\n const rows = getRows(table);\n let active = activeCellRef.current;\n const targets = new Set<HTMLTableCellElement>();\n if (\n !active ||\n !active.isConnected ||\n !rows.includes(active.parentElement as HTMLTableRowElement)\n ) {\n active = rows[0]?.cells[0] ?? null;\n activeCellRef.current = active;\n if (active) targets.add(active);\n }\n if (interactionCellRef.current !== null && interactionCellRef.current !== active) {\n const previous = interactionCellRef.current;\n interactionCellRef.current = null;\n targets.add(previous);\n }\n const interaction = interactionCellRef.current;\n\n if (cells === undefined) {\n for (const row of rows) for (const cell of Array.from(row.cells)) targets.add(cell);\n } else {\n for (const cell of cells) if (cell) targets.add(cell);\n }\n for (const cell of targets) {\n if (!cell.isConnected || getCell(table, cell) !== cell) continue;\n syncCell(cell, cell === active, cell === interaction);\n }\n },\n [syncCell],\n );\n\n React.useLayoutEffect(() => {\n if (!grid) return;\n sync(grid);\n if (typeof MutationObserver === 'undefined') return;\n const observer = new MutationObserver((records) => {\n let relevant = false;\n let all = false;\n const dirty = new Set<HTMLTableCellElement>();\n for (const record of records) {\n const target = record.target;\n if (!(target instanceof Element)) continue;\n // A nested composite manages its own items (and their tab indexes).\n if (isInsideNestedComposite(target, grid)) continue;\n if (record.type === 'attributes') {\n // The record of one of our own writes.\n if (\n record.attributeName === 'tabindex' &&\n written.has(target) &&\n written.get(target) === target.getAttribute('tabindex')\n ) {\n continue;\n }\n relevant = true;\n const cell = getCell(grid, target);\n if (cell) dirty.add(cell);\n else if (target.localName === 'tr') collectCells(grid, target, dirty);\n else all = true;\n continue;\n }\n relevant = true;\n const cell = getCell(grid, target);\n if (cell) {\n dirty.add(cell);\n continue;\n }\n // Rows, sections or cells added (removed ones need no sync; `sync` re-resolves the\n // active cell when it went away).\n for (const node of Array.from(record.addedNodes)) collectCells(grid, node, dirty);\n }\n if (!relevant) return;\n // The active cell's target can depend on other cells (a radio group whose checked radio\n // changed, reflected by the rows' `aria-selected`), so it is always re-checked.\n if (activeCellRef.current) dirty.add(activeCellRef.current);\n sync(grid, all ? undefined : dirty);\n });\n observer.observe(grid, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: OBSERVED_ATTRIBUTES,\n });\n return () => observer.disconnect();\n }, [grid, sync, written]);\n\n /**\n * Makes `cell` the active cell (in interaction mode when `interaction`) and re-syncs only the\n * cells involved: the previous active and interaction cells and `cell`.\n */\n const activate = React.useCallback(\n (table: HTMLTableElement, cell: HTMLTableCellElement, interaction: boolean) => {\n const previousActive = activeCellRef.current;\n const previousInteraction = interactionCellRef.current;\n activeCellRef.current = cell;\n interactionCellRef.current = interaction ? cell : null;\n sync(table, [previousActive, previousInteraction, cell]);\n },\n [sync],\n );\n\n const focusCell = React.useCallback(\n (table: HTMLTableElement, cell: HTMLTableCellElement) => {\n activate(table, cell, false);\n getTarget(cell, getWidgets(cell, true), true).focus();\n },\n [activate, getTarget, getWidgets],\n );\n\n const onKeyDown = React.useCallback<React.KeyboardEventHandler<HTMLTableElement>>(\n (event) => {\n const table = event.currentTarget;\n if (event.defaultPrevented) return;\n const cell = getCell(table, event.target);\n if (!cell) return;\n const widgets = getWidgets(cell, true);\n // A lone non-text widget is in navigation mode when focused, even an unchecked radio that\n // the radio group rule keeps from holding the tab stop.\n const widgetTarget = getTarget(cell, widgets, false);\n const onCell = event.target === cell;\n\n if (!onCell && event.target !== widgetTarget) {\n // Interaction mode: the widget keeps its keys; Escape returns to the cell.\n if (event.key === 'Escape') {\n event.preventDefault();\n focusCell(table, cell);\n }\n return;\n }\n if (event.altKey || event.metaKey) return;\n\n if ((event.key === 'Enter' || event.key === 'F2') && onCell) {\n let entry: HTMLElement | null = null;\n for (const widget of widgets) {\n entry = widget.composite ? getCompositeEntry(widget.element) : widget.element;\n if (entry) break;\n }\n if (!entry) return;\n event.preventDefault();\n activate(table, cell, true);\n entry.focus();\n return;\n }\n\n const rows = getRows(table);\n const rowIndex = rows.findIndex((row) => row === cell.parentElement);\n if (rowIndex < 0) return;\n const columnIndex = Array.from(rows[rowIndex].cells).indexOf(cell);\n const lastRow = rows.length - 1;\n const ctrl = event.ctrlKey;\n /** Left/Right, Home/End: target row and cell index in it (`Infinity`: the row's last cell). */\n let next: [row: number, column: number] | null = null;\n /** Up/Down, PageUp/PageDown: the first row to try and the direction to keep going in. */\n let vertical: [row: number, step: 1 | -1] | null = null;\n\n switch (event.key) {\n case 'ArrowLeft':\n case 'ArrowRight': {\n if (ctrl) break;\n const intent = getArrowIntent(event.key, {\n orientation: 'horizontal',\n dir: getDirection(table),\n });\n next = [rowIndex, columnIndex + (intent === 'next' ? 1 : -1)];\n break;\n }\n case 'ArrowDown':\n if (!ctrl) vertical = [rowIndex + 1, 1];\n break;\n case 'ArrowUp':\n if (!ctrl) vertical = [rowIndex - 1, -1];\n break;\n case 'Home':\n next = ctrl ? [0, 0] : [rowIndex, 0];\n break;\n case 'End':\n next = ctrl ? [lastRow, Infinity] : [rowIndex, Infinity];\n break;\n case 'PageDown':\n if (!ctrl) vertical = [Math.min(rowIndex + pageSizeRef.current, lastRow), 1];\n break;\n case 'PageUp':\n if (!ctrl) vertical = [Math.max(rowIndex - pageSizeRef.current, 0), -1];\n break;\n default:\n break;\n }\n if (!next && !vertical) return;\n // A handled key never scrolls the page or reaches the widget, even at the grid's edge.\n event.preventDefault();\n let destination: HTMLTableCellElement | null = null;\n if (vertical) {\n // Up/Down keep the visual column (spans counted), clamped to a shorter row; the rows a\n // row-spanning cell covers are skipped when moving away from it.\n const columns = getColumnMap(rows);\n const column = Math.max(columns[rowIndex].indexOf(cell), 0);\n for (let row = vertical[0]; row >= 0 && row <= lastRow; row += vertical[1]) {\n const candidate = getCellAtColumn(columns[row], column);\n if (candidate && candidate !== cell) {\n destination = candidate;\n break;\n }\n }\n } else if (next) {\n // Left/Right stop at the row's edges (no cell there).\n const [nextRow, nextColumn] = next;\n const rowCells = rows[nextRow].cells;\n destination =\n nextColumn === Infinity ? rowCells[rowCells.length - 1] : rowCells.item(nextColumn);\n }\n if (destination && destination !== cell) focusCell(table, destination);\n },\n [activate, focusCell, getTarget, getWidgets],\n );\n\n const onFocus = React.useCallback<React.FocusEventHandler<HTMLTableElement>>(\n (event) => {\n const table = event.currentTarget;\n // React types `target` as the handler's element type; it is the focused descendant.\n const target: EventTarget = event.target;\n const cell = getCell(table, target);\n if (!cell) return;\n const interaction =\n target !== cell && target !== getTarget(cell, getWidgets(cell, true), false);\n activate(table, cell, interaction);\n },\n [activate, getTarget, getWidgets],\n );\n\n const onBlur = React.useCallback<React.FocusEventHandler<HTMLTableElement>>(\n (event) => {\n const table = event.currentTarget;\n // React types `target` as the handler's element type; it is the blurred descendant.\n const left: EventTarget = event.target;\n const cells: HTMLTableCellElement[] = [];\n // A cell kept focusable while focused (see syncCell) is re-synced now that it is blurred\n // (during focusout the blurred element is no longer the active element).\n if (left instanceof Element && isCellElement(left) && retained.has(left)) cells.push(left);\n const next = event.relatedTarget;\n const interaction = interactionCellRef.current;\n if (interaction !== null && !(next instanceof Node && table.contains(next))) {\n interactionCellRef.current = null;\n cells.push(interaction);\n }\n if (cells.length > 0) sync(table, cells);\n },\n [retained, sync],\n );\n\n const gridProps = React.useMemo<GridNavigationHandlers>(\n () => ({ onKeyDown, onFocus, onBlur }),\n [onBlur, onFocus, onKeyDown],\n );\n\n return { gridRef: setGrid, gridProps };\n}\n"],"mappings":"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"}