imean-service-engine-htmx-plugin 2.10.1 → 2.11.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.
package/dist/index.js CHANGED
@@ -878,6 +878,22 @@ function convertBoolean(value) {
878
878
  }
879
879
  return value;
880
880
  }
881
+ function convertDate(value) {
882
+ if (typeof value === "string") {
883
+ const trimmed = value.trim();
884
+ if (trimmed !== "") {
885
+ const dateValue = new Date(trimmed);
886
+ if (!isNaN(dateValue.getTime())) {
887
+ return dateValue;
888
+ }
889
+ }
890
+ return value;
891
+ }
892
+ if (value instanceof Date) {
893
+ return value;
894
+ }
895
+ return value;
896
+ }
881
897
  function processArrayElement(item, elementType) {
882
898
  if (typeof item === "object" && item !== null && !Array.isArray(item)) {
883
899
  const { typeName } = resolveZodType(elementType);
@@ -997,6 +1013,9 @@ function preprocessFormData(data, zodSchema) {
997
1013
  case "boolean":
998
1014
  processed[fieldName] = convertBoolean(value);
999
1015
  break;
1016
+ case "date":
1017
+ processed[fieldName] = convertDate(value);
1018
+ break;
1000
1019
  case "array":
1001
1020
  processed[fieldName] = processArrayType(value, actualSchema);
1002
1021
  break;
@@ -1019,6 +1038,8 @@ function convertValueByType(value, schema) {
1019
1038
  return convertNumber(value);
1020
1039
  case "boolean":
1021
1040
  return convertBoolean(value);
1041
+ case "date":
1042
+ return convertDate(value);
1022
1043
  default:
1023
1044
  return value;
1024
1045
  }
@@ -1244,6 +1265,11 @@ var BaseModelFeature = class extends BaseFeature {
1244
1265
  };
1245
1266
  var BaseFormFeature = class extends BaseModelFeature {
1246
1267
  groups;
1268
+ options;
1269
+ constructor(options) {
1270
+ super(options);
1271
+ this.options = options;
1272
+ }
1247
1273
  /**
1248
1274
  * 设置标题和描述到 context
1249
1275
  */
@@ -1316,13 +1342,13 @@ var BaseFormFeature = class extends BaseModelFeature {
1316
1342
  * 处理请求
1317
1343
  */
1318
1344
  async handler(context) {
1319
- context.actions = this.getDefaultActions(context);
1345
+ context.actions = [
1346
+ ...this.options.actions ?? [],
1347
+ ...this.getDefaultActions(context)
1348
+ ];
1320
1349
  if (context.ctx.req.method === "GET") {
1321
1350
  const initialData = await this.getInitialData(context);
1322
- await this.setTitleAndDescription(
1323
- context,
1324
- initialData
1325
- );
1351
+ await this.setTitleAndDescription(context, initialData);
1326
1352
  return this.renderForm(context, initialData);
1327
1353
  }
1328
1354
  const formData = preprocessFormData(context.body, this.schema);
@@ -1424,22 +1450,21 @@ var BaseFormFeature = class extends BaseModelFeature {
1424
1450
 
1425
1451
  // src/features/default-create-feature.tsx
1426
1452
  var DefaultCreateFeature = class extends BaseFormFeature {
1427
- constructor(options) {
1453
+ createItem;
1454
+ constructor(createOptions) {
1428
1455
  super({
1429
- ...options,
1456
+ ...createOptions,
1430
1457
  name: "create",
1431
1458
  type: "create",
1432
1459
  routes: [
1433
1460
  { method: "get", path: "/new" },
1434
1461
  { method: "post", path: "" }
1435
1462
  ],
1436
- permission: options.permission || "create"
1463
+ permission: createOptions.permission || "create"
1437
1464
  });
1438
- this.options = options;
1439
- this.createItem = options.createItem;
1440
- this.groups = options.groups;
1465
+ this.createItem = createOptions.createItem;
1466
+ this.groups = createOptions.groups;
1441
1467
  }
1442
- createItem;
1443
1468
  getFormAction() {
1444
1469
  return "create";
1445
1470
  }
@@ -1964,24 +1989,23 @@ var DefaultDetailFeature = class extends BaseModelFeature {
1964
1989
 
1965
1990
  // src/features/default-edit-feature.tsx
1966
1991
  var DefaultEditFeature = class extends BaseFormFeature {
1967
- constructor(options) {
1992
+ getItem;
1993
+ updateItem;
1994
+ constructor(editOptions) {
1968
1995
  super({
1969
- ...options,
1996
+ ...editOptions,
1970
1997
  name: "edit",
1971
1998
  type: "edit",
1972
1999
  routes: [
1973
2000
  { method: "get", path: "/edit/:id" },
1974
2001
  { method: "put", path: "/:id" }
1975
2002
  ],
1976
- permission: options.permission || "edit"
2003
+ permission: editOptions.permission || "edit"
1977
2004
  });
1978
- this.options = options;
1979
- this.getItem = options.getItem;
1980
- this.updateItem = options.updateItem;
1981
- this.groups = options.groups;
2005
+ this.getItem = editOptions.getItem;
2006
+ this.updateItem = editOptions.updateItem;
2007
+ this.groups = editOptions.groups;
1982
2008
  }
1983
- getItem;
1984
- updateItem;
1985
2009
  getFormAction() {
1986
2010
  return "edit";
1987
2011
  }
@@ -2023,154 +2047,315 @@ var DefaultEditFeature = class extends BaseFormFeature {
2023
2047
  return void 0;
2024
2048
  }
2025
2049
  };
2026
- function FilterForm(props) {
2027
- const {
2028
- fields,
2029
- listPath,
2030
- currentFilters = {}
2031
- } = props;
2032
- function getFieldValue(field) {
2033
- const value = currentFilters[field.name];
2034
- if (value === null || value === void 0 || value === "") {
2035
- return "";
2050
+ function SubmitButton() {
2051
+ return /* @__PURE__ */ jsxRuntime.jsx(
2052
+ "button",
2053
+ {
2054
+ type: "submit",
2055
+ className: "px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium text-sm",
2056
+ children: "\u7B5B\u9009"
2036
2057
  }
2037
- return String(value);
2038
- }
2039
- function getCheckboxValue(field) {
2040
- const value = currentFilters[field.name];
2041
- if (value === "true" || value === true) {
2042
- return true;
2058
+ );
2059
+ }
2060
+ function ResetButton({ listPath }) {
2061
+ return /* @__PURE__ */ jsxRuntime.jsx(
2062
+ "a",
2063
+ {
2064
+ href: listPath,
2065
+ "hx-get": listPath,
2066
+ className: "px-4 py-2 bg-gray-200 text-gray-800 rounded-lg hover:bg-gray-300 transition-colors font-medium text-sm",
2067
+ children: "\u91CD\u7F6E"
2043
2068
  }
2044
- return false;
2045
- }
2069
+ );
2070
+ }
2071
+ function AddConditionButton() {
2072
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2073
+ "button",
2074
+ {
2075
+ type: "button",
2076
+ "x-show": "getAvailableFields().length > 0",
2077
+ className: "px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors font-medium text-sm flex items-center gap-2",
2078
+ "x-on:click": "showFieldSelector=!showFieldSelector",
2079
+ children: [
2080
+ /* @__PURE__ */ jsxRuntime.jsx(
2081
+ "svg",
2082
+ {
2083
+ className: "w-4 h-4",
2084
+ fill: "none",
2085
+ stroke: "currentColor",
2086
+ viewBox: "0 0 24 24",
2087
+ "aria-hidden": "true",
2088
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2089
+ "path",
2090
+ {
2091
+ strokeLinecap: "round",
2092
+ strokeLinejoin: "round",
2093
+ strokeWidth: 2,
2094
+ d: "M12 4v16m8-8H4"
2095
+ }
2096
+ )
2097
+ }
2098
+ ),
2099
+ "\u6DFB\u52A0\u7B5B\u9009\u6761\u4EF6"
2100
+ ]
2101
+ }
2102
+ );
2103
+ }
2104
+ function FieldInput() {
2105
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2106
+ /* @__PURE__ */ jsxRuntime.jsx("template", { "x-if": "getField(condition.fieldName)?.type === 'checkbox'", children: /* @__PURE__ */ jsxRuntime.jsxs(
2107
+ "select",
2108
+ {
2109
+ className: "w-full h-full px-3 py-2 border-0 rounded-none focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-sm appearance-none",
2110
+ "x-bind:name": "condition.fieldName",
2111
+ "x-model": "condition.value",
2112
+ autocomplete: "off",
2113
+ children: [
2114
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "true", children: "\u662F" }),
2115
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "false", children: "\u5426" })
2116
+ ]
2117
+ }
2118
+ ) }),
2119
+ /* @__PURE__ */ jsxRuntime.jsx("template", { "x-if": "getField(condition.fieldName)?.type === 'select' && getField(condition.fieldName)?.options", children: /* @__PURE__ */ jsxRuntime.jsx(
2120
+ "select",
2121
+ {
2122
+ className: "w-full h-full px-3 py-2 border-0 rounded-none focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-sm appearance-none",
2123
+ "x-bind:name": "condition.fieldName",
2124
+ "x-model": "condition.value",
2125
+ autocomplete: "off",
2126
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2127
+ "template",
2128
+ {
2129
+ "x-for": "option in getField(condition.fieldName)?.options",
2130
+ "x-bind:key": "option.value",
2131
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2132
+ "option",
2133
+ {
2134
+ "x-bind:value": "String(option.value)",
2135
+ "x-text": "option.label"
2136
+ }
2137
+ )
2138
+ }
2139
+ )
2140
+ }
2141
+ ) }),
2142
+ /* @__PURE__ */ jsxRuntime.jsx("template", { "x-if": "getField(condition.fieldName)?.type === 'date'", children: /* @__PURE__ */ jsxRuntime.jsx(
2143
+ "input",
2144
+ {
2145
+ type: "date",
2146
+ className: "w-full h-full px-3 py-2 border-0 rounded-none focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-sm",
2147
+ "x-bind:name": "condition.fieldName",
2148
+ "x-model": "condition.value",
2149
+ autocomplete: "off"
2150
+ }
2151
+ ) }),
2152
+ /* @__PURE__ */ jsxRuntime.jsx("template", { "x-if": "getField(condition.fieldName)?.type === 'number'", children: /* @__PURE__ */ jsxRuntime.jsx(
2153
+ "input",
2154
+ {
2155
+ type: "number",
2156
+ className: "w-full h-full px-3 py-2 border-0 rounded-none focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-sm",
2157
+ "x-bind:name": "condition.fieldName",
2158
+ "x-bind:placeholder": "getField(condition.fieldName)?.placeholder || '\u8BF7\u8F93\u5165' + getField(condition.fieldName)?.label",
2159
+ "x-bind:step": "getField(condition.fieldName)?.step",
2160
+ "x-model": "condition.value",
2161
+ autocomplete: "off"
2162
+ }
2163
+ ) }),
2164
+ /* @__PURE__ */ jsxRuntime.jsx("template", { "x-if": "getField(condition.fieldName)?.type === 'email'", children: /* @__PURE__ */ jsxRuntime.jsx(
2165
+ "input",
2166
+ {
2167
+ type: "email",
2168
+ className: "w-full h-full px-3 py-2 border-0 rounded-none focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-sm",
2169
+ "x-bind:name": "condition.fieldName",
2170
+ "x-bind:placeholder": "getField(condition.fieldName)?.placeholder || '\u8BF7\u8F93\u5165' + getField(condition.fieldName)?.label",
2171
+ "x-model": "condition.value",
2172
+ autocomplete: "off"
2173
+ }
2174
+ ) }),
2175
+ /* @__PURE__ */ jsxRuntime.jsx("template", { "x-if": "!['checkbox', 'select', 'date', 'number', 'email'].includes(getField(condition.fieldName)?.type || '')", children: /* @__PURE__ */ jsxRuntime.jsx(
2176
+ "input",
2177
+ {
2178
+ type: "text",
2179
+ className: "w-full h-full px-3 py-2 border-0 rounded-none focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-sm",
2180
+ "x-bind:name": "condition.fieldName",
2181
+ "x-bind:placeholder": "getField(condition.fieldName)?.placeholder || '\u8BF7\u8F93\u5165' + getField(condition.fieldName)?.label",
2182
+ "x-model": "condition.value",
2183
+ autocomplete: "off"
2184
+ }
2185
+ ) })
2186
+ ] });
2187
+ }
2188
+ function FieldSelectorDropdown({ fields }) {
2189
+ const transitionProps = {
2190
+ "x-show": "showFieldSelector",
2191
+ "x-transition:enter": "transition ease-out duration-200",
2192
+ "x-transition:enter-start": "opacity-0 scale-95",
2193
+ "x-transition:enter-end": "opacity-100 scale-100",
2194
+ "x-transition:leave": "transition ease-in duration-150",
2195
+ "x-transition:leave-start": "opacity-100 scale-100",
2196
+ "x-transition:leave-end": "opacity-0 scale-95"
2197
+ };
2198
+ return /* @__PURE__ */ jsxRuntime.jsx(
2199
+ "div",
2200
+ {
2201
+ className: "absolute top-full left-0 mt-2 w-64 bg-white border border-gray-200 rounded-lg shadow-lg z-10",
2202
+ ...transitionProps,
2203
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-3", children: [
2204
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-xs font-semibold text-gray-600 mb-2", children: "\u9009\u62E9\u7B5B\u9009\u5B57\u6BB5" }),
2205
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1 max-h-64 overflow-y-auto", children: /* @__PURE__ */ jsxRuntime.jsx(
2206
+ "template",
2207
+ {
2208
+ "x-for": "field in getAvailableFields()",
2209
+ "x-bind:key": "field.name",
2210
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2211
+ "button",
2212
+ {
2213
+ type: "button",
2214
+ className: "w-full text-left px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded-lg transition-colors",
2215
+ "x-on:click": "onFieldSelected(field.name)",
2216
+ "x-text": "field.label"
2217
+ }
2218
+ )
2219
+ }
2220
+ ) })
2221
+ ] })
2222
+ }
2223
+ );
2224
+ }
2225
+ function FilterConditionItem({ children }) {
2226
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-[auto_1fr_auto] items-stretch h-10 bg-gray-50 rounded-lg border border-gray-200 overflow-hidden", children: [
2227
+ /* @__PURE__ */ jsxRuntime.jsx(
2228
+ "label",
2229
+ {
2230
+ className: "px-3 flex items-center text-xs font-semibold text-gray-600 whitespace-nowrap",
2231
+ "x-text": "getFieldLabel(condition.fieldName)"
2232
+ }
2233
+ ),
2234
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full border-l border-r border-gray-300", children }),
2235
+ /* @__PURE__ */ jsxRuntime.jsx(
2236
+ "button",
2237
+ {
2238
+ type: "button",
2239
+ className: "px-3 flex items-center justify-center text-gray-400 hover:text-red-600 hover:bg-red-50 transition-colors",
2240
+ "x-on:click": "removeCondition(condition.id)",
2241
+ title: "\u5220\u9664\u6B64\u7B5B\u9009\u6761\u4EF6",
2242
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2243
+ "svg",
2244
+ {
2245
+ className: "w-4 h-4",
2246
+ fill: "none",
2247
+ stroke: "currentColor",
2248
+ viewBox: "0 0 24 24",
2249
+ "aria-hidden": "true",
2250
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2251
+ "path",
2252
+ {
2253
+ strokeLinecap: "round",
2254
+ strokeLinejoin: "round",
2255
+ strokeWidth: 2,
2256
+ d: "M6 18L18 6M6 6l12 12"
2257
+ }
2258
+ )
2259
+ }
2260
+ )
2261
+ }
2262
+ )
2263
+ ] });
2264
+ }
2265
+ function DynamicFilters(props) {
2266
+ const { fields, listPath, currentFilters = {} } = props;
2046
2267
  if (fields.length === 0) {
2047
2268
  return null;
2048
2269
  }
2049
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-white border border-gray-200 rounded-lg shadow-sm p-4 mb-6", "data-testid": "filter-form-container", children: /* @__PURE__ */ jsxRuntime.jsxs(
2270
+ const systemParams = /* @__PURE__ */ new Set(["page", "pageSize", "sortBy", "sortOrder"]);
2271
+ const initialConditions = [];
2272
+ for (const field of fields) {
2273
+ if (currentFilters.hasOwnProperty(field.name) && !systemParams.has(field.name)) {
2274
+ const value = currentFilters[field.name];
2275
+ initialConditions.push({
2276
+ id: `condition-${Date.now()}-${Math.random()}`,
2277
+ fieldName: field.name,
2278
+ value: value === null || value === void 0 ? "" : String(value)
2279
+ });
2280
+ }
2281
+ }
2282
+ const availableFields = fields.map((field) => ({
2283
+ name: field.name,
2284
+ label: field.label,
2285
+ type: field.type,
2286
+ options: field.options,
2287
+ step: field.step,
2288
+ placeholder: field.placeholder
2289
+ }));
2290
+ const escapedListPath = listPath.replace(/'/g, "\\'");
2291
+ const alpineData = `{
2292
+ conditions: ${JSON.stringify(initialConditions)},
2293
+ availableFields: ${JSON.stringify(availableFields)},
2294
+ showFieldSelector: false,
2295
+ listPath: '${escapedListPath}',
2296
+
2297
+
2298
+ // \u83B7\u53D6\u5B57\u6BB5\u5B9A\u4E49
2299
+ getField(fieldName) {
2300
+ return this.availableFields.find(f => f.name === fieldName);
2301
+ },
2302
+
2303
+ // \u83B7\u53D6\u5B57\u6BB5\u6807\u7B7E
2304
+ getFieldLabel(fieldName) {
2305
+ const field = this.getField(fieldName);
2306
+ return field ? field.label : fieldName;
2307
+ },
2308
+
2309
+ // \u68C0\u67E5\u5B57\u6BB5\u662F\u5426\u5DF2\u88AB\u4F7F\u7528
2310
+ isFieldUsed(fieldName) {
2311
+ return this.conditions.some(c => c.fieldName === fieldName);
2312
+ },
2313
+
2314
+ // \u83B7\u53D6\u6240\u6709\u53EF\u7528\u5B57\u6BB5\uFF08\u672A\u4F7F\u7528\u7684\u5B57\u6BB5\uFF09
2315
+ getAvailableFields() {
2316
+ return this.availableFields.filter(f => !this.isFieldUsed(f.name));
2317
+ },
2318
+
2319
+ // \u5220\u9664\u6761\u4EF6
2320
+ removeCondition(id) {
2321
+ this.conditions = this.conditions.filter(c => c.id !== id);
2322
+ },
2323
+
2324
+ // \u5B57\u6BB5\u9009\u62E9\u56DE\u8C03\uFF08\u4ECE\u6D6E\u5C42\u9009\u62E9\u5B57\u6BB5\u65F6\u8C03\u7528\uFF09
2325
+ onFieldSelected(fieldName) {
2326
+ // \u68C0\u67E5\u5B57\u6BB5\u662F\u5426\u53EF\u7528
2327
+ if (!this.isFieldUsed(fieldName)) {
2328
+ const newCondition = {
2329
+ id: 'condition-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9),
2330
+ fieldName: fieldName,
2331
+ value: ''
2332
+ };
2333
+ setTimeout(() => {
2334
+ this.conditions.push(newCondition);
2335
+ }, 200);
2336
+ }
2337
+ this.showFieldSelector = false;
2338
+ }
2339
+ }`;
2340
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-white border border-gray-200 rounded-lg shadow-sm p-4 mb-6", children: /* @__PURE__ */ jsxRuntime.jsx(
2050
2341
  "form",
2051
2342
  {
2052
2343
  method: "get",
2053
2344
  action: listPath,
2054
2345
  "hx-get": listPath,
2055
- className: "space-y-4",
2056
- "data-testid": "filter-form",
2057
- children: [
2058
- fields.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4", children: fields.map((field) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", "data-testid": `filter-field-${field.name}`, children: field.type === "checkbox" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 pt-7", children: [
2059
- /* @__PURE__ */ jsxRuntime.jsx(
2060
- "input",
2061
- {
2062
- id: `filter-${field.name}`,
2063
- name: field.name,
2064
- type: "checkbox",
2065
- value: "true",
2066
- checked: getCheckboxValue(field),
2067
- className: "w-4 h-4 text-blue-600 bg-white border-gray-300 rounded focus:ring-blue-500 focus:ring-2",
2068
- "data-testid": `filter-checkbox-${field.name}`
2069
- }
2070
- ),
2071
- /* @__PURE__ */ jsxRuntime.jsx(
2072
- "label",
2073
- {
2074
- htmlFor: `filter-${field.name}`,
2075
- className: "text-sm font-semibold text-gray-700 cursor-pointer",
2076
- "data-testid": `filter-label-${field.name}`,
2077
- children: field.label
2078
- }
2079
- )
2080
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2081
- /* @__PURE__ */ jsxRuntime.jsx(
2082
- "label",
2083
- {
2084
- htmlFor: `filter-${field.name}`,
2085
- className: "block text-sm font-semibold text-gray-700",
2086
- "data-testid": `filter-label-${field.name}`,
2087
- children: field.label
2088
- }
2089
- ),
2090
- field.type === "select" ? /* @__PURE__ */ jsxRuntime.jsxs(
2091
- "select",
2092
- {
2093
- id: `filter-${field.name}`,
2094
- name: field.name,
2095
- className: "w-full px-4 py-2.5 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 bg-white",
2096
- "data-testid": `filter-select-${field.name}`,
2097
- children: [
2098
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", selected: getFieldValue(field) === "", children: "\u5168\u90E8" }),
2099
- field.options?.map((option) => {
2100
- const optionValue = String(option.value);
2101
- const isSelected = getFieldValue(field) === optionValue;
2102
- return /* @__PURE__ */ jsxRuntime.jsx("option", { value: optionValue, selected: isSelected, children: option.label }, optionValue);
2103
- })
2104
- ]
2105
- }
2106
- ) : field.type === "date" ? /* @__PURE__ */ jsxRuntime.jsx(
2107
- "input",
2108
- {
2109
- id: `filter-${field.name}`,
2110
- name: field.name,
2111
- type: "date",
2112
- value: getFieldValue(field),
2113
- className: "w-full px-4 py-2.5 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 bg-white",
2114
- "data-testid": `filter-input-${field.name}`
2115
- }
2116
- ) : field.type === "number" ? /* @__PURE__ */ jsxRuntime.jsx(
2117
- "input",
2118
- {
2119
- id: `filter-${field.name}`,
2120
- name: field.name,
2121
- type: "number",
2122
- value: getFieldValue(field),
2123
- placeholder: field.placeholder || `\u8BF7\u8F93\u5165${field.label}`,
2124
- step: field.step,
2125
- className: "w-full px-4 py-2.5 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 bg-white",
2126
- "data-testid": `filter-input-${field.name}`
2127
- }
2128
- ) : field.type === "email" ? /* @__PURE__ */ jsxRuntime.jsx(
2129
- "input",
2130
- {
2131
- id: `filter-${field.name}`,
2132
- name: field.name,
2133
- type: "email",
2134
- value: getFieldValue(field),
2135
- placeholder: field.placeholder || `\u8BF7\u8F93\u5165${field.label}`,
2136
- className: "w-full px-4 py-2.5 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 bg-white",
2137
- "data-testid": `filter-input-${field.name}`
2138
- }
2139
- ) : /* @__PURE__ */ jsxRuntime.jsx(
2140
- "input",
2141
- {
2142
- id: `filter-${field.name}`,
2143
- name: field.name,
2144
- type: "text",
2145
- value: getFieldValue(field),
2146
- placeholder: field.placeholder || `\u8BF7\u8F93\u5165${field.label}`,
2147
- className: "w-full px-4 py-2.5 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 bg-white",
2148
- "data-testid": `filter-input-${field.name}`
2149
- }
2150
- )
2151
- ] }) }, field.name)) }),
2152
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 pt-2", children: [
2153
- /* @__PURE__ */ jsxRuntime.jsx(
2154
- "button",
2155
- {
2156
- type: "submit",
2157
- className: "px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium",
2158
- "data-testid": "filter-submit-button",
2159
- children: "\u7B5B\u9009"
2160
- }
2161
- ),
2162
- /* @__PURE__ */ jsxRuntime.jsx(
2163
- "a",
2164
- {
2165
- href: listPath,
2166
- "hx-get": listPath,
2167
- className: "px-4 py-2 bg-gray-200 text-gray-800 rounded-lg hover:bg-gray-300 transition-colors font-medium",
2168
- "data-testid": "filter-reset-button",
2169
- children: "\u91CD\u7F6E"
2170
- }
2171
- )
2346
+ className: "space-y-4 mb-0",
2347
+ "x-data": alpineData,
2348
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
2349
+ /* @__PURE__ */ jsxRuntime.jsx("template", { "x-for": "condition in conditions", "x-bind:key": "condition.id", children: /* @__PURE__ */ jsxRuntime.jsx(FilterConditionItem, { children: /* @__PURE__ */ jsxRuntime.jsx(FieldInput, {}) }) }),
2350
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
2351
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
2352
+ /* @__PURE__ */ jsxRuntime.jsx(AddConditionButton, {}),
2353
+ /* @__PURE__ */ jsxRuntime.jsx(FieldSelectorDropdown, { fields })
2354
+ ] }),
2355
+ /* @__PURE__ */ jsxRuntime.jsx(SubmitButton, {}),
2356
+ /* @__PURE__ */ jsxRuntime.jsx(ResetButton, { listPath })
2172
2357
  ] })
2173
- ]
2358
+ ] })
2174
2359
  }
2175
2360
  ) });
2176
2361
  }
@@ -2596,7 +2781,7 @@ function ListPage(props) {
2596
2781
  ];
2597
2782
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
2598
2783
  filterFields && filterFields.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
2599
- FilterForm,
2784
+ DynamicFilters,
2600
2785
  {
2601
2786
  fields: filterFields,
2602
2787
  listPath,
@@ -2634,21 +2819,42 @@ function ListPage(props) {
2634
2819
  }
2635
2820
 
2636
2821
  // src/utils/params.ts
2637
- function parseListParams(ctx) {
2822
+ function parseListParams(ctx, filterSchema) {
2638
2823
  const url = new URL(ctx.req.url);
2639
2824
  const systemParams = /* @__PURE__ */ new Set(["page", "pageSize", "sortBy", "sortOrder"]);
2640
2825
  const filters = {};
2641
2826
  for (const [key, value] of url.searchParams.entries()) {
2642
- if (!systemParams.has(key) && value !== "") {
2827
+ if (!systemParams.has(key)) {
2643
2828
  filters[key] = value;
2644
2829
  }
2645
2830
  }
2831
+ let processedFilters = void 0;
2832
+ if (filterSchema && Object.keys(filters).length > 0) {
2833
+ const emptyStringFields = /* @__PURE__ */ new Set();
2834
+ for (const [key, value] of Object.entries(filters)) {
2835
+ if (value === "") {
2836
+ emptyStringFields.add(key);
2837
+ }
2838
+ }
2839
+ const preprocessed = preprocessFormData(filters, filterSchema);
2840
+ const cleanedFilters = {};
2841
+ for (const [key, value] of Object.entries(preprocessed)) {
2842
+ if (emptyStringFields.has(key)) {
2843
+ cleanedFilters[key] = "";
2844
+ } else if (value !== void 0 && value !== null) {
2845
+ cleanedFilters[key] = value;
2846
+ }
2847
+ }
2848
+ processedFilters = Object.keys(cleanedFilters).length > 0 ? cleanedFilters : void 0;
2849
+ } else if (Object.keys(filters).length > 0) {
2850
+ processedFilters = filters;
2851
+ }
2646
2852
  return {
2647
2853
  page: parseInt(url.searchParams.get("page") || "1", 10),
2648
2854
  pageSize: parseInt(url.searchParams.get("pageSize") || "10", 10),
2649
2855
  sortBy: url.searchParams.get("sortBy") || void 0,
2650
2856
  sortOrder: url.searchParams.get("sortOrder") || void 0,
2651
- filters: Object.keys(filters).length > 0 ? filters : void 0
2857
+ filters: processedFilters
2652
2858
  };
2653
2859
  }
2654
2860
  var DefaultListFeature = class extends BaseModelFeature {
@@ -2677,7 +2883,7 @@ var DefaultListFeature = class extends BaseModelFeature {
2677
2883
  const prefix = context.prefix || "";
2678
2884
  const basePath = `${prefix}/${model.modelName}`;
2679
2885
  const hasCreate = model.features.get("create") !== void 0;
2680
- const actions = [];
2886
+ const actions = [...this.options.actions ?? []];
2681
2887
  if (hasCreate) {
2682
2888
  const createMode = this.openMode?.create || "dialog";
2683
2889
  const createUrl = createMode === "dialog" ? `${basePath}/new?dialog=true` : `${basePath}/new`;
@@ -2699,8 +2905,10 @@ var DefaultListFeature = class extends BaseModelFeature {
2699
2905
  return actions;
2700
2906
  }
2701
2907
  async handler(context) {
2702
- const params = parseListParams(context.ctx);
2703
- const result = await this.getList(params);
2908
+ const params = this.filterSchema ? parseListParams(context.ctx, this.filterSchema) : parseListParams(context.ctx);
2909
+ const result = await this.getList(
2910
+ params
2911
+ );
2704
2912
  context.actions = this.getDefaultActions(context);
2705
2913
  const title = this.options?.title;
2706
2914
  if (typeof title === "function") {
@@ -5431,9 +5639,9 @@ exports.DefaultEditFeature = DefaultEditFeature;
5431
5639
  exports.DefaultListFeature = DefaultListFeature;
5432
5640
  exports.DetailFieldRenderer = DetailFieldRenderer;
5433
5641
  exports.Dialog = Dialog;
5642
+ exports.DynamicFilters = DynamicFilters;
5434
5643
  exports.EmptyState = EmptyState;
5435
5644
  exports.ErrorAlert = ErrorAlert;
5436
- exports.FilterForm = FilterForm;
5437
5645
  exports.FragmentFeature = FragmentFeature;
5438
5646
  exports.Header = Header;
5439
5647
  exports.HtmxAdminPlugin = HtmxAdminPlugin;