@open-mercato/ui 0.8.1-develop.7296.1.2111d779db → 0.9.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 (85) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/backend/CrudForm.js +19 -6
  3. package/dist/backend/CrudForm.js.map +2 -2
  4. package/dist/backend/DataTable.js +62 -10
  5. package/dist/backend/DataTable.js.map +2 -2
  6. package/dist/backend/RowActions.js +54 -5
  7. package/dist/backend/RowActions.js.map +2 -2
  8. package/dist/backend/charts/KpiCard.js +2 -1
  9. package/dist/backend/charts/KpiCard.js.map +2 -2
  10. package/dist/backend/detail/AttachmentsSection.js +17 -5
  11. package/dist/backend/detail/AttachmentsSection.js.map +2 -2
  12. package/dist/backend/detail/NotesSection.js +38 -20
  13. package/dist/backend/detail/NotesSection.js.map +2 -2
  14. package/dist/backend/dev/DevRuntimeDiagnosticsBanner.js +11 -1
  15. package/dist/backend/dev/DevRuntimeDiagnosticsBanner.js.map +2 -2
  16. package/dist/backend/icons/lucideRegistry.generated.js +26 -0
  17. package/dist/backend/icons/lucideRegistry.generated.js.map +2 -2
  18. package/dist/backend/injection/eventBridge.js +33 -0
  19. package/dist/backend/injection/eventBridge.js.map +2 -2
  20. package/dist/backend/inputs/LookupSelect.js +20 -3
  21. package/dist/backend/inputs/LookupSelect.js.map +2 -2
  22. package/dist/backend/inputs/PhoneNumberField.js +1 -1
  23. package/dist/backend/inputs/PhoneNumberField.js.map +2 -2
  24. package/dist/backend/inputs/TagsInput.js +20 -2
  25. package/dist/backend/inputs/TagsInput.js.map +2 -2
  26. package/dist/backend/messages/EmailThreadsPanel.js +2 -1
  27. package/dist/backend/messages/EmailThreadsPanel.js.map +2 -2
  28. package/dist/backend/messages/MessageComposer.js.map +2 -2
  29. package/dist/backend/messages/index.js.map +2 -2
  30. package/dist/backend/messages/message-compose-form-groups.js +24 -0
  31. package/dist/backend/messages/message-compose-form-groups.js.map +2 -2
  32. package/dist/backend/messages/useMessageCompose.js +27 -0
  33. package/dist/backend/messages/useMessageCompose.js.map +2 -2
  34. package/dist/backend/messages/useMessageComposeOperations.js +1 -0
  35. package/dist/backend/messages/useMessageComposeOperations.js.map +2 -2
  36. package/dist/backend/notifications/NotificationItem.js +8 -1
  37. package/dist/backend/notifications/NotificationItem.js.map +2 -2
  38. package/dist/portal/PortalShell.js +2 -2
  39. package/dist/portal/PortalShell.js.map +2 -2
  40. package/dist/primitives/calendar.js +69 -14
  41. package/dist/primitives/calendar.js.map +2 -2
  42. package/package.json +4 -5
  43. package/src/backend/CrudForm.tsx +30 -8
  44. package/src/backend/DataTable.tsx +95 -20
  45. package/src/backend/RowActions.tsx +114 -6
  46. package/src/backend/__tests__/AttachmentsSection.upload.test.tsx +183 -0
  47. package/src/backend/__tests__/CrudForm.fieldInjection.test.tsx +139 -2
  48. package/src/backend/__tests__/CrudForm.hiddenGroups.test.tsx +1 -1
  49. package/src/backend/__tests__/DataTable.extensions.test.tsx +30 -0
  50. package/src/backend/__tests__/DataTable.perspectiveReconcile.test.tsx +320 -2
  51. package/src/backend/__tests__/DataTable.stickyResponsive.test.tsx +19 -0
  52. package/src/backend/__tests__/DevRuntimeDiagnosticsBanner.test.tsx +117 -0
  53. package/src/backend/__tests__/NotesSection.test.tsx +138 -0
  54. package/src/backend/__tests__/RowActions.test.tsx +202 -0
  55. package/src/backend/charts/KpiCard.tsx +1 -0
  56. package/src/backend/charts/__tests__/KpiCard.test.tsx +20 -0
  57. package/src/backend/detail/AttachmentsSection.tsx +17 -5
  58. package/src/backend/detail/NotesSection.tsx +122 -66
  59. package/src/backend/dev/DevRuntimeDiagnosticsBanner.tsx +24 -1
  60. package/src/backend/icons/lucideRegistry.generated.tsx +26 -0
  61. package/src/backend/injection/__tests__/eventBridge.test.tsx +47 -1
  62. package/src/backend/injection/eventBridge.ts +35 -0
  63. package/src/backend/inputs/LookupSelect.tsx +32 -3
  64. package/src/backend/inputs/PhoneNumberField.tsx +1 -1
  65. package/src/backend/inputs/TagsInput.tsx +24 -3
  66. package/src/backend/inputs/__tests__/LookupSelect.test.tsx +166 -1
  67. package/src/backend/inputs/__tests__/PhoneNumberField.typeahead.test.tsx +43 -0
  68. package/src/backend/inputs/__tests__/TagsInput.test.tsx +81 -0
  69. package/src/backend/messages/EmailThreadsPanel.tsx +21 -1
  70. package/src/backend/messages/MessageComposer.tsx +1 -0
  71. package/src/backend/messages/__tests__/EmailThreadsPanel.senderLabel.test.tsx +99 -0
  72. package/src/backend/messages/__tests__/MessageComposer.test.tsx +44 -0
  73. package/src/backend/messages/__tests__/useMessageCompose.test.tsx +69 -0
  74. package/src/backend/messages/__tests__/useMessageComposeSender.test.tsx +208 -0
  75. package/src/backend/messages/index.ts +1 -0
  76. package/src/backend/messages/message-compose-form-groups.tsx +41 -0
  77. package/src/backend/messages/message-composer.types.ts +26 -0
  78. package/src/backend/messages/useMessageCompose.ts +46 -0
  79. package/src/backend/messages/useMessageComposeOperations.ts +8 -0
  80. package/src/backend/notifications/NotificationItem.tsx +7 -0
  81. package/src/backend/notifications/__tests__/NotificationItem.customRenderer.test.tsx +139 -0
  82. package/src/portal/PortalShell.tsx +7 -4
  83. package/src/portal/__tests__/PortalShell.test.tsx +29 -1
  84. package/src/primitives/__tests__/calendar.test.tsx +174 -1
  85. package/src/primitives/calendar.tsx +168 -96
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/primitives/calendar.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { DayPicker, useDayPicker } from 'react-day-picker'\nimport type { DayPickerProps, CalendarMonth } from 'react-day-picker'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\nimport { format } from 'date-fns/format'\nimport type { Locale } from 'date-fns/locale'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { CompactButton } from './compact-button'\n\nexport type CalendarProps = DayPickerProps & { daySize?: 36 | 40 }\n\nexport type CalendarMonthSelectorProps = React.HTMLAttributes<HTMLDivElement> & {\n month: Date\n locale?: Locale\n onPreviousMonth?: () => void\n onNextMonth?: () => void\n disabledPrevious?: boolean\n disabledNext?: boolean\n}\n\nexport function CalendarMonthSelector({\n month, locale, onPreviousMonth, onNextMonth, disabledPrevious, disabledNext, className, ...props\n}: CalendarMonthSelectorProps) {\n const t = useT()\n return (\n <div data-slot=\"calendar-month-selector\" className={cn('flex h-9 items-center gap-1.5 rounded-md bg-muted p-1.5', className)} {...props}>\n {onPreviousMonth\n ? <CompactButton appearance=\"white\" size={24} className=\"shadow-xs\" onClick={onPreviousMonth} disabled={disabledPrevious} aria-label={t('ui.calendar.previousMonth', 'Previous month')}><ChevronLeft className=\"size-5\" aria-hidden=\"true\" /></CompactButton>\n : <span className=\"size-6 shrink-0\" aria-hidden=\"true\" />}\n <span className=\"min-w-0 flex-1 text-center text-sm font-medium text-muted-foreground\" aria-live=\"polite\">{format(month, 'MMMM yyyy', locale ? { locale } : undefined)}</span>\n {onNextMonth\n ? <CompactButton appearance=\"white\" size={24} className=\"shadow-xs\" onClick={onNextMonth} disabled={disabledNext} aria-label={t('ui.calendar.nextMonth', 'Next month')}><ChevronRight className=\"size-5\" aria-hidden=\"true\" /></CompactButton>\n : <span className=\"size-6 shrink-0\" aria-hidden=\"true\" />}\n </div>\n )\n}\n\n// `LLLL`/`LLL`, not `MMMM`/`MMM`: these labels name a month on its own, and languages that\n// inflect month names \u2014 Polish among the five this repo ships \u2014 spell that differently from a\n// month inside a full date. `MMMM` is the inflected (genitive) form, so a Polish header read\n// \"stycznia\" (\"of January\") where it should read \"stycze\u0144\". The two tokens are identical in\n// English, which is why the bug was invisible until a non-English tenant opened a picker.\nconst STANDALONE_MONTH_YEAR = 'LLLL yyyy'\nconst STANDALONE_MONTH_SHORT = 'LLL'\n\nconst navButtonClassName = cn(\n 'h-9 w-9 inline-flex items-center justify-center rounded-md shrink-0',\n 'border border-border bg-background text-muted-foreground transition-colors',\n 'hover:bg-accent hover:text-accent-foreground hover:border-input',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n 'disabled:opacity-30 disabled:pointer-events-none disabled:hover:bg-background',\n)\n\nfunction MonthNavButton({\n direction,\n locale,\n}: {\n direction: 'prev' | 'next'\n locale?: Locale\n}) {\n const dayPicker = useDayPicker() as unknown as {\n previousMonth?: Date\n nextMonth?: Date\n goToMonth?: (month: Date) => void\n }\n const target = direction === 'prev' ? dayPicker.previousMonth : dayPicker.nextMonth\n const Icon = direction === 'prev' ? ChevronLeft : ChevronRight\n const targetLabel = format(target ?? new Date(), STANDALONE_MONTH_YEAR, locale ? { locale } : undefined)\n const ariaLabel = `Go to ${direction === 'prev' ? 'previous' : 'next'} month: ${targetLabel}`\n return (\n <button\n type=\"button\"\n disabled={!target}\n aria-label={ariaLabel}\n onClick={() => {\n if (target && dayPicker.goToMonth) dayPicker.goToMonth(target)\n }}\n className={navButtonClassName}\n >\n <Icon className=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n )\n}\n\nfunction buildMonthCaption(\n locale: Locale | undefined,\n totalMonths: number,\n onOpenMonthGrid: (() => void) | null,\n) {\n return function MonthCaption({\n calendarMonth,\n displayIndex,\n }: {\n calendarMonth: CalendarMonth\n displayIndex?: number\n }) {\n const label = format(calendarMonth.date, STANDALONE_MONTH_YEAR, locale ? { locale } : undefined)\n const index = typeof displayIndex === 'number' ? displayIndex : 0\n // For multi-month layouts (e.g. range pickers) only the leftmost month\n // exposes the previous-month chevron and only the rightmost exposes the\n // next-month chevron. Navigation is always global across all visible\n // months, so showing both arrows on every month is confusing.\n const showPrev = index === 0\n const showNext = index === totalMonths - 1\n // The month grid (fast navigation) is only offered on single-month\n // calendars; on the leftmost caption it owns the click target.\n const labelInteractive = onOpenMonthGrid !== null && index === 0\n return (\n <div className=\"flex items-center justify-between gap-2 mb-3\">\n {showPrev ? (\n <MonthNavButton direction=\"prev\" locale={locale} />\n ) : (\n <div className=\"h-9 w-9 shrink-0\" aria-hidden=\"true\" />\n )}\n {labelInteractive ? (\n <button\n type=\"button\"\n onClick={onOpenMonthGrid ?? undefined}\n aria-label={`${label} \u2013 open month and year navigation`}\n className={cn(\n 'flex-1 flex items-center justify-center h-9 rounded-md bg-muted px-3 text-sm font-medium',\n 'transition-colors hover:bg-accent hover:text-accent-foreground',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n )}\n >\n {label}\n </button>\n ) : (\n <div\n className=\"flex-1 flex items-center justify-center h-9 rounded-md bg-muted px-3 text-sm font-medium\"\n aria-live=\"polite\"\n >\n {label}\n </div>\n )}\n {showNext ? (\n <MonthNavButton direction=\"next\" locale={locale} />\n ) : (\n <div className=\"h-9 w-9 shrink-0\" aria-hidden=\"true\" />\n )}\n </div>\n )\n }\n}\n\nfunction MonthGrid({\n initialYear,\n selectedMonth,\n locale,\n onSelectMonth,\n onClose,\n}: {\n initialYear: number\n selectedMonth: Date\n locale?: Locale\n onSelectMonth: (month: Date) => void\n onClose: () => void\n}) {\n const [year, setYear] = React.useState(initialYear)\n const today = new Date()\n const monthLabels = React.useMemo(\n () =>\n Array.from({ length: 12 }, (_, monthIndex) =>\n format(new Date(year, monthIndex, 1), STANDALONE_MONTH_SHORT, locale ? { locale } : undefined),\n ),\n [year, locale],\n )\n const yearLabel = format(new Date(year, 0, 1), 'yyyy', locale ? { locale } : undefined)\n return (\n <div\n className=\"absolute inset-0 z-10 flex flex-col rounded-md bg-popover p-3\"\n role=\"dialog\"\n aria-label=\"Select month and year\"\n >\n <div className=\"flex items-center justify-between gap-2 mb-3\">\n <button\n type=\"button\"\n aria-label={`Go to previous year: ${year - 1}`}\n onClick={() => setYear((current) => current - 1)}\n className={navButtonClassName}\n >\n <ChevronLeft className=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n <button\n type=\"button\"\n onClick={onClose}\n aria-label={`${yearLabel} \u2013 back to day selection`}\n className={cn(\n 'flex-1 flex items-center justify-center h-9 rounded-md bg-muted px-3 text-sm font-medium',\n 'transition-colors hover:bg-accent hover:text-accent-foreground',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n )}\n >\n {yearLabel}\n </button>\n <button\n type=\"button\"\n aria-label={`Go to next year: ${year + 1}`}\n onClick={() => setYear((current) => current + 1)}\n className={navButtonClassName}\n >\n <ChevronRight className=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n </div>\n <div className=\"grid grid-cols-3 gap-2\">\n {monthLabels.map((monthLabel, monthIndex) => {\n const isSelected =\n selectedMonth.getFullYear() === year && selectedMonth.getMonth() === monthIndex\n const isCurrentMonth =\n today.getFullYear() === year && today.getMonth() === monthIndex\n return (\n <button\n key={monthIndex}\n type=\"button\"\n aria-pressed={isSelected}\n onClick={() => onSelectMonth(new Date(year, monthIndex, 1))}\n className={cn(\n 'h-9 rounded-md text-sm font-normal transition-colors',\n 'inline-flex items-center justify-center',\n 'hover:bg-accent hover:text-accent-foreground',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n isCurrentMonth && !isSelected && 'font-semibold text-primary',\n isSelected && '!bg-primary !text-primary-foreground hover:!bg-primary',\n )}\n >\n {monthLabel}\n </button>\n )\n })}\n </div>\n </div>\n )\n}\n\nexport function Calendar({\n className,\n classNames,\n showOutsideDays = true,\n daySize = 36,\n fixedWeeks = true,\n locale,\n components,\n numberOfMonths = 1,\n pagedNavigation = true,\n month,\n defaultMonth,\n onMonthChange,\n ...props\n}: CalendarProps) {\n // The month/year grid (fast navigation) is only meaningful for single-month\n // calendars; multi-month layouts (range pickers) keep the static caption.\n const monthGridEnabled = numberOfMonths === 1\n const [displayMonth, setDisplayMonth] = React.useState<Date>(\n () => month ?? defaultMonth ?? new Date(),\n )\n const [showMonthGrid, setShowMonthGrid] = React.useState(false)\n\n // Honor a controlled `month` prop when consumers drive navigation externally.\n React.useEffect(() => {\n if (month) setDisplayMonth(month)\n }, [month])\n\n const handleMonthChange = React.useCallback(\n (next: Date) => {\n setDisplayMonth(next)\n onMonthChange?.(next)\n },\n [onMonthChange],\n )\n\n const handleSelectMonth = React.useCallback(\n (next: Date) => {\n handleMonthChange(next)\n setShowMonthGrid(false)\n },\n [handleMonthChange],\n )\n\n const monthCaption = React.useMemo(\n () =>\n buildMonthCaption(\n locale as Locale | undefined,\n numberOfMonths,\n monthGridEnabled ? () => setShowMonthGrid(true) : null,\n ),\n [locale, numberOfMonths, monthGridEnabled],\n )\n\n return (\n <div className={monthGridEnabled ? 'relative' : 'contents'}>\n <div\n className={monthGridEnabled && showMonthGrid ? 'pointer-events-none' : 'contents'}\n aria-hidden={monthGridEnabled && showMonthGrid ? true : undefined}\n >\n <DayPicker\n showOutsideDays={showOutsideDays}\n fixedWeeks={fixedWeeks}\n pagedNavigation={pagedNavigation}\n locale={locale}\n numberOfMonths={numberOfMonths}\n month={displayMonth}\n onMonthChange={handleMonthChange}\n className={cn('p-3', className)}\n classNames={{\n months: 'flex flex-col sm:flex-row gap-4',\n month: 'space-y-2',\n month_caption: '',\n caption_label: 'sr-only',\n nav: 'sr-only',\n month_grid: 'w-full border-collapse',\n weekdays: 'flex',\n weekday: cn('text-muted-foreground rounded-md font-normal text-xs', daySize === 40 ? 'w-10' : 'w-9'),\n weeks: 'w-full border-collapse',\n week: 'flex w-full mt-1',\n day: cn('text-center text-sm p-0 relative focus-within:relative focus-within:z-20', daySize === 40 ? 'size-10' : 'size-9'),\n day_button: cn(\n 'p-0 font-normal aria-selected:opacity-100',\n daySize === 40 ? 'size-10' : 'size-9',\n 'inline-flex items-center justify-center rounded-md text-sm',\n 'transition-colors focus:outline-none focus-visible:outline-none disabled:pointer-events-none',\n // Focus indicator is a soft accent fill instead of a ring overlay \u2014 keyboard\n // users get a visible cue, but mouse-click focus does not leave a stuck ring\n // on top of the selected cell.\n 'hover:bg-accent hover:text-accent-foreground',\n 'focus-visible:bg-accent focus-visible:text-accent-foreground',\n ),\n // React-day-picker v9 applies `classNames.selected` / `range_*` to the day\n // CELL (`<td>`) wrapper, not to the inner `<button>`. To keep the parent\n // fill visible through interaction, we (a) paint the cell with the desired\n // bg/text, and (b) force the inner button to render transparent \u2014 so the\n // button's own hover/focus-visible bg overrides cannot cover the cell fill.\n selected: cn(\n '!bg-primary !text-primary-foreground rounded-md',\n '[&_button]:!bg-transparent [&_button]:!text-primary-foreground',\n '[&_button:hover]:!bg-transparent [&_button:hover]:!text-primary-foreground',\n '[&_button:focus-visible]:!bg-transparent [&_button:focus-visible]:!text-primary-foreground',\n ),\n range_start: cn(\n '!bg-primary !text-primary-foreground rounded-l-md !rounded-r-none',\n '[&_button]:!bg-transparent [&_button]:!text-primary-foreground',\n '[&_button:hover]:!bg-transparent [&_button:hover]:!text-primary-foreground',\n '[&_button:focus-visible]:!bg-transparent [&_button:focus-visible]:!text-primary-foreground',\n ),\n range_end: cn(\n '!bg-primary !text-primary-foreground rounded-r-md !rounded-l-none',\n '[&_button]:!bg-transparent [&_button]:!text-primary-foreground',\n '[&_button:hover]:!bg-transparent [&_button:hover]:!text-primary-foreground',\n '[&_button:focus-visible]:!bg-transparent [&_button:focus-visible]:!text-primary-foreground',\n ),\n range_middle: cn(\n '!bg-accent !text-accent-foreground !rounded-none',\n '[&_button]:!bg-transparent [&_button]:!text-accent-foreground',\n '[&_button:hover]:!bg-transparent [&_button:hover]:!text-accent-foreground',\n '[&_button:focus-visible]:!bg-transparent [&_button:focus-visible]:!text-accent-foreground',\n ),\n today: 'font-semibold text-primary rounded-md',\n outside:\n 'day-outside text-muted-foreground opacity-40 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30',\n disabled: 'text-muted-foreground opacity-50',\n hidden: 'invisible',\n ...classNames,\n }}\n components={{\n MonthCaption: monthCaption,\n ...components,\n }}\n {...props}\n />\n </div>\n {monthGridEnabled && showMonthGrid ? (\n <MonthGrid\n initialYear={displayMonth.getFullYear()}\n selectedMonth={displayMonth}\n locale={locale as Locale | undefined}\n onSelectMonth={handleSelectMonth}\n onClose={() => setShowMonthGrid(false)}\n />\n ) : null}\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { DayPicker, useDayPicker } from 'react-day-picker'\nimport type { DayPickerProps, CalendarMonth } from 'react-day-picker'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\nimport { format } from 'date-fns/format'\nimport type { Locale } from 'date-fns/locale'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useOptionalT, useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport { createTranslator } from '@open-mercato/shared/lib/i18n/translate'\nimport { CompactButton } from './compact-button'\n\nexport type CalendarProps = DayPickerProps & { daySize?: 36 | 40 }\n\nexport type CalendarMonthSelectorProps = React.HTMLAttributes<HTMLDivElement> & {\n month: Date\n locale?: Locale\n onPreviousMonth?: () => void\n onNextMonth?: () => void\n disabledPrevious?: boolean\n disabledNext?: boolean\n}\n\nexport function CalendarMonthSelector({\n month, locale, onPreviousMonth, onNextMonth, disabledPrevious, disabledNext, className, ...props\n}: CalendarMonthSelectorProps) {\n const t = useT()\n return (\n <div data-slot=\"calendar-month-selector\" className={cn('flex h-9 items-center gap-1.5 rounded-md bg-muted p-1.5', className)} {...props}>\n {onPreviousMonth\n ? <CompactButton appearance=\"white\" size={24} className=\"shadow-xs\" onClick={onPreviousMonth} disabled={disabledPrevious} aria-label={t('ui.calendar.previousMonth', 'Previous month')}><ChevronLeft className=\"size-5\" aria-hidden=\"true\" /></CompactButton>\n : <span className=\"size-6 shrink-0\" aria-hidden=\"true\" />}\n <span className=\"min-w-0 flex-1 text-center text-sm font-medium text-muted-foreground\" aria-live=\"polite\">{format(month, 'MMMM yyyy', locale ? { locale } : undefined)}</span>\n {onNextMonth\n ? <CompactButton appearance=\"white\" size={24} className=\"shadow-xs\" onClick={onNextMonth} disabled={disabledNext} aria-label={t('ui.calendar.nextMonth', 'Next month')}><ChevronRight className=\"size-5\" aria-hidden=\"true\" /></CompactButton>\n : <span className=\"size-6 shrink-0\" aria-hidden=\"true\" />}\n </div>\n )\n}\n\n// `LLLL`/`LLL`, not `MMMM`/`MMM`: these labels name a month on its own, and languages that\n// inflect month names \u2014 Polish among the five this repo ships \u2014 spell that differently from a\n// month inside a full date. `MMMM` is the inflected (genitive) form, so a Polish header read\n// \"stycznia\" (\"of January\") where it should read \"stycze\u0144\". The two tokens are identical in\n// English, which is why the bug was invisible until a non-English tenant opened a picker.\nconst STANDALONE_MONTH_YEAR = 'LLLL yyyy'\nconst STANDALONE_MONTH_SHORT = 'LLL'\n\nconst fallbackTranslate = createTranslator({})\n\nfunction useCalendarT(): TranslateFn {\n return useOptionalT() ?? fallbackTranslate\n}\n\ntype CalendarLabels = DayPickerProps['labels']\n\nconst CalendarLabelsContext = React.createContext<CalendarLabels>(undefined)\n\nconst navButtonClassName = cn(\n 'h-9 w-9 inline-flex items-center justify-center rounded-md shrink-0',\n 'border border-border bg-background text-muted-foreground transition-colors',\n 'hover:bg-accent hover:text-accent-foreground hover:border-input',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n 'disabled:opacity-30 disabled:pointer-events-none disabled:hover:bg-background',\n)\n\nfunction MonthNavButton({\n direction,\n locale,\n}: {\n direction: 'prev' | 'next'\n locale?: Locale\n}) {\n const t = useCalendarT()\n const labels = React.useContext(CalendarLabelsContext)\n const dayPicker = useDayPicker() as unknown as {\n previousMonth?: Date\n nextMonth?: Date\n goToMonth?: (month: Date) => void\n }\n const target = direction === 'prev' ? dayPicker.previousMonth : dayPicker.nextMonth\n const Icon = direction === 'prev' ? ChevronLeft : ChevronRight\n const targetLabel = format(target ?? new Date(), STANDALONE_MONTH_YEAR, locale ? { locale } : undefined)\n const customLabel = direction === 'prev' ? labels?.labelPrevious : labels?.labelNext\n const ariaLabel = customLabel\n ? customLabel(target)\n : direction === 'prev'\n ? t('ui.calendar.goToPreviousMonth', 'Go to previous month: {month}', { month: targetLabel })\n : t('ui.calendar.goToNextMonth', 'Go to next month: {month}', { month: targetLabel })\n return (\n <button\n type=\"button\"\n disabled={!target}\n aria-label={ariaLabel}\n onClick={() => {\n if (target && dayPicker.goToMonth) dayPicker.goToMonth(target)\n }}\n className={navButtonClassName}\n >\n <Icon className=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n )\n}\n\nfunction buildMonthCaption(\n locale: Locale | undefined,\n totalMonths: number,\n onOpenMonthGrid: (() => void) | null,\n monthGridTriggerRef: React.Ref<HTMLButtonElement>,\n) {\n return function MonthCaption({\n calendarMonth,\n displayIndex,\n }: {\n calendarMonth: CalendarMonth\n displayIndex?: number\n }) {\n const t = useCalendarT()\n const label = format(calendarMonth.date, STANDALONE_MONTH_YEAR, locale ? { locale } : undefined)\n const index = typeof displayIndex === 'number' ? displayIndex : 0\n // For multi-month layouts (e.g. range pickers) only the leftmost month\n // exposes the previous-month chevron and only the rightmost exposes the\n // next-month chevron. Navigation is always global across all visible\n // months, so showing both arrows on every month is confusing.\n const showPrev = index === 0\n const showNext = index === totalMonths - 1\n // The month grid (fast navigation) is only offered on single-month\n // calendars; on the leftmost caption it owns the click target.\n const labelInteractive = onOpenMonthGrid !== null && index === 0\n return (\n <div className=\"flex items-center justify-between gap-2 mb-3\">\n {showPrev ? (\n <MonthNavButton direction=\"prev\" locale={locale} />\n ) : (\n <div className=\"h-9 w-9 shrink-0\" aria-hidden=\"true\" />\n )}\n {labelInteractive ? (\n <button\n ref={monthGridTriggerRef}\n type=\"button\"\n onClick={onOpenMonthGrid ?? undefined}\n aria-label={t('ui.calendar.openMonthYearNavigation', '{month} \u2013 open month and year navigation', { month: label })}\n className={cn(\n 'flex-1 flex items-center justify-center h-9 rounded-md bg-muted px-3 text-sm font-medium',\n 'transition-colors hover:bg-accent hover:text-accent-foreground',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n )}\n >\n {label}\n </button>\n ) : (\n <div\n className=\"flex-1 flex items-center justify-center h-9 rounded-md bg-muted px-3 text-sm font-medium\"\n aria-live=\"polite\"\n >\n {label}\n </div>\n )}\n {showNext ? (\n <MonthNavButton direction=\"next\" locale={locale} />\n ) : (\n <div className=\"h-9 w-9 shrink-0\" aria-hidden=\"true\" />\n )}\n </div>\n )\n }\n}\n\nfunction MonthGrid({\n initialYear,\n selectedMonth,\n locale,\n onSelectMonth,\n onClose,\n onRestoreFocus,\n}: {\n initialYear: number\n selectedMonth: Date\n locale?: Locale\n onSelectMonth: (month: Date) => void\n onClose: () => void\n onRestoreFocus: () => void\n}) {\n const t = useCalendarT()\n const [year, setYear] = React.useState(initialYear)\n const gridRef = React.useRef<HTMLDivElement>(null)\n const selectedMonthRef = React.useRef<HTMLButtonElement>(null)\n React.useEffect(() => {\n selectedMonthRef.current?.focus()\n return onRestoreFocus\n }, [onRestoreFocus])\n\n React.useEffect(() => {\n const handleEscape = (event: KeyboardEvent) => {\n if (event.key !== 'Escape' || !(event.target instanceof Node) || !gridRef.current?.contains(event.target)) return\n event.preventDefault()\n event.stopPropagation()\n onClose()\n }\n window.addEventListener('keydown', handleEscape, true)\n return () => window.removeEventListener('keydown', handleEscape, true)\n }, [onClose])\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.key !== 'Tab') return\n\n const buttons = event.currentTarget.querySelectorAll<HTMLButtonElement>('button:not(:disabled)')\n const firstButton = buttons[0]\n const lastButton = buttons[buttons.length - 1]\n if (event.shiftKey && document.activeElement === firstButton) {\n event.preventDefault()\n lastButton?.focus()\n } else if (!event.shiftKey && document.activeElement === lastButton) {\n event.preventDefault()\n firstButton?.focus()\n }\n }\n const today = new Date()\n const monthLabels = React.useMemo(\n () =>\n Array.from({ length: 12 }, (_, monthIndex) =>\n format(new Date(year, monthIndex, 1), STANDALONE_MONTH_SHORT, locale ? { locale } : undefined),\n ),\n [year, locale],\n )\n const yearLabel = format(new Date(year, 0, 1), 'yyyy', locale ? { locale } : undefined)\n return (\n <div\n ref={gridRef}\n className=\"absolute inset-0 z-10 flex flex-col rounded-md bg-popover p-3\"\n role=\"dialog\"\n aria-label={t('ui.calendar.selectMonthAndYear', 'Select month and year')}\n onKeyDown={handleKeyDown}\n >\n <div className=\"flex items-center justify-between gap-2 mb-3\">\n <button\n type=\"button\"\n aria-label={t('ui.calendar.goToPreviousYear', 'Go to previous year: {year}', { year: year - 1 })}\n onClick={() => setYear((current) => current - 1)}\n className={navButtonClassName}\n >\n <ChevronLeft className=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n <button\n type=\"button\"\n onClick={onClose}\n aria-label={t('ui.calendar.backToDaySelection', '{year} \u2013 back to day selection', { year: yearLabel })}\n className={cn(\n 'flex-1 flex items-center justify-center h-9 rounded-md bg-muted px-3 text-sm font-medium',\n 'transition-colors hover:bg-accent hover:text-accent-foreground',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n )}\n >\n {yearLabel}\n </button>\n <button\n type=\"button\"\n aria-label={t('ui.calendar.goToNextYear', 'Go to next year: {year}', { year: year + 1 })}\n onClick={() => setYear((current) => current + 1)}\n className={navButtonClassName}\n >\n <ChevronRight className=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n </div>\n <div className=\"grid grid-cols-3 gap-2\">\n {monthLabels.map((monthLabel, monthIndex) => {\n const isSelected =\n selectedMonth.getFullYear() === year && selectedMonth.getMonth() === monthIndex\n const isCurrentMonth =\n today.getFullYear() === year && today.getMonth() === monthIndex\n return (\n <button\n key={monthIndex}\n ref={monthIndex === selectedMonth.getMonth() ? selectedMonthRef : undefined}\n type=\"button\"\n aria-pressed={isSelected}\n onClick={() => onSelectMonth(new Date(year, monthIndex, 1))}\n className={cn(\n 'h-9 rounded-md text-sm font-normal transition-colors',\n 'inline-flex items-center justify-center',\n 'hover:bg-accent hover:text-accent-foreground',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n isCurrentMonth && !isSelected && 'font-semibold text-primary',\n isSelected && '!bg-primary !text-primary-foreground hover:!bg-primary',\n )}\n >\n {monthLabel}\n </button>\n )\n })}\n </div>\n </div>\n )\n}\n\nexport function Calendar({\n className,\n classNames,\n showOutsideDays = true,\n daySize = 36,\n fixedWeeks = true,\n locale,\n components,\n numberOfMonths = 1,\n pagedNavigation = true,\n month,\n defaultMonth,\n onMonthChange,\n labels,\n ...props\n}: CalendarProps) {\n // The month/year grid (fast navigation) is only meaningful for single-month\n // calendars; multi-month layouts (range pickers) keep the static caption.\n const monthGridEnabled = numberOfMonths === 1\n const [displayMonth, setDisplayMonth] = React.useState<Date>(\n () => month ?? defaultMonth ?? new Date(),\n )\n const [showMonthGrid, setShowMonthGrid] = React.useState(false)\n const monthGridTriggerRef = React.useRef<HTMLButtonElement>(null)\n const restoreDayViewFocus = React.useCallback(() => {\n monthGridTriggerRef.current?.focus()\n }, [])\n\n // Honor a controlled `month` prop when consumers drive navigation externally.\n React.useEffect(() => {\n if (month) setDisplayMonth(month)\n }, [month])\n\n const handleMonthChange = React.useCallback(\n (next: Date) => {\n setDisplayMonth(next)\n onMonthChange?.(next)\n },\n [onMonthChange],\n )\n\n const handleSelectMonth = React.useCallback(\n (next: Date) => {\n handleMonthChange(next)\n setShowMonthGrid(false)\n },\n [handleMonthChange],\n )\n\n const monthCaption = React.useMemo(\n () =>\n buildMonthCaption(\n locale as Locale | undefined,\n numberOfMonths,\n monthGridEnabled ? () => setShowMonthGrid(true) : null,\n monthGridTriggerRef,\n ),\n [locale, numberOfMonths, monthGridEnabled],\n )\n\n return (\n <CalendarLabelsContext.Provider value={labels}>\n <div className={monthGridEnabled ? 'relative' : 'contents'}>\n <div\n className={monthGridEnabled && showMonthGrid ? 'invisible pointer-events-none' : 'contents'}\n aria-hidden={monthGridEnabled && showMonthGrid ? true : undefined}\n inert={monthGridEnabled && showMonthGrid ? true : undefined}\n >\n <DayPicker\n showOutsideDays={showOutsideDays}\n fixedWeeks={fixedWeeks}\n pagedNavigation={pagedNavigation}\n hideNavigation\n locale={locale}\n labels={labels}\n numberOfMonths={numberOfMonths}\n month={displayMonth}\n onMonthChange={handleMonthChange}\n className={cn('p-3', className)}\n classNames={{\n months: 'flex flex-col sm:flex-row gap-4',\n month: 'space-y-2',\n month_caption: '',\n caption_label: 'sr-only',\n nav: 'sr-only',\n month_grid: 'w-full border-collapse',\n weekdays: 'flex',\n weekday: cn('text-muted-foreground rounded-md font-normal text-xs', daySize === 40 ? 'w-10' : 'w-9'),\n weeks: 'w-full border-collapse',\n week: 'flex w-full mt-1',\n day: cn('text-center text-sm p-0 relative focus-within:relative focus-within:z-20', daySize === 40 ? 'size-10' : 'size-9'),\n day_button: cn(\n 'p-0 font-normal aria-selected:opacity-100',\n daySize === 40 ? 'size-10' : 'size-9',\n 'inline-flex items-center justify-center rounded-md text-sm',\n 'transition-colors focus:outline-none focus-visible:outline-none disabled:pointer-events-none',\n // Focus indicator is a soft accent fill instead of a ring overlay \u2014 keyboard\n // users get a visible cue, but mouse-click focus does not leave a stuck ring\n // on top of the selected cell.\n 'hover:bg-accent hover:text-accent-foreground',\n 'focus-visible:bg-accent focus-visible:text-accent-foreground',\n ),\n // React-day-picker v9 applies `classNames.selected` / `range_*` to the day\n // CELL (`<td>`) wrapper, not to the inner `<button>`. To keep the parent\n // fill visible through interaction, we (a) paint the cell with the desired\n // bg/text, and (b) force the inner button to render transparent \u2014 so the\n // button's own hover/focus-visible bg overrides cannot cover the cell fill.\n selected: cn(\n '!bg-primary !text-primary-foreground rounded-md',\n '[&_button]:!bg-transparent [&_button]:!text-primary-foreground',\n '[&_button:hover]:!bg-transparent [&_button:hover]:!text-primary-foreground',\n '[&_button:focus-visible]:!bg-transparent [&_button:focus-visible]:!text-primary-foreground',\n ),\n range_start: cn(\n '!bg-primary !text-primary-foreground rounded-l-md !rounded-r-none',\n '[&_button]:!bg-transparent [&_button]:!text-primary-foreground',\n '[&_button:hover]:!bg-transparent [&_button:hover]:!text-primary-foreground',\n '[&_button:focus-visible]:!bg-transparent [&_button:focus-visible]:!text-primary-foreground',\n ),\n range_end: cn(\n '!bg-primary !text-primary-foreground rounded-r-md !rounded-l-none',\n '[&_button]:!bg-transparent [&_button]:!text-primary-foreground',\n '[&_button:hover]:!bg-transparent [&_button:hover]:!text-primary-foreground',\n '[&_button:focus-visible]:!bg-transparent [&_button:focus-visible]:!text-primary-foreground',\n ),\n range_middle: cn(\n '!bg-accent !text-accent-foreground !rounded-none',\n '[&_button]:!bg-transparent [&_button]:!text-accent-foreground',\n '[&_button:hover]:!bg-transparent [&_button:hover]:!text-accent-foreground',\n '[&_button:focus-visible]:!bg-transparent [&_button:focus-visible]:!text-accent-foreground',\n ),\n today: 'font-semibold text-primary rounded-md',\n outside:\n 'day-outside text-muted-foreground opacity-40 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30',\n disabled: 'text-muted-foreground opacity-50',\n hidden: 'invisible',\n ...classNames,\n }}\n components={{\n MonthCaption: monthCaption,\n ...components,\n }}\n {...props}\n />\n </div>\n {monthGridEnabled && showMonthGrid ? (\n <MonthGrid\n initialYear={displayMonth.getFullYear()}\n selectedMonth={displayMonth}\n locale={locale as Locale | undefined}\n onSelectMonth={handleSelectMonth}\n onClose={() => setShowMonthGrid(false)}\n onRestoreFocus={restoreDayViewFocus}\n />\n ) : null}\n </div>\n </CalendarLabelsContext.Provider>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/ui",
3
- "version": "0.8.1-develop.7296.1.2111d779db",
3
+ "version": "0.9.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -155,14 +155,14 @@
155
155
  "remark-gfm": "^4.0.1"
156
156
  },
157
157
  "peerDependencies": {
158
- "@open-mercato/shared": "0.8.1-develop.7296.1.2111d779db",
158
+ "@open-mercato/shared": "0.9.0",
159
159
  "react": ">=18.0.0",
160
160
  "react-dom": ">=18.0.0",
161
161
  "react-is": ">=18.0.0"
162
162
  },
163
163
  "devDependencies": {
164
164
  "@figma/code-connect": "^1.3.4",
165
- "@open-mercato/shared": "0.8.1-develop.7296.1.2111d779db",
165
+ "@open-mercato/shared": "0.9.0",
166
166
  "@testing-library/dom": "^10.4.1",
167
167
  "@testing-library/jest-dom": "^7.0.1",
168
168
  "@testing-library/react": "^16.3.3",
@@ -186,6 +186,5 @@
186
186
  "type": "git",
187
187
  "url": "https://github.com/open-mercato/open-mercato",
188
188
  "directory": "packages/ui"
189
- },
190
- "stableVersion": "0.8.0"
189
+ }
191
190
  }
@@ -1991,6 +1991,27 @@ export function CrudForm<TValues extends Record<string, unknown>>({
1991
1991
  () => new Set(injectedFieldDefinitions.map((definition) => definition.id)),
1992
1992
  [injectedFieldDefinitions],
1993
1993
  )
1994
+ // An injected field may reuse a host-declared field id to replace the host's built-in
1995
+ // input for that field (the later, injected entry wins in `fieldById`) — e.g.
1996
+ // `customer_groups`' `GroupPickerField` replacing catalog's plain-text `customerGroupId`.
1997
+ // Such a field is still a host schema field, so its value must reach `coreValues` and the
1998
+ // host `onSubmit` payload. Only an injected field with NO host-declared counterpart is an
1999
+ // "extra" field that is stripped before schema validation/submission (widgets still see
2000
+ // it via `onBeforeSave`/`onSave`). See UPGRADE_NOTES.md (0.8.1). Host fields declared
2001
+ // inline in `groups[].fields` count as host-declared too.
2002
+ const hostFieldIdSet = React.useMemo(() => {
2003
+ const ids = new Set(fields.map((field) => field.id))
2004
+ for (const group of groups ?? []) {
2005
+ for (const entry of group.fields ?? []) {
2006
+ if (typeof entry !== 'string') ids.add(entry.id)
2007
+ }
2008
+ }
2009
+ return ids
2010
+ }, [fields, groups])
2011
+ const injectedOnlyFieldIdSet = React.useMemo(
2012
+ () => new Set(Array.from(injectedFieldIdSet).filter((id) => !hostFieldIdSet.has(id))),
2013
+ [injectedFieldIdSet, hostFieldIdSet],
2014
+ )
1994
2015
 
1995
2016
  const injectedCrudFields = React.useMemo<CrudField[]>(() => {
1996
2017
  return injectedFieldDefinitions.map((definition) => {
@@ -2043,8 +2064,9 @@ export function CrudForm<TValues extends Record<string, unknown>>({
2043
2064
  }, [allFields])
2044
2065
 
2045
2066
  // Declared base fields whose id is a dot-path (e.g. `metadata.category`).
2046
- // Injected and custom (cf) fields already get dot-path/cf-path hydration and
2047
- // are excluded here so their dedicated handling is preserved. CrudForm hydrates
2067
+ // Injected-only and custom (cf) fields already get dot-path/cf-path hydration and
2068
+ // are excluded here so their dedicated handling is preserved; an injected field
2069
+ // that reuses a host field id stays a host field and is collapsed. CrudForm hydrates
2048
2070
  // these flat keys from nested initial values on load and projects them back into
2049
2071
  // nested objects before schema.safeParse on submit. See issue #2503.
2050
2072
  const dotPathBaseFieldIds = React.useMemo(() => {
@@ -2053,13 +2075,13 @@ export function CrudForm<TValues extends Record<string, unknown>>({
2053
2075
  for (const field of allFields) {
2054
2076
  const fieldId = field.id
2055
2077
  if (!fieldId.includes('.')) continue
2056
- if (injectedFieldIdSet.has(fieldId)) continue
2078
+ if (injectedOnlyFieldIdSet.has(fieldId)) continue
2057
2079
  if (fieldId.startsWith('cf_') || fieldId.startsWith('cf:')) continue
2058
2080
  if (cfFieldIds.has(fieldId)) continue
2059
2081
  ids.add(fieldId)
2060
2082
  }
2061
2083
  return ids
2062
- }, [allFields, injectedFieldIdSet, cfFields])
2084
+ }, [allFields, injectedOnlyFieldIdSet, cfFields])
2063
2085
 
2064
2086
  const validateFieldOnBlur = React.useCallback(async (
2065
2087
  fieldId: string,
@@ -2132,7 +2154,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
2132
2154
  delete widgetValues[hiddenId]
2133
2155
  }
2134
2156
  const coreValues = { ...widgetValues }
2135
- for (const injectedId of injectedFieldIdSet) {
2157
+ for (const injectedId of injectedOnlyFieldIdSet) {
2136
2158
  delete coreValues[injectedId]
2137
2159
  }
2138
2160
  const result = schema.safeParse(collapseDotPathFields(coreValues, dotPathBaseFieldIds))
@@ -2181,7 +2203,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
2181
2203
  hiddenBaseFieldIds,
2182
2204
  hiddenGroupFieldIds,
2183
2205
  hiddenInjectedFieldIds,
2184
- injectedFieldIdSet,
2206
+ injectedOnlyFieldIdSet,
2185
2207
  mapDefsForValidation,
2186
2208
  schema,
2187
2209
  t,
@@ -3050,7 +3072,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
3050
3072
  delete widgetValues[hiddenId]
3051
3073
  }
3052
3074
  const coreValues = { ...widgetValues }
3053
- for (const injectedId of injectedFieldIdSet) {
3075
+ for (const injectedId of injectedOnlyFieldIdSet) {
3054
3076
  delete coreValues[injectedId]
3055
3077
  }
3056
3078
  if (customEntity) {
@@ -3089,7 +3111,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
3089
3111
  if (result.data) {
3090
3112
  submitValues = result.data as TValues
3091
3113
  const projectedCoreValues = { ...(result.data as Record<string, unknown>) }
3092
- for (const injectedId of injectedFieldIdSet) {
3114
+ for (const injectedId of injectedOnlyFieldIdSet) {
3093
3115
  delete projectedCoreValues[injectedId]
3094
3116
  }
3095
3117
  if (customEntity) {
@@ -63,6 +63,7 @@ import type {
63
63
  InjectionRowActionDefinition,
64
64
  } from '@open-mercato/shared/modules/widgets/injection'
65
65
  import { ComponentReplacementHandles } from '@open-mercato/shared/modules/widgets/component-registry'
66
+ import { useRegisteredComponent } from './injection/useRegisteredComponent'
66
67
  import { dataTableExtensionSpotId, extensionSpotChildId } from '@open-mercato/shared/modules/widgets/extension-points'
67
68
  import { insertByInjectionPlacement } from '@open-mercato/shared/modules/widgets/injection-position'
68
69
  import { useVirtualizer } from '@tanstack/react-virtual'
@@ -1252,7 +1253,7 @@ function ViewSwitcherDropdown({
1252
1253
  )
1253
1254
  }
1254
1255
 
1255
- export function DataTable<T extends RowData>({
1256
+ function DataTableImpl<T extends RowData>({
1256
1257
  columns,
1257
1258
  data,
1258
1259
  toolbar,
@@ -1396,6 +1397,11 @@ export function DataTable<T extends RowData>({
1396
1397
  // cannot serve here: applying a snapshot rewrites the stored copy with a
1397
1398
  // fresh `Date.now()`, which would make every server row look older than it is.
1398
1399
  const hydratedSnapshotRef = React.useRef<PerspectiveSnapshot | null>(null)
1400
+ const hydratedAdvancedFilterRestoredRef = React.useRef(false)
1401
+ const pendingPerspectiveFiltersRef = React.useRef<{ filters: PerspectiveSettings['filters'] } | null>(null)
1402
+ React.useLayoutEffect(() => {
1403
+ pendingPerspectiveFiltersRef.current = null
1404
+ }, [advancedFilter?.value, perspectiveTableId])
1399
1405
  const initialSettingsSource = perspectiveConfig?.initialState?.initialSettings ?? null
1400
1406
  // Memoized on the host's own object: `sanitizePerspectiveSettings` returns a
1401
1407
  // fresh result on every call, so without this every effect keyed on the
@@ -2032,6 +2038,12 @@ export function DataTable<T extends RowData>({
2032
2038
  },
2033
2039
  ) => {
2034
2040
  const normalized = sanitizePerspectiveSettings(settings) ?? {}
2041
+ if (!options?.preserveAdvancedFilter && advancedFilter?.onApplyTree) {
2042
+ hydratedAdvancedFilterRestoredRef.current = false
2043
+ pendingPerspectiveFiltersRef.current = {
2044
+ filters: isPersistedFilterTree(normalized.filters) ? normalized.filters : undefined,
2045
+ }
2046
+ }
2035
2047
  // The applied settings replace the baseline outright, so the defaults hidden under
2036
2048
  // the *previous* view must not leak into it. The pass below re-runs and re-folds
2037
2049
  // whatever this view leaves undecided.
@@ -2042,7 +2054,12 @@ export function DataTable<T extends RowData>({
2042
2054
  // filter change the user never made.
2043
2055
  setViewBaseline(
2044
2056
  options?.preserveAdvancedFilter
2045
- ? { ...normalized, filters: getCurrentSettings().filters }
2057
+ ? {
2058
+ ...normalized,
2059
+ filters: pendingPerspectiveFiltersRef.current
2060
+ ? pendingPerspectiveFiltersRef.current.filters
2061
+ : getCurrentSettings().filters,
2062
+ }
2046
2063
  : normalized,
2047
2064
  )
2048
2065
  if (normalized.columnOrder && normalized.columnOrder.length) {
@@ -2110,7 +2127,14 @@ export function DataTable<T extends RowData>({
2110
2127
  if (perspectiveTableId) {
2111
2128
  writePerspectiveCookie(perspectiveTableId, nextId)
2112
2129
  if (nextId) {
2113
- const snapshot: PerspectiveSnapshot = { perspectiveId: nextId, settings: normalized, updatedAt: Date.now() }
2130
+ const previousSnapshot = readPerspectiveSnapshot(perspectiveTableId) ?? initialSnapshotRef.current
2131
+ const preserveClearedFilter = options?.preserveAdvancedFilter
2132
+ && !getCurrentSettings().filters
2133
+ && !previousSnapshot?.settings.filters
2134
+ const snapshotSettings = preserveClearedFilter
2135
+ ? { ...normalized, filters: undefined }
2136
+ : normalized
2137
+ const snapshot: PerspectiveSnapshot = { perspectiveId: nextId, settings: snapshotSettings, updatedAt: Date.now() }
2114
2138
  writePerspectiveSnapshot(perspectiveTableId, snapshot)
2115
2139
  initialSnapshotRef.current = snapshot
2116
2140
  } else if (normalized.columnSizing && Object.keys(normalized.columnSizing).length) {
@@ -2175,29 +2199,45 @@ export function DataTable<T extends RowData>({
2175
2199
  if (!perspectiveTableId) return
2176
2200
  if (snapshotHydratedTableRef.current === perspectiveTableId) return
2177
2201
  snapshotHydratedTableRef.current = perspectiveTableId
2202
+ hydratedAdvancedFilterRestoredRef.current = false
2178
2203
  const snapshot = readPerspectiveSnapshot(perspectiveTableId)
2179
2204
  if (!snapshot) return
2180
2205
  initialSnapshotRef.current = snapshot
2181
2206
  hydratedSnapshotRef.current = snapshot
2182
- // When the host page wired an advanced-filter tree (`advancedFilter.onApplyTree`),
2183
- // the host owns filter persistence — typically by hydrating from / writing to the
2184
- // URL (see CRM People/Companies/Deals lazy useState initializers + URL writer
2185
- // effects). The mount-time snapshot from a prior session can be arbitrarily
2186
- // stale and MUST NOT override the host's filter — including the empty case
2187
- // (Clear all → refresh would otherwise resurrect the previously-saved rules).
2188
- // The snapshot still drives non-filter settings: column order, visibility,
2189
- // sorting, search. Explicit perspective selection and "No view" go through
2190
- // a different applyPerspectiveSettings call without this option, so they
2191
- // still update the host filter as expected.
2192
- const preserveAdvancedFilter = !!advancedFilter?.onApplyTree
2207
+ const restoredTree = isPersistedFilterTree(snapshot.settings.filters)
2208
+ ? deserializeTreeFromPersist(snapshot.settings.filters)
2209
+ : null
2210
+ const preserveAdvancedFilter = !!advancedFilter?.onApplyTree && (
2211
+ advancedFilter.value.root.children.length > 0 || !restoredTree?.root.children.length
2212
+ )
2193
2213
  applyPerspectiveSettings(
2194
2214
  snapshot.settings,
2195
2215
  snapshot.perspectiveId ?? null,
2196
2216
  { preserveAdvancedFilter },
2197
2217
  )
2218
+ hydratedAdvancedFilterRestoredRef.current = !!advancedFilter?.onApplyTree && !preserveAdvancedFilter
2198
2219
  initialPerspectiveAppliedRef.current = true
2199
2220
  }, [perspectiveTableId, applyPerspectiveSettings, advancedFilter])
2200
2221
 
2222
+ const previousAdvancedFilterRef = React.useRef<{
2223
+ tableId: string | null
2224
+ hasRules: boolean
2225
+ } | null>(null)
2226
+ const advancedFilterHasRules = !!advancedFilter?.value.root.children.length
2227
+ React.useEffect(() => {
2228
+ const previous = previousAdvancedFilterRef.current
2229
+ previousAdvancedFilterRef.current = { tableId: perspectiveTableId, hasRules: advancedFilterHasRules }
2230
+ if (!perspectiveTableId || !advancedFilter?.onApplyTree) return
2231
+ if (previous?.tableId !== perspectiveTableId || !previous.hasRules || advancedFilterHasRules) return
2232
+ const snapshot = readPerspectiveSnapshot(perspectiveTableId) ?? initialSnapshotRef.current
2233
+ if (!snapshot) return
2234
+ const settings = { ...snapshot.settings }
2235
+ delete settings.filters
2236
+ const clearedSnapshot: PerspectiveSnapshot = { ...snapshot, settings, updatedAt: Date.now() }
2237
+ writePerspectiveSnapshot(perspectiveTableId, clearedSnapshot)
2238
+ initialSnapshotRef.current = clearedSnapshot
2239
+ }, [perspectiveTableId, advancedFilterHasRules, advancedFilter?.onApplyTree])
2240
+
2201
2241
  type SavePerspectivePayload = {
2202
2242
  name: string
2203
2243
  isDefault: boolean
@@ -2716,11 +2756,23 @@ export function DataTable<T extends RowData>({
2716
2756
  React.useLayoutEffect(() => {
2717
2757
  if (!canUsePerspectives) return
2718
2758
  if (!perspectiveTableId) return
2759
+ if (
2760
+ (initialSnapshotRef.current || initialPerspectiveAppliedRef.current)
2761
+ && serverReconciledTableRef.current === perspectiveTableId
2762
+ ) return
2719
2763
 
2720
2764
  const source = perspectiveData ?? perspectiveConfig?.initialState?.response
2721
2765
  if (!source) return
2722
2766
 
2723
2767
  let orphanedSnapshotDropped = false
2768
+ const currentFilters = pendingPerspectiveFiltersRef.current
2769
+ ? pendingPerspectiveFiltersRef.current.filters
2770
+ : getCurrentSettings().filters
2771
+ const restoredFilterStillActive = hydratedAdvancedFilterRestoredRef.current && diffPerspectiveSettings(
2772
+ { filters: currentFilters },
2773
+ { filters: hydratedSnapshotRef.current?.settings.filters },
2774
+ ).length === 0
2775
+ const preserveAdvancedFilter = !!advancedFilter?.onApplyTree && !restoredFilterStillActive
2724
2776
 
2725
2777
  const tryResolve = (id: string | null | undefined): PerspectiveDto | RolePerspectiveDto | undefined => {
2726
2778
  if (!id) return undefined
@@ -2736,7 +2788,6 @@ export function DataTable<T extends RowData>({
2736
2788
  // localStorage entry (#5113). One-shot matters as much as reconciling at
2737
2789
  // all — a later refetch must not clobber edits made after mount.
2738
2790
  if (initialSnapshotRef.current || initialPerspectiveAppliedRef.current) {
2739
- if (serverReconciledTableRef.current === perspectiveTableId) return
2740
2791
  serverReconciledTableRef.current = perspectiveTableId
2741
2792
  // A snapshot only speaks for the active perspective: once the user has
2742
2793
  // picked a different view, reconciling the mount-time one would undo that
@@ -2768,7 +2819,7 @@ export function DataTable<T extends RowData>({
2768
2819
  // selection: on a host that owns filter persistence through the URL,
2769
2820
  // it must not overwrite the filter currently on screen.
2770
2821
  applyPerspectiveSettings(local.settings, local.id, {
2771
- preserveAdvancedFilter: !!advancedFilter?.onApplyTree,
2822
+ preserveAdvancedFilter,
2772
2823
  })
2773
2824
  initialPerspectiveAppliedRef.current = true
2774
2825
  }
@@ -2808,7 +2859,7 @@ export function DataTable<T extends RowData>({
2808
2859
  applyPerspectiveSettings(
2809
2860
  target.settings,
2810
2861
  target.id,
2811
- orphanedSnapshotDropped ? { preserveAdvancedFilter: !!advancedFilter?.onApplyTree } : undefined,
2862
+ orphanedSnapshotDropped ? { preserveAdvancedFilter } : undefined,
2812
2863
  )
2813
2864
  } else if (orphanedSnapshotDropped) {
2814
2865
  // Nothing is left to fall back to — the deleted view was the only one. The
@@ -2820,11 +2871,11 @@ export function DataTable<T extends RowData>({
2820
2871
  // another session — so it must not clear the filter a host that owns
2821
2872
  // filter persistence through the URL currently has on screen.
2822
2873
  applyPerspectiveSettings({}, null, {
2823
- preserveAdvancedFilter: !!advancedFilter?.onApplyTree,
2874
+ preserveAdvancedFilter,
2824
2875
  })
2825
2876
  }
2826
2877
  initialPerspectiveAppliedRef.current = true
2827
- }, [canUsePerspectives, perspectiveData, perspectiveTableId, perspectiveConfig, applyPerspectiveSettings, activePerspectiveId, advancedFilter?.onApplyTree])
2878
+ }, [canUsePerspectives, perspectiveData, perspectiveTableId, perspectiveConfig, applyPerspectiveSettings, activePerspectiveId, advancedFilter?.onApplyTree, getCurrentSettings])
2828
2879
 
2829
2880
  const scrollTableIntoView = React.useCallback(() => {
2830
2881
  const rect = containerRef.current?.getBoundingClientRect()
@@ -3497,7 +3548,9 @@ export function DataTable<T extends RowData>({
3497
3548
  // the wrapped action buttons rendered over it on narrow layouts.
3498
3549
  const headerContentClassName = 'flex flex-col gap-2 sm:flex-row sm:flex-wrap sm:items-center sm:justify-between'
3499
3550
  const toolbarWrapperClassName = embedded ? 'mt-2' : 'mt-3 pt-3 border-t'
3500
- const tableScrollWrapperClassName = embedded ? '' : 'overflow-auto'
3551
+ // `isolate` keeps the z-index of pinned cells local to the table so they
3552
+ // scroll beneath the sticky app top bar instead of over it.
3553
+ const tableScrollWrapperClassName = embedded ? 'isolate' : 'isolate overflow-auto'
3501
3554
 
3502
3555
  const virtualScrollRef = React.useRef<HTMLDivElement>(null)
3503
3556
  // Measure the horizontal scroll viewport so the empty state can center within
@@ -4004,3 +4057,25 @@ export function DataTable<T extends RowData>({
4004
4057
  </TooltipProvider>
4005
4058
  )
4006
4059
  }
4060
+
4061
+ const DataTableFallback = DataTableImpl as unknown as React.ComponentType<DataTableProps<RowData>>
4062
+
4063
+ /**
4064
+ * Resolves the public DataTable component handle before mounting the table so
4065
+ * registered props transforms, wrappers, and replacements execute at the real
4066
+ * host boundary rather than being exposed only as a diagnostic DOM attribute.
4067
+ */
4068
+ export function DataTable<T extends RowData>(props: DataTableProps<T>) {
4069
+ const extensionTableId = props.perspective?.tableId
4070
+ ?? props.extensionTableId
4071
+ ?? (props.injectionSpotId?.startsWith('data-table:')
4072
+ ? props.injectionSpotId.slice('data-table:'.length)
4073
+ : null)
4074
+ const handle = props.replacementHandle
4075
+ ?? ComponentReplacementHandles.dataTable(extensionTableId ?? 'unknown')
4076
+ const Resolved = useRegisteredComponent<DataTableProps<T>>(
4077
+ handle,
4078
+ DataTableFallback as unknown as React.ComponentType<DataTableProps<T>>,
4079
+ )
4080
+ return <Resolved {...props} />
4081
+ }