@lmb-it/kitsconcerto-native 0.1.284 → 0.1.286

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 (75) hide show
  1. package/apps/mobile/src/Core/AutoComplete/index.cjs +12 -4
  2. package/apps/mobile/src/Core/AutoComplete/index.cjs.map +1 -1
  3. package/apps/mobile/src/Core/AutoComplete/index.js +12 -4
  4. package/apps/mobile/src/Core/AutoComplete/index.js.map +1 -1
  5. package/apps/mobile/src/Core/MultiSelect/index.cjs +7 -4
  6. package/apps/mobile/src/Core/MultiSelect/index.cjs.map +1 -1
  7. package/apps/mobile/src/Core/MultiSelect/index.js +7 -4
  8. package/apps/mobile/src/Core/MultiSelect/index.js.map +1 -1
  9. package/apps/mobile/src/ui/accordion/index.js +2 -2
  10. package/components/ImageCropper/compression.cjs +63 -0
  11. package/components/ImageCropper/compression.cjs.map +1 -0
  12. package/components/ImageCropper/compression.js +58 -0
  13. package/components/ImageCropper/compression.js.map +1 -0
  14. package/components/ImageCropper/constants.cjs +35 -0
  15. package/components/ImageCropper/constants.cjs.map +1 -0
  16. package/components/ImageCropper/constants.js +23 -0
  17. package/components/ImageCropper/constants.js.map +1 -0
  18. package/components/ImageCropper/cropperState.cjs +119 -0
  19. package/components/ImageCropper/cropperState.cjs.map +1 -0
  20. package/components/ImageCropper/cropperState.js +114 -0
  21. package/components/ImageCropper/cropperState.js.map +1 -0
  22. package/components/ImageCropper/encode.cjs +45 -0
  23. package/components/ImageCropper/encode.cjs.map +1 -0
  24. package/components/ImageCropper/encode.js +42 -0
  25. package/components/ImageCropper/encode.js.map +1 -0
  26. package/components/ImageCropper/encoderRegistry.cjs +13 -0
  27. package/components/ImageCropper/encoderRegistry.cjs.map +1 -0
  28. package/components/ImageCropper/encoderRegistry.js +9 -0
  29. package/components/ImageCropper/encoderRegistry.js.map +1 -0
  30. package/components/ImageCropper/encoders/rnImageEncoder.cjs +54 -0
  31. package/components/ImageCropper/encoders/rnImageEncoder.cjs.map +1 -0
  32. package/components/ImageCropper/encoders/rnImageEncoder.js +51 -0
  33. package/components/ImageCropper/encoders/rnImageEncoder.js.map +1 -0
  34. package/components/ImageCropper/geometry.cjs +208 -0
  35. package/components/ImageCropper/geometry.cjs.map +1 -0
  36. package/components/ImageCropper/geometry.js +186 -0
  37. package/components/ImageCropper/geometry.js.map +1 -0
  38. package/components/ImageCropper/index.cjs +436 -0
  39. package/components/ImageCropper/index.cjs.map +1 -0
  40. package/components/ImageCropper/index.js +434 -0
  41. package/components/ImageCropper/index.js.map +1 -0
  42. package/components/ImageCropper/pinchZoom.cjs +8 -0
  43. package/components/ImageCropper/pinchZoom.cjs.map +1 -0
  44. package/components/ImageCropper/pinchZoom.js +5 -0
  45. package/components/ImageCropper/pinchZoom.js.map +1 -0
  46. package/components/ImageCropper/useImageSource.cjs +87 -0
  47. package/components/ImageCropper/useImageSource.cjs.map +1 -0
  48. package/components/ImageCropper/useImageSource.js +84 -0
  49. package/components/ImageCropper/useImageSource.js.map +1 -0
  50. package/form/helpers/FormContainer/index.cjs +12 -2
  51. package/form/helpers/FormContainer/index.cjs.map +1 -1
  52. package/form/helpers/FormContainer/index.js +12 -2
  53. package/form/helpers/FormContainer/index.js.map +1 -1
  54. package/form/selects/AutoComplete/index.cjs +10 -1
  55. package/form/selects/AutoComplete/index.cjs.map +1 -1
  56. package/form/selects/AutoComplete/index.js +11 -2
  57. package/form/selects/AutoComplete/index.js.map +1 -1
  58. package/form/selects/MultiSelect/index.cjs +8 -1
  59. package/form/selects/MultiSelect/index.cjs.map +1 -1
  60. package/form/selects/MultiSelect/index.js +9 -2
  61. package/form/selects/MultiSelect/index.js.map +1 -1
  62. package/hooks/useComponentDefaults.cjs +62 -8
  63. package/hooks/useComponentDefaults.cjs.map +1 -1
  64. package/hooks/useComponentDefaults.js +62 -8
  65. package/hooks/useComponentDefaults.js.map +1 -1
  66. package/index.cjs +120 -115
  67. package/index.cjs.map +1 -1
  68. package/index.d.ts +494 -16
  69. package/index.js +2 -0
  70. package/index.js.map +1 -1
  71. package/package.json +13 -1
  72. package/packages/types/src/Theme/defaultTheme.cjs +135 -4
  73. package/packages/types/src/Theme/defaultTheme.cjs.map +1 -1
  74. package/packages/types/src/Theme/defaultTheme.js +135 -4
  75. package/packages/types/src/Theme/defaultTheme.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../../../../../../../apps/mobile/src/Core/MultiSelect/index.tsx"],"sourcesContent":["import React, { useMemo, useRef, useState } from 'react';\nimport {\n View,\n Text,\n TouchableOpacity,\n Modal,\n FlatList,\n TextInput,\n StyleSheet,\n Pressable,\n ActivityIndicator,\n Dimensions,\n} from 'react-native';\nimport { readerEdge } from '../../Factory/helpers/readerEdge';\nimport { resolveThemeTokenForNative } from '../../Factory/helpers/style';\n\nexport type MultiSelectChangeEvent<T> = React.ChangeEvent<{\n value: T[];\n}>;\n\nexport interface MultiSelectProps<T> {\n value?: T[] | null;\n onChange?: (e: MultiSelectChangeEvent<T>, selectedItems: T[]) => void;\n isModal?: boolean;\n\n options?: T[];\n\n optionLabel?: keyof T | string;\n optionValue?: keyof T | string;\n\n optionGroupLabel?: keyof T | string;\n optionGroupChildren?: keyof T | string;\n\n placeholder?: string;\n disabled?: boolean;\n loading?: boolean;\n filter?: boolean;\n emptyFilterMessage?: string;\n /**\n * Placeholder for the panel's filter field, already written in the reader's\n * language by the KitsConcerto wrapper — this component lives in\n * `apps/mobile`, which `packages/ui` imports, so it cannot translate.\n */\n filterPlaceholder?: string;\n /** Reader's direction, from the wrapper's `useLanguage().isRTL`. */\n isRTL?: boolean;\n /**\n * Font family the app's theme names for body text, resolved by the wrapper.\n * React Native does not cascade fontFamily through Views, so each text sink\n * must set it — see the Dropdown's own note.\n */\n fontFamily?: string;\n /**\n * Open the options in a popup that owns the screen instead of a panel\n * anchored to the trigger. Default false — see the Dropdown's own `popup`\n * for why the anchor is the thing being escaped.\n */\n popup?: boolean;\n /**\n * Supplies the popup's container. Injected by the KitsConcerto wrapper: the\n * container is this library's `KitsDialogControlled`, which lives in\n * `packages/ui`, and `packages/ui` imports THIS file.\n */\n renderPopup?: (args: {\n visible: boolean;\n onClose: () => void;\n title?: React.ReactNode;\n children: React.ReactNode;\n }) => React.ReactNode;\n /** Title for the popup header. */\n popupTitle?: React.ReactNode;\n\n display?: 'comma' | 'chip';\n maxSelectedLabels?: number;\n\n itemTemplate?: (item: T) => React.ReactElement;\n selectedItemTemplate?: (item: T) => React.ReactElement;\n style?: import('react-native').ViewStyle;\n}\n\nconst t = resolveThemeTokenForNative;\n\nconst TEXT_STYLE_KEYS = new Set([\n 'fontSize', 'fontFamily', 'fontWeight', 'fontStyle',\n 'color', 'letterSpacing', 'lineHeight', 'textAlign',\n 'textDecorationLine', 'textTransform',\n]);\n\nfunction splitStyle(combined?: any): { viewStyle?: any; textStyle?: any } {\n if (!combined) return {};\n const view: Record<string, any> = {};\n const text: Record<string, any> = {};\n for (const [k, v] of Object.entries(combined)) {\n if (TEXT_STYLE_KEYS.has(k)) text[k] = v;\n else view[k] = v;\n }\n return {\n viewStyle: Object.keys(view).length ? view : undefined,\n textStyle: Object.keys(text).length ? text : undefined,\n };\n}\n\nexport default function MultiSelect<T>(props: MultiSelectProps<T>) {\n const {\n value,\n onChange,\n options = [],\n optionLabel,\n optionValue,\n optionGroupLabel,\n optionGroupChildren,\n isModal = false,\n placeholder = 'Select',\n disabled,\n loading,\n filter,\n // ⚠️ English literals of last resort, not translations — the wrapper always\n // passes the reader's own sentence. These only apply when this component is\n // used directly from `apps/mobile`, so a panel can never render blank.\n emptyFilterMessage = 'No results found',\n filterPlaceholder = 'Search...',\n isRTL = false,\n fontFamily,\n popup = false,\n renderPopup,\n popupTitle,\n display = 'chip',\n maxSelectedLabels = 10,\n itemTemplate,\n selectedItemTemplate,\n style: externalStyle,\n } = props;\n\n /* Physical edges to write so content lands where the reader starts. */\n const edge = readerEdge(isRTL);\n\n const triggerRef = useRef<View>(null);\n const [open, setOpen] = useState(false);\n const [query, setQuery] = useState('');\n const [triggerLayout, setTriggerLayout] = useState<{\n x: number; y: number; width: number; height: number;\n } | null>(null);\n\n const close = () => setOpen(false);\n\n const toggleOpen = () => {\n if (disabled) return;\n if (open) {\n setOpen(false);\n return;\n }\n triggerRef.current?.measureInWindow((x, y, width, height) => {\n setTriggerLayout({ x, y, width, height });\n setOpen(true);\n });\n };\n\n const { viewStyle: externalViewStyle, textStyle: externalTextStyle } = splitStyle(externalStyle);\n\n const colors = {\n border: t('border') || t('gray.300'),\n borderFocus: t('primary'),\n bg: t('surface-card') || '#fff',\n disabledBg: t('surface-ground') || t('gray.100'),\n placeholder: t('text-secondary') || t('gray.400'),\n text: t('text') || t('gray.800'),\n textSecondary: t('text-secondary') || t('gray.500'),\n chipBg: t('primary.100') || t('indigo.100'),\n chipText: t('primary.700') || t('indigo.700'),\n chipRemove: t('primary.500') || t('indigo.500'),\n chipMoreBg: t('surface-ground') || t('gray.100'),\n headerBorder: t('border') || t('gray.200'),\n groupBg: t('surface-ground') || t('gray.50'),\n groupBorder: t('border') || t('gray.200'),\n itemBorder: t('surface-ground') || t('gray.100'),\n itemSelectedBg: t('highlight-bg') || t('indigo.50'),\n itemSelectedText: t('primary.700') || t('indigo.700'),\n checkboxBorder: t('border') || t('gray.300'),\n checkboxChecked: t('primary'),\n invalidBorder: t('danger') || t('red.500'),\n searchBg: t('surface-ground') || t('gray.50'),\n };\n\n const getValue = (item: T, key?: keyof T | string) =>\n key ? (item as any)[key] : item;\n\n const getLabel = (item: T, key?: keyof T | string) =>\n String(key ? (item as any)[key] : item);\n\n const selectedValues = Array.isArray(value) ? value : [];\n\n // Build display items: flat list with optional group headers\n const displayItems: { type: 'group' | 'item'; data: any }[] = useMemo(() => {\n if (optionGroupLabel && optionGroupChildren) {\n const result: { type: 'group' | 'item'; data: any }[] = [];\n options.forEach((group: any) => {\n result.push({ type: 'group', data: group });\n const children = group[optionGroupChildren] ?? [];\n children.forEach((child: any) => result.push({ type: 'item', data: child }));\n });\n return result;\n }\n return options.map((item) => ({ type: 'item' as const, data: item }));\n }, [options, optionGroupLabel, optionGroupChildren]);\n\n const filtered = useMemo(() => {\n if (!filter || !query) return displayItems;\n return displayItems.filter((entry) => {\n if (entry.type === 'group') return true; // keep group headers\n return getLabel(entry.data, optionLabel)\n .toLowerCase()\n .includes(query.toLowerCase());\n });\n }, [query, displayItems]);\n\n const isSelected = (item: T) =>\n selectedValues.some(\n (v) => getValue(v, optionValue) === getValue(item, optionValue),\n );\n\n const toggleItem = (item: T) => {\n const exists = isSelected(item);\n const next = exists\n ? selectedValues.filter(\n (v) => getValue(v, optionValue) !== getValue(item, optionValue),\n )\n : [...selectedValues, item];\n\n const syntheticEvent = {\n target: { value: next },\n } as React.ChangeEvent<any>;\n\n onChange?.(syntheticEvent, next);\n };\n\n const removeItem = (item: T) => {\n const next = selectedValues.filter(\n (v) => getValue(v, optionValue) !== getValue(item, optionValue),\n );\n const syntheticEvent = {\n target: { value: next },\n } as React.ChangeEvent<any>;\n onChange?.(syntheticEvent, next);\n };\n\n const renderCheckbox = (checked: boolean) => (\n <View\n style={[\n styles.checkbox,\n { borderColor: colors.checkboxBorder, backgroundColor: colors.bg },\n checked && { backgroundColor: colors.checkboxChecked, borderColor: colors.checkboxChecked },\n ]}\n >\n {checked && <Text style={styles.checkmark}>✓</Text>}\n </View>\n );\n\n const renderListContent = () => {\n if (loading) {\n return (\n <View style={styles.loadingContainer}>\n <ActivityIndicator size=\"small\" color={colors.checkboxChecked} />\n <Text style={[styles.loadingText, { color: colors.placeholder }]}>Loading...</Text>\n </View>\n );\n }\n\n const itemsOnly = filtered.filter((e) => e.type === 'item');\n if (itemsOnly.length === 0) {\n return (\n <View style={styles.emptyContainer}>\n <Text style={[styles.emptyText, { color: colors.placeholder }]}>{emptyFilterMessage}</Text>\n </View>\n );\n }\n\n return (\n <FlatList\n data={filtered}\n keyExtractor={(entry, i) =>\n entry.type === 'group' ? `group-${i}` : `item-${i}`\n }\n keyboardShouldPersistTaps=\"handled\"\n renderItem={({ item: entry }) => {\n if (entry.type === 'group') {\n return (\n <View style={[styles.groupHeader, { backgroundColor: colors.groupBg, borderBottomColor: colors.groupBorder }]}>\n <Text style={[styles.groupHeaderText, { color: colors.textSecondary }]}>\n {getLabel(entry.data, optionGroupLabel)}\n </Text>\n </View>\n );\n }\n const checked = isSelected(entry.data);\n return (\n <TouchableOpacity\n onPress={() => toggleItem(entry.data)}\n style={[\n styles.item,\n { borderBottomColor: colors.itemBorder },\n checked && { backgroundColor: colors.itemSelectedBg },\n ]}\n activeOpacity={0.7}\n >\n {renderCheckbox(checked)}\n {itemTemplate ? (\n itemTemplate(entry.data)\n ) : (\n <Text style={[\n styles.itemText,\n { color: colors.text, fontFamily },\n checked && { color: colors.itemSelectedText, fontWeight: '500' },\n /* ⛔ The physical edge is COMPUTED — React Native mirrors\n `textAlign` when the process is RTL, so naming the RTL edge\n for an RTL reader lands on the LTR one. See `readerEdge`. */\n {\n textAlign: edge.textAlign,\n writingDirection: isRTL ? ('rtl' as const) : ('ltr' as const),\n },\n ]}>\n {getLabel(entry.data, optionLabel)}\n </Text>\n )}\n </TouchableOpacity>\n );\n }}\n />\n );\n };\n\n const renderSelected = () => {\n if (!selectedValues.length) {\n return <Text style={[styles.placeholder, { color: colors.placeholder }, externalTextStyle]}>{placeholder}</Text>;\n }\n\n if (display === 'chip') {\n const visibleItems = selectedValues.slice(0, maxSelectedLabels);\n const remaining = selectedValues.length - maxSelectedLabels;\n return (\n <View style={styles.chips}>\n {visibleItems.map((item, i) =>\n selectedItemTemplate ? (\n <React.Fragment key={i}>{selectedItemTemplate(item)}</React.Fragment>\n ) : (\n <View key={i} style={[styles.chip, { backgroundColor: colors.chipBg }]}>\n <Text style={[styles.chipText, { color: colors.chipText }]}>{getLabel(item, optionLabel)}</Text>\n <TouchableOpacity\n onPress={() => removeItem(item)}\n hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}\n >\n <Text style={[styles.chipRemove, { color: colors.chipRemove }]}>✕</Text>\n </TouchableOpacity>\n </View>\n ),\n )}\n {remaining > 0 && (\n <View style={[styles.chipMore, { backgroundColor: colors.chipMoreBg }]}>\n <Text style={[styles.chipMoreText, { color: colors.textSecondary }]}>+{remaining}</Text>\n </View>\n )}\n </View>\n );\n }\n\n return (\n <Text numberOfLines={1} style={[styles.commaText, { color: colors.text }, externalTextStyle]}>\n {selectedValues\n .slice(0, maxSelectedLabels)\n .map((v) => getLabel(v, optionLabel))\n .join(', ')}\n </Text>\n );\n };\n\n const usePopup = popup && typeof renderPopup === 'function';\n\n const panelContent = (\n <View style={{ flex: 1 }}>\n {/* Header */}\n <View style={[styles.panelHeader, { borderBottomColor: colors.headerBorder }]}>\n <Text style={[styles.panelHeaderText, { color: colors.textSecondary }]}>\n {selectedValues.length} item{selectedValues.length !== 1 ? 's' : ''} selected\n </Text>\n {/* In popup mode the sheet already draws a close control, and two ✕\n in one header is a question the reader should not have to answer.\n The count stays — it is information, not chrome. */}\n {!usePopup && (\n <TouchableOpacity onPress={() => setOpen(false)}>\n <Text style={[styles.panelCloseText, { color: colors.placeholder }]}>✕</Text>\n </TouchableOpacity>\n )}\n </View>\n\n {/* Search */}\n {filter && (\n <View style={[styles.searchContainer, { borderColor: colors.border, backgroundColor: colors.searchBg }]}>\n <Text style={styles.searchIcon}>🔍</Text>\n <TextInput\n value={query}\n onChangeText={setQuery}\n placeholder={filterPlaceholder}\n style={[\n styles.searchInput,\n { color: colors.text, fontFamily },\n // The filter follows the reader's language, not the platform's\n // layout direction — same rule as the Dropdown's panel.\n isRTL ? styles.searchRtl : styles.searchLtr,\n ]}\n placeholderTextColor={colors.placeholder}\n />\n </View>\n )}\n\n {/* List */}\n {renderListContent()}\n </View>\n );\n\n return (\n <View style={{ width: '100%' }}>\n {/* Input trigger */}\n <TouchableOpacity\n ref={triggerRef as any}\n activeOpacity={0.8}\n disabled={disabled}\n onPress={toggleOpen}\n style={[\n styles.input,\n { borderColor: colors.border, backgroundColor: colors.bg },\n disabled && { backgroundColor: colors.disabledBg, opacity: 0.6 },\n open && { borderColor: colors.borderFocus },\n externalViewStyle,\n ]}\n >\n <View style={styles.inputContent}>\n {renderSelected()}\n </View>\n <Text style={[styles.dropdownArrow, { color: colors.placeholder }]}>{open ? '▲' : '▼'}</Text>\n </TouchableOpacity>\n\n {/* Popup — the container comes from the wrapper (this library's own\n Dialog in sheet position); `panelContent` is unchanged. */}\n {usePopup && renderPopup!({\n visible: open,\n onClose: () => setOpen(false),\n title: popupTitle,\n children: panelContent,\n })}\n\n {/* Modal mode */}\n {!usePopup && isModal && (\n <Modal visible={open} transparent animationType=\"fade\">\n <Pressable style={styles.overlay} onPress={() => setOpen(false)}>\n <View\n style={[styles.modalPanel, { backgroundColor: colors.bg }]}\n onStartShouldSetResponder={() => true}\n >\n {panelContent}\n </View>\n </Pressable>\n </Modal>\n )}\n\n {/* Inline mode — Modal-portal popover anchored to trigger */}\n {!usePopup && !isModal && (\n <Modal\n visible={open && !!triggerLayout}\n transparent\n animationType=\"none\"\n onRequestClose={close}\n >\n <Pressable style={styles.popoverOverlay} onPress={close}>\n {triggerLayout && (() => {\n const screen = Dimensions.get('window');\n const PANEL_MAX_HEIGHT = 320;\n const GAP = 4;\n const spaceBelow = screen.height - (triggerLayout.y + triggerLayout.height);\n const spaceAbove = triggerLayout.y;\n const openUp = spaceBelow < PANEL_MAX_HEIGHT && spaceAbove > spaceBelow;\n const maxHeight = Math.min(\n PANEL_MAX_HEIGHT,\n openUp ? spaceAbove - GAP : spaceBelow - GAP,\n );\n const top = openUp\n ? triggerLayout.y - maxHeight - GAP\n : triggerLayout.y + triggerLayout.height + GAP;\n\n return (\n <View\n style={[\n styles.popoverPanel,\n {\n top,\n left: triggerLayout.x,\n width: triggerLayout.width,\n maxHeight,\n borderColor: colors.border,\n backgroundColor: colors.bg,\n },\n ]}\n onStartShouldSetResponder={() => true}\n >\n {panelContent}\n </View>\n );\n })()}\n </Pressable>\n </Modal>\n )}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n // Input\n input: {\n minHeight: 42,\n borderWidth: 1,\n borderRadius: 6,\n paddingHorizontal: 10,\n paddingVertical: 6,\n flexDirection: 'row',\n alignItems: 'center',\n },\n inputContent: {\n flex: 1,\n },\n dropdownArrow: {\n fontSize: 10,\n marginLeft: 8,\n },\n\n // Placeholder\n placeholder: { fontSize: 14 },\n commaText: { fontSize: 14 },\n\n // Chips display\n chips: {\n flexDirection: 'row',\n flexWrap: 'wrap',\n gap: 4,\n },\n chip: {\n flexDirection: 'row',\n alignItems: 'center',\n paddingLeft: 10,\n paddingRight: 6,\n paddingVertical: 3,\n borderRadius: 16,\n gap: 4,\n },\n chipText: {\n fontSize: 13,\n },\n chipRemove: {\n fontSize: 12,\n paddingHorizontal: 2,\n },\n chipMore: {\n paddingHorizontal: 10,\n paddingVertical: 3,\n borderRadius: 16,\n },\n chipMoreText: {\n fontSize: 13,\n },\n\n // Panel header\n panelHeader: {\n flexDirection: 'row',\n justifyContent: 'space-between',\n alignItems: 'center',\n paddingHorizontal: 12,\n paddingVertical: 10,\n borderBottomWidth: 1,\n },\n panelHeaderText: {\n fontSize: 13,\n },\n panelCloseText: {\n fontSize: 18,\n fontWeight: '600',\n },\n\n // Search\n searchContainer: {\n flexDirection: 'row',\n alignItems: 'center',\n margin: 8,\n paddingHorizontal: 10,\n height: 36,\n borderWidth: 1,\n borderRadius: 6,\n },\n searchIcon: {\n fontSize: 14,\n marginRight: 6,\n },\n searchInput: {\n flex: 1,\n fontSize: 14,\n paddingVertical: 0,\n },\n searchLtr: {\n textAlign: 'left' as const,\n writingDirection: 'ltr' as const,\n },\n searchRtl: {\n textAlign: 'right' as const,\n writingDirection: 'rtl' as const,\n },\n\n // Group header\n groupHeader: {\n paddingHorizontal: 12,\n paddingVertical: 8,\n borderBottomWidth: StyleSheet.hairlineWidth,\n },\n groupHeaderText: {\n fontSize: 12,\n fontWeight: '700',\n textTransform: 'uppercase',\n letterSpacing: 0.5,\n },\n\n // Items\n item: {\n flexDirection: 'row',\n alignItems: 'center',\n paddingVertical: 10,\n paddingHorizontal: 12,\n gap: 10,\n borderBottomWidth: StyleSheet.hairlineWidth,\n },\n itemText: {\n fontSize: 14,\n /* The label takes the row's spare width so `textAlign` has somewhere to\n push to — a shrink-wrapped Text ignores it. Already here, which is why\n MultiSelect needs no extra entry; Dropdown's label style carries no flex\n and got `optionLabelFill`. */\n flex: 1,\n },\n\n // Checkbox\n checkbox: {\n width: 20,\n height: 20,\n borderWidth: 2,\n borderRadius: 4,\n justifyContent: 'center',\n alignItems: 'center',\n },\n checkmark: {\n color: '#fff',\n fontSize: 13,\n fontWeight: '700',\n },\n\n // Loading / Empty\n loadingContainer: {\n padding: 24,\n alignItems: 'center',\n gap: 8,\n },\n loadingText: {\n fontSize: 13,\n },\n emptyContainer: {\n padding: 24,\n alignItems: 'center',\n },\n emptyText: {\n fontSize: 14,\n },\n\n // Modal\n overlay: {\n flex: 1,\n backgroundColor: 'rgba(0,0,0,0.4)',\n justifyContent: 'center',\n },\n modalPanel: {\n margin: 24,\n maxHeight: '70%',\n borderRadius: 12,\n overflow: 'hidden',\n },\n\n // Popover (Modal-portal)\n popoverOverlay: {\n flex: 1,\n backgroundColor: 'transparent',\n },\n popoverPanel: {\n position: 'absolute',\n borderWidth: 1,\n borderRadius: 6,\n overflow: 'hidden',\n elevation: 8,\n shadowColor: '#000',\n shadowOffset: { width: 0, height: 2 },\n shadowOpacity: 0.15,\n shadowRadius: 8,\n },\n});\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../../../../../../../../../apps/mobile/src/Core/MultiSelect/index.tsx"],"sourcesContent":["import React, { useMemo, useRef, useState } from 'react';\nimport {\n View,\n Text,\n TouchableOpacity,\n Modal,\n FlatList,\n TextInput,\n StyleSheet,\n Pressable,\n ActivityIndicator,\n Dimensions,\n} from 'react-native';\nimport { readerEdge } from '../../Factory/helpers/readerEdge';\nimport { resolveThemeTokenForNative } from '../../Factory/helpers/style';\n\nexport type MultiSelectChangeEvent<T> = React.ChangeEvent<{\n value: T[];\n}>;\n\nexport interface MultiSelectProps<T> {\n value?: T[] | null;\n onChange?: (e: MultiSelectChangeEvent<T>, selectedItems: T[]) => void;\n isModal?: boolean;\n\n options?: T[];\n\n optionLabel?: keyof T | string;\n optionValue?: keyof T | string;\n\n optionGroupLabel?: keyof T | string;\n optionGroupChildren?: keyof T | string;\n\n placeholder?: string;\n disabled?: boolean;\n loading?: boolean;\n filter?: boolean;\n emptyFilterMessage?: string;\n /**\n * Placeholder for the panel's filter field, already written in the reader's\n * language by the KitsConcerto wrapper — this component lives in\n * `apps/mobile`, which `packages/ui` imports, so it cannot translate.\n */\n filterPlaceholder?: string;\n /** Reader's direction, from the wrapper's `useLanguage().isRTL`. */\n isRTL?: boolean;\n /**\n * Font family the app's theme names for body text, resolved by the wrapper.\n * React Native does not cascade fontFamily through Views, so each text sink\n * must set it — see the Dropdown's own note.\n */\n fontFamily?: string;\n /**\n * Open the options in a popup that owns the screen instead of a panel\n * anchored to the trigger. Default false — see the Dropdown's own `popup`\n * for why the anchor is the thing being escaped.\n */\n popup?: boolean;\n /**\n * Supplies the popup's container. Injected by the KitsConcerto wrapper: the\n * container is this library's `KitsDialogControlled`, which lives in\n * `packages/ui`, and `packages/ui` imports THIS file.\n */\n renderPopup?: (args: {\n visible: boolean;\n onClose: () => void;\n title?: React.ReactNode;\n children: React.ReactNode;\n }) => React.ReactNode;\n /** Title for the popup header. */\n popupTitle?: React.ReactNode;\n\n display?: 'comma' | 'chip';\n maxSelectedLabels?: number;\n\n itemTemplate?: (item: T) => React.ReactElement;\n selectedItemTemplate?: (item: T) => React.ReactElement;\n style?: import('react-native').ViewStyle;\n\n /* ─── Chip slots ─────────────────────────────────────────────────────\n Same three props, same meaning, as `Core/AutoComplete` — so the two\n chip-bearing controls take their chip styling from one place in the\n theme instead of each hardcoding its own. */\n /** A single chip (the `View`): background, radius, padding, margin. */\n tokenStyle?: import('react-native').ViewStyle;\n /** The chip's label `Text`: color, fontSize, fontWeight, fontFamily. */\n tokenLabelStyle?: import('react-native').TextStyle;\n /** The chip's remove (✕) `Text`: color, fontSize. */\n tokenIconStyle?: import('react-native').TextStyle;\n}\n\nconst t = resolveThemeTokenForNative;\n\nconst TEXT_STYLE_KEYS = new Set([\n 'fontSize', 'fontFamily', 'fontWeight', 'fontStyle',\n 'color', 'letterSpacing', 'lineHeight', 'textAlign',\n 'textDecorationLine', 'textTransform',\n]);\n\nfunction splitStyle(combined?: any): { viewStyle?: any; textStyle?: any } {\n if (!combined) return {};\n const view: Record<string, any> = {};\n const text: Record<string, any> = {};\n for (const [k, v] of Object.entries(combined)) {\n if (TEXT_STYLE_KEYS.has(k)) text[k] = v;\n else view[k] = v;\n }\n return {\n viewStyle: Object.keys(view).length ? view : undefined,\n textStyle: Object.keys(text).length ? text : undefined,\n };\n}\n\nexport default function MultiSelect<T>(props: MultiSelectProps<T>) {\n const {\n value,\n onChange,\n options = [],\n optionLabel,\n optionValue,\n optionGroupLabel,\n optionGroupChildren,\n isModal = false,\n placeholder = 'Select',\n disabled,\n loading,\n filter,\n // ⚠️ English literals of last resort, not translations — the wrapper always\n // passes the reader's own sentence. These only apply when this component is\n // used directly from `apps/mobile`, so a panel can never render blank.\n emptyFilterMessage = 'No results found',\n filterPlaceholder = 'Search...',\n isRTL = false,\n fontFamily,\n popup = false,\n renderPopup,\n popupTitle,\n display = 'chip',\n maxSelectedLabels = 10,\n itemTemplate,\n selectedItemTemplate,\n style: externalStyle,\n tokenStyle,\n tokenLabelStyle,\n tokenIconStyle,\n } = props;\n\n /* Physical edges to write so content lands where the reader starts. */\n const edge = readerEdge(isRTL);\n\n const triggerRef = useRef<View>(null);\n const [open, setOpen] = useState(false);\n const [query, setQuery] = useState('');\n const [triggerLayout, setTriggerLayout] = useState<{\n x: number; y: number; width: number; height: number;\n } | null>(null);\n\n const close = () => setOpen(false);\n\n const toggleOpen = () => {\n if (disabled) return;\n if (open) {\n setOpen(false);\n return;\n }\n triggerRef.current?.measureInWindow((x, y, width, height) => {\n setTriggerLayout({ x, y, width, height });\n setOpen(true);\n });\n };\n\n const { viewStyle: externalViewStyle, textStyle: externalTextStyle } = splitStyle(externalStyle);\n\n const colors = {\n border: t('border') || t('gray.300'),\n borderFocus: t('primary'),\n bg: t('surface-card') || '#fff',\n disabledBg: t('surface-ground') || t('gray.100'),\n placeholder: t('text-secondary') || t('gray.400'),\n text: t('text') || t('gray.800'),\n textSecondary: t('text-secondary') || t('gray.500'),\n chipBg: t('primary.100') || t('indigo.100'),\n chipText: t('primary.700') || t('indigo.700'),\n chipRemove: t('primary.500') || t('indigo.500'),\n chipMoreBg: t('surface-ground') || t('gray.100'),\n headerBorder: t('border') || t('gray.200'),\n groupBg: t('surface-ground') || t('gray.50'),\n groupBorder: t('border') || t('gray.200'),\n itemBorder: t('surface-ground') || t('gray.100'),\n itemSelectedBg: t('highlight-bg') || t('indigo.50'),\n itemSelectedText: t('primary.700') || t('indigo.700'),\n checkboxBorder: t('border') || t('gray.300'),\n checkboxChecked: t('primary'),\n invalidBorder: t('danger') || t('red.500'),\n searchBg: t('surface-ground') || t('gray.50'),\n };\n\n const getValue = (item: T, key?: keyof T | string) =>\n key ? (item as any)[key] : item;\n\n const getLabel = (item: T, key?: keyof T | string) =>\n String(key ? (item as any)[key] : item);\n\n const selectedValues = Array.isArray(value) ? value : [];\n\n // Build display items: flat list with optional group headers\n const displayItems: { type: 'group' | 'item'; data: any }[] = useMemo(() => {\n if (optionGroupLabel && optionGroupChildren) {\n const result: { type: 'group' | 'item'; data: any }[] = [];\n options.forEach((group: any) => {\n result.push({ type: 'group', data: group });\n const children = group[optionGroupChildren] ?? [];\n children.forEach((child: any) => result.push({ type: 'item', data: child }));\n });\n return result;\n }\n return options.map((item) => ({ type: 'item' as const, data: item }));\n }, [options, optionGroupLabel, optionGroupChildren]);\n\n const filtered = useMemo(() => {\n if (!filter || !query) return displayItems;\n return displayItems.filter((entry) => {\n if (entry.type === 'group') return true; // keep group headers\n return getLabel(entry.data, optionLabel)\n .toLowerCase()\n .includes(query.toLowerCase());\n });\n }, [query, displayItems]);\n\n const isSelected = (item: T) =>\n selectedValues.some(\n (v) => getValue(v, optionValue) === getValue(item, optionValue),\n );\n\n const toggleItem = (item: T) => {\n const exists = isSelected(item);\n const next = exists\n ? selectedValues.filter(\n (v) => getValue(v, optionValue) !== getValue(item, optionValue),\n )\n : [...selectedValues, item];\n\n const syntheticEvent = {\n target: { value: next },\n } as React.ChangeEvent<any>;\n\n onChange?.(syntheticEvent, next);\n };\n\n const removeItem = (item: T) => {\n const next = selectedValues.filter(\n (v) => getValue(v, optionValue) !== getValue(item, optionValue),\n );\n const syntheticEvent = {\n target: { value: next },\n } as React.ChangeEvent<any>;\n onChange?.(syntheticEvent, next);\n };\n\n const renderCheckbox = (checked: boolean) => (\n <View\n style={[\n styles.checkbox,\n { borderColor: colors.checkboxBorder, backgroundColor: colors.bg },\n checked && { backgroundColor: colors.checkboxChecked, borderColor: colors.checkboxChecked },\n ]}\n >\n {checked && <Text style={styles.checkmark}>✓</Text>}\n </View>\n );\n\n const renderListContent = () => {\n if (loading) {\n return (\n <View style={styles.loadingContainer}>\n <ActivityIndicator size=\"small\" color={colors.checkboxChecked} />\n <Text style={[styles.loadingText, { color: colors.placeholder }]}>Loading...</Text>\n </View>\n );\n }\n\n const itemsOnly = filtered.filter((e) => e.type === 'item');\n if (itemsOnly.length === 0) {\n return (\n <View style={styles.emptyContainer}>\n <Text style={[styles.emptyText, { color: colors.placeholder }]}>{emptyFilterMessage}</Text>\n </View>\n );\n }\n\n return (\n <FlatList\n data={filtered}\n keyExtractor={(entry, i) =>\n entry.type === 'group' ? `group-${i}` : `item-${i}`\n }\n keyboardShouldPersistTaps=\"handled\"\n renderItem={({ item: entry }) => {\n if (entry.type === 'group') {\n return (\n <View style={[styles.groupHeader, { backgroundColor: colors.groupBg, borderBottomColor: colors.groupBorder }]}>\n <Text style={[styles.groupHeaderText, { color: colors.textSecondary }]}>\n {getLabel(entry.data, optionGroupLabel)}\n </Text>\n </View>\n );\n }\n const checked = isSelected(entry.data);\n return (\n <TouchableOpacity\n onPress={() => toggleItem(entry.data)}\n style={[\n styles.item,\n { borderBottomColor: colors.itemBorder },\n checked && { backgroundColor: colors.itemSelectedBg },\n ]}\n activeOpacity={0.7}\n >\n {renderCheckbox(checked)}\n {itemTemplate ? (\n itemTemplate(entry.data)\n ) : (\n <Text style={[\n styles.itemText,\n { color: colors.text, fontFamily },\n checked && { color: colors.itemSelectedText, fontWeight: '500' },\n /* ⛔ The physical edge is COMPUTED — React Native mirrors\n `textAlign` when the process is RTL, so naming the RTL edge\n for an RTL reader lands on the LTR one. See `readerEdge`. */\n {\n textAlign: edge.textAlign,\n writingDirection: isRTL ? ('rtl' as const) : ('ltr' as const),\n },\n ]}>\n {getLabel(entry.data, optionLabel)}\n </Text>\n )}\n </TouchableOpacity>\n );\n }}\n />\n );\n };\n\n const renderSelected = () => {\n if (!selectedValues.length) {\n return <Text style={[styles.placeholder, { color: colors.placeholder }, externalTextStyle]}>{placeholder}</Text>;\n }\n\n if (display === 'chip') {\n const visibleItems = selectedValues.slice(0, maxSelectedLabels);\n const remaining = selectedValues.length - maxSelectedLabels;\n return (\n <View style={styles.chips}>\n {visibleItems.map((item, i) =>\n selectedItemTemplate ? (\n <React.Fragment key={i}>{selectedItemTemplate(item)}</React.Fragment>\n ) : (\n <View key={i} style={[styles.chip, { backgroundColor: colors.chipBg }, tokenStyle]}>\n <Text style={[styles.chipText, { color: colors.chipText }, tokenLabelStyle]}>{getLabel(item, optionLabel)}</Text>\n <TouchableOpacity\n onPress={() => removeItem(item)}\n hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}\n >\n <Text style={[styles.chipRemove, { color: colors.chipRemove }, tokenIconStyle]}>✕</Text>\n </TouchableOpacity>\n </View>\n ),\n )}\n {remaining > 0 && (\n <View style={[styles.chipMore, { backgroundColor: colors.chipMoreBg }]}>\n <Text style={[styles.chipMoreText, { color: colors.textSecondary }]}>+{remaining}</Text>\n </View>\n )}\n </View>\n );\n }\n\n return (\n <Text numberOfLines={1} style={[styles.commaText, { color: colors.text }, externalTextStyle]}>\n {selectedValues\n .slice(0, maxSelectedLabels)\n .map((v) => getLabel(v, optionLabel))\n .join(', ')}\n </Text>\n );\n };\n\n const usePopup = popup && typeof renderPopup === 'function';\n\n const panelContent = (\n <View style={{ flex: 1 }}>\n {/* Header */}\n <View style={[styles.panelHeader, { borderBottomColor: colors.headerBorder }]}>\n <Text style={[styles.panelHeaderText, { color: colors.textSecondary }]}>\n {selectedValues.length} item{selectedValues.length !== 1 ? 's' : ''} selected\n </Text>\n {/* In popup mode the sheet already draws a close control, and two ✕\n in one header is a question the reader should not have to answer.\n The count stays — it is information, not chrome. */}\n {!usePopup && (\n <TouchableOpacity onPress={() => setOpen(false)}>\n <Text style={[styles.panelCloseText, { color: colors.placeholder }]}>✕</Text>\n </TouchableOpacity>\n )}\n </View>\n\n {/* Search */}\n {filter && (\n <View style={[styles.searchContainer, { borderColor: colors.border, backgroundColor: colors.searchBg }]}>\n <Text style={styles.searchIcon}>🔍</Text>\n <TextInput\n value={query}\n onChangeText={setQuery}\n placeholder={filterPlaceholder}\n style={[\n styles.searchInput,\n { color: colors.text, fontFamily },\n // The filter follows the reader's language, not the platform's\n // layout direction — same rule as the Dropdown's panel.\n isRTL ? styles.searchRtl : styles.searchLtr,\n ]}\n placeholderTextColor={colors.placeholder}\n />\n </View>\n )}\n\n {/* List */}\n {renderListContent()}\n </View>\n );\n\n return (\n <View style={{ width: '100%' }}>\n {/* Input trigger */}\n <TouchableOpacity\n ref={triggerRef as any}\n activeOpacity={0.8}\n disabled={disabled}\n onPress={toggleOpen}\n style={[\n styles.input,\n { borderColor: colors.border, backgroundColor: colors.bg },\n disabled && { backgroundColor: colors.disabledBg, opacity: 0.6 },\n open && { borderColor: colors.borderFocus },\n externalViewStyle,\n ]}\n >\n <View style={styles.inputContent}>\n {renderSelected()}\n </View>\n <Text style={[styles.dropdownArrow, { color: colors.placeholder }]}>{open ? '▲' : '▼'}</Text>\n </TouchableOpacity>\n\n {/* Popup — the container comes from the wrapper (this library's own\n Dialog in sheet position); `panelContent` is unchanged. */}\n {usePopup && renderPopup!({\n visible: open,\n onClose: () => setOpen(false),\n title: popupTitle,\n children: panelContent,\n })}\n\n {/* Modal mode */}\n {!usePopup && isModal && (\n <Modal visible={open} transparent animationType=\"fade\">\n <Pressable style={styles.overlay} onPress={() => setOpen(false)}>\n <View\n style={[styles.modalPanel, { backgroundColor: colors.bg }]}\n onStartShouldSetResponder={() => true}\n >\n {panelContent}\n </View>\n </Pressable>\n </Modal>\n )}\n\n {/* Inline mode — Modal-portal popover anchored to trigger */}\n {!usePopup && !isModal && (\n <Modal\n visible={open && !!triggerLayout}\n transparent\n animationType=\"none\"\n onRequestClose={close}\n >\n <Pressable style={styles.popoverOverlay} onPress={close}>\n {triggerLayout && (() => {\n const screen = Dimensions.get('window');\n const PANEL_MAX_HEIGHT = 320;\n const GAP = 4;\n const spaceBelow = screen.height - (triggerLayout.y + triggerLayout.height);\n const spaceAbove = triggerLayout.y;\n const openUp = spaceBelow < PANEL_MAX_HEIGHT && spaceAbove > spaceBelow;\n const maxHeight = Math.min(\n PANEL_MAX_HEIGHT,\n openUp ? spaceAbove - GAP : spaceBelow - GAP,\n );\n const top = openUp\n ? triggerLayout.y - maxHeight - GAP\n : triggerLayout.y + triggerLayout.height + GAP;\n\n return (\n <View\n style={[\n styles.popoverPanel,\n {\n top,\n left: triggerLayout.x,\n width: triggerLayout.width,\n maxHeight,\n borderColor: colors.border,\n backgroundColor: colors.bg,\n },\n ]}\n onStartShouldSetResponder={() => true}\n >\n {panelContent}\n </View>\n );\n })()}\n </Pressable>\n </Modal>\n )}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n // Input\n input: {\n minHeight: 42,\n borderWidth: 1,\n borderRadius: 6,\n paddingHorizontal: 10,\n paddingVertical: 6,\n flexDirection: 'row',\n alignItems: 'center',\n },\n inputContent: {\n flex: 1,\n },\n dropdownArrow: {\n fontSize: 10,\n marginLeft: 8,\n },\n\n // Placeholder\n placeholder: { fontSize: 14 },\n commaText: { fontSize: 14 },\n\n // Chips display\n chips: {\n flexDirection: 'row',\n flexWrap: 'wrap',\n gap: 4,\n },\n chip: {\n flexDirection: 'row',\n alignItems: 'center',\n paddingLeft: 10,\n paddingRight: 6,\n paddingVertical: 3,\n borderRadius: 16,\n gap: 4,\n },\n chipText: {\n fontSize: 13,\n },\n chipRemove: {\n fontSize: 12,\n paddingHorizontal: 2,\n },\n chipMore: {\n paddingHorizontal: 10,\n paddingVertical: 3,\n borderRadius: 16,\n },\n chipMoreText: {\n fontSize: 13,\n },\n\n // Panel header\n panelHeader: {\n flexDirection: 'row',\n justifyContent: 'space-between',\n alignItems: 'center',\n paddingHorizontal: 12,\n paddingVertical: 10,\n borderBottomWidth: 1,\n },\n panelHeaderText: {\n fontSize: 13,\n },\n panelCloseText: {\n fontSize: 18,\n fontWeight: '600',\n },\n\n // Search\n searchContainer: {\n flexDirection: 'row',\n alignItems: 'center',\n margin: 8,\n paddingHorizontal: 10,\n height: 36,\n borderWidth: 1,\n borderRadius: 6,\n },\n searchIcon: {\n fontSize: 14,\n marginRight: 6,\n },\n searchInput: {\n flex: 1,\n fontSize: 14,\n paddingVertical: 0,\n },\n searchLtr: {\n textAlign: 'left' as const,\n writingDirection: 'ltr' as const,\n },\n searchRtl: {\n textAlign: 'right' as const,\n writingDirection: 'rtl' as const,\n },\n\n // Group header\n groupHeader: {\n paddingHorizontal: 12,\n paddingVertical: 8,\n borderBottomWidth: StyleSheet.hairlineWidth,\n },\n groupHeaderText: {\n fontSize: 12,\n fontWeight: '700',\n textTransform: 'uppercase',\n letterSpacing: 0.5,\n },\n\n // Items\n item: {\n flexDirection: 'row',\n alignItems: 'center',\n paddingVertical: 10,\n paddingHorizontal: 12,\n gap: 10,\n borderBottomWidth: StyleSheet.hairlineWidth,\n },\n itemText: {\n fontSize: 14,\n /* The label takes the row's spare width so `textAlign` has somewhere to\n push to — a shrink-wrapped Text ignores it. Already here, which is why\n MultiSelect needs no extra entry; Dropdown's label style carries no flex\n and got `optionLabelFill`. */\n flex: 1,\n },\n\n // Checkbox\n checkbox: {\n width: 20,\n height: 20,\n borderWidth: 2,\n borderRadius: 4,\n justifyContent: 'center',\n alignItems: 'center',\n },\n checkmark: {\n color: '#fff',\n fontSize: 13,\n fontWeight: '700',\n },\n\n // Loading / Empty\n loadingContainer: {\n padding: 24,\n alignItems: 'center',\n gap: 8,\n },\n loadingText: {\n fontSize: 13,\n },\n emptyContainer: {\n padding: 24,\n alignItems: 'center',\n },\n emptyText: {\n fontSize: 14,\n },\n\n // Modal\n overlay: {\n flex: 1,\n backgroundColor: 'rgba(0,0,0,0.4)',\n justifyContent: 'center',\n },\n modalPanel: {\n margin: 24,\n maxHeight: '70%',\n borderRadius: 12,\n overflow: 'hidden',\n },\n\n // Popover (Modal-portal)\n popoverOverlay: {\n flex: 1,\n backgroundColor: 'transparent',\n },\n popoverPanel: {\n position: 'absolute',\n borderWidth: 1,\n borderRadius: 6,\n overflow: 'hidden',\n elevation: 8,\n shadowColor: '#000',\n shadowOffset: { width: 0, height: 2 },\n shadowOpacity: 0.15,\n shadowRadius: 8,\n },\n});\n"],"names":[],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import React from 'react';
3
3
  import { createAccordion } from '@gluestack-ui/core/accordion/creator';
4
- import { View, Platform, Text, Pressable } from 'react-native';
5
- import { tva, withStyleContext, useStyleContext } from '@gluestack-ui/utils/nativewind-utils';
4
+ import { View, Text, Pressable, Platform } from 'react-native';
5
+ import { useStyleContext, tva, withStyleContext } from '@gluestack-ui/utils/nativewind-utils';
6
6
  import { H3 } from '@expo/html-elements';
7
7
  import { cssInterop } from 'nativewind';
8
8
  import { UIIcon, PrimitiveIcon } from '@gluestack-ui/core/icon/creator';
@@ -0,0 +1,63 @@
1
+ 'use strict';
2
+
3
+ var constants = require('./constants.cjs');
4
+ var geometry = require('./geometry.cjs');
5
+
6
+ const isLossless = (format) => format === "png";
7
+ const bisect = (low, high) => (low + high) / 2;
8
+ function* encodePlan(natural, max, format) {
9
+ let size = geometry.fitWithin(natural, { width: max.width, height: max.height });
10
+ for (let pass = 0; pass <= constants.MAX_DOWNSCALE_PASSES; pass++) {
11
+ if (isLossless(format)) {
12
+ const outcome = yield { size, quality: 1 };
13
+ if (outcome.bytes <= max.bytes) return;
14
+ } else {
15
+ let low = constants.QUALITY_MIN;
16
+ let high = constants.QUALITY_MAX;
17
+ const best = yield { size, quality: high };
18
+ if (best.bytes <= max.bytes) return;
19
+ let fitted = false;
20
+ for (let step = 0; step < constants.QUALITY_STEPS; step++) {
21
+ const quality = bisect(low, high);
22
+ const outcome = yield { size, quality };
23
+ if (outcome.bytes <= max.bytes) {
24
+ fitted = true;
25
+ low = quality;
26
+ } else {
27
+ high = quality;
28
+ }
29
+ }
30
+ if (fitted) return;
31
+ }
32
+ const next = Math.max(size.width, size.height) * constants.DOWNSCALE_FACTOR;
33
+ size = geometry.fitWithin(size, { width: next, height: next });
34
+ if (size.width < 1 || size.height < 1) return;
35
+ }
36
+ }
37
+ async function runEncodePlan(natural, max, format, encode) {
38
+ const plan = encodePlan(natural, max, format);
39
+ let best;
40
+ let smallest;
41
+ let outcome;
42
+ for (; ; ) {
43
+ const step = outcome === void 0 ? plan.next() : plan.next(outcome);
44
+ if (step.done === true) break;
45
+ const attempt = step.value;
46
+ const { bytes, value } = await encode(attempt);
47
+ const record = { value, bytes, size: attempt.size, quality: attempt.quality };
48
+ if (bytes <= max.bytes) best = record;
49
+ if (!smallest || bytes < smallest.bytes) smallest = record;
50
+ outcome = { bytes };
51
+ }
52
+ const chosen = best ?? smallest;
53
+ if (!chosen) {
54
+ throw new Error("[ImageCropper] the encode plan produced no attempts at all");
55
+ }
56
+ return { ...chosen, withinBudget: chosen.bytes <= max.bytes };
57
+ }
58
+
59
+ exports.bisect = bisect;
60
+ exports.encodePlan = encodePlan;
61
+ exports.isLossless = isLossless;
62
+ exports.runEncodePlan = runEncodePlan;
63
+ //# sourceMappingURL=compression.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compression.cjs","sources":["../../../../src/components/ImageCropper/compression.ts"],"sourcesContent":["import type {CropOutputFormat, ICropSize} from \"@lmb-it/kitsconcerto-types\";\nimport {\n DOWNSCALE_FACTOR,\n MAX_DOWNSCALE_PASSES,\n QUALITY_MAX,\n QUALITY_MIN,\n QUALITY_STEPS,\n} from \"./constants\";\nimport {fitWithin} from \"./geometry\";\n\n/**\n * How hard to squeeze, and when to stop squeezing and shrink instead.\n *\n * ⛔ THE POLICY IS HERE AND THE ENCODER IS NOT, ON PURPOSE. Web encodes with\n * `canvas.toBlob`, native with expo-image-manipulator; neither can run in the\n * other's runtime, and neither can be unit-tested without one. What CAN be\n * tested — and what decides whether a 4 MB photo comes back at 480 KB or at\n * 2 MB — is the search: how the quality band is walked, when the band is\n * exhausted, and how far to scale down after that. Both platforms drive the\n * same plan from here and only supply `encode`.\n */\n\n/** One attempt: what the caller's encoder is asked to produce. */\nexport interface IEncodeAttempt {\n size: ICropSize;\n /** 0–1. Ignored by lossless formats. */\n quality: number;\n}\n\n/** What an encoder reports back. */\nexport interface IEncodeOutcome {\n bytes: number;\n}\n\n/** Lossless formats cannot trade quality for bytes — only dimensions. */\nexport const isLossless = (format: CropOutputFormat): boolean => format === \"png\";\n\n/**\n * The quality to try on step `step` of a binary search that has so far bracketed\n * the answer to [`low`, `high`].\n *\n * ⚠️ A PLAIN MIDPOINT, NOT A GUESS FROM THE BYTE RATIO. Encoded size is not\n * linear in quality — it is flat near the top of the band and falls off a cliff\n * near the bottom — so a ratio-based jump overshoots into the blocky region on\n * the first move. Bisection is slower by a few encodes and lands where it says.\n */\nexport const bisect = (low: number, high: number): number => (low + high) / 2;\n\n/**\n * The plan for one encode pass, as a sequence of (size, quality) attempts.\n *\n * Walks the quality band from {@link QUALITY_MAX} down by bisection for\n * {@link QUALITY_STEPS} steps, keeping the best attempt that fits. When the\n * whole band is over the ceiling it scales the image down by\n * {@link DOWNSCALE_FACTOR} and walks it again, up to\n * {@link MAX_DOWNSCALE_PASSES} times.\n *\n * ⚠️ IT IS A GENERATOR, NOT A LOOP THAT CALLS YOU BACK. The caller owns the\n * encoder, the async, and the cancellation; this owns only the order to try\n * things in. That is what lets the same policy be driven by `canvas.toBlob` on\n * one platform and a native module on the other, and be tested with neither.\n */\nexport function* encodePlan(\n natural: ICropSize,\n max: {width?: number; height?: number; bytes: number},\n format: CropOutputFormat,\n): Generator<IEncodeAttempt, void, IEncodeOutcome> {\n let size = fitWithin(natural, {width: max.width, height: max.height});\n\n for (let pass = 0; pass <= MAX_DOWNSCALE_PASSES; pass++) {\n if (isLossless(format)) {\n // ⛔ NO QUALITY KNOB TO TURN. A single attempt per size; if it is\n // over, the only lever left is the next downscale pass.\n const outcome = yield {size, quality: 1};\n if (outcome.bytes <= max.bytes) return;\n } else {\n let low = QUALITY_MIN;\n let high = QUALITY_MAX;\n\n // The top of the band first: most images fit at best quality, and\n // when they do nothing else should be tried.\n const best = yield {size, quality: high};\n if (best.bytes <= max.bytes) return;\n\n let fitted = false;\n for (let step = 0; step < QUALITY_STEPS; step++) {\n const quality = bisect(low, high);\n const outcome = yield {size, quality};\n if (outcome.bytes <= max.bytes) {\n fitted = true;\n low = quality; // it fits — try to buy quality back\n } else {\n high = quality;\n }\n }\n if (fitted) return;\n }\n\n // ⚠️ THE LONG EDGE SHRINKS, AND THE RATIO IS KEPT BY `fitWithin`, so a\n // downscale pass can never change the crop's shape. A crop that came\n // back a different aspect than the window showed would be the worst\n // kind of silent failure here.\n const next = Math.max(size.width, size.height) * DOWNSCALE_FACTOR;\n size = fitWithin(size, {width: next, height: next});\n if (size.width < 1 || size.height < 1) return;\n }\n}\n\n/**\n * Drives {@link encodePlan} with a real encoder and returns the best result\n * that met the ceiling — or the smallest one produced, if none did.\n *\n * ⚠️ IT RETURNS THE LAST ATTEMPT EVEN WHEN NOTHING FIT, with `withinBudget`\n * false. Handing back nothing would mean a user who cropped a huge PNG gets a\n * spinner and no file; handing back the smallest we managed, clearly flagged,\n * lets the caller decide between warning and refusing.\n */\nexport async function runEncodePlan<T>(\n natural: ICropSize,\n max: {width?: number; height?: number; bytes: number},\n format: CropOutputFormat,\n encode: (attempt: IEncodeAttempt) => Promise<{bytes: number; value: T}>,\n): Promise<{value: T; bytes: number; size: ICropSize; quality: number; withinBudget: boolean}> {\n const plan = encodePlan(natural, max, format);\n let best: {value: T; bytes: number; size: ICropSize; quality: number} | undefined;\n let smallest: {value: T; bytes: number; size: ICropSize; quality: number} | undefined;\n\n // ⚠️ `step` IS A `const` DECLARED INSIDE THE LOOP, AND THAT IS NOT STYLE.\n // Hoisted to a reassigned `let`, TypeScript stops narrowing it across the\n // back edge and `step.value` comes out as `IEncodeAttempt | void` — which\n // then needs a cast, and a cast here would be asserting exactly the thing\n // the `done` check is supposed to prove.\n let outcome: IEncodeOutcome | undefined;\n for (;;) {\n const step = outcome === undefined ? plan.next() : plan.next(outcome);\n if (step.done === true) break;\n\n const attempt = step.value;\n const {bytes, value} = await encode(attempt);\n const record = {value, bytes, size: attempt.size, quality: attempt.quality};\n\n if (bytes <= max.bytes) best = record;\n if (!smallest || bytes < smallest.bytes) smallest = record;\n\n outcome = {bytes};\n }\n\n const chosen = best ?? smallest;\n if (!chosen) {\n throw new Error(\"[ImageCropper] the encode plan produced no attempts at all\");\n }\n return {...chosen, withinBudget: chosen.bytes <= max.bytes};\n}\n"],"names":["fitWithin","MAX_DOWNSCALE_PASSES","QUALITY_MIN","QUALITY_MAX","QUALITY_STEPS","DOWNSCALE_FACTOR"],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ import { MAX_DOWNSCALE_PASSES, QUALITY_MAX, QUALITY_STEPS, DOWNSCALE_FACTOR, QUALITY_MIN } from './constants.js';
2
+ import { fitWithin } from './geometry.js';
3
+
4
+ const isLossless = (format) => format === "png";
5
+ const bisect = (low, high) => (low + high) / 2;
6
+ function* encodePlan(natural, max, format) {
7
+ let size = fitWithin(natural, { width: max.width, height: max.height });
8
+ for (let pass = 0; pass <= MAX_DOWNSCALE_PASSES; pass++) {
9
+ if (isLossless(format)) {
10
+ const outcome = yield { size, quality: 1 };
11
+ if (outcome.bytes <= max.bytes) return;
12
+ } else {
13
+ let low = QUALITY_MIN;
14
+ let high = QUALITY_MAX;
15
+ const best = yield { size, quality: high };
16
+ if (best.bytes <= max.bytes) return;
17
+ let fitted = false;
18
+ for (let step = 0; step < QUALITY_STEPS; step++) {
19
+ const quality = bisect(low, high);
20
+ const outcome = yield { size, quality };
21
+ if (outcome.bytes <= max.bytes) {
22
+ fitted = true;
23
+ low = quality;
24
+ } else {
25
+ high = quality;
26
+ }
27
+ }
28
+ if (fitted) return;
29
+ }
30
+ const next = Math.max(size.width, size.height) * DOWNSCALE_FACTOR;
31
+ size = fitWithin(size, { width: next, height: next });
32
+ if (size.width < 1 || size.height < 1) return;
33
+ }
34
+ }
35
+ async function runEncodePlan(natural, max, format, encode) {
36
+ const plan = encodePlan(natural, max, format);
37
+ let best;
38
+ let smallest;
39
+ let outcome;
40
+ for (; ; ) {
41
+ const step = outcome === void 0 ? plan.next() : plan.next(outcome);
42
+ if (step.done === true) break;
43
+ const attempt = step.value;
44
+ const { bytes, value } = await encode(attempt);
45
+ const record = { value, bytes, size: attempt.size, quality: attempt.quality };
46
+ if (bytes <= max.bytes) best = record;
47
+ if (!smallest || bytes < smallest.bytes) smallest = record;
48
+ outcome = { bytes };
49
+ }
50
+ const chosen = best ?? smallest;
51
+ if (!chosen) {
52
+ throw new Error("[ImageCropper] the encode plan produced no attempts at all");
53
+ }
54
+ return { ...chosen, withinBudget: chosen.bytes <= max.bytes };
55
+ }
56
+
57
+ export { bisect, encodePlan, isLossless, runEncodePlan };
58
+ //# sourceMappingURL=compression.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compression.js","sources":["../../../../src/components/ImageCropper/compression.ts"],"sourcesContent":["import type {CropOutputFormat, ICropSize} from \"@lmb-it/kitsconcerto-types\";\nimport {\n DOWNSCALE_FACTOR,\n MAX_DOWNSCALE_PASSES,\n QUALITY_MAX,\n QUALITY_MIN,\n QUALITY_STEPS,\n} from \"./constants\";\nimport {fitWithin} from \"./geometry\";\n\n/**\n * How hard to squeeze, and when to stop squeezing and shrink instead.\n *\n * ⛔ THE POLICY IS HERE AND THE ENCODER IS NOT, ON PURPOSE. Web encodes with\n * `canvas.toBlob`, native with expo-image-manipulator; neither can run in the\n * other's runtime, and neither can be unit-tested without one. What CAN be\n * tested — and what decides whether a 4 MB photo comes back at 480 KB or at\n * 2 MB — is the search: how the quality band is walked, when the band is\n * exhausted, and how far to scale down after that. Both platforms drive the\n * same plan from here and only supply `encode`.\n */\n\n/** One attempt: what the caller's encoder is asked to produce. */\nexport interface IEncodeAttempt {\n size: ICropSize;\n /** 0–1. Ignored by lossless formats. */\n quality: number;\n}\n\n/** What an encoder reports back. */\nexport interface IEncodeOutcome {\n bytes: number;\n}\n\n/** Lossless formats cannot trade quality for bytes — only dimensions. */\nexport const isLossless = (format: CropOutputFormat): boolean => format === \"png\";\n\n/**\n * The quality to try on step `step` of a binary search that has so far bracketed\n * the answer to [`low`, `high`].\n *\n * ⚠️ A PLAIN MIDPOINT, NOT A GUESS FROM THE BYTE RATIO. Encoded size is not\n * linear in quality — it is flat near the top of the band and falls off a cliff\n * near the bottom — so a ratio-based jump overshoots into the blocky region on\n * the first move. Bisection is slower by a few encodes and lands where it says.\n */\nexport const bisect = (low: number, high: number): number => (low + high) / 2;\n\n/**\n * The plan for one encode pass, as a sequence of (size, quality) attempts.\n *\n * Walks the quality band from {@link QUALITY_MAX} down by bisection for\n * {@link QUALITY_STEPS} steps, keeping the best attempt that fits. When the\n * whole band is over the ceiling it scales the image down by\n * {@link DOWNSCALE_FACTOR} and walks it again, up to\n * {@link MAX_DOWNSCALE_PASSES} times.\n *\n * ⚠️ IT IS A GENERATOR, NOT A LOOP THAT CALLS YOU BACK. The caller owns the\n * encoder, the async, and the cancellation; this owns only the order to try\n * things in. That is what lets the same policy be driven by `canvas.toBlob` on\n * one platform and a native module on the other, and be tested with neither.\n */\nexport function* encodePlan(\n natural: ICropSize,\n max: {width?: number; height?: number; bytes: number},\n format: CropOutputFormat,\n): Generator<IEncodeAttempt, void, IEncodeOutcome> {\n let size = fitWithin(natural, {width: max.width, height: max.height});\n\n for (let pass = 0; pass <= MAX_DOWNSCALE_PASSES; pass++) {\n if (isLossless(format)) {\n // ⛔ NO QUALITY KNOB TO TURN. A single attempt per size; if it is\n // over, the only lever left is the next downscale pass.\n const outcome = yield {size, quality: 1};\n if (outcome.bytes <= max.bytes) return;\n } else {\n let low = QUALITY_MIN;\n let high = QUALITY_MAX;\n\n // The top of the band first: most images fit at best quality, and\n // when they do nothing else should be tried.\n const best = yield {size, quality: high};\n if (best.bytes <= max.bytes) return;\n\n let fitted = false;\n for (let step = 0; step < QUALITY_STEPS; step++) {\n const quality = bisect(low, high);\n const outcome = yield {size, quality};\n if (outcome.bytes <= max.bytes) {\n fitted = true;\n low = quality; // it fits — try to buy quality back\n } else {\n high = quality;\n }\n }\n if (fitted) return;\n }\n\n // ⚠️ THE LONG EDGE SHRINKS, AND THE RATIO IS KEPT BY `fitWithin`, so a\n // downscale pass can never change the crop's shape. A crop that came\n // back a different aspect than the window showed would be the worst\n // kind of silent failure here.\n const next = Math.max(size.width, size.height) * DOWNSCALE_FACTOR;\n size = fitWithin(size, {width: next, height: next});\n if (size.width < 1 || size.height < 1) return;\n }\n}\n\n/**\n * Drives {@link encodePlan} with a real encoder and returns the best result\n * that met the ceiling — or the smallest one produced, if none did.\n *\n * ⚠️ IT RETURNS THE LAST ATTEMPT EVEN WHEN NOTHING FIT, with `withinBudget`\n * false. Handing back nothing would mean a user who cropped a huge PNG gets a\n * spinner and no file; handing back the smallest we managed, clearly flagged,\n * lets the caller decide between warning and refusing.\n */\nexport async function runEncodePlan<T>(\n natural: ICropSize,\n max: {width?: number; height?: number; bytes: number},\n format: CropOutputFormat,\n encode: (attempt: IEncodeAttempt) => Promise<{bytes: number; value: T}>,\n): Promise<{value: T; bytes: number; size: ICropSize; quality: number; withinBudget: boolean}> {\n const plan = encodePlan(natural, max, format);\n let best: {value: T; bytes: number; size: ICropSize; quality: number} | undefined;\n let smallest: {value: T; bytes: number; size: ICropSize; quality: number} | undefined;\n\n // ⚠️ `step` IS A `const` DECLARED INSIDE THE LOOP, AND THAT IS NOT STYLE.\n // Hoisted to a reassigned `let`, TypeScript stops narrowing it across the\n // back edge and `step.value` comes out as `IEncodeAttempt | void` — which\n // then needs a cast, and a cast here would be asserting exactly the thing\n // the `done` check is supposed to prove.\n let outcome: IEncodeOutcome | undefined;\n for (;;) {\n const step = outcome === undefined ? plan.next() : plan.next(outcome);\n if (step.done === true) break;\n\n const attempt = step.value;\n const {bytes, value} = await encode(attempt);\n const record = {value, bytes, size: attempt.size, quality: attempt.quality};\n\n if (bytes <= max.bytes) best = record;\n if (!smallest || bytes < smallest.bytes) smallest = record;\n\n outcome = {bytes};\n }\n\n const chosen = best ?? smallest;\n if (!chosen) {\n throw new Error(\"[ImageCropper] the encode plan produced no attempts at all\");\n }\n return {...chosen, withinBudget: chosen.bytes <= max.bytes};\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ 'use strict';
2
+
3
+ const DEFAULT_ASPECT = [1, 1];
4
+ const DEFAULT_MAX_BYTES = 500 * 1024;
5
+ const DEFAULT_FORMAT = "jpeg";
6
+ const DEFAULT_MAX_ZOOM = 8;
7
+ const MIN_CROP_SIZE = { width: 48, height: 48 };
8
+ const QUALITY_MAX = 0.92;
9
+ const QUALITY_MIN = 0.4;
10
+ const QUALITY_STEPS = 7;
11
+ const DOWNSCALE_FACTOR = 0.8;
12
+ const MAX_DOWNSCALE_PASSES = 6;
13
+ const DEFAULT_LABELS = {
14
+ cancel: "Cancel",
15
+ save: "Save",
16
+ rotate: "Rotate",
17
+ reset: "Reset",
18
+ zoom: "Zoom",
19
+ preview: "Preview",
20
+ dropZone: "Choose an image",
21
+ processing: "Processing\u2026"
22
+ };
23
+
24
+ exports.DEFAULT_ASPECT = DEFAULT_ASPECT;
25
+ exports.DEFAULT_FORMAT = DEFAULT_FORMAT;
26
+ exports.DEFAULT_LABELS = DEFAULT_LABELS;
27
+ exports.DEFAULT_MAX_BYTES = DEFAULT_MAX_BYTES;
28
+ exports.DEFAULT_MAX_ZOOM = DEFAULT_MAX_ZOOM;
29
+ exports.DOWNSCALE_FACTOR = DOWNSCALE_FACTOR;
30
+ exports.MAX_DOWNSCALE_PASSES = MAX_DOWNSCALE_PASSES;
31
+ exports.MIN_CROP_SIZE = MIN_CROP_SIZE;
32
+ exports.QUALITY_MAX = QUALITY_MAX;
33
+ exports.QUALITY_MIN = QUALITY_MIN;
34
+ exports.QUALITY_STEPS = QUALITY_STEPS;
35
+ //# sourceMappingURL=constants.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.cjs","sources":["../../../../src/components/ImageCropper/constants.ts"],"sourcesContent":["import type {CropAspect, CropOutputFormat} from \"@lmb-it/kitsconcerto-types\";\n\n/** A square, which is what an avatar wants. */\nexport const DEFAULT_ASPECT: CropAspect = [1, 1];\n\n/**\n * 500 KB.\n *\n * ⚠️ BYTES, NOT \"KB\" — a number named `500` that turns out to mean kilobytes is\n * how an upload limit gets enforced a thousand times too loosely. Every byte\n * ceiling in this component is in bytes, and `maxBytes` says so in its name.\n */\nexport const DEFAULT_MAX_BYTES = 500 * 1024;\n\nexport const DEFAULT_FORMAT: CropOutputFormat = \"jpeg\";\n\n/** Zoom ceiling, as a multiple of the scale that just covers the window. */\nexport const DEFAULT_MAX_ZOOM = 8;\n\n/** How much one wheel notch moves the zoom. */\nexport const WHEEL_ZOOM_STEP = 0.0015;\n\n/** The smallest crop window the grips may produce, in viewport units. */\nexport const MIN_CROP_SIZE = {width: 48, height: 48};\n\n/** Hit size of a resize grip, in viewport units. */\nexport const HANDLE_HIT_SIZE = 20;\n\n/* ─── Encoder search ──────────────────────────────────────────────────────── */\n\n/**\n * Quality band for the lossy encoders.\n *\n * ⚠️ THE FLOOR IS 0.4, NOT 0.1. Below roughly 0.4 a JPEG of a face stops being\n * a smaller picture and becomes a different, blocky one — and a cropper that\n * silently returns that has not honoured the ceiling, it has destroyed the\n * photo to meet a number. When the floor is not enough the search scales the\n * image DOWN instead, which loses detail evenly rather than in 8×8 squares.\n */\nexport const QUALITY_MAX = 0.92;\nexport const QUALITY_MIN = 0.4;\n\n/** Binary-search steps over the quality band. Seven gets within ~0.004. */\nexport const QUALITY_STEPS = 7;\n\n/** Each downscale pass after the quality floor, as a factor on the long edge. */\nexport const DOWNSCALE_FACTOR = 0.8;\n\n/** How many downscale passes before giving up and reporting `too-large`. */\nexport const MAX_DOWNSCALE_PASSES = 6;\n\n/** The labels the component falls back to when a consumer passes none. */\nexport const DEFAULT_LABELS = {\n cancel: \"Cancel\",\n save: \"Save\",\n rotate: \"Rotate\",\n reset: \"Reset\",\n zoom: \"Zoom\",\n preview: \"Preview\",\n dropZone: \"Choose an image\",\n processing: \"Processing…\",\n} as const;\n"],"names":[],"mappings":";;AAGO,MAAM,cAAA,GAA6B,CAAC,CAAA,EAAG,CAAC;AASxC,MAAM,oBAAoB,GAAA,GAAM;AAEhC,MAAM,cAAA,GAAmC;AAGzC,MAAM,gBAAA,GAAmB;AAMzB,MAAM,aAAA,GAAgB,EAAC,KAAA,EAAO,EAAA,EAAI,QAAQ,EAAA;AAgB1C,MAAM,WAAA,GAAc;AACpB,MAAM,WAAA,GAAc;AAGpB,MAAM,aAAA,GAAgB;AAGtB,MAAM,gBAAA,GAAmB;AAGzB,MAAM,oBAAA,GAAuB;AAG7B,MAAM,cAAA,GAAiB;AAAA,EAC1B,MAAA,EAAQ,QAAA;AAAA,EACR,IAAA,EAAM,MAAA;AAAA,EACN,MAAA,EAAQ,QAAA;AAAA,EACR,KAAA,EAAO,OAAA;AAAA,EACP,IAAA,EAAM,MAAA;AAAA,EACN,OAAA,EAAS,SAAA;AAAA,EACT,QAAA,EAAU,iBAAA;AAAA,EACV,UAAA,EAAY;AAChB;;;;;;;;;;;;;;"}
@@ -0,0 +1,23 @@
1
+ const DEFAULT_ASPECT = [1, 1];
2
+ const DEFAULT_MAX_BYTES = 500 * 1024;
3
+ const DEFAULT_FORMAT = "jpeg";
4
+ const DEFAULT_MAX_ZOOM = 8;
5
+ const MIN_CROP_SIZE = { width: 48, height: 48 };
6
+ const QUALITY_MAX = 0.92;
7
+ const QUALITY_MIN = 0.4;
8
+ const QUALITY_STEPS = 7;
9
+ const DOWNSCALE_FACTOR = 0.8;
10
+ const MAX_DOWNSCALE_PASSES = 6;
11
+ const DEFAULT_LABELS = {
12
+ cancel: "Cancel",
13
+ save: "Save",
14
+ rotate: "Rotate",
15
+ reset: "Reset",
16
+ zoom: "Zoom",
17
+ preview: "Preview",
18
+ dropZone: "Choose an image",
19
+ processing: "Processing\u2026"
20
+ };
21
+
22
+ export { DEFAULT_ASPECT, DEFAULT_FORMAT, DEFAULT_LABELS, DEFAULT_MAX_BYTES, DEFAULT_MAX_ZOOM, DOWNSCALE_FACTOR, MAX_DOWNSCALE_PASSES, MIN_CROP_SIZE, QUALITY_MAX, QUALITY_MIN, QUALITY_STEPS };
23
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sources":["../../../../src/components/ImageCropper/constants.ts"],"sourcesContent":["import type {CropAspect, CropOutputFormat} from \"@lmb-it/kitsconcerto-types\";\n\n/** A square, which is what an avatar wants. */\nexport const DEFAULT_ASPECT: CropAspect = [1, 1];\n\n/**\n * 500 KB.\n *\n * ⚠️ BYTES, NOT \"KB\" — a number named `500` that turns out to mean kilobytes is\n * how an upload limit gets enforced a thousand times too loosely. Every byte\n * ceiling in this component is in bytes, and `maxBytes` says so in its name.\n */\nexport const DEFAULT_MAX_BYTES = 500 * 1024;\n\nexport const DEFAULT_FORMAT: CropOutputFormat = \"jpeg\";\n\n/** Zoom ceiling, as a multiple of the scale that just covers the window. */\nexport const DEFAULT_MAX_ZOOM = 8;\n\n/** How much one wheel notch moves the zoom. */\nexport const WHEEL_ZOOM_STEP = 0.0015;\n\n/** The smallest crop window the grips may produce, in viewport units. */\nexport const MIN_CROP_SIZE = {width: 48, height: 48};\n\n/** Hit size of a resize grip, in viewport units. */\nexport const HANDLE_HIT_SIZE = 20;\n\n/* ─── Encoder search ──────────────────────────────────────────────────────── */\n\n/**\n * Quality band for the lossy encoders.\n *\n * ⚠️ THE FLOOR IS 0.4, NOT 0.1. Below roughly 0.4 a JPEG of a face stops being\n * a smaller picture and becomes a different, blocky one — and a cropper that\n * silently returns that has not honoured the ceiling, it has destroyed the\n * photo to meet a number. When the floor is not enough the search scales the\n * image DOWN instead, which loses detail evenly rather than in 8×8 squares.\n */\nexport const QUALITY_MAX = 0.92;\nexport const QUALITY_MIN = 0.4;\n\n/** Binary-search steps over the quality band. Seven gets within ~0.004. */\nexport const QUALITY_STEPS = 7;\n\n/** Each downscale pass after the quality floor, as a factor on the long edge. */\nexport const DOWNSCALE_FACTOR = 0.8;\n\n/** How many downscale passes before giving up and reporting `too-large`. */\nexport const MAX_DOWNSCALE_PASSES = 6;\n\n/** The labels the component falls back to when a consumer passes none. */\nexport const DEFAULT_LABELS = {\n cancel: \"Cancel\",\n save: \"Save\",\n rotate: \"Rotate\",\n reset: \"Reset\",\n zoom: \"Zoom\",\n preview: \"Preview\",\n dropZone: \"Choose an image\",\n processing: \"Processing…\",\n} as const;\n"],"names":[],"mappings":"AAGO,MAAM,cAAA,GAA6B,CAAC,CAAA,EAAG,CAAC;AASxC,MAAM,oBAAoB,GAAA,GAAM;AAEhC,MAAM,cAAA,GAAmC;AAGzC,MAAM,gBAAA,GAAmB;AAMzB,MAAM,aAAA,GAAgB,EAAC,KAAA,EAAO,EAAA,EAAI,QAAQ,EAAA;AAgB1C,MAAM,WAAA,GAAc;AACpB,MAAM,WAAA,GAAc;AAGpB,MAAM,aAAA,GAAgB;AAGtB,MAAM,gBAAA,GAAmB;AAGzB,MAAM,oBAAA,GAAuB;AAG7B,MAAM,cAAA,GAAiB;AAAA,EAC1B,MAAA,EAAQ,QAAA;AAAA,EACR,IAAA,EAAM,MAAA;AAAA,EACN,MAAA,EAAQ,QAAA;AAAA,EACR,KAAA,EAAO,OAAA;AAAA,EACP,IAAA,EAAM,MAAA;AAAA,EACN,OAAA,EAAS,SAAA;AAAA,EACT,QAAA,EAAU,iBAAA;AAAA,EACV,UAAA,EAAY;AAChB;;;;"}
@@ -0,0 +1,119 @@
1
+ 'use strict';
2
+
3
+ var geometry = require('./geometry.cjs');
4
+ var constants = require('./constants.cjs');
5
+
6
+ const scaleRange = (state) => {
7
+ const oriented = geometry.orientedSize(state.config.source, state.rotation);
8
+ const min = geometry.minScaleToCover(oriented, state.crop);
9
+ return { min, max: min * state.config.maxZoom };
10
+ };
11
+ const zoomFraction = (state) => {
12
+ const { min, max } = scaleRange(state);
13
+ if (!Number.isFinite(min) || max <= min) return 0;
14
+ return geometry.clamp((state.transform.scale - min) / (max - min), 0, 1);
15
+ };
16
+ const settle = (state) => {
17
+ const oriented = geometry.orientedSize(state.config.source, state.rotation);
18
+ const stage = { x: 0, y: 0, ...state.config.viewport };
19
+ const crop = geometry.clampRectInside(state.crop, stage);
20
+ const min = geometry.minScaleToCover(oriented, crop);
21
+ const scale = geometry.clamp(state.transform.scale, min, min * state.config.maxZoom);
22
+ const scaled = geometry.zoomAbout(
23
+ state.transform,
24
+ { x: crop.x + crop.width / 2, y: crop.y + crop.height / 2 },
25
+ scale
26
+ );
27
+ return { ...state, crop, transform: geometry.clampTransformToCover(scaled, oriented, crop) };
28
+ };
29
+ const reframe = (config, rotation) => {
30
+ const oriented = geometry.orientedSize(config.source, rotation);
31
+ const crop = geometry.centerRect(geometry.largestRectOfRatio(config.viewport, config.ratio), config.viewport);
32
+ const scale = geometry.minScaleToCover(oriented, crop);
33
+ const img = geometry.imageRect(oriented, { scale, x: 0, y: 0 });
34
+ return settle({
35
+ config,
36
+ rotation,
37
+ crop,
38
+ transform: {
39
+ scale,
40
+ x: crop.x + (crop.width - img.width) / 2,
41
+ y: crop.y + (crop.height - img.height) / 2
42
+ }
43
+ });
44
+ };
45
+ const initialCropperState = (config) => reframe(config, 0);
46
+ const cropperReducer = (state, action) => {
47
+ switch (action.type) {
48
+ case "configure": {
49
+ const config = { ...state.config, ...action.config };
50
+ const previous = state.config.viewport;
51
+ const next = config.viewport;
52
+ const framingChanged = config.ratio !== state.config.ratio || config.source.width !== state.config.source.width || config.source.height !== state.config.source.height;
53
+ if (framingChanged) return reframe(config, state.rotation);
54
+ const resized = next.width !== previous.width || next.height !== previous.height;
55
+ if (!resized) return settle({ ...state, config });
56
+ if (previous.width <= 1 || previous.height <= 1) return reframe(config, state.rotation);
57
+ const k = Math.min(next.width / previous.width, next.height / previous.height);
58
+ return settle({
59
+ ...state,
60
+ config,
61
+ crop: {
62
+ x: state.crop.x * k,
63
+ y: state.crop.y * k,
64
+ width: state.crop.width * k,
65
+ height: state.crop.height * k
66
+ },
67
+ transform: {
68
+ scale: state.transform.scale * k,
69
+ x: state.transform.x * k,
70
+ y: state.transform.y * k
71
+ }
72
+ });
73
+ }
74
+ case "pan":
75
+ return settle({
76
+ ...state,
77
+ transform: {
78
+ ...state.transform,
79
+ x: state.transform.x + action.dx,
80
+ y: state.transform.y + action.dy
81
+ }
82
+ });
83
+ case "zoomAt": {
84
+ const { min, max } = scaleRange(state);
85
+ const scale = geometry.clamp(action.scale, min, max);
86
+ return settle({ ...state, transform: geometry.zoomAbout(state.transform, action.anchor, scale) });
87
+ }
88
+ case "setZoom": {
89
+ const { min, max } = scaleRange(state);
90
+ const scale = min + geometry.clamp(action.value, 0, 1) * (max - min);
91
+ const anchor = {
92
+ x: state.crop.x + state.crop.width / 2,
93
+ y: state.crop.y + state.crop.height / 2
94
+ };
95
+ return settle({ ...state, transform: geometry.zoomAbout(state.transform, anchor, scale) });
96
+ }
97
+ case "resize": {
98
+ if (!state.config.resizable) return state;
99
+ const crop = geometry.resizeRect(state.crop, action.handle, action.pointer, {
100
+ ratio: state.config.ratio,
101
+ min: constants.MIN_CROP_SIZE,
102
+ bounds: { x: 0, y: 0, ...state.config.viewport }
103
+ });
104
+ return settle({ ...state, crop });
105
+ }
106
+ case "rotate":
107
+ return reframe(state.config, geometry.rotateCw(state.rotation));
108
+ case "reset":
109
+ return reframe(state.config, 0);
110
+ default:
111
+ return state;
112
+ }
113
+ };
114
+
115
+ exports.cropperReducer = cropperReducer;
116
+ exports.initialCropperState = initialCropperState;
117
+ exports.scaleRange = scaleRange;
118
+ exports.zoomFraction = zoomFraction;
119
+ //# sourceMappingURL=cropperState.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cropperState.cjs","sources":["../../../../src/components/ImageCropper/cropperState.ts"],"sourcesContent":["import type {\n CropHandle,\n CropRotation,\n ICropPoint,\n ICropRect,\n ICropSize,\n ICropTransform,\n} from \"@lmb-it/kitsconcerto-types\";\nimport {\n clamp,\n clampRectInside,\n clampTransformToCover,\n imageRect,\n largestRectOfRatio,\n centerRect,\n minScaleToCover,\n orientedSize,\n resizeRect,\n rotateCw,\n zoomAbout,\n} from \"./geometry\";\nimport {HANDLE_HIT_SIZE, MIN_CROP_SIZE} from \"./constants\";\n\n/**\n * The cropper's behaviour, as a pure reducer.\n *\n * ⛔ A REDUCER AND NOT A HOOK, BECAUSE THIS REPO'S VITEST HAS NO DOM. The\n * config is `environment: 'node'` and jsdom is deliberately not installed —\n * its own comment says the first test that renders a component is what earns\n * it. A `useCropperState` hook would therefore have been shipped untested,\n * which for the part that decides where a crop lands is not a trade worth\n * making. As a reducer every transition is an ordinary function call, and the\n * platform files are left holding nothing but `useReducer` and the events.\n *\n * ⚠️ TWO INVARIANTS HOLD AFTER EVERY ACTION, not just after the ones that\n * obviously threaten them:\n * 1. the image covers the crop window — no empty corners, ever;\n * 2. the scale is inside [cover, cover × maxZoom] — you can never zoom out\n * past the point where the window is filled.\n * They are re-applied in one place, `settle`, rather than at each call site,\n * because \"I added an action and forgot the clamp\" is the shape this bug takes.\n */\n\nexport interface ICropperConfig {\n /** The image's natural size, in source pixels. */\n source: ICropSize;\n /** The stage, in viewport units. */\n viewport: ICropSize;\n /** Locked crop ratio, width ÷ height. */\n ratio: number;\n /** Zoom ceiling, as a multiple of the cover scale. */\n maxZoom: number;\n /** Whether the eight grips may resize the window. */\n resizable: boolean;\n}\n\nexport interface ICropperState {\n config: ICropperConfig;\n crop: ICropRect;\n transform: ICropTransform;\n rotation: CropRotation;\n}\n\nexport type CropperAction =\n /** The stage was measured, or re-measured on resize. */\n | {type: \"configure\"; config: Partial<ICropperConfig>}\n /** Drag the image behind a fixed window. */\n | {type: \"pan\"; dx: number; dy: number}\n /** Wheel or pinch, anchored so the pixel under the pointer stays there. */\n | {type: \"zoomAt\"; anchor: ICropPoint; scale: number}\n /** The zoom slider, in 0–1 over the legal band. */\n | {type: \"setZoom\"; value: number}\n /** Drag one of the eight grips. */\n | {type: \"resize\"; handle: CropHandle; pointer: ICropPoint}\n /** The rotate button: one quarter turn clockwise. */\n | {type: \"rotate\"}\n /** Back to how it opened. */\n | {type: \"reset\"};\n\n/* ─── Derived values ──────────────────────────────────────────────────────── */\n\n/** The scale band: cover at the bottom, `maxZoom` times that at the top. */\nexport const scaleRange = (state: ICropperState): {min: number; max: number} => {\n const oriented = orientedSize(state.config.source, state.rotation);\n const min = minScaleToCover(oriented, state.crop);\n return {min, max: min * state.config.maxZoom};\n};\n\n/**\n * Where the slider sits, 0–1 over the legal band.\n *\n * ⚠️ LINEAR IN SCALE, NOT LOGARITHMIC. A log mapping feels better over a wide\n * band and is what a photo editor would use; it is also a different control to\n * the one the pinch gesture drives, and two controls that disagree about what\n * \"half way\" means is worse than one that is slightly bottom-heavy. Change both\n * or neither.\n */\nexport const zoomFraction = (state: ICropperState): number => {\n const {min, max} = scaleRange(state);\n if (!Number.isFinite(min) || max <= min) return 0;\n return clamp((state.transform.scale - min) / (max - min), 0, 1);\n};\n\n/** The grips a renderer should draw — empty when resizing is off. */\nexport const activeHandles = (state: ICropperState): CropHandle[] =>\n state.config.resizable ? [\"nw\", \"n\", \"ne\", \"e\", \"se\", \"s\", \"sw\", \"w\"] : [];\n\n/** Hit size of a grip, so web and native agree on how fat a target is. */\nexport const handleHitSize = (): number => HANDLE_HIT_SIZE;\n\n/* ─── The two invariants, applied in one place ────────────────────────────── */\n\nconst settle = (state: ICropperState): ICropperState => {\n const oriented = orientedSize(state.config.source, state.rotation);\n\n // The window itself may never leave the stage.\n const stage: ICropRect = {x: 0, y: 0, ...state.config.viewport};\n const crop = clampRectInside(state.crop, stage);\n\n // Scale into the legal band…\n const min = minScaleToCover(oriented, crop);\n const scale = clamp(state.transform.scale, min, min * state.config.maxZoom);\n\n // …then slide the image until the window is filled. ⚠️ ORDER MATTERS: a\n // cover clamp computed against a scale that is about to change puts the\n // image where it needed to be a moment ago.\n const scaled = zoomAbout(\n state.transform,\n {x: crop.x + crop.width / 2, y: crop.y + crop.height / 2},\n scale,\n );\n return {...state, crop, transform: clampTransformToCover(scaled, oriented, crop)};\n};\n\n/* ─── Start ───────────────────────────────────────────────────────────────── */\n\n/**\n * The natural framing for a config and a rotation: the biggest window of the\n * ratio the stage allows, with the image just covering it, centred.\n *\n * ⚠️ ONE FUNCTION FOR OPEN, RESET AND ROTATE, because all three mean the same\n * thing — \"frame this from scratch\" — and three copies of it is three chances\n * for one of them to forget `settle`.\n */\nconst reframe = (config: ICropperConfig, rotation: CropRotation): ICropperState => {\n const oriented = orientedSize(config.source, rotation);\n const crop = centerRect(largestRectOfRatio(config.viewport, config.ratio), config.viewport);\n const scale = minScaleToCover(oriented, crop);\n const img = imageRect(oriented, {scale, x: 0, y: 0});\n return settle({\n config,\n rotation,\n crop,\n transform: {\n scale,\n x: crop.x + (crop.width - img.width) / 2,\n y: crop.y + (crop.height - img.height) / 2,\n },\n });\n};\n\nexport const initialCropperState = (config: ICropperConfig): ICropperState =>\n reframe(config, 0);\n\n/* ─── Transitions ─────────────────────────────────────────────────────────── */\n\nexport const cropperReducer = (state: ICropperState, action: CropperAction): ICropperState => {\n switch (action.type) {\n case \"configure\": {\n const config = {...state.config, ...action.config};\n const previous = state.config.viewport;\n const next = config.viewport;\n\n // ⛔ A NEW IMAGE OR A NEW RATIO IS GENUINELY A NEW FRAMING. Keeping\n // the old window across a ratio change hands back a window of the\n // wrong shape; across a new image it frames pixels that are not\n // there any more. Rotation is the user's choice about the PHOTO, so\n // it survives.\n const framingChanged =\n config.ratio !== state.config.ratio ||\n config.source.width !== state.config.source.width ||\n config.source.height !== state.config.source.height;\n if (framingChanged) return reframe(config, state.rotation);\n\n const resized = next.width !== previous.width || next.height !== previous.height;\n if (!resized) return settle({...state, config});\n\n // ⛔ A RE-MEASURED STAGE MUST NOT THROW THE USER'S WORK AWAY, AND\n // FOR ONE BUILD IT DID. Saving a crop made the result preview\n // appear below the component, the page grew a scrollbar, the stage\n // lost ~15px of width, the ResizeObserver fired — and the cropper\n // silently snapped back to 1.0× and unrotated, in the same frame\n // the user pressed Save. Nothing about that is a new framing: the\n // same photo is being cropped the same way on a slightly narrower\n // stage. Scaling the whole framing by the stage's own factor keeps\n // it, and `settle` fixes up whatever no longer fits.\n //\n // ⚠️ THE FIRST MEASURE IS NOT A RESIZE. The stage starts at the 1×1\n // placeholder the component uses before the ResizeObserver has\n // reported, and scaling by a factor of several hundred from that is\n // not preserving anything.\n if (previous.width <= 1 || previous.height <= 1) return reframe(config, state.rotation);\n\n const k = Math.min(next.width / previous.width, next.height / previous.height);\n return settle({\n ...state,\n config,\n crop: {\n x: state.crop.x * k,\n y: state.crop.y * k,\n width: state.crop.width * k,\n height: state.crop.height * k,\n },\n transform: {\n scale: state.transform.scale * k,\n x: state.transform.x * k,\n y: state.transform.y * k,\n },\n });\n }\n\n case \"pan\":\n return settle({\n ...state,\n transform: {\n ...state.transform,\n x: state.transform.x + action.dx,\n y: state.transform.y + action.dy,\n },\n });\n\n case \"zoomAt\": {\n const {min, max} = scaleRange(state);\n const scale = clamp(action.scale, min, max);\n return settle({...state, transform: zoomAbout(state.transform, action.anchor, scale)});\n }\n\n case \"setZoom\": {\n const {min, max} = scaleRange(state);\n const scale = min + clamp(action.value, 0, 1) * (max - min);\n // The slider has no pointer to anchor on, so it zooms about the\n // window's centre — the one point the user is certainly looking at.\n const anchor = {\n x: state.crop.x + state.crop.width / 2,\n y: state.crop.y + state.crop.height / 2,\n };\n return settle({...state, transform: zoomAbout(state.transform, anchor, scale)});\n }\n\n case \"resize\": {\n if (!state.config.resizable) return state;\n const crop = resizeRect(state.crop, action.handle, action.pointer, {\n ratio: state.config.ratio,\n min: MIN_CROP_SIZE,\n bounds: {x: 0, y: 0, ...state.config.viewport},\n });\n // ⚠️ SETTLE AFTER A RESIZE TOO. A bigger window can need a bigger\n // scale to stay covered, and the grip is exactly the control that\n // makes the window bigger.\n return settle({...state, crop});\n }\n\n case \"rotate\":\n // ⛔ THE WINDOW IS REBUILT, NOT KEPT. A quarter turn transposes the\n // image, so a scale that covered the old window may not cover the\n // new one at all, and the old offset points somewhere else entirely.\n // Reopening at the natural framing for the new orientation is what\n // every other cropper does, and it is the only state guaranteed to\n // satisfy both invariants.\n return reframe(state.config, rotateCw(state.rotation));\n\n case \"reset\":\n return reframe(state.config, 0);\n\n default:\n return state;\n }\n};\n"],"names":["orientedSize","minScaleToCover","clamp","clampRectInside","zoomAbout","clampTransformToCover","centerRect","largestRectOfRatio","imageRect","resizeRect","MIN_CROP_SIZE","rotateCw"],"mappings":"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"}