@almadar/mobile 1.13.0 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1968,6 +1968,8 @@ RelationSelect.displayName = "RelationSelect";
1968
1968
  // src/components/molecules/FilterGroup.tsx
1969
1969
  import React22, { useState as useState5, useCallback } from "react";
1970
1970
  import { View as View19, StyleSheet as StyleSheet16, TouchableOpacity as TouchableOpacity12 } from "react-native";
1971
+ var resolveFilterType = (filter) => filter.filterType ?? filter.type;
1972
+ var clampRangeValue = (value, filter) => Math.max(filter.min ?? -Infinity, Math.min(filter.max ?? Infinity, value));
1971
1973
  var FilterGroup = ({
1972
1974
  entity,
1973
1975
  filters,
@@ -1984,6 +1986,7 @@ var FilterGroup = ({
1984
1986
  const [selectedValues, setSelectedValues] = useState5(
1985
1987
  {}
1986
1988
  );
1989
+ const [rangeValues, setRangeValues] = useState5({});
1987
1990
  const handleFilterSelect = useCallback(
1988
1991
  (field, value) => {
1989
1992
  setSelectedValues((prev) => {
@@ -2004,12 +2007,35 @@ var FilterGroup = ({
2004
2007
  },
2005
2008
  [onFilterChange, eventBus, entity, query]
2006
2009
  );
2010
+ const handleRangeFilterSelect = useCallback(
2011
+ (field, next) => {
2012
+ setRangeValues((prev) => ({ ...prev, [field]: next }));
2013
+ if (next.min === void 0 && next.max === void 0) {
2014
+ onFilterChange?.(field, null);
2015
+ eventBus.emit("UI:FILTER", { entity, field: `${field}_min`, value: null, query });
2016
+ eventBus.emit("UI:FILTER", { entity, field: `${field}_max`, value: null, query });
2017
+ return;
2018
+ }
2019
+ if (next.min === void 0 || next.max === void 0) {
2020
+ return;
2021
+ }
2022
+ const value = { min: next.min, max: next.max };
2023
+ onFilterChange?.(field, value);
2024
+ eventBus.emit("UI:FILTER", { entity, field: `${field}_min`, value: String(next.min), query });
2025
+ eventBus.emit("UI:FILTER", { entity, field: `${field}_max`, value: String(next.max), query });
2026
+ },
2027
+ [onFilterChange, eventBus, entity, query]
2028
+ );
2007
2029
  const handleClearAll = useCallback(() => {
2008
2030
  setSelectedValues({});
2031
+ setRangeValues({});
2009
2032
  onClearAll?.();
2010
2033
  eventBus.emit("UI:CLEAR_FILTERS", { entity, query });
2011
2034
  }, [onClearAll, eventBus, entity, query]);
2012
- const activeFilterCount = Object.keys(selectedValues).length;
2035
+ const activeRangeFilterCount = Object.values(rangeValues).filter(
2036
+ (v) => v.min !== void 0 && v.max !== void 0
2037
+ ).length;
2038
+ const activeFilterCount = Object.keys(selectedValues).length + activeRangeFilterCount;
2013
2039
  const buildSelectOptions = (filter) => {
2014
2040
  return [
2015
2041
  { value: "all", label: "All" },
@@ -2019,6 +2045,74 @@ var FilterGroup = ({
2019
2045
  })) || []
2020
2046
  ];
2021
2047
  };
2048
+ const renderFilterControl = (filter, selectOptions = buildSelectOptions(filter)) => {
2049
+ const resolvedType = resolveFilterType(filter);
2050
+ if (resolvedType === "numberrange" || resolvedType === "number-range") {
2051
+ const range = rangeValues[filter.field] ?? {};
2052
+ return /* @__PURE__ */ React22.createElement(HStack, { spacing: 8, align: "center" }, /* @__PURE__ */ React22.createElement(
2053
+ Input,
2054
+ {
2055
+ keyboardType: "numeric",
2056
+ placeholder: "Min",
2057
+ value: range.min !== void 0 ? String(range.min) : "",
2058
+ onChangeText: (text) => {
2059
+ if (text === "") {
2060
+ handleRangeFilterSelect(filter.field, { ...range, min: void 0 });
2061
+ return;
2062
+ }
2063
+ const parsed = Number(text);
2064
+ if (Number.isNaN(parsed)) return;
2065
+ handleRangeFilterSelect(filter.field, {
2066
+ ...range,
2067
+ min: clampRangeValue(parsed, filter)
2068
+ });
2069
+ },
2070
+ containerStyle: styles16.rangeInput
2071
+ }
2072
+ ), /* @__PURE__ */ React22.createElement(Typography, { variant: "caption", color: theme.colors["muted-foreground"] }, "-"), /* @__PURE__ */ React22.createElement(
2073
+ Input,
2074
+ {
2075
+ keyboardType: "numeric",
2076
+ placeholder: "Max",
2077
+ value: range.max !== void 0 ? String(range.max) : "",
2078
+ onChangeText: (text) => {
2079
+ if (text === "") {
2080
+ handleRangeFilterSelect(filter.field, { ...range, max: void 0 });
2081
+ return;
2082
+ }
2083
+ const parsed = Number(text);
2084
+ if (Number.isNaN(parsed)) return;
2085
+ handleRangeFilterSelect(filter.field, {
2086
+ ...range,
2087
+ max: clampRangeValue(parsed, filter)
2088
+ });
2089
+ },
2090
+ containerStyle: styles16.rangeInput
2091
+ }
2092
+ ));
2093
+ }
2094
+ if (resolvedType === "text") {
2095
+ return /* @__PURE__ */ React22.createElement(
2096
+ Input,
2097
+ {
2098
+ value: selectedValues[filter.field] || "",
2099
+ onChangeText: (text) => handleFilterSelect(filter.field, text || null),
2100
+ placeholder: filter.label
2101
+ }
2102
+ );
2103
+ }
2104
+ if (resolvedType === "date" || resolvedType === "daterange" || resolvedType === "date-range") {
2105
+ return null;
2106
+ }
2107
+ return /* @__PURE__ */ React22.createElement(
2108
+ Select,
2109
+ {
2110
+ value: selectedValues[filter.field] || "all",
2111
+ onChange: (newValue) => handleFilterSelect(filter.field, newValue),
2112
+ options: selectOptions
2113
+ }
2114
+ );
2115
+ };
2022
2116
  if (variant === "pills") {
2023
2117
  return /* @__PURE__ */ React22.createElement(
2024
2118
  HStack,
@@ -2028,61 +2122,65 @@ var FilterGroup = ({
2028
2122
  style: [styles16.pillsContainer, style ?? {}]
2029
2123
  },
2030
2124
  showIcon && /* @__PURE__ */ React22.createElement(Typography, { variant: "caption", color: theme.colors["muted-foreground"] }, "\u{1F50D}"),
2031
- filters.map((filter) => /* @__PURE__ */ React22.createElement(HStack, { key: filter.field, spacing: 8, align: "center" }, /* @__PURE__ */ React22.createElement(
2032
- Typography,
2033
- {
2034
- variant: "caption",
2035
- color: theme.colors["muted-foreground"]
2036
- },
2037
- filter.label,
2038
- ":"
2039
- ), /* @__PURE__ */ React22.createElement(HStack, { spacing: 0, style: styles16.pillGroup }, /* @__PURE__ */ React22.createElement(
2040
- TouchableOpacity12,
2041
- {
2042
- onPress: () => handleFilterSelect(filter.field, null),
2043
- style: [
2044
- styles16.pill,
2045
- {
2046
- backgroundColor: !selectedValues[filter.field] ? theme.colors.primary : theme.colors.card,
2047
- borderColor: theme.colors.border
2048
- }
2049
- ]
2050
- },
2051
- /* @__PURE__ */ React22.createElement(
2125
+ filters.map((filter) => {
2126
+ const resolvedType = resolveFilterType(filter);
2127
+ const usesOptionPills = resolvedType === void 0 || resolvedType === "select" || resolvedType === "toggle" || resolvedType === "checkbox";
2128
+ return /* @__PURE__ */ React22.createElement(HStack, { key: filter.field, spacing: 8, align: "center" }, /* @__PURE__ */ React22.createElement(
2052
2129
  Typography,
2053
2130
  {
2054
2131
  variant: "caption",
2055
- style: {
2056
- color: !selectedValues[filter.field] ? theme.colors["primary-foreground"] : theme.colors["muted-foreground"]
2057
- }
2132
+ color: theme.colors["muted-foreground"]
2058
2133
  },
2059
- "All"
2060
- )
2061
- ), filter.options?.map((option) => /* @__PURE__ */ React22.createElement(
2062
- TouchableOpacity12,
2063
- {
2064
- key: option,
2065
- onPress: () => handleFilterSelect(filter.field, option),
2066
- style: [
2067
- styles16.pill,
2068
- styles16.pillWithBorder,
2134
+ filter.label,
2135
+ ":"
2136
+ ), usesOptionPills ? /* @__PURE__ */ React22.createElement(HStack, { spacing: 0, style: styles16.pillGroup }, /* @__PURE__ */ React22.createElement(
2137
+ TouchableOpacity12,
2138
+ {
2139
+ onPress: () => handleFilterSelect(filter.field, null),
2140
+ style: [
2141
+ styles16.pill,
2142
+ {
2143
+ backgroundColor: !selectedValues[filter.field] ? theme.colors.primary : theme.colors.card,
2144
+ borderColor: theme.colors.border
2145
+ }
2146
+ ]
2147
+ },
2148
+ /* @__PURE__ */ React22.createElement(
2149
+ Typography,
2069
2150
  {
2070
- backgroundColor: selectedValues[filter.field] === option ? theme.colors.primary : theme.colors.card,
2071
- borderColor: theme.colors.border
2072
- }
2073
- ]
2074
- },
2075
- /* @__PURE__ */ React22.createElement(
2076
- Typography,
2151
+ variant: "caption",
2152
+ style: {
2153
+ color: !selectedValues[filter.field] ? theme.colors["primary-foreground"] : theme.colors["muted-foreground"]
2154
+ }
2155
+ },
2156
+ "All"
2157
+ )
2158
+ ), filter.options?.map((option) => /* @__PURE__ */ React22.createElement(
2159
+ TouchableOpacity12,
2077
2160
  {
2078
- variant: "caption",
2079
- style: {
2080
- color: selectedValues[filter.field] === option ? theme.colors["primary-foreground"] : theme.colors["muted-foreground"]
2081
- }
2161
+ key: option,
2162
+ onPress: () => handleFilterSelect(filter.field, option),
2163
+ style: [
2164
+ styles16.pill,
2165
+ styles16.pillWithBorder,
2166
+ {
2167
+ backgroundColor: selectedValues[filter.field] === option ? theme.colors.primary : theme.colors.card,
2168
+ borderColor: theme.colors.border
2169
+ }
2170
+ ]
2082
2171
  },
2083
- option
2084
- )
2085
- ))))),
2172
+ /* @__PURE__ */ React22.createElement(
2173
+ Typography,
2174
+ {
2175
+ variant: "caption",
2176
+ style: {
2177
+ color: selectedValues[filter.field] === option ? theme.colors["primary-foreground"] : theme.colors["muted-foreground"]
2178
+ }
2179
+ },
2180
+ option
2181
+ )
2182
+ ))) : renderFilterControl(filter));
2183
+ }),
2086
2184
  activeFilterCount > 0 && /* @__PURE__ */ React22.createElement(Button, { variant: "ghost", size: "sm", onPress: handleClearAll }, "Clear")
2087
2185
  );
2088
2186
  }
@@ -2101,14 +2199,7 @@ var FilterGroup = ({
2101
2199
  color: theme.colors["muted-foreground"]
2102
2200
  },
2103
2201
  filter.label
2104
- ), /* @__PURE__ */ React22.createElement(
2105
- Select,
2106
- {
2107
- value: selectedValues[filter.field] || "all",
2108
- onChange: (newValue) => handleFilterSelect(filter.field, newValue),
2109
- options: buildSelectOptions(filter)
2110
- }
2111
- ))), activeFilterCount > 0 && /* @__PURE__ */ React22.createElement(Button, { variant: "ghost", size: "sm", onPress: handleClearAll }, "Clear all"));
2202
+ ), renderFilterControl(filter))), activeFilterCount > 0 && /* @__PURE__ */ React22.createElement(Button, { variant: "ghost", size: "sm", onPress: handleClearAll }, "Clear all"));
2112
2203
  }
2113
2204
  if (variant === "compact") {
2114
2205
  return /* @__PURE__ */ React22.createElement(
@@ -2119,20 +2210,13 @@ var FilterGroup = ({
2119
2210
  style: [styles16.compactContainer, style ?? {}]
2120
2211
  },
2121
2212
  showIcon && /* @__PURE__ */ React22.createElement(Typography, { variant: "caption", color: theme.colors["muted-foreground"] }, "\u{1F50D}"),
2122
- filters.map((filter) => /* @__PURE__ */ React22.createElement(View19, { key: filter.field, style: styles16.compactSelect }, /* @__PURE__ */ React22.createElement(
2123
- Select,
2124
- {
2125
- value: selectedValues[filter.field] || "all",
2126
- onChange: (newValue) => handleFilterSelect(filter.field, newValue),
2127
- options: [
2128
- { value: "all", label: `All ${filter.label}` },
2129
- ...filter.options?.map((opt) => ({
2130
- value: opt,
2131
- label: opt
2132
- })) || []
2133
- ]
2134
- }
2135
- ))),
2213
+ filters.map((filter) => /* @__PURE__ */ React22.createElement(View19, { key: filter.field, style: styles16.compactSelect }, renderFilterControl(filter, [
2214
+ { value: "all", label: `All ${filter.label}` },
2215
+ ...filter.options?.map((opt) => ({
2216
+ value: opt,
2217
+ label: opt
2218
+ })) || []
2219
+ ]))),
2136
2220
  activeFilterCount > 0 && /* @__PURE__ */ React22.createElement(React22.Fragment, null, Object.entries(selectedValues).map(([field, value]) => {
2137
2221
  const filterDef = filters.find((f) => f.field === field);
2138
2222
  return /* @__PURE__ */ React22.createElement(
@@ -2172,14 +2256,7 @@ var FilterGroup = ({
2172
2256
  color: theme.colors["muted-foreground"]
2173
2257
  },
2174
2258
  filter.label
2175
- ), /* @__PURE__ */ React22.createElement(
2176
- Select,
2177
- {
2178
- value: selectedValues[filter.field] || "all",
2179
- onChange: (newValue) => handleFilterSelect(filter.field, newValue),
2180
- options: buildSelectOptions(filter)
2181
- }
2182
- ))), activeFilterCount > 0 && /* @__PURE__ */ React22.createElement(HStack, { spacing: 12, align: "center", style: styles16.clearSection }, /* @__PURE__ */ React22.createElement(Badge, { variant: "primary", size: "md" }, activeFilterCount, " active"), /* @__PURE__ */ React22.createElement(Button, { variant: "ghost", size: "sm", onPress: handleClearAll }, "Clear all")))
2259
+ ), renderFilterControl(filter))), activeFilterCount > 0 && /* @__PURE__ */ React22.createElement(HStack, { spacing: 12, align: "center", style: styles16.clearSection }, /* @__PURE__ */ React22.createElement(Badge, { variant: "primary", size: "md" }, activeFilterCount, " active"), /* @__PURE__ */ React22.createElement(Button, { variant: "ghost", size: "sm", onPress: handleClearAll }, "Clear all")))
2183
2260
  );
2184
2261
  };
2185
2262
  var styles16 = StyleSheet16.create({
@@ -2207,6 +2284,9 @@ var styles16 = StyleSheet16.create({
2207
2284
  compactSelect: {
2208
2285
  minWidth: 120
2209
2286
  },
2287
+ rangeInput: {
2288
+ width: 84
2289
+ },
2210
2290
  defaultContainer: {
2211
2291
  padding: 16,
2212
2292
  borderRadius: 8,
@@ -5617,4 +5697,4 @@ export {
5617
5697
  Lightbox,
5618
5698
  ScaledDiagram
5619
5699
  };
5620
- //# sourceMappingURL=chunk-2H7HIGZO.js.map
5700
+ //# sourceMappingURL=chunk-B4JZIFI6.js.map