@open-mercato/ui 0.5.1-develop.2949.009dcdd2d5 → 0.5.1-develop.2954.610bab2d08

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 (96) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +8 -0
  3. package/dist/backend/CrudForm.js +57 -29
  4. package/dist/backend/CrudForm.js.map +2 -2
  5. package/dist/backend/DataTable.js +32 -14
  6. package/dist/backend/DataTable.js.map +2 -2
  7. package/dist/backend/FilterOverlay.js +23 -17
  8. package/dist/backend/FilterOverlay.js.map +2 -2
  9. package/dist/backend/JsonBuilder.js +32 -18
  10. package/dist/backend/JsonBuilder.js.map +2 -2
  11. package/dist/backend/columns/ColumnChooserPanel.js +12 -13
  12. package/dist/backend/columns/ColumnChooserPanel.js.map +2 -2
  13. package/dist/backend/custom-fields/FieldDefinitionsEditor.js +71 -62
  14. package/dist/backend/custom-fields/FieldDefinitionsEditor.js.map +2 -2
  15. package/dist/backend/date-range/DateRangeSelect.js +11 -10
  16. package/dist/backend/date-range/DateRangeSelect.js.map +2 -2
  17. package/dist/backend/date-range/InlineDateRangeSelect.js +10 -22
  18. package/dist/backend/date-range/InlineDateRangeSelect.js.map +2 -2
  19. package/dist/backend/detail/ActivitiesSection.js +20 -12
  20. package/dist/backend/detail/ActivitiesSection.js.map +2 -2
  21. package/dist/backend/detail/AddressEditor.js +24 -7
  22. package/dist/backend/detail/AddressEditor.js.map +2 -2
  23. package/dist/backend/detail/InlineEditors.js +12 -6
  24. package/dist/backend/detail/InlineEditors.js.map +2 -2
  25. package/dist/backend/detail/NotesSection.js +20 -14
  26. package/dist/backend/detail/NotesSection.js.map +2 -2
  27. package/dist/backend/filters/AdvancedFilterBuilder.js +52 -24
  28. package/dist/backend/filters/AdvancedFilterBuilder.js.map +2 -2
  29. package/dist/backend/injection/InjectedField.js +12 -7
  30. package/dist/backend/injection/InjectedField.js.map +2 -2
  31. package/dist/backend/inputs/ComboboxInput.js.map +2 -2
  32. package/dist/backend/inputs/EventSelect.js +22 -6
  33. package/dist/backend/inputs/EventSelect.js.map +2 -2
  34. package/dist/backend/inputs/PhoneNumberField.js +2 -2
  35. package/dist/backend/inputs/PhoneNumberField.js.map +2 -2
  36. package/dist/backend/inputs/TimeInput.js +9 -10
  37. package/dist/backend/inputs/TimeInput.js.map +2 -2
  38. package/dist/backend/messages/message-compose-form-groups.js +12 -7
  39. package/dist/backend/messages/message-compose-form-groups.js.map +2 -2
  40. package/dist/backend/messages/useMessageCompose.js +7 -1
  41. package/dist/backend/messages/useMessageCompose.js.map +2 -2
  42. package/dist/frontend/LanguageSwitcher.js +19 -14
  43. package/dist/frontend/LanguageSwitcher.js.map +2 -2
  44. package/dist/index.js +5 -0
  45. package/dist/index.js.map +2 -2
  46. package/dist/primitives/checkbox-field.js +17 -5
  47. package/dist/primitives/checkbox-field.js.map +2 -2
  48. package/dist/primitives/input.js +71 -14
  49. package/dist/primitives/input.js.map +2 -2
  50. package/dist/primitives/radio-field.js +74 -0
  51. package/dist/primitives/radio-field.js.map +7 -0
  52. package/dist/primitives/radio.js +37 -0
  53. package/dist/primitives/radio.js.map +7 -0
  54. package/dist/primitives/select.js +155 -0
  55. package/dist/primitives/select.js.map +7 -0
  56. package/dist/primitives/switch-field.js +76 -0
  57. package/dist/primitives/switch-field.js.map +7 -0
  58. package/dist/primitives/switch.js +17 -3
  59. package/dist/primitives/switch.js.map +2 -2
  60. package/dist/primitives/textarea.js +48 -12
  61. package/dist/primitives/textarea.js.map +2 -2
  62. package/dist/primitives/tooltip.js +44 -15
  63. package/dist/primitives/tooltip.js.map +2 -2
  64. package/package.json +5 -3
  65. package/src/backend/CrudForm.tsx +104 -37
  66. package/src/backend/DataTable.tsx +38 -20
  67. package/src/backend/FilterOverlay.tsx +35 -21
  68. package/src/backend/JsonBuilder.tsx +38 -20
  69. package/src/backend/__tests__/FieldDefinitionsEditor.test.tsx +23 -6
  70. package/src/backend/columns/ColumnChooserPanel.tsx +9 -10
  71. package/src/backend/custom-fields/FieldDefinitionsEditor.tsx +120 -87
  72. package/src/backend/date-range/DateRangeSelect.tsx +19 -12
  73. package/src/backend/date-range/InlineDateRangeSelect.tsx +16 -20
  74. package/src/backend/detail/ActivitiesSection.tsx +35 -23
  75. package/src/backend/detail/AddressEditor.tsx +30 -16
  76. package/src/backend/detail/InlineEditors.tsx +21 -11
  77. package/src/backend/detail/NotesSection.tsx +35 -25
  78. package/src/backend/filters/AdvancedFilterBuilder.tsx +60 -34
  79. package/src/backend/injection/InjectedField.tsx +21 -12
  80. package/src/backend/inputs/ComboboxInput.tsx +4 -0
  81. package/src/backend/inputs/EventSelect.tsx +30 -17
  82. package/src/backend/inputs/PhoneNumberField.tsx +2 -2
  83. package/src/backend/inputs/TimeInput.tsx +9 -10
  84. package/src/backend/messages/message-compose-form-groups.tsx +21 -12
  85. package/src/backend/messages/useMessageCompose.ts +20 -1
  86. package/src/frontend/LanguageSwitcher.tsx +20 -17
  87. package/src/index.ts +5 -0
  88. package/src/primitives/checkbox-field.tsx +10 -2
  89. package/src/primitives/input.tsx +73 -12
  90. package/src/primitives/radio-field.tsx +92 -0
  91. package/src/primitives/radio.tsx +42 -0
  92. package/src/primitives/select.tsx +200 -0
  93. package/src/primitives/switch-field.tsx +100 -0
  94. package/src/primitives/switch.tsx +17 -4
  95. package/src/primitives/textarea.tsx +67 -11
  96. package/src/primitives/tooltip.tsx +68 -24
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/filters/AdvancedFilterBuilder.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { ChevronDown, Plus, Trash2, X } from 'lucide-react'\nimport { Button } from '../../primitives/button'\nimport { IconButton } from '../../primitives/icon-button'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type {\n AdvancedFilterState,\n FilterCondition,\n FilterFieldDef,\n FilterFieldType,\n FilterJoinOperator,\n FilterOperator,\n} from '@open-mercato/shared/lib/query/advanced-filter'\nimport {\n OPERATORS_BY_FIELD_TYPE,\n getDefaultOperator,\n isValuelessOperator,\n createEmptyCondition,\n normalizeAdvancedFilterState,\n} from '@open-mercato/shared/lib/query/advanced-filter'\n\nexport type AdvancedFilterBuilderProps = {\n fields: FilterFieldDef[]\n value: AdvancedFilterState\n onChange: (state: AdvancedFilterState) => void\n onApply: () => void\n onClear: () => void\n}\n\nconst OPERATOR_LABELS: Record<FilterOperator, string> = {\n is: 'is',\n is_not: 'is not',\n contains: 'contains',\n does_not_contain: 'does not contain',\n starts_with: 'starts with',\n ends_with: 'ends with',\n is_empty: 'is empty',\n is_not_empty: 'is not empty',\n equals: 'equals',\n not_equals: 'not equals',\n greater_than: 'greater than',\n less_than: 'less than',\n greater_or_equal: 'greater or equal',\n less_or_equal: 'less or equal',\n between: 'between',\n is_before: 'is before',\n is_after: 'is after',\n is_any_of: 'is any of',\n is_none_of: 'is none of',\n is_true: 'is true',\n is_false: 'is false',\n has_any_of: 'has any of',\n has_all_of: 'has all of',\n has_none_of: 'has none of',\n}\n\nfunction getFieldType(fields: FilterFieldDef[], fieldKey: string): FilterFieldType {\n const field = fields.find((f) => f.key === fieldKey)\n return field?.type ?? 'text'\n}\n\nfunction ConditionRow({\n condition,\n index,\n fields,\n join,\n onUpdate,\n onRemove,\n onToggleJoin,\n t,\n}: {\n condition: FilterCondition\n index: number\n fields: FilterFieldDef[]\n join: FilterJoinOperator\n onUpdate: (id: string, updates: Partial<FilterCondition>) => void\n onRemove: (id: string) => void\n onToggleJoin: (id: string) => void\n t: ReturnType<typeof useT>\n}) {\n const fieldType = getFieldType(fields, condition.field)\n const operators = OPERATORS_BY_FIELD_TYPE[fieldType] ?? OPERATORS_BY_FIELD_TYPE.text\n const valueless = isValuelessOperator(condition.operator)\n\n const handleFieldChange = (newField: string) => {\n const newType = getFieldType(fields, newField)\n const newOp = getDefaultOperator(newType)\n onUpdate(condition.id, { field: newField, operator: newOp, value: '' })\n }\n\n const joinLabel = join === 'and'\n ? t('ui.advancedFilter.and', 'And')\n : t('ui.advancedFilter.or', 'Or')\n\n return (\n <div className=\"flex flex-wrap items-start gap-2\">\n <div className=\"w-20 shrink-0 pt-0.5 text-sm text-muted-foreground\">\n {index === 0 ? (\n <span>{t('ui.advancedFilter.where', 'Where')}</span>\n ) : (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-9 min-w-[5rem] justify-between px-3 text-sm font-medium\"\n onClick={() => onToggleJoin(condition.id)}\n aria-label={t('ui.advancedFilter.toggleJoin', 'Toggle filter join operator')}\n title={t('ui.advancedFilter.toggleJoinHint', 'Click to switch between AND and OR')}\n >\n <span>{joinLabel}</span>\n <ChevronDown className=\"size-3.5 text-muted-foreground\" />\n </Button>\n )}\n </div>\n\n <select\n className=\"rounded border bg-background px-2 py-1.5 text-sm min-w-[140px]\"\n value={condition.field}\n onChange={(e) => handleFieldChange(e.target.value)}\n aria-label={t('ui.advancedFilter.selectField', 'Select field')}\n >\n <option value=\"\" disabled>{t('ui.advancedFilter.selectFieldPlaceholder', 'Select field...')}</option>\n {fields.map((f) => (\n <option key={f.key} value={f.key}>{f.label}</option>\n ))}\n </select>\n\n <select\n className=\"rounded border bg-background px-2 py-1.5 text-sm min-w-[120px]\"\n value={condition.operator}\n onChange={(e) => onUpdate(condition.id, { operator: e.target.value as FilterOperator, value: '' })}\n aria-label={t('ui.advancedFilter.selectOperator', 'Select operator')}\n >\n {operators.map((op) => (\n <option key={op} value={op}>\n {t(`ui.advancedFilter.operator.${op}`, OPERATOR_LABELS[op])}\n </option>\n ))}\n </select>\n\n {!valueless ? (\n <ValueInput\n condition={condition}\n fields={fields}\n fieldType={fieldType}\n onUpdate={onUpdate}\n t={t}\n />\n ) : null}\n\n <IconButton\n variant=\"ghost\"\n size=\"sm\"\n type=\"button\"\n onClick={() => onRemove(condition.id)}\n aria-label={t('ui.advancedFilter.removeCondition', 'Remove condition')}\n >\n <Trash2 className=\"size-4 text-muted-foreground\" />\n </IconButton>\n </div>\n )\n}\n\nfunction ValueInput({\n condition,\n fields,\n fieldType,\n onUpdate,\n t,\n}: {\n condition: FilterCondition\n fields: FilterFieldDef[]\n fieldType: FilterFieldType\n onUpdate: (id: string, updates: Partial<FilterCondition>) => void\n t: ReturnType<typeof useT>\n}) {\n const fieldDef = fields.find((f) => f.key === condition.field)\n const value = condition.value\n\n if (fieldType === 'select' && fieldDef?.options) {\n return (\n <select\n className=\"rounded border bg-background px-2 py-1.5 text-sm min-w-[140px]\"\n value={typeof value === 'string' ? value : ''}\n onChange={(e) => onUpdate(condition.id, { value: e.target.value })}\n aria-label={t('ui.advancedFilter.selectValue', 'Select value')}\n >\n <option value=\"\" disabled>{t('ui.advancedFilter.selectValuePlaceholder', 'Select...')}</option>\n {fieldDef.options.map((opt) => (\n <option key={opt.value} value={opt.value}>{opt.label}</option>\n ))}\n </select>\n )\n }\n\n if (fieldType === 'date') {\n return (\n <input\n type=\"date\"\n className=\"rounded border bg-background px-2 py-1.5 text-sm\"\n value={typeof value === 'string' ? value : ''}\n onChange={(e) => onUpdate(condition.id, { value: e.target.value })}\n aria-label={t('ui.advancedFilter.dateValue', 'Date value')}\n />\n )\n }\n\n if (fieldType === 'number') {\n return (\n <input\n type=\"number\"\n className=\"rounded border bg-background px-2 py-1.5 text-sm w-[120px]\"\n value={typeof value === 'number' ? value : typeof value === 'string' ? value : ''}\n onChange={(e) => onUpdate(condition.id, { value: e.target.value })}\n placeholder={t('ui.advancedFilter.numberPlaceholder', 'Value')}\n aria-label={t('ui.advancedFilter.numberValue', 'Number value')}\n />\n )\n }\n\n return (\n <input\n type=\"text\"\n className=\"rounded border bg-background px-2 py-1.5 text-sm min-w-[140px]\"\n value={typeof value === 'string' ? value : ''}\n onChange={(e) => onUpdate(condition.id, { value: e.target.value })}\n placeholder={t('ui.advancedFilter.textPlaceholder', 'Value...')}\n aria-label={t('ui.advancedFilter.textValue', 'Text value')}\n />\n )\n}\n\nexport function AdvancedFilterBuilder({\n fields,\n value,\n onChange,\n onApply,\n onClear,\n}: AdvancedFilterBuilderProps) {\n const t = useT()\n const normalizedValue = React.useMemo(() => normalizeAdvancedFilterState(value), [value])\n const emitChange = React.useCallback((next: AdvancedFilterState) => {\n onChange(normalizeAdvancedFilterState(next))\n }, [onChange])\n\n const updateCondition = React.useCallback((id: string, updates: Partial<FilterCondition>) => {\n emitChange({\n ...normalizedValue,\n conditions: normalizedValue.conditions.map((c) => (c.id === id ? { ...c, ...updates } : c)),\n })\n }, [emitChange, normalizedValue])\n\n const removeCondition = React.useCallback((id: string) => {\n emitChange({\n ...normalizedValue,\n conditions: normalizedValue.conditions.filter((c) => c.id !== id),\n })\n }, [emitChange, normalizedValue])\n\n const addCondition = React.useCallback(() => {\n const newCondition = createEmptyCondition()\n if (fields.length > 0) {\n newCondition.field = fields[0].key\n newCondition.operator = getDefaultOperator(fields[0].type)\n }\n emitChange({\n ...normalizedValue,\n conditions: [...normalizedValue.conditions, newCondition],\n })\n }, [emitChange, fields, normalizedValue])\n\n const toggleConditionJoin = React.useCallback((id: string) => {\n emitChange({\n ...normalizedValue,\n conditions: normalizedValue.conditions.map((condition) => (\n condition.id === id\n ? { ...condition, join: condition.join === 'or' ? 'and' : 'or' }\n : condition\n )),\n })\n }, [emitChange, normalizedValue])\n\n return (\n <div className=\"inline-flex flex-col gap-3 p-3\">\n {normalizedValue.conditions.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('ui.advancedFilter.noConditions', 'No filter conditions. Click \"Add filter\" to start.')}\n </p>\n ) : (\n <div className=\"space-y-2\">\n {normalizedValue.conditions.map((condition, index) => (\n <ConditionRow\n key={condition.id}\n condition={condition}\n index={index}\n fields={fields}\n join={condition.join ?? normalizedValue.logic}\n onUpdate={updateCondition}\n onRemove={removeCondition}\n onToggleJoin={toggleConditionJoin}\n t={t}\n />\n ))}\n </div>\n )}\n\n <div className=\"flex flex-wrap items-center gap-3 pt-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-auto px-0 py-0 text-base font-medium text-primary hover:bg-transparent hover:text-primary/90\"\n onClick={addCondition}\n >\n <Plus className=\"size-4\" />\n {t('ui.advancedFilter.addFilter', 'Add filter')}\n </Button>\n {normalizedValue.conditions.length > 0 ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-auto px-0 py-0 text-base text-muted-foreground hover:bg-transparent hover:text-foreground\"\n onClick={onClear}\n >\n <X className=\"size-4\" />\n {t('ui.advancedFilter.clear', 'Clear')}\n </Button>\n ) : null}\n {normalizedValue.conditions.length > 0 ? (\n <Button type=\"button\" className=\"ml-auto min-w-[8rem]\" onClick={onApply}>\n {t('ui.advancedFilter.apply', 'Apply')}\n </Button>\n ) : null}\n </div>\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { ChevronDown, Plus, Trash2, X } from 'lucide-react'\nimport { Button } from '../../primitives/button'\nimport { IconButton } from '../../primitives/icon-button'\nimport { Input } from '../../primitives/input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../../primitives/select'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type {\n AdvancedFilterState,\n FilterCondition,\n FilterFieldDef,\n FilterFieldType,\n FilterJoinOperator,\n FilterOperator,\n} from '@open-mercato/shared/lib/query/advanced-filter'\nimport {\n OPERATORS_BY_FIELD_TYPE,\n getDefaultOperator,\n isValuelessOperator,\n createEmptyCondition,\n normalizeAdvancedFilterState,\n} from '@open-mercato/shared/lib/query/advanced-filter'\n\nexport type AdvancedFilterBuilderProps = {\n fields: FilterFieldDef[]\n value: AdvancedFilterState\n onChange: (state: AdvancedFilterState) => void\n onApply: () => void\n onClear: () => void\n}\n\nconst OPERATOR_LABELS: Record<FilterOperator, string> = {\n is: 'is',\n is_not: 'is not',\n contains: 'contains',\n does_not_contain: 'does not contain',\n starts_with: 'starts with',\n ends_with: 'ends with',\n is_empty: 'is empty',\n is_not_empty: 'is not empty',\n equals: 'equals',\n not_equals: 'not equals',\n greater_than: 'greater than',\n less_than: 'less than',\n greater_or_equal: 'greater or equal',\n less_or_equal: 'less or equal',\n between: 'between',\n is_before: 'is before',\n is_after: 'is after',\n is_any_of: 'is any of',\n is_none_of: 'is none of',\n is_true: 'is true',\n is_false: 'is false',\n has_any_of: 'has any of',\n has_all_of: 'has all of',\n has_none_of: 'has none of',\n}\n\nfunction getFieldType(fields: FilterFieldDef[], fieldKey: string): FilterFieldType {\n const field = fields.find((f) => f.key === fieldKey)\n return field?.type ?? 'text'\n}\n\nfunction ConditionRow({\n condition,\n index,\n fields,\n join,\n onUpdate,\n onRemove,\n onToggleJoin,\n t,\n}: {\n condition: FilterCondition\n index: number\n fields: FilterFieldDef[]\n join: FilterJoinOperator\n onUpdate: (id: string, updates: Partial<FilterCondition>) => void\n onRemove: (id: string) => void\n onToggleJoin: (id: string) => void\n t: ReturnType<typeof useT>\n}) {\n const fieldType = getFieldType(fields, condition.field)\n const operators = OPERATORS_BY_FIELD_TYPE[fieldType] ?? OPERATORS_BY_FIELD_TYPE.text\n const valueless = isValuelessOperator(condition.operator)\n\n const handleFieldChange = (newField: string) => {\n const newType = getFieldType(fields, newField)\n const newOp = getDefaultOperator(newType)\n onUpdate(condition.id, { field: newField, operator: newOp, value: '' })\n }\n\n const joinLabel = join === 'and'\n ? t('ui.advancedFilter.and', 'And')\n : t('ui.advancedFilter.or', 'Or')\n\n return (\n <div className=\"flex flex-wrap items-start gap-2\">\n <div className=\"w-20 shrink-0 pt-0.5 text-sm text-muted-foreground\">\n {index === 0 ? (\n <span>{t('ui.advancedFilter.where', 'Where')}</span>\n ) : (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-9 min-w-[5rem] justify-between px-3 text-sm font-medium\"\n onClick={() => onToggleJoin(condition.id)}\n aria-label={t('ui.advancedFilter.toggleJoin', 'Toggle filter join operator')}\n title={t('ui.advancedFilter.toggleJoinHint', 'Click to switch between AND and OR')}\n >\n <span>{joinLabel}</span>\n <ChevronDown className=\"size-3.5 text-muted-foreground\" />\n </Button>\n )}\n </div>\n\n <Select\n value={condition.field || undefined}\n onValueChange={(next) => handleFieldChange(next ?? '')}\n >\n <SelectTrigger\n className=\"min-w-[140px]\"\n aria-label={t('ui.advancedFilter.selectField', 'Select field')}\n >\n <SelectValue placeholder={t('ui.advancedFilter.selectFieldPlaceholder', 'Select field...')} />\n </SelectTrigger>\n <SelectContent>\n {fields.map((f) => (\n <SelectItem key={f.key} value={f.key}>{f.label}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n <Select\n value={condition.operator}\n onValueChange={(next) => onUpdate(condition.id, { operator: next as FilterOperator, value: '' })}\n >\n <SelectTrigger\n className=\"min-w-[120px]\"\n aria-label={t('ui.advancedFilter.selectOperator', 'Select operator')}\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {operators.map((op) => (\n <SelectItem key={op} value={op}>\n {t(`ui.advancedFilter.operator.${op}`, OPERATOR_LABELS[op])}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n {!valueless ? (\n <ValueInput\n condition={condition}\n fields={fields}\n fieldType={fieldType}\n onUpdate={onUpdate}\n t={t}\n />\n ) : null}\n\n <IconButton\n variant=\"ghost\"\n size=\"sm\"\n type=\"button\"\n onClick={() => onRemove(condition.id)}\n aria-label={t('ui.advancedFilter.removeCondition', 'Remove condition')}\n >\n <Trash2 className=\"size-4 text-muted-foreground\" />\n </IconButton>\n </div>\n )\n}\n\nfunction ValueInput({\n condition,\n fields,\n fieldType,\n onUpdate,\n t,\n}: {\n condition: FilterCondition\n fields: FilterFieldDef[]\n fieldType: FilterFieldType\n onUpdate: (id: string, updates: Partial<FilterCondition>) => void\n t: ReturnType<typeof useT>\n}) {\n const fieldDef = fields.find((f) => f.key === condition.field)\n const value = condition.value\n\n if (fieldType === 'select' && fieldDef?.options) {\n return (\n <Select\n value={typeof value === 'string' && value ? value : undefined}\n onValueChange={(next) => onUpdate(condition.id, { value: next ?? '' })}\n >\n <SelectTrigger\n className=\"min-w-[140px]\"\n aria-label={t('ui.advancedFilter.selectValue', 'Select value')}\n >\n <SelectValue placeholder={t('ui.advancedFilter.selectValuePlaceholder', 'Select...')} />\n </SelectTrigger>\n <SelectContent>\n {fieldDef.options.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n )\n }\n\n if (fieldType === 'date') {\n return (\n <input\n type=\"date\"\n className=\"rounded border bg-background px-2 py-1.5 text-sm\"\n value={typeof value === 'string' ? value : ''}\n onChange={(e) => onUpdate(condition.id, { value: e.target.value })}\n aria-label={t('ui.advancedFilter.dateValue', 'Date value')}\n />\n )\n }\n\n if (fieldType === 'number') {\n return (\n <Input\n type=\"number\"\n size=\"sm\"\n className=\"w-[120px]\"\n value={typeof value === 'number' ? value : typeof value === 'string' ? value : ''}\n onChange={(e) => onUpdate(condition.id, { value: e.target.value })}\n placeholder={t('ui.advancedFilter.numberPlaceholder', 'Value')}\n aria-label={t('ui.advancedFilter.numberValue', 'Number value')}\n />\n )\n }\n\n return (\n <Input\n type=\"text\"\n size=\"sm\"\n className=\"min-w-[140px]\"\n value={typeof value === 'string' ? value : ''}\n onChange={(e) => onUpdate(condition.id, { value: e.target.value })}\n placeholder={t('ui.advancedFilter.textPlaceholder', 'Value...')}\n aria-label={t('ui.advancedFilter.textValue', 'Text value')}\n />\n )\n}\n\nexport function AdvancedFilterBuilder({\n fields,\n value,\n onChange,\n onApply,\n onClear,\n}: AdvancedFilterBuilderProps) {\n const t = useT()\n const normalizedValue = React.useMemo(() => normalizeAdvancedFilterState(value), [value])\n const emitChange = React.useCallback((next: AdvancedFilterState) => {\n onChange(normalizeAdvancedFilterState(next))\n }, [onChange])\n\n const updateCondition = React.useCallback((id: string, updates: Partial<FilterCondition>) => {\n emitChange({\n ...normalizedValue,\n conditions: normalizedValue.conditions.map((c) => (c.id === id ? { ...c, ...updates } : c)),\n })\n }, [emitChange, normalizedValue])\n\n const removeCondition = React.useCallback((id: string) => {\n emitChange({\n ...normalizedValue,\n conditions: normalizedValue.conditions.filter((c) => c.id !== id),\n })\n }, [emitChange, normalizedValue])\n\n const addCondition = React.useCallback(() => {\n const newCondition = createEmptyCondition()\n if (fields.length > 0) {\n newCondition.field = fields[0].key\n newCondition.operator = getDefaultOperator(fields[0].type)\n }\n emitChange({\n ...normalizedValue,\n conditions: [...normalizedValue.conditions, newCondition],\n })\n }, [emitChange, fields, normalizedValue])\n\n const toggleConditionJoin = React.useCallback((id: string) => {\n emitChange({\n ...normalizedValue,\n conditions: normalizedValue.conditions.map((condition) => (\n condition.id === id\n ? { ...condition, join: condition.join === 'or' ? 'and' : 'or' }\n : condition\n )),\n })\n }, [emitChange, normalizedValue])\n\n return (\n <div className=\"inline-flex flex-col gap-3 p-3\">\n {normalizedValue.conditions.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('ui.advancedFilter.noConditions', 'No filter conditions. Click \"Add filter\" to start.')}\n </p>\n ) : (\n <div className=\"space-y-2\">\n {normalizedValue.conditions.map((condition, index) => (\n <ConditionRow\n key={condition.id}\n condition={condition}\n index={index}\n fields={fields}\n join={condition.join ?? normalizedValue.logic}\n onUpdate={updateCondition}\n onRemove={removeCondition}\n onToggleJoin={toggleConditionJoin}\n t={t}\n />\n ))}\n </div>\n )}\n\n <div className=\"flex flex-wrap items-center gap-3 pt-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-auto px-0 py-0 text-base font-medium text-primary hover:bg-transparent hover:text-primary/90\"\n onClick={addCondition}\n >\n <Plus className=\"size-4\" />\n {t('ui.advancedFilter.addFilter', 'Add filter')}\n </Button>\n {normalizedValue.conditions.length > 0 ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-auto px-0 py-0 text-base text-muted-foreground hover:bg-transparent hover:text-foreground\"\n onClick={onClear}\n >\n <X className=\"size-4\" />\n {t('ui.advancedFilter.clear', 'Clear')}\n </Button>\n ) : null}\n {normalizedValue.conditions.length > 0 ? (\n <Button type=\"button\" className=\"ml-auto min-w-[8rem]\" onClick={onApply}>\n {t('ui.advancedFilter.apply', 'Apply')}\n </Button>\n ) : null}\n </div>\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -7,6 +7,13 @@ import { Input } from "../../primitives/input.js";
7
7
  import { Checkbox } from "../../primitives/checkbox.js";
8
8
  import { Textarea } from "../../primitives/textarea.js";
9
9
  import { Label } from "../../primitives/label.js";
10
+ import {
11
+ Select,
12
+ SelectContent,
13
+ SelectItem,
14
+ SelectTrigger,
15
+ SelectValue
16
+ } from "../../primitives/select.js";
10
17
  import { Spinner } from "../../primitives/spinner.js";
11
18
  const MAX_CACHE_ENTRIES = 100;
12
19
  const optionsCache = /* @__PURE__ */ new Map();
@@ -30,16 +37,14 @@ function SelectField({
30
37
  return /* @__PURE__ */ jsxs("div", { className: "space-y-2", "data-crud-field-id": field.id, children: [
31
38
  /* @__PURE__ */ jsx(Label, { htmlFor: field.id, children: label }),
32
39
  /* @__PURE__ */ jsxs(
33
- "select",
40
+ Select,
34
41
  {
35
- id: field.id,
36
- className: "flex h-9 w-full rounded-md border border-input bg-background pl-3 pr-8 py-2 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
37
- value: typeof value === "string" ? value : "",
42
+ value: typeof value === "string" && value ? value : void 0,
43
+ onValueChange: (next) => onChange(field.id, next || void 0),
38
44
  disabled: disabled || options.length === 0 && !field.options?.length,
39
- onChange: (event) => onChange(field.id, event.target.value || void 0),
40
45
  children: [
41
- /* @__PURE__ */ jsx("option", { value: "", children: t("ui.filters.select.placeholder", "Select...") }),
42
- options.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, children: option.labelKey ? t(option.labelKey, option.label) : option.label }, option.value))
46
+ /* @__PURE__ */ jsx(SelectTrigger, { id: field.id, children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("ui.filters.select.placeholder", "Select...") }) }),
47
+ /* @__PURE__ */ jsx(SelectContent, { children: options.map((option) => /* @__PURE__ */ jsx(SelectItem, { value: option.value, children: option.labelKey ? t(option.labelKey, option.label) : option.label }, option.value)) })
43
48
  ]
44
49
  }
45
50
  ),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/injection/InjectedField.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport type { InjectionFieldDefinition, FieldContext } from '@open-mercato/shared/modules/widgets/injection'\nimport { evaluateInjectedVisibility } from './visibility-utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Input } from '../../primitives/input'\nimport { Checkbox } from '../../primitives/checkbox'\nimport { Textarea } from '../../primitives/textarea'\nimport { Label } from '../../primitives/label'\nimport { Spinner } from '../../primitives/spinner'\n\ntype InjectedFieldProps = {\n field: InjectionFieldDefinition\n value: unknown\n onChange: (fieldId: string, value: unknown) => void\n context: FieldContext\n formData: Record<string, unknown>\n readOnly?: boolean\n}\n\ntype Option = { value: string; label: string; labelKey?: string }\n\nconst MAX_CACHE_ENTRIES = 100\nconst optionsCache = new Map<string, { expiresAt: number; options: Option[] }>()\n\nfunction evictExpiredCacheEntries() {\n if (optionsCache.size <= MAX_CACHE_ENTRIES) return\n const now = Date.now()\n for (const [key, entry] of optionsCache) {\n if (entry.expiresAt < now) optionsCache.delete(key)\n }\n}\n\nfunction SelectField({\n field,\n value,\n onChange,\n disabled,\n options,\n optionsError,\n label,\n}: {\n field: InjectionFieldDefinition\n value: unknown\n onChange: (fieldId: string, value: unknown) => void\n disabled?: boolean\n options: Option[]\n optionsError: boolean\n label: string\n}) {\n const t = useT()\n return (\n <div className=\"space-y-2\" data-crud-field-id={field.id}>\n <Label htmlFor={field.id}>{label}</Label>\n <select\n id={field.id}\n className=\"flex h-9 w-full rounded-md border border-input bg-background pl-3 pr-8 py-2 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50\"\n value={typeof value === 'string' ? value : ''}\n disabled={disabled || (options.length === 0 && !field.options?.length)}\n onChange={(event) => onChange(field.id, event.target.value || undefined)}\n >\n <option value=\"\">{t('ui.filters.select.placeholder', 'Select...')}</option>\n {options.map((option) => (\n <option key={option.value} value={option.value}>\n {option.labelKey ? t(option.labelKey, option.label) : option.label}\n </option>\n ))}\n </select>\n {optionsError ? (\n <div className=\"text-xs text-muted-foreground\">{t('ui.forms.optionsUnavailable', 'Options unavailable')}</div>\n ) : null}\n </div>\n )\n}\n\n\nexport function InjectedField({ field, value, onChange, context, formData, readOnly = false }: InjectedFieldProps) {\n const t = useT()\n const [dynamicOptions, setDynamicOptions] = React.useState<Option[] | null>(null)\n const [optionsError, setOptionsError] = React.useState(false)\n\n React.useEffect(() => {\n let cancelled = false\n const loadOptions = async () => {\n if (typeof field.optionsLoader !== 'function') return\n const ttl = Math.max(1, field.optionsCacheTtl ?? 60)\n const cacheKey = `${field.id}:${context.organizationId ?? ''}:${context.tenantId ?? ''}`\n const cached = optionsCache.get(cacheKey)\n if (cached && cached.expiresAt > Date.now()) {\n setDynamicOptions(cached.options)\n return\n }\n try {\n const loaded = await field.optionsLoader(context)\n if (cancelled) return\n const normalized = Array.isArray(loaded) ? loaded : []\n setDynamicOptions(normalized)\n setOptionsError(false)\n evictExpiredCacheEntries()\n optionsCache.set(cacheKey, { options: normalized, expiresAt: Date.now() + ttl * 1000 })\n } catch {\n if (cancelled) return\n setDynamicOptions(Array.isArray(field.options) ? field.options : null)\n setOptionsError(true)\n }\n }\n void loadOptions()\n return () => {\n cancelled = true\n }\n }, [context, field.id, field.options, field.optionsCacheTtl, field.optionsLoader])\n\n if (!evaluateInjectedVisibility(field.visibleWhen, formData, context)) return null\n\n const label = field.labelKey ? t(field.labelKey, field.label) : t(field.label, field.label)\n const disabled = readOnly || field.readOnly\n const options = dynamicOptions ?? field.options ?? []\n\n if (field.type === 'custom' && field.customComponent) {\n const CustomComponent = field.customComponent\n return (\n <React.Suspense fallback={<Spinner size=\"sm\" />}>\n <CustomComponent\n value={value}\n onChange={(next) => onChange(field.id, next)}\n context={context}\n disabled={disabled}\n />\n </React.Suspense>\n )\n }\n\n if (field.type === 'textarea') {\n return (\n <div className=\"space-y-2\" data-crud-field-id={field.id}>\n <Label htmlFor={field.id}>{label}</Label>\n <Textarea\n id={field.id}\n className=\"min-h-[96px]\"\n value={typeof value === 'string' ? value : ''}\n disabled={disabled}\n onChange={(event) => onChange(field.id, event.target.value)}\n />\n </div>\n )\n }\n\n if (field.type === 'select') {\n return (\n <SelectField\n field={field}\n value={value}\n onChange={onChange}\n disabled={disabled}\n options={options}\n optionsError={optionsError}\n label={label}\n />\n )\n }\n\n if (field.type === 'boolean') {\n return (\n <label className=\"flex items-center gap-2 text-sm\" data-crud-field-id={field.id}>\n <Checkbox\n checked={value === true}\n disabled={disabled}\n onCheckedChange={(checked) => onChange(field.id, checked === true)}\n />\n <span>{label}</span>\n </label>\n )\n }\n\n return (\n <div className=\"space-y-2\" data-crud-field-id={field.id}>\n <Label htmlFor={field.id}>{label}</Label>\n <Input\n id={field.id}\n type={field.type === 'number' ? 'number' : field.type === 'date' ? 'date' : 'text'}\n value={typeof value === 'string' || typeof value === 'number' ? String(value) : ''}\n disabled={disabled}\n onChange={(event) => {\n if (field.type === 'number') {\n onChange(field.id, event.target.value === '' ? undefined : Number(event.target.value))\n return\n }\n onChange(field.id, event.target.value)\n }}\n />\n </div>\n )\n}\n\nexport default InjectedField\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport type { InjectionFieldDefinition, FieldContext } from '@open-mercato/shared/modules/widgets/injection'\nimport { evaluateInjectedVisibility } from './visibility-utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Input } from '../../primitives/input'\nimport { Checkbox } from '../../primitives/checkbox'\nimport { Textarea } from '../../primitives/textarea'\nimport { Label } from '../../primitives/label'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../../primitives/select'\nimport { Spinner } from '../../primitives/spinner'\n\ntype InjectedFieldProps = {\n field: InjectionFieldDefinition\n value: unknown\n onChange: (fieldId: string, value: unknown) => void\n context: FieldContext\n formData: Record<string, unknown>\n readOnly?: boolean\n}\n\ntype Option = { value: string; label: string; labelKey?: string }\n\nconst MAX_CACHE_ENTRIES = 100\nconst optionsCache = new Map<string, { expiresAt: number; options: Option[] }>()\n\nfunction evictExpiredCacheEntries() {\n if (optionsCache.size <= MAX_CACHE_ENTRIES) return\n const now = Date.now()\n for (const [key, entry] of optionsCache) {\n if (entry.expiresAt < now) optionsCache.delete(key)\n }\n}\n\nfunction SelectField({\n field,\n value,\n onChange,\n disabled,\n options,\n optionsError,\n label,\n}: {\n field: InjectionFieldDefinition\n value: unknown\n onChange: (fieldId: string, value: unknown) => void\n disabled?: boolean\n options: Option[]\n optionsError: boolean\n label: string\n}) {\n const t = useT()\n return (\n <div className=\"space-y-2\" data-crud-field-id={field.id}>\n <Label htmlFor={field.id}>{label}</Label>\n <Select\n value={typeof value === 'string' && value ? value : undefined}\n onValueChange={(next) => onChange(field.id, next || undefined)}\n disabled={disabled || (options.length === 0 && !field.options?.length)}\n >\n <SelectTrigger id={field.id}>\n <SelectValue placeholder={t('ui.filters.select.placeholder', 'Select...')} />\n </SelectTrigger>\n <SelectContent>\n {options.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.labelKey ? t(option.labelKey, option.label) : option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {optionsError ? (\n <div className=\"text-xs text-muted-foreground\">{t('ui.forms.optionsUnavailable', 'Options unavailable')}</div>\n ) : null}\n </div>\n )\n}\n\n\nexport function InjectedField({ field, value, onChange, context, formData, readOnly = false }: InjectedFieldProps) {\n const t = useT()\n const [dynamicOptions, setDynamicOptions] = React.useState<Option[] | null>(null)\n const [optionsError, setOptionsError] = React.useState(false)\n\n React.useEffect(() => {\n let cancelled = false\n const loadOptions = async () => {\n if (typeof field.optionsLoader !== 'function') return\n const ttl = Math.max(1, field.optionsCacheTtl ?? 60)\n const cacheKey = `${field.id}:${context.organizationId ?? ''}:${context.tenantId ?? ''}`\n const cached = optionsCache.get(cacheKey)\n if (cached && cached.expiresAt > Date.now()) {\n setDynamicOptions(cached.options)\n return\n }\n try {\n const loaded = await field.optionsLoader(context)\n if (cancelled) return\n const normalized = Array.isArray(loaded) ? loaded : []\n setDynamicOptions(normalized)\n setOptionsError(false)\n evictExpiredCacheEntries()\n optionsCache.set(cacheKey, { options: normalized, expiresAt: Date.now() + ttl * 1000 })\n } catch {\n if (cancelled) return\n setDynamicOptions(Array.isArray(field.options) ? field.options : null)\n setOptionsError(true)\n }\n }\n void loadOptions()\n return () => {\n cancelled = true\n }\n }, [context, field.id, field.options, field.optionsCacheTtl, field.optionsLoader])\n\n if (!evaluateInjectedVisibility(field.visibleWhen, formData, context)) return null\n\n const label = field.labelKey ? t(field.labelKey, field.label) : t(field.label, field.label)\n const disabled = readOnly || field.readOnly\n const options = dynamicOptions ?? field.options ?? []\n\n if (field.type === 'custom' && field.customComponent) {\n const CustomComponent = field.customComponent\n return (\n <React.Suspense fallback={<Spinner size=\"sm\" />}>\n <CustomComponent\n value={value}\n onChange={(next) => onChange(field.id, next)}\n context={context}\n disabled={disabled}\n />\n </React.Suspense>\n )\n }\n\n if (field.type === 'textarea') {\n return (\n <div className=\"space-y-2\" data-crud-field-id={field.id}>\n <Label htmlFor={field.id}>{label}</Label>\n <Textarea\n id={field.id}\n className=\"min-h-[96px]\"\n value={typeof value === 'string' ? value : ''}\n disabled={disabled}\n onChange={(event) => onChange(field.id, event.target.value)}\n />\n </div>\n )\n }\n\n if (field.type === 'select') {\n return (\n <SelectField\n field={field}\n value={value}\n onChange={onChange}\n disabled={disabled}\n options={options}\n optionsError={optionsError}\n label={label}\n />\n )\n }\n\n if (field.type === 'boolean') {\n return (\n <label className=\"flex items-center gap-2 text-sm\" data-crud-field-id={field.id}>\n <Checkbox\n checked={value === true}\n disabled={disabled}\n onCheckedChange={(checked) => onChange(field.id, checked === true)}\n />\n <span>{label}</span>\n </label>\n )\n }\n\n return (\n <div className=\"space-y-2\" data-crud-field-id={field.id}>\n <Label htmlFor={field.id}>{label}</Label>\n <Input\n id={field.id}\n type={field.type === 'number' ? 'number' : field.type === 'date' ? 'date' : 'text'}\n value={typeof value === 'string' || typeof value === 'number' ? String(value) : ''}\n disabled={disabled}\n onChange={(event) => {\n if (field.type === 'number') {\n onChange(field.id, event.target.value === '' ? undefined : Number(event.target.value))\n return\n }\n onChange(field.id, event.target.value)\n }}\n />\n </div>\n )\n}\n\nexport default InjectedField\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/inputs/ComboboxInput.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Button } from '../../primitives/button'\n\nexport type ComboboxOption = {\n value: string\n label: string\n description?: string | null\n}\n\nexport type ComboboxInputProps = {\n value: string\n onChange: (next: string) => void\n placeholder?: string\n suggestions?: Array<string | ComboboxOption>\n loadSuggestions?: (query?: string) => Promise<Array<string | ComboboxOption>>\n resolveLabel?: (value: string) => string\n resolveDescription?: (value: string) => string | null | undefined\n autoFocus?: boolean\n disabled?: boolean\n allowCustomValues?: boolean\n}\n\nfunction normalizeOptions(input?: Array<string | ComboboxOption>): ComboboxOption[] {\n if (!Array.isArray(input)) return []\n return input\n .map((option) => {\n if (typeof option === 'string') {\n const trimmed = option.trim()\n if (!trimmed) return null\n return { value: trimmed, label: trimmed }\n }\n const value = typeof option.value === 'string' ? option.value.trim() : ''\n if (!value) return null\n return {\n value,\n label: option.label?.trim() || value,\n description: option.description ?? null,\n }\n })\n .filter((option): option is ComboboxOption => !!option)\n}\n\nexport function ComboboxInput({\n value,\n onChange,\n placeholder,\n suggestions,\n loadSuggestions,\n resolveLabel,\n resolveDescription,\n autoFocus,\n disabled = false,\n allowCustomValues = true,\n}: ComboboxInputProps) {\n const [input, setInput] = React.useState('')\n const [asyncOptions, setAsyncOptions] = React.useState<ComboboxOption[]>([])\n const [loading, setLoading] = React.useState(false)\n const [touched, setTouched] = React.useState(false)\n const [showSuggestions, setShowSuggestions] = React.useState(false)\n const [selectedIndex, setSelectedIndex] = React.useState(-1)\n const inputRef = React.useRef<HTMLInputElement>(null)\n\n const staticOptions = React.useMemo(() => normalizeOptions(suggestions), [suggestions])\n\n const optionMap = React.useMemo(() => {\n const map = new Map<string, ComboboxOption>()\n const register = (option: ComboboxOption) => {\n if (!map.has(option.value)) {\n map.set(option.value, option)\n }\n }\n staticOptions.forEach(register)\n asyncOptions.forEach(register)\n if (value) {\n const existing = map.get(value)\n if (!existing) {\n map.set(value, {\n value,\n label: resolveLabel?.(value) ?? value,\n description: resolveDescription?.(value) ?? null,\n })\n }\n }\n return map\n }, [asyncOptions, resolveDescription, resolveLabel, staticOptions, value])\n\n const availableOptions = React.useMemo(() => {\n return Array.from(optionMap.values())\n }, [optionMap])\n\n const filteredSuggestions = React.useMemo(() => {\n const query = input.toLowerCase().trim()\n if (!query) return availableOptions\n return availableOptions.filter((option) => {\n const labelMatch = option.label.toLowerCase().includes(query)\n const descMatch = option.description?.toLowerCase().includes(query)\n return labelMatch || Boolean(descMatch)\n })\n }, [availableOptions, input])\n\n React.useEffect(() => {\n if (!loadSuggestions || !touched || disabled) return\n const query = input.trim()\n let cancelled = false\n const handle = window.setTimeout(async () => {\n setLoading(true)\n try {\n const items = await loadSuggestions(query)\n if (!cancelled) {\n setAsyncOptions(normalizeOptions(items))\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }, 200)\n return () => {\n cancelled = true\n window.clearTimeout(handle)\n }\n }, [disabled, input, loadSuggestions, touched])\n\n // Sync input with value when value changes externally and input is not focused\n React.useEffect(() => {\n if (document.activeElement !== inputRef.current) {\n const option = optionMap.get(value)\n setInput(option?.label ?? value ?? '')\n }\n }, [value, optionMap])\n\n const selectValue = React.useCallback(\n (nextValue: string) => {\n if (disabled) return\n const trimmed = nextValue.trim()\n onChange(trimmed)\n const option = optionMap.get(trimmed)\n setInput(option?.label ?? trimmed)\n setShowSuggestions(false)\n setSelectedIndex(-1)\n },\n [disabled, onChange, optionMap]\n )\n\n const findOptionForInput = React.useCallback(\n (raw: string): ComboboxOption | null => {\n const query = raw.trim().toLowerCase()\n if (!query) return null\n for (const option of optionMap.values()) {\n if (option.value === raw.trim()) return option\n if (option.label.toLowerCase() === query) return option\n }\n return null\n },\n [optionMap]\n )\n\n const confirmSelection = React.useCallback(\n (raw: string) => {\n if (disabled) return\n const option = findOptionForInput(raw)\n if (option) {\n selectValue(option.value)\n return\n }\n if (!allowCustomValues) {\n // Revert to current value if custom values not allowed\n const currentOption = optionMap.get(value)\n setInput(currentOption?.label ?? value ?? '')\n setShowSuggestions(false)\n return\n }\n selectValue(raw)\n },\n [allowCustomValues, disabled, findOptionForInput, optionMap, selectValue, value]\n )\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLInputElement>) => {\n if (disabled) return\n\n if (event.key === 'ArrowDown') {\n event.preventDefault()\n if (!showSuggestions) {\n setShowSuggestions(true)\n setSelectedIndex(0)\n } else {\n setSelectedIndex((prev) => Math.min(prev + 1, filteredSuggestions.length - 1))\n }\n } else if (event.key === 'ArrowUp') {\n event.preventDefault()\n setSelectedIndex((prev) => Math.max(prev - 1, -1))\n } else if (event.key === 'Enter') {\n event.preventDefault()\n if (selectedIndex >= 0 && filteredSuggestions[selectedIndex]) {\n selectValue(filteredSuggestions[selectedIndex].value)\n } else {\n confirmSelection(input)\n }\n } else if (event.key === 'Escape') {\n event.preventDefault()\n setShowSuggestions(false)\n setSelectedIndex(-1)\n }\n },\n [confirmSelection, disabled, filteredSuggestions, input, selectValue, selectedIndex, showSuggestions]\n )\n\n return (\n <div className=\"relative w-full\">\n <input\n ref={inputRef}\n type=\"text\"\n className=\"w-full h-9 rounded border px-2 text-sm disabled:bg-muted disabled:text-muted-foreground disabled:cursor-not-allowed\"\n value={input}\n placeholder={placeholder || 'Type to search...'}\n autoFocus={autoFocus}\n data-crud-focus-target=\"\"\n disabled={disabled}\n onFocus={() => {\n setTouched(true)\n setShowSuggestions(true)\n }}\n onChange={(event) => {\n setTouched(true)\n setInput(event.target.value)\n setShowSuggestions(true)\n setSelectedIndex(-1)\n }}\n onKeyDown={handleKeyDown}\n onBlur={() => {\n // Delay to allow click on suggestions\n setTimeout(() => {\n if (disabled) return\n confirmSelection(input)\n }, 200)\n }}\n />\n\n {showSuggestions && !disabled && (loading || filteredSuggestions.length > 0) && (\n <div className=\"absolute z-dropdown w-full mt-1 rounded border bg-popover shadow-lg max-h-48 sm:max-h-60 overflow-auto\">\n {loading && touched ? (\n <div className=\"px-3 py-2 text-xs text-muted-foreground\">Loading suggestions\u2026</div>\n ) : (\n filteredSuggestions.map((option, index) => (\n <Button\n key={option.value}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={[\n 'w-full h-auto justify-start font-normal text-left flex flex-col items-start px-3 py-2',\n index === selectedIndex ? 'bg-accent' : '',\n ]\n .filter(Boolean)\n .join(' ')}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => selectValue(option.value)}\n onMouseEnter={() => setSelectedIndex(index)}\n >\n <span className=\"font-medium\">{option.label}</span>\n {option.description ? (\n <span className=\"text-xs text-muted-foreground\">{option.description}</span>\n ) : null}\n </Button>\n ))\n )}\n </div>\n )}\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Button } from '../../primitives/button'\n\nexport type ComboboxOption = {\n value: string\n label: string\n description?: string | null\n}\n\nexport type ComboboxInputProps = {\n value: string\n onChange: (next: string) => void\n placeholder?: string\n suggestions?: Array<string | ComboboxOption>\n loadSuggestions?: (query?: string) => Promise<Array<string | ComboboxOption>>\n resolveLabel?: (value: string) => string\n resolveDescription?: (value: string) => string | null | undefined\n autoFocus?: boolean\n disabled?: boolean\n allowCustomValues?: boolean\n}\n\nfunction normalizeOptions(input?: Array<string | ComboboxOption>): ComboboxOption[] {\n if (!Array.isArray(input)) return []\n return input\n .map((option) => {\n if (typeof option === 'string') {\n const trimmed = option.trim()\n if (!trimmed) return null\n return { value: trimmed, label: trimmed }\n }\n const value = typeof option.value === 'string' ? option.value.trim() : ''\n if (!value) return null\n return {\n value,\n label: option.label?.trim() || value,\n description: option.description ?? null,\n }\n })\n .filter((option): option is ComboboxOption => !!option)\n}\n\nexport function ComboboxInput({\n value,\n onChange,\n placeholder,\n suggestions,\n loadSuggestions,\n resolveLabel,\n resolveDescription,\n autoFocus,\n disabled = false,\n allowCustomValues = true,\n}: ComboboxInputProps) {\n const [input, setInput] = React.useState('')\n const [asyncOptions, setAsyncOptions] = React.useState<ComboboxOption[]>([])\n const [loading, setLoading] = React.useState(false)\n const [touched, setTouched] = React.useState(false)\n const [showSuggestions, setShowSuggestions] = React.useState(false)\n const [selectedIndex, setSelectedIndex] = React.useState(-1)\n const inputRef = React.useRef<HTMLInputElement>(null)\n\n const staticOptions = React.useMemo(() => normalizeOptions(suggestions), [suggestions])\n\n const optionMap = React.useMemo(() => {\n const map = new Map<string, ComboboxOption>()\n const register = (option: ComboboxOption) => {\n if (!map.has(option.value)) {\n map.set(option.value, option)\n }\n }\n staticOptions.forEach(register)\n asyncOptions.forEach(register)\n if (value) {\n const existing = map.get(value)\n if (!existing) {\n map.set(value, {\n value,\n label: resolveLabel?.(value) ?? value,\n description: resolveDescription?.(value) ?? null,\n })\n }\n }\n return map\n }, [asyncOptions, resolveDescription, resolveLabel, staticOptions, value])\n\n const availableOptions = React.useMemo(() => {\n return Array.from(optionMap.values())\n }, [optionMap])\n\n const filteredSuggestions = React.useMemo(() => {\n const query = input.toLowerCase().trim()\n if (!query) return availableOptions\n return availableOptions.filter((option) => {\n const labelMatch = option.label.toLowerCase().includes(query)\n const descMatch = option.description?.toLowerCase().includes(query)\n return labelMatch || Boolean(descMatch)\n })\n }, [availableOptions, input])\n\n React.useEffect(() => {\n if (!loadSuggestions || !touched || disabled) return\n const query = input.trim()\n let cancelled = false\n const handle = window.setTimeout(async () => {\n setLoading(true)\n try {\n const items = await loadSuggestions(query)\n if (!cancelled) {\n setAsyncOptions(normalizeOptions(items))\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }, 200)\n return () => {\n cancelled = true\n window.clearTimeout(handle)\n }\n }, [disabled, input, loadSuggestions, touched])\n\n // Sync input with value when value changes externally and input is not focused\n React.useEffect(() => {\n if (document.activeElement !== inputRef.current) {\n const option = optionMap.get(value)\n setInput(option?.label ?? value ?? '')\n }\n }, [value, optionMap])\n\n const selectValue = React.useCallback(\n (nextValue: string) => {\n if (disabled) return\n const trimmed = nextValue.trim()\n onChange(trimmed)\n const option = optionMap.get(trimmed)\n setInput(option?.label ?? trimmed)\n setShowSuggestions(false)\n setSelectedIndex(-1)\n },\n [disabled, onChange, optionMap]\n )\n\n const findOptionForInput = React.useCallback(\n (raw: string): ComboboxOption | null => {\n const query = raw.trim().toLowerCase()\n if (!query) return null\n for (const option of optionMap.values()) {\n if (option.value === raw.trim()) return option\n if (option.label.toLowerCase() === query) return option\n }\n return null\n },\n [optionMap]\n )\n\n const confirmSelection = React.useCallback(\n (raw: string) => {\n if (disabled) return\n const option = findOptionForInput(raw)\n if (option) {\n selectValue(option.value)\n return\n }\n if (!allowCustomValues) {\n // Revert to current value if custom values not allowed\n const currentOption = optionMap.get(value)\n setInput(currentOption?.label ?? value ?? '')\n setShowSuggestions(false)\n return\n }\n selectValue(raw)\n },\n [allowCustomValues, disabled, findOptionForInput, optionMap, selectValue, value]\n )\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLInputElement>) => {\n if (disabled) return\n\n if (event.key === 'ArrowDown') {\n event.preventDefault()\n if (!showSuggestions) {\n setShowSuggestions(true)\n setSelectedIndex(0)\n } else {\n setSelectedIndex((prev) => Math.min(prev + 1, filteredSuggestions.length - 1))\n }\n } else if (event.key === 'ArrowUp') {\n event.preventDefault()\n setSelectedIndex((prev) => Math.max(prev - 1, -1))\n } else if (event.key === 'Enter') {\n event.preventDefault()\n if (selectedIndex >= 0 && filteredSuggestions[selectedIndex]) {\n selectValue(filteredSuggestions[selectedIndex].value)\n } else {\n confirmSelection(input)\n }\n } else if (event.key === 'Escape') {\n event.preventDefault()\n setShowSuggestions(false)\n setSelectedIndex(-1)\n }\n },\n [confirmSelection, disabled, filteredSuggestions, input, selectValue, selectedIndex, showSuggestions]\n )\n\n return (\n <div className=\"relative w-full\">\n {/* Use raw <input> here instead of the DS Input primitive: ComboboxInput's\n focus / suggestions-popup interplay relies on the trigger being a plain\n input element. The DS wrapper introduces a <div> that desyncs autocomplete\n on this specific surface. Keeps the rest of the form on Input primitive. */}\n <input\n ref={inputRef}\n type=\"text\"\n className=\"w-full h-9 rounded border px-2 text-sm disabled:bg-muted disabled:text-muted-foreground disabled:cursor-not-allowed\"\n value={input}\n placeholder={placeholder || 'Type to search...'}\n autoFocus={autoFocus}\n data-crud-focus-target=\"\"\n disabled={disabled}\n onFocus={() => {\n setTouched(true)\n setShowSuggestions(true)\n }}\n onChange={(event) => {\n setTouched(true)\n setInput(event.target.value)\n setShowSuggestions(true)\n setSelectedIndex(-1)\n }}\n onKeyDown={handleKeyDown}\n onBlur={() => {\n // Delay to allow click on suggestions\n setTimeout(() => {\n if (disabled) return\n confirmSelection(input)\n }, 200)\n }}\n />\n\n {showSuggestions && !disabled && (loading || filteredSuggestions.length > 0) && (\n <div className=\"absolute z-dropdown w-full mt-1 rounded border bg-popover shadow-lg max-h-48 sm:max-h-60 overflow-auto\">\n {loading && touched ? (\n <div className=\"px-3 py-2 text-xs text-muted-foreground\">Loading suggestions\u2026</div>\n ) : (\n filteredSuggestions.map((option, index) => (\n <Button\n key={option.value}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={[\n 'w-full h-auto justify-start font-normal text-left flex flex-col items-start px-3 py-2',\n index === selectedIndex ? 'bg-accent' : '',\n ]\n .filter(Boolean)\n .join(' ')}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => selectValue(option.value)}\n onMouseEnter={() => setSelectedIndex(index)}\n >\n <span className=\"font-medium\">{option.label}</span>\n {option.description ? (\n <span className=\"text-xs text-muted-foreground\">{option.description}</span>\n ) : null}\n </Button>\n ))\n )}\n </div>\n )}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -3,6 +3,15 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useMemo } from "react";
4
4
  import { useQuery } from "@tanstack/react-query";
5
5
  import { apiCall } from "../utils/apiCall.js";
6
+ import {
7
+ Select,
8
+ SelectContent,
9
+ SelectGroup,
10
+ SelectItem,
11
+ SelectLabel,
12
+ SelectTrigger,
13
+ SelectValue
14
+ } from "../../primitives/select.js";
6
15
  function EventSelect({
7
16
  value,
8
17
  onChange,
@@ -51,15 +60,22 @@ function EventSelect({
51
60
  };
52
61
  const isEmpty = !isLoading && filteredEvents.length === 0;
53
62
  return /* @__PURE__ */ jsxs(
54
- "select",
63
+ Select,
55
64
  {
56
- value,
57
- onChange: (e) => onChange(e.target.value),
58
- className: `h-10 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${className || ""}`,
65
+ value: value || void 0,
66
+ onValueChange: (next) => onChange(next ?? ""),
59
67
  disabled: disabled || isLoading,
60
68
  children: [
61
- /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: isLoading ? "Loading..." : isEmpty ? "No events available" : placeholder }),
62
- Object.entries(eventsByModule).map(([module, moduleEvents]) => /* @__PURE__ */ jsx("optgroup", { label: formatModuleName(module), children: moduleEvents.map((event) => /* @__PURE__ */ jsx("option", { value: event.id, children: event.label }, event.id)) }, module))
69
+ /* @__PURE__ */ jsx(SelectTrigger, { size: "lg", className, children: /* @__PURE__ */ jsx(
70
+ SelectValue,
71
+ {
72
+ placeholder: isLoading ? "Loading..." : isEmpty ? "No events available" : placeholder
73
+ }
74
+ ) }),
75
+ /* @__PURE__ */ jsx(SelectContent, { children: Object.entries(eventsByModule).map(([module, moduleEvents]) => /* @__PURE__ */ jsxs(SelectGroup, { children: [
76
+ /* @__PURE__ */ jsx(SelectLabel, { children: formatModuleName(module) }),
77
+ moduleEvents.map((event) => /* @__PURE__ */ jsx(SelectItem, { value: event.id, children: event.label }, event.id))
78
+ ] }, module)) })
63
79
  ]
64
80
  }
65
81
  );
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/inputs/EventSelect.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useMemo } from 'react'\nimport { useQuery } from '@tanstack/react-query'\nimport { apiCall } from '../utils/apiCall'\n\n/**\n * Event definition returned by the API\n */\nexport interface EventDefinition {\n id: string\n label: string\n description?: string\n category?: 'crud' | 'lifecycle' | 'system' | 'custom'\n module?: string\n entity?: string\n excludeFromTriggers?: boolean\n}\n\nexport interface EventSelectProps {\n /** Current selected event ID */\n value: string\n /** Called when event is selected */\n onChange: (eventId: string) => void\n /** Placeholder text when no event selected */\n placeholder?: string\n /** Additional CSS classes */\n className?: string\n /** Whether the select is disabled */\n disabled?: boolean\n /** Filter events by category */\n categories?: Array<'crud' | 'lifecycle' | 'system' | 'custom'>\n /** Filter events by module */\n modules?: string[]\n /** Whether to exclude events marked as excludeFromTriggers (default: true) */\n excludeTriggerExcluded?: boolean\n}\n\n/**\n * EventSelect - A reusable select component for choosing declared events\n *\n * Fetches available events from the API and groups them by module.\n */\nexport function EventSelect({\n value,\n onChange,\n placeholder = 'Select an event...',\n className,\n disabled,\n categories,\n modules,\n excludeTriggerExcluded = true,\n}: EventSelectProps) {\n // Fetch events from the API\n const { data: allEvents = [], isLoading } = useQuery({\n queryKey: ['declared-events', excludeTriggerExcluded],\n queryFn: async () => {\n const result = await apiCall<{ data: EventDefinition[]; total: number }>(\n `/api/events?excludeTriggerExcluded=${excludeTriggerExcluded}`\n )\n if (!result.ok) return []\n return result.result?.data || []\n },\n staleTime: 5 * 60 * 1000, // Cache for 5 minutes\n })\n\n // Filter events based on props\n const filteredEvents = useMemo(() => {\n let events = allEvents\n\n if (categories?.length) {\n events = events.filter(e => e.category && categories.includes(e.category))\n }\n if (modules?.length) {\n events = events.filter(e => e.module && modules.includes(e.module))\n }\n\n return events\n }, [allEvents, categories, modules])\n\n // Group events by module for better UX\n const eventsByModule = useMemo(() => {\n const grouped: Record<string, EventDefinition[]> = {}\n for (const event of filteredEvents) {\n const module = event.module || 'other'\n if (!grouped[module]) grouped[module] = []\n grouped[module].push(event)\n }\n // Sort modules alphabetically\n return Object.fromEntries(\n Object.entries(grouped).sort(([a], [b]) => a.localeCompare(b))\n )\n }, [filteredEvents])\n\n // Format module name for display\n const formatModuleName = (module: string): string => {\n return module.charAt(0).toUpperCase() + module.slice(1).replace(/_/g, ' ')\n }\n\n const isEmpty = !isLoading && filteredEvents.length === 0\n\n return (\n <select\n value={value}\n onChange={(e) => onChange(e.target.value)}\n className={`h-10 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${className || ''}`}\n disabled={disabled || isLoading}\n >\n <option value=\"\" disabled>\n {isLoading ? 'Loading...' : isEmpty ? 'No events available' : placeholder}\n </option>\n {Object.entries(eventsByModule).map(([module, moduleEvents]) => (\n <optgroup key={module} label={formatModuleName(module)}>\n {moduleEvents.map(event => (\n <option key={event.id} value={event.id}>\n {event.label}\n </option>\n ))}\n </optgroup>\n ))}\n </select>\n )\n}\n\n/**\n * Hook for getting available events\n */\nexport function useAvailableEvents(options?: {\n categories?: Array<'crud' | 'lifecycle' | 'system' | 'custom'>\n modules?: string[]\n excludeTriggerExcluded?: boolean\n}) {\n const excludeTriggerExcluded = options?.excludeTriggerExcluded !== false\n\n const { data: allEvents = [], isLoading, error, refetch } = useQuery({\n queryKey: ['declared-events', excludeTriggerExcluded],\n queryFn: async () => {\n const result = await apiCall<{ data: EventDefinition[]; total: number }>(\n `/api/events?excludeTriggerExcluded=${excludeTriggerExcluded}`\n )\n if (!result.ok) return []\n return result.result?.data || []\n },\n staleTime: 5 * 60 * 1000,\n })\n\n const filteredEvents = useMemo(() => {\n let events = allEvents\n\n if (options?.categories?.length) {\n events = events.filter(e => e.category && options.categories!.includes(e.category))\n }\n if (options?.modules?.length) {\n events = events.filter(e => e.module && options.modules!.includes(e.module))\n }\n\n return events\n }, [allEvents, options])\n\n // Group by module\n const eventsByModule = useMemo(() => {\n const grouped: Record<string, EventDefinition[]> = {}\n for (const event of filteredEvents) {\n const module = event.module || 'other'\n if (!grouped[module]) grouped[module] = []\n grouped[module].push(event)\n }\n return Object.fromEntries(\n Object.entries(grouped).sort(([a], [b]) => a.localeCompare(b))\n )\n }, [filteredEvents])\n\n return {\n events: filteredEvents,\n eventsByModule,\n isLoading,\n error,\n refetch,\n }\n}\n\nexport default EventSelect\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useMemo } from 'react'\nimport { useQuery } from '@tanstack/react-query'\nimport { apiCall } from '../utils/apiCall'\nimport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectTrigger,\n SelectValue,\n} from '../../primitives/select'\n\n/**\n * Event definition returned by the API\n */\nexport interface EventDefinition {\n id: string\n label: string\n description?: string\n category?: 'crud' | 'lifecycle' | 'system' | 'custom'\n module?: string\n entity?: string\n excludeFromTriggers?: boolean\n}\n\nexport interface EventSelectProps {\n /** Current selected event ID */\n value: string\n /** Called when event is selected */\n onChange: (eventId: string) => void\n /** Placeholder text when no event selected */\n placeholder?: string\n /** Additional CSS classes */\n className?: string\n /** Whether the select is disabled */\n disabled?: boolean\n /** Filter events by category */\n categories?: Array<'crud' | 'lifecycle' | 'system' | 'custom'>\n /** Filter events by module */\n modules?: string[]\n /** Whether to exclude events marked as excludeFromTriggers (default: true) */\n excludeTriggerExcluded?: boolean\n}\n\n/**\n * EventSelect - A reusable select component for choosing declared events\n *\n * Fetches available events from the API and groups them by module.\n */\nexport function EventSelect({\n value,\n onChange,\n placeholder = 'Select an event...',\n className,\n disabled,\n categories,\n modules,\n excludeTriggerExcluded = true,\n}: EventSelectProps) {\n // Fetch events from the API\n const { data: allEvents = [], isLoading } = useQuery({\n queryKey: ['declared-events', excludeTriggerExcluded],\n queryFn: async () => {\n const result = await apiCall<{ data: EventDefinition[]; total: number }>(\n `/api/events?excludeTriggerExcluded=${excludeTriggerExcluded}`\n )\n if (!result.ok) return []\n return result.result?.data || []\n },\n staleTime: 5 * 60 * 1000, // Cache for 5 minutes\n })\n\n // Filter events based on props\n const filteredEvents = useMemo(() => {\n let events = allEvents\n\n if (categories?.length) {\n events = events.filter(e => e.category && categories.includes(e.category))\n }\n if (modules?.length) {\n events = events.filter(e => e.module && modules.includes(e.module))\n }\n\n return events\n }, [allEvents, categories, modules])\n\n // Group events by module for better UX\n const eventsByModule = useMemo(() => {\n const grouped: Record<string, EventDefinition[]> = {}\n for (const event of filteredEvents) {\n const module = event.module || 'other'\n if (!grouped[module]) grouped[module] = []\n grouped[module].push(event)\n }\n // Sort modules alphabetically\n return Object.fromEntries(\n Object.entries(grouped).sort(([a], [b]) => a.localeCompare(b))\n )\n }, [filteredEvents])\n\n // Format module name for display\n const formatModuleName = (module: string): string => {\n return module.charAt(0).toUpperCase() + module.slice(1).replace(/_/g, ' ')\n }\n\n const isEmpty = !isLoading && filteredEvents.length === 0\n\n return (\n <Select\n value={value || undefined}\n onValueChange={(next) => onChange(next ?? '')}\n disabled={disabled || isLoading}\n >\n <SelectTrigger size=\"lg\" className={className}>\n <SelectValue\n placeholder={isLoading ? 'Loading...' : isEmpty ? 'No events available' : placeholder}\n />\n </SelectTrigger>\n <SelectContent>\n {Object.entries(eventsByModule).map(([module, moduleEvents]) => (\n <SelectGroup key={module}>\n <SelectLabel>{formatModuleName(module)}</SelectLabel>\n {moduleEvents.map(event => (\n <SelectItem key={event.id} value={event.id}>\n {event.label}\n </SelectItem>\n ))}\n </SelectGroup>\n ))}\n </SelectContent>\n </Select>\n )\n}\n\n/**\n * Hook for getting available events\n */\nexport function useAvailableEvents(options?: {\n categories?: Array<'crud' | 'lifecycle' | 'system' | 'custom'>\n modules?: string[]\n excludeTriggerExcluded?: boolean\n}) {\n const excludeTriggerExcluded = options?.excludeTriggerExcluded !== false\n\n const { data: allEvents = [], isLoading, error, refetch } = useQuery({\n queryKey: ['declared-events', excludeTriggerExcluded],\n queryFn: async () => {\n const result = await apiCall<{ data: EventDefinition[]; total: number }>(\n `/api/events?excludeTriggerExcluded=${excludeTriggerExcluded}`\n )\n if (!result.ok) return []\n return result.result?.data || []\n },\n staleTime: 5 * 60 * 1000,\n })\n\n const filteredEvents = useMemo(() => {\n let events = allEvents\n\n if (options?.categories?.length) {\n events = events.filter(e => e.category && options.categories!.includes(e.category))\n }\n if (options?.modules?.length) {\n events = events.filter(e => e.module && options.modules!.includes(e.module))\n }\n\n return events\n }, [allEvents, options])\n\n // Group by module\n const eventsByModule = useMemo(() => {\n const grouped: Record<string, EventDefinition[]> = {}\n for (const event of filteredEvents) {\n const module = event.module || 'other'\n if (!grouped[module]) grouped[module] = []\n grouped[module].push(event)\n }\n return Object.fromEntries(\n Object.entries(grouped).sort(([a], [b]) => a.localeCompare(b))\n )\n }, [filteredEvents])\n\n return {\n events: filteredEvents,\n eventsByModule,\n isLoading,\n error,\n refetch,\n }\n}\n\nexport default EventSelect\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -2,6 +2,7 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { extractPhoneDigits, validatePhoneNumber } from "@open-mercato/shared/lib/phone";
5
+ import { Input } from "../../primitives/input.js";
5
6
  const DEFAULT_MIN_DIGITS = 6;
6
7
  const DEFAULT_INVALID_LABEL = "Enter a valid phone number with country code (e.g. +1 212 555 1234)";
7
8
  function PhoneNumberField({
@@ -105,10 +106,9 @@ function PhoneNumberField({
105
106
  }, [invalidLabel, local, onDigitsChange, onValueChange]);
106
107
  return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
107
108
  /* @__PURE__ */ jsx(
108
- "input",
109
+ Input,
109
110
  {
110
111
  type: "tel",
111
- className: "w-full h-9 rounded border px-2 text-sm",
112
112
  value: local,
113
113
  onChange: handleChange,
114
114
  onBlur: handleBlur,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/inputs/PhoneNumberField.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { extractPhoneDigits, validatePhoneNumber } from '@open-mercato/shared/lib/phone'\n\nexport type PhoneDuplicateMatch = {\n id: string\n label: string\n href: string\n}\n\nexport type PhoneNumberFieldProps = {\n value?: string | null\n onValueChange: (next: string | undefined) => void\n onDigitsChange?: (digits: string | null) => void\n externalError?: string | null\n disabled?: boolean\n autoFocus?: boolean\n placeholder?: string\n minDigits?: number\n checkingLabel?: string\n duplicateLabel?: (match: PhoneDuplicateMatch) => string\n duplicateLinkLabel?: string\n invalidLabel?: string\n onDuplicateLookup?: (normalizedValue: string) => Promise<PhoneDuplicateMatch | null>\n}\n\nconst DEFAULT_MIN_DIGITS = 6\nconst DEFAULT_INVALID_LABEL = 'Enter a valid phone number with country code (e.g. +1 212 555 1234)'\n\nexport function PhoneNumberField({\n value,\n onValueChange,\n onDigitsChange,\n externalError,\n disabled = false,\n autoFocus,\n placeholder,\n minDigits = DEFAULT_MIN_DIGITS,\n checkingLabel,\n duplicateLabel,\n duplicateLinkLabel,\n invalidLabel,\n onDuplicateLookup,\n}: PhoneNumberFieldProps) {\n const [local, setLocal] = React.useState<string>(() => {\n if (value == null || value === '') return ''\n return String(value)\n })\n const [duplicate, setDuplicate] = React.useState<PhoneDuplicateMatch | null>(null)\n const [checking, setChecking] = React.useState(false)\n const [validationHint, setValidationHint] = React.useState<string | null>(null)\n const userEditingRef = React.useRef(false)\n const visibleValidationHint = externalError ? null : validationHint\n\n React.useEffect(() => {\n if (userEditingRef.current) return\n if (value == null || value === '') {\n setLocal('')\n onDigitsChange?.(null)\n return\n }\n const nextValue = String(value)\n setLocal(nextValue)\n onDigitsChange?.(extractPhoneDigits(nextValue) || null)\n }, [value, onDigitsChange])\n\n React.useEffect(() => {\n if (!onDuplicateLookup || disabled) {\n setDuplicate(null)\n setChecking(false)\n return\n }\n const digits = extractPhoneDigits(local)\n if (!digits || digits.length < minDigits) {\n setDuplicate(null)\n setChecking(false)\n return\n }\n\n let cancelled = false\n setChecking(true)\n const handle = window.setTimeout(async () => {\n try {\n const match = await onDuplicateLookup(digits)\n if (!cancelled) setDuplicate(match)\n } catch {\n if (!cancelled) setDuplicate(null)\n } finally {\n if (!cancelled) setChecking(false)\n }\n }, 350)\n\n return () => {\n cancelled = true\n window.clearTimeout(handle)\n }\n }, [local, disabled, minDigits, onDuplicateLookup])\n\n const handleChange = React.useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n const next = event.target.value\n const cleanDigits = extractPhoneDigits(next)\n userEditingRef.current = true\n setLocal(next)\n setValidationHint(null)\n onValueChange(next.length ? next : undefined)\n onDigitsChange?.(cleanDigits.length ? cleanDigits : null)\n },\n [onValueChange, onDigitsChange]\n )\n\n const handleBlur = React.useCallback(() => {\n userEditingRef.current = false\n const trimmed = local.trim()\n if (!trimmed) {\n setLocal('')\n setValidationHint(null)\n onValueChange(undefined)\n onDigitsChange?.(null)\n return\n }\n const result = validatePhoneNumber(trimmed)\n if (result.valid) {\n setLocal(result.normalized ?? '')\n setValidationHint(null)\n onValueChange(result.normalized || undefined)\n onDigitsChange?.(result.digits || null)\n } else {\n setLocal(trimmed)\n setValidationHint(invalidLabel ?? DEFAULT_INVALID_LABEL)\n onValueChange(trimmed)\n onDigitsChange?.(result.digits || null)\n }\n }, [invalidLabel, local, onDigitsChange, onValueChange])\n\n return (\n <div className=\"space-y-2\">\n <input\n type=\"tel\"\n className=\"w-full h-9 rounded border px-2 text-sm\"\n value={local}\n onChange={handleChange}\n onBlur={handleBlur}\n placeholder={placeholder}\n autoFocus={autoFocus}\n disabled={disabled}\n data-crud-focus-target=\"\"\n />\n {visibleValidationHint ? (\n <p className=\"text-xs text-destructive\">{visibleValidationHint}</p>\n ) : null}\n {!disabled && duplicate && duplicateLabel && duplicateLinkLabel ? (\n <p className=\"text-xs text-amber-600\">\n {duplicateLabel(duplicate)}{' '}\n <a className=\"font-medium text-primary underline underline-offset-2\" href={duplicate.href}>\n {duplicateLinkLabel}\n </a>\n </p>\n ) : null}\n {!disabled && !duplicate && checking && checkingLabel ? (\n <p className=\"text-xs text-muted-foreground\">{checkingLabel}</p>\n ) : null}\n </div>\n )\n}\n"],
5
- "mappings": ";AA0IM,cAeE,YAfF;AAxIN,YAAY,WAAW;AACvB,SAAS,oBAAoB,2BAA2B;AAwBxD,MAAM,qBAAqB;AAC3B,MAAM,wBAAwB;AAEvB,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA0B;AACxB,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAiB,MAAM;AACrD,QAAI,SAAS,QAAQ,UAAU,GAAI,QAAO;AAC1C,WAAO,OAAO,KAAK;AAAA,EACrB,CAAC;AACD,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAqC,IAAI;AACjF,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,KAAK;AACpD,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,MAAM,SAAwB,IAAI;AAC9E,QAAM,iBAAiB,MAAM,OAAO,KAAK;AACzC,QAAM,wBAAwB,gBAAgB,OAAO;AAErD,QAAM,UAAU,MAAM;AACpB,QAAI,eAAe,QAAS;AAC5B,QAAI,SAAS,QAAQ,UAAU,IAAI;AACjC,eAAS,EAAE;AACX,uBAAiB,IAAI;AACrB;AAAA,IACF;AACA,UAAM,YAAY,OAAO,KAAK;AAC9B,aAAS,SAAS;AAClB,qBAAiB,mBAAmB,SAAS,KAAK,IAAI;AAAA,EACxD,GAAG,CAAC,OAAO,cAAc,CAAC;AAE1B,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,qBAAqB,UAAU;AAClC,mBAAa,IAAI;AACjB,kBAAY,KAAK;AACjB;AAAA,IACF;AACA,UAAM,SAAS,mBAAmB,KAAK;AACvC,QAAI,CAAC,UAAU,OAAO,SAAS,WAAW;AACxC,mBAAa,IAAI;AACjB,kBAAY,KAAK;AACjB;AAAA,IACF;AAEA,QAAI,YAAY;AAChB,gBAAY,IAAI;AAChB,UAAM,SAAS,OAAO,WAAW,YAAY;AAC3C,UAAI;AACF,cAAM,QAAQ,MAAM,kBAAkB,MAAM;AAC5C,YAAI,CAAC,UAAW,cAAa,KAAK;AAAA,MACpC,QAAQ;AACN,YAAI,CAAC,UAAW,cAAa,IAAI;AAAA,MACnC,UAAE;AACA,YAAI,CAAC,UAAW,aAAY,KAAK;AAAA,MACnC;AAAA,IACF,GAAG,GAAG;AAEN,WAAO,MAAM;AACX,kBAAY;AACZ,aAAO,aAAa,MAAM;AAAA,IAC5B;AAAA,EACF,GAAG,CAAC,OAAO,UAAU,WAAW,iBAAiB,CAAC;AAElD,QAAM,eAAe,MAAM;AAAA,IACzB,CAAC,UAA+C;AAC9C,YAAM,OAAO,MAAM,OAAO;AAC1B,YAAM,cAAc,mBAAmB,IAAI;AAC3C,qBAAe,UAAU;AACzB,eAAS,IAAI;AACb,wBAAkB,IAAI;AACtB,oBAAc,KAAK,SAAS,OAAO,MAAS;AAC5C,uBAAiB,YAAY,SAAS,cAAc,IAAI;AAAA,IAC1D;AAAA,IACA,CAAC,eAAe,cAAc;AAAA,EAChC;AAEA,QAAM,aAAa,MAAM,YAAY,MAAM;AACzC,mBAAe,UAAU;AACzB,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,CAAC,SAAS;AACZ,eAAS,EAAE;AACX,wBAAkB,IAAI;AACtB,oBAAc,MAAS;AACvB,uBAAiB,IAAI;AACrB;AAAA,IACF;AACA,UAAM,SAAS,oBAAoB,OAAO;AAC1C,QAAI,OAAO,OAAO;AAChB,eAAS,OAAO,cAAc,EAAE;AAChC,wBAAkB,IAAI;AACtB,oBAAc,OAAO,cAAc,MAAS;AAC5C,uBAAiB,OAAO,UAAU,IAAI;AAAA,IACxC,OAAO;AACL,eAAS,OAAO;AAChB,wBAAkB,gBAAgB,qBAAqB;AACvD,oBAAc,OAAO;AACrB,uBAAiB,OAAO,UAAU,IAAI;AAAA,IACxC;AAAA,EACF,GAAG,CAAC,cAAc,OAAO,gBAAgB,aAAa,CAAC;AAEvD,SACE,qBAAC,SAAI,WAAU,aACb;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,WAAU;AAAA,QACV,OAAO;AAAA,QACP,UAAU;AAAA,QACV,QAAQ;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,QACA,0BAAuB;AAAA;AAAA,IACzB;AAAA,IACC,wBACC,oBAAC,OAAE,WAAU,4BAA4B,iCAAsB,IAC7D;AAAA,IACH,CAAC,YAAY,aAAa,kBAAkB,qBAC3C,qBAAC,OAAE,WAAU,0BACV;AAAA,qBAAe,SAAS;AAAA,MAAG;AAAA,MAC5B,oBAAC,OAAE,WAAU,yDAAwD,MAAM,UAAU,MAClF,8BACH;AAAA,OACF,IACE;AAAA,IACH,CAAC,YAAY,CAAC,aAAa,YAAY,gBACtC,oBAAC,OAAE,WAAU,iCAAiC,yBAAc,IAC1D;AAAA,KACN;AAEJ;",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { extractPhoneDigits, validatePhoneNumber } from '@open-mercato/shared/lib/phone'\nimport { Input } from '../../primitives/input'\n\nexport type PhoneDuplicateMatch = {\n id: string\n label: string\n href: string\n}\n\nexport type PhoneNumberFieldProps = {\n value?: string | null\n onValueChange: (next: string | undefined) => void\n onDigitsChange?: (digits: string | null) => void\n externalError?: string | null\n disabled?: boolean\n autoFocus?: boolean\n placeholder?: string\n minDigits?: number\n checkingLabel?: string\n duplicateLabel?: (match: PhoneDuplicateMatch) => string\n duplicateLinkLabel?: string\n invalidLabel?: string\n onDuplicateLookup?: (normalizedValue: string) => Promise<PhoneDuplicateMatch | null>\n}\n\nconst DEFAULT_MIN_DIGITS = 6\nconst DEFAULT_INVALID_LABEL = 'Enter a valid phone number with country code (e.g. +1 212 555 1234)'\n\nexport function PhoneNumberField({\n value,\n onValueChange,\n onDigitsChange,\n externalError,\n disabled = false,\n autoFocus,\n placeholder,\n minDigits = DEFAULT_MIN_DIGITS,\n checkingLabel,\n duplicateLabel,\n duplicateLinkLabel,\n invalidLabel,\n onDuplicateLookup,\n}: PhoneNumberFieldProps) {\n const [local, setLocal] = React.useState<string>(() => {\n if (value == null || value === '') return ''\n return String(value)\n })\n const [duplicate, setDuplicate] = React.useState<PhoneDuplicateMatch | null>(null)\n const [checking, setChecking] = React.useState(false)\n const [validationHint, setValidationHint] = React.useState<string | null>(null)\n const userEditingRef = React.useRef(false)\n const visibleValidationHint = externalError ? null : validationHint\n\n React.useEffect(() => {\n if (userEditingRef.current) return\n if (value == null || value === '') {\n setLocal('')\n onDigitsChange?.(null)\n return\n }\n const nextValue = String(value)\n setLocal(nextValue)\n onDigitsChange?.(extractPhoneDigits(nextValue) || null)\n }, [value, onDigitsChange])\n\n React.useEffect(() => {\n if (!onDuplicateLookup || disabled) {\n setDuplicate(null)\n setChecking(false)\n return\n }\n const digits = extractPhoneDigits(local)\n if (!digits || digits.length < minDigits) {\n setDuplicate(null)\n setChecking(false)\n return\n }\n\n let cancelled = false\n setChecking(true)\n const handle = window.setTimeout(async () => {\n try {\n const match = await onDuplicateLookup(digits)\n if (!cancelled) setDuplicate(match)\n } catch {\n if (!cancelled) setDuplicate(null)\n } finally {\n if (!cancelled) setChecking(false)\n }\n }, 350)\n\n return () => {\n cancelled = true\n window.clearTimeout(handle)\n }\n }, [local, disabled, minDigits, onDuplicateLookup])\n\n const handleChange = React.useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n const next = event.target.value\n const cleanDigits = extractPhoneDigits(next)\n userEditingRef.current = true\n setLocal(next)\n setValidationHint(null)\n onValueChange(next.length ? next : undefined)\n onDigitsChange?.(cleanDigits.length ? cleanDigits : null)\n },\n [onValueChange, onDigitsChange]\n )\n\n const handleBlur = React.useCallback(() => {\n userEditingRef.current = false\n const trimmed = local.trim()\n if (!trimmed) {\n setLocal('')\n setValidationHint(null)\n onValueChange(undefined)\n onDigitsChange?.(null)\n return\n }\n const result = validatePhoneNumber(trimmed)\n if (result.valid) {\n setLocal(result.normalized ?? '')\n setValidationHint(null)\n onValueChange(result.normalized || undefined)\n onDigitsChange?.(result.digits || null)\n } else {\n setLocal(trimmed)\n setValidationHint(invalidLabel ?? DEFAULT_INVALID_LABEL)\n onValueChange(trimmed)\n onDigitsChange?.(result.digits || null)\n }\n }, [invalidLabel, local, onDigitsChange, onValueChange])\n\n return (\n <div className=\"space-y-2\">\n <Input\n type=\"tel\"\n value={local}\n onChange={handleChange}\n onBlur={handleBlur}\n placeholder={placeholder}\n autoFocus={autoFocus}\n disabled={disabled}\n data-crud-focus-target=\"\"\n />\n {visibleValidationHint ? (\n <p className=\"text-xs text-destructive\">{visibleValidationHint}</p>\n ) : null}\n {!disabled && duplicate && duplicateLabel && duplicateLinkLabel ? (\n <p className=\"text-xs text-amber-600\">\n {duplicateLabel(duplicate)}{' '}\n <a className=\"font-medium text-primary underline underline-offset-2\" href={duplicate.href}>\n {duplicateLinkLabel}\n </a>\n </p>\n ) : null}\n {!disabled && !duplicate && checking && checkingLabel ? (\n <p className=\"text-xs text-muted-foreground\">{checkingLabel}</p>\n ) : null}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -3,6 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { cn } from "@open-mercato/shared/lib/utils";
5
5
  import { useT } from "@open-mercato/shared/lib/i18n/context";
6
+ import { Input } from "../../primitives/input.js";
6
7
  function padTwo(n) {
7
8
  return String(n).padStart(2, "0");
8
9
  }
@@ -86,15 +87,11 @@ function TimeInput({
86
87
  },
87
88
  [disabled, emitChange, hour, minuteStep]
88
89
  );
89
- const inputClass = cn(
90
- "w-14 h-9 rounded border text-center text-sm tabular-nums",
91
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
92
- disabled && "bg-muted text-muted-foreground cursor-not-allowed",
93
- "disabled:bg-muted disabled:text-muted-foreground disabled:cursor-not-allowed"
94
- );
90
+ const wrapperClass = "w-14";
91
+ const innerInputClass = "text-center tabular-nums";
95
92
  return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-1", className), children: [
96
93
  /* @__PURE__ */ jsx(
97
- "input",
94
+ Input,
98
95
  {
99
96
  type: "number",
100
97
  min: 0,
@@ -105,12 +102,13 @@ function TimeInput({
105
102
  disabled,
106
103
  "aria-label": hourLabel,
107
104
  "data-crud-focus-target": "",
108
- className: inputClass
105
+ className: wrapperClass,
106
+ inputClassName: innerInputClass
109
107
  }
110
108
  ),
111
109
  /* @__PURE__ */ jsx("span", { className: "text-sm font-medium select-none", children: ":" }),
112
110
  /* @__PURE__ */ jsx(
113
- "input",
111
+ Input,
114
112
  {
115
113
  type: "number",
116
114
  min: 0,
@@ -121,7 +119,8 @@ function TimeInput({
121
119
  onKeyDown: handleMinuteKeyDown,
122
120
  disabled,
123
121
  "aria-label": minuteLabel,
124
- className: inputClass
122
+ className: wrapperClass,
123
+ inputClassName: innerInputClass
125
124
  }
126
125
  )
127
126
  ] });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/inputs/TimeInput.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\nexport type TimeInputProps = {\n value?: string | null\n onChange: (time: string) => void\n disabled?: boolean\n className?: string\n minuteStep?: number\n hourLabel?: string\n minuteLabel?: string\n}\n\nfunction padTwo(n: number): string {\n return String(n).padStart(2, '0')\n}\n\nfunction parseTime(value: string | null | undefined): { hour: number; minute: number } {\n if (!value) return { hour: 0, minute: 0 }\n const parts = value.split(':')\n const hour = parseInt(parts[0] ?? '0', 10)\n const minute = parseInt(parts[1] ?? '0', 10)\n return {\n hour: isNaN(hour) ? 0 : Math.max(0, Math.min(23, hour)),\n minute: isNaN(minute) ? 0 : Math.max(0, Math.min(59, minute)),\n }\n}\n\nfunction snapMinute(minute: number, step: number): number {\n if (step <= 1) return minute\n return Math.round(minute / step) * step % 60\n}\n\nexport function TimeInput({\n value,\n onChange,\n disabled = false,\n className,\n minuteStep = 1,\n hourLabel: hourLabelProp,\n minuteLabel: minuteLabelProp,\n}: TimeInputProps) {\n const t = useT()\n const hourLabel = hourLabelProp ?? t('ui.timePicker.hourLabel', 'Hour')\n const minuteLabel = minuteLabelProp ?? t('ui.timePicker.minuteLabel', 'Minute')\n\n const { hour, minute } = parseTime(value)\n\n const emitChange = React.useCallback(\n (nextHour: number, nextMinute: number) => {\n onChange(`${padTwo(nextHour)}:${padTwo(nextMinute)}`)\n },\n [onChange]\n )\n\n const handleHourKeyDown = React.useCallback(\n (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (disabled) return\n if (e.key === 'ArrowUp') {\n e.preventDefault()\n emitChange((hour + 1) % 24, minute)\n } else if (e.key === 'ArrowDown') {\n e.preventDefault()\n emitChange((hour + 23) % 24, minute)\n }\n },\n [disabled, emitChange, hour, minute]\n )\n\n const handleMinuteKeyDown = React.useCallback(\n (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (disabled) return\n if (e.key === 'ArrowUp') {\n e.preventDefault()\n const step = minuteStep > 1 ? minuteStep : 1\n emitChange(hour, (minute + step) % 60)\n } else if (e.key === 'ArrowDown') {\n e.preventDefault()\n const step = minuteStep > 1 ? minuteStep : 1\n emitChange(hour, (minute + 60 - step) % 60)\n }\n },\n [disabled, emitChange, hour, minute, minuteStep]\n )\n\n const handleHourChange = React.useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n if (disabled) return\n const raw = parseInt(e.target.value, 10)\n if (isNaN(raw)) return\n emitChange(Math.max(0, Math.min(23, raw)), minute)\n },\n [disabled, emitChange, minute]\n )\n\n const handleMinuteChange = React.useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n if (disabled) return\n const raw = parseInt(e.target.value, 10)\n if (isNaN(raw)) return\n const snapped = minuteStep > 1 ? snapMinute(raw, minuteStep) : Math.max(0, Math.min(59, raw))\n emitChange(hour, snapped)\n },\n [disabled, emitChange, hour, minuteStep]\n )\n\n const inputClass = cn(\n 'w-14 h-9 rounded border text-center text-sm tabular-nums',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',\n disabled && 'bg-muted text-muted-foreground cursor-not-allowed',\n 'disabled:bg-muted disabled:text-muted-foreground disabled:cursor-not-allowed'\n )\n\n return (\n <div className={cn('flex items-center gap-1', className)}>\n <input\n type=\"number\"\n min={0}\n max={23}\n value={padTwo(hour)}\n onChange={handleHourChange}\n onKeyDown={handleHourKeyDown}\n disabled={disabled}\n aria-label={hourLabel}\n data-crud-focus-target=\"\"\n className={inputClass}\n />\n <span className=\"text-sm font-medium select-none\">:</span>\n <input\n type=\"number\"\n min={0}\n max={59}\n step={minuteStep}\n value={padTwo(minute)}\n onChange={handleMinuteChange}\n onKeyDown={handleMinuteKeyDown}\n disabled={disabled}\n aria-label={minuteLabel}\n className={inputClass}\n />\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Input } from '../../primitives/input'\n\nexport type TimeInputProps = {\n value?: string | null\n onChange: (time: string) => void\n disabled?: boolean\n className?: string\n minuteStep?: number\n hourLabel?: string\n minuteLabel?: string\n}\n\nfunction padTwo(n: number): string {\n return String(n).padStart(2, '0')\n}\n\nfunction parseTime(value: string | null | undefined): { hour: number; minute: number } {\n if (!value) return { hour: 0, minute: 0 }\n const parts = value.split(':')\n const hour = parseInt(parts[0] ?? '0', 10)\n const minute = parseInt(parts[1] ?? '0', 10)\n return {\n hour: isNaN(hour) ? 0 : Math.max(0, Math.min(23, hour)),\n minute: isNaN(minute) ? 0 : Math.max(0, Math.min(59, minute)),\n }\n}\n\nfunction snapMinute(minute: number, step: number): number {\n if (step <= 1) return minute\n return Math.round(minute / step) * step % 60\n}\n\nexport function TimeInput({\n value,\n onChange,\n disabled = false,\n className,\n minuteStep = 1,\n hourLabel: hourLabelProp,\n minuteLabel: minuteLabelProp,\n}: TimeInputProps) {\n const t = useT()\n const hourLabel = hourLabelProp ?? t('ui.timePicker.hourLabel', 'Hour')\n const minuteLabel = minuteLabelProp ?? t('ui.timePicker.minuteLabel', 'Minute')\n\n const { hour, minute } = parseTime(value)\n\n const emitChange = React.useCallback(\n (nextHour: number, nextMinute: number) => {\n onChange(`${padTwo(nextHour)}:${padTwo(nextMinute)}`)\n },\n [onChange]\n )\n\n const handleHourKeyDown = React.useCallback(\n (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (disabled) return\n if (e.key === 'ArrowUp') {\n e.preventDefault()\n emitChange((hour + 1) % 24, minute)\n } else if (e.key === 'ArrowDown') {\n e.preventDefault()\n emitChange((hour + 23) % 24, minute)\n }\n },\n [disabled, emitChange, hour, minute]\n )\n\n const handleMinuteKeyDown = React.useCallback(\n (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (disabled) return\n if (e.key === 'ArrowUp') {\n e.preventDefault()\n const step = minuteStep > 1 ? minuteStep : 1\n emitChange(hour, (minute + step) % 60)\n } else if (e.key === 'ArrowDown') {\n e.preventDefault()\n const step = minuteStep > 1 ? minuteStep : 1\n emitChange(hour, (minute + 60 - step) % 60)\n }\n },\n [disabled, emitChange, hour, minute, minuteStep]\n )\n\n const handleHourChange = React.useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n if (disabled) return\n const raw = parseInt(e.target.value, 10)\n if (isNaN(raw)) return\n emitChange(Math.max(0, Math.min(23, raw)), minute)\n },\n [disabled, emitChange, minute]\n )\n\n const handleMinuteChange = React.useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n if (disabled) return\n const raw = parseInt(e.target.value, 10)\n if (isNaN(raw)) return\n const snapped = minuteStep > 1 ? snapMinute(raw, minuteStep) : Math.max(0, Math.min(59, raw))\n emitChange(hour, snapped)\n },\n [disabled, emitChange, hour, minuteStep]\n )\n\n const wrapperClass = 'w-14'\n const innerInputClass = 'text-center tabular-nums'\n\n return (\n <div className={cn('flex items-center gap-1', className)}>\n <Input\n type=\"number\"\n min={0}\n max={23}\n value={padTwo(hour)}\n onChange={handleHourChange}\n onKeyDown={handleHourKeyDown}\n disabled={disabled}\n aria-label={hourLabel}\n data-crud-focus-target=\"\"\n className={wrapperClass}\n inputClassName={innerInputClass}\n />\n <span className=\"text-sm font-medium select-none\">:</span>\n <Input\n type=\"number\"\n min={0}\n max={59}\n step={minuteStep}\n value={padTwo(minute)}\n onChange={handleMinuteChange}\n onKeyDown={handleMinuteKeyDown}\n disabled={disabled}\n aria-label={minuteLabel}\n className={wrapperClass}\n inputClassName={innerInputClass}\n />\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -3,6 +3,13 @@ import { FileCode, Globe, Lock } from "lucide-react";
3
3
  import { IconButton } from "../../primitives/icon-button.js";
4
4
  import { Input } from "../../primitives/input.js";
5
5
  import { Label } from "../../primitives/label.js";
6
+ import {
7
+ Select,
8
+ SelectContent,
9
+ SelectItem,
10
+ SelectTrigger,
11
+ SelectValue
12
+ } from "../../primitives/select.js";
6
13
  import { Switch } from "../../primitives/switch.js";
7
14
  import { AttachmentsSection } from "../detail/AttachmentsSection.js";
8
15
  import { SwitchableMarkdownInput } from "../inputs/SwitchableMarkdownInput.js";
@@ -82,15 +89,13 @@ function ContextActionsSection({ compose }) {
82
89
  compose.normalizedRequiredActionMode === "required" || compose.contextActionRequired ? /* @__PURE__ */ jsxs("div", { className: "space-y-2 sm:col-span-2", children: [
83
90
  /* @__PURE__ */ jsx(Label, { htmlFor: "messages-compose-context-action-type", children: compose.t("messages.composer.objectPicker.actionTypeLabel", "Action type") }),
84
91
  /* @__PURE__ */ jsxs(
85
- "select",
92
+ Select,
86
93
  {
87
- id: "messages-compose-context-action-type",
88
- value: compose.contextActionType,
89
- onChange: (event) => compose.setContextActionType(event.target.value),
90
- className: "h-9 w-full rounded-md border bg-background px-3 text-sm",
94
+ value: compose.contextActionType || void 0,
95
+ onValueChange: (value) => compose.setContextActionType(value || ""),
91
96
  children: [
92
- /* @__PURE__ */ jsx("option", { value: "", children: compose.t("messages.composer.objectPicker.actionTypePlaceholder", "Select action") }),
93
- compose.contextActionOptions.map((option) => /* @__PURE__ */ jsx("option", { value: option.id, children: option.label }, option.id))
97
+ /* @__PURE__ */ jsx(SelectTrigger, { id: "messages-compose-context-action-type", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: compose.t("messages.composer.objectPicker.actionTypePlaceholder", "Select action") }) }),
98
+ /* @__PURE__ */ jsx(SelectContent, { children: compose.contextActionOptions.map((option) => /* @__PURE__ */ jsx(SelectItem, { value: option.id, children: option.label }, option.id)) })
94
99
  ]
95
100
  }
96
101
  )