imean-service-engine-htmx-plugin 2.5.0 → 2.7.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.mjs CHANGED
@@ -78,7 +78,6 @@ function renderActionButton(action, index) {
78
78
  close,
79
79
  submit,
80
80
  formId,
81
- onClick,
82
81
  className = "",
83
82
  target
84
83
  } = action;
@@ -104,8 +103,7 @@ function renderActionButton(action, index) {
104
103
  index
105
104
  );
106
105
  }
107
- const finalOnClick = close ? CLOSE_DIALOG_SCRIPT : onClick;
108
- if (finalOnClick) {
106
+ if (close) {
109
107
  const variantStyles = {
110
108
  primary: "bg-blue-600 text-white hover:bg-blue-700",
111
109
  secondary: "bg-gray-200 text-gray-800 hover:bg-gray-300",
@@ -123,7 +121,8 @@ function renderActionButton(action, index) {
123
121
  "button",
124
122
  {
125
123
  type: "button",
126
- _: finalOnClick,
124
+ "x-data": CLOSE_DIALOG_ALPINE_DATA,
125
+ "x-on:click": "closeDialog()",
127
126
  className: `px-4 py-2 rounded transition-colors font-medium ${buttonStyle} ${className}`,
128
127
  "data-testid": testId2,
129
128
  ...confirm && { "data-confirm": confirm },
@@ -167,15 +166,25 @@ function renderActionButton(action, index) {
167
166
  function renderActionButtons(actions) {
168
167
  return actions.map((action, index) => renderActionButton(action, index));
169
168
  }
170
- var CLOSE_DIALOG_SCRIPT;
169
+ var CLOSE_DIALOG_ALPINE_DATA;
171
170
  var init_action_button_renderer = __esm({
172
171
  "src/utils/action-button-renderer.tsx"() {
173
172
  init_button();
174
- CLOSE_DIALOG_SCRIPT = `on click
175
- add .dialog-exit to .dialog-backdrop
176
- add .dialog-content-exit to .dialog-content
177
- wait 200ms
178
- set #dialog-container's innerHTML to '' end`;
173
+ CLOSE_DIALOG_ALPINE_DATA = `{
174
+ closeDialog() {
175
+ const backdrop = this.$el.closest('.dialog-backdrop');
176
+ if (backdrop) {
177
+ backdrop.classList.add('dialog-exit');
178
+ const content = backdrop.querySelector('.dialog-content');
179
+ if (content) {
180
+ content.classList.add('dialog-content-exit');
181
+ }
182
+ setTimeout(() => {
183
+ backdrop.remove();
184
+ }, 200);
185
+ }
186
+ }
187
+ }`;
179
188
  }
180
189
  });
181
190
 
@@ -305,11 +314,6 @@ var init_cdn_cache = __esm({
305
314
  url: "https://unpkg.com/htmx.org@latest",
306
315
  mimeType: "application/javascript"
307
316
  },
308
- {
309
- name: "hyperscript",
310
- url: "https://unpkg.com/hyperscript.org@latest",
311
- mimeType: "application/javascript"
312
- },
313
317
  {
314
318
  name: "tailwindcss",
315
319
  url: "https://cdn.tailwindcss.com",
@@ -319,16 +323,6 @@ var init_cdn_cache = __esm({
319
323
  name: "alpinejs",
320
324
  url: "https://unpkg.com/alpinejs@latest/dist/cdn.min.js",
321
325
  mimeType: "application/javascript"
322
- },
323
- {
324
- name: "idiomorph",
325
- url: "https://unpkg.com/idiomorph@0.7.4/dist/idiomorph-ext.min.js",
326
- mimeType: "application/javascript"
327
- },
328
- {
329
- name: "sortablejs",
330
- url: "https://unpkg.com/sortablejs@latest/Sortable.min.js",
331
- mimeType: "application/javascript"
332
326
  }
333
327
  ];
334
328
  cache = /* @__PURE__ */ new Map();
@@ -553,8 +547,9 @@ function renderFormField(field, initialData, formFieldRenderers) {
553
547
  /* @__PURE__ */ jsx("div", { children: customRenderer({
554
548
  field,
555
549
  value: parsedValue,
556
- initialData,
557
- fieldName: field.name
550
+ item: initialData || {},
551
+ name: field.name,
552
+ label: field.label
558
553
  }) }),
559
554
  field.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 mt-1", children: field.description })
560
555
  ]
@@ -1851,7 +1846,12 @@ function renderDefaultValue(value) {
1851
1846
  function renderField(field, value, item) {
1852
1847
  let content;
1853
1848
  if (field.render) {
1854
- const rendered = field.render(value, item);
1849
+ const rendered = field.render({
1850
+ value,
1851
+ item,
1852
+ name: field.key,
1853
+ label: field.label
1854
+ });
1855
1855
  if (rendered === null || rendered === void 0) {
1856
1856
  content = /* @__PURE__ */ jsx("span", { className: "text-gray-400", children: "-" });
1857
1857
  } else if (typeof rendered === "string" || typeof rendered === "number" || typeof rendered === "boolean") {
@@ -1987,11 +1987,14 @@ var DefaultDetailFeature = class extends BaseFeature {
1987
1987
  const groupFields = groupSchemas.map(
1988
1988
  ({ label, schema: schema2, fields: fieldNames }) => {
1989
1989
  const groupFields2 = parseSchemaToFields(schema2);
1990
- const detailFields2 = groupFields2.map((field) => ({
1991
- key: field.name,
1992
- label: field.label,
1993
- render: this.fieldRenderers?.[field.name]
1994
- }));
1990
+ const detailFields2 = groupFields2.map((field) => {
1991
+ const renderer = this.fieldRenderers?.[field.name];
1992
+ return {
1993
+ key: field.name,
1994
+ label: field.label,
1995
+ render: renderer
1996
+ };
1997
+ });
1995
1998
  return {
1996
1999
  label,
1997
2000
  fields: detailFields2,
@@ -2023,12 +2026,16 @@ var DefaultDetailFeature = class extends BaseFeature {
2023
2026
  }
2024
2027
  }
2025
2028
  const detailFieldNames = getFieldNamesFromFields(detailFields);
2026
- const fields = detailFieldNames.map((fieldName) => ({
2027
- key: fieldName,
2028
- label: getFieldLabelFromFields(this.fields || [], fieldName) || fieldName,
2029
- render: this.fieldRenderers?.[fieldName]
2030
- // 如果有自定义渲染函数则使用
2031
- }));
2029
+ const fields = detailFieldNames.map((fieldName) => {
2030
+ const label = getFieldLabelFromFields(this.fields || [], fieldName) || fieldName;
2031
+ const renderer = this.fieldRenderers?.[fieldName];
2032
+ return {
2033
+ key: fieldName,
2034
+ label,
2035
+ // 直接使用统一的渲染器类型
2036
+ render: renderer
2037
+ };
2038
+ });
2032
2039
  return /* @__PURE__ */ jsx(DetailPage, { item, fields });
2033
2040
  }
2034
2041
  async getActions(context) {
@@ -2807,286 +2814,6 @@ var DefaultListFeature = class extends BaseFeature {
2807
2814
  }
2808
2815
  };
2809
2816
 
2810
- // src/component-system/store.ts
2811
- var STATE_EXPIRATION_TIME = 864e5;
2812
- var StateStore = class _StateStore {
2813
- static instance;
2814
- state;
2815
- constructor() {
2816
- this.state = /* @__PURE__ */ new Map();
2817
- setInterval(() => {
2818
- this.state.forEach((state) => {
2819
- if (state.lastUpdated < Date.now() - STATE_EXPIRATION_TIME) {
2820
- this.state.delete(state.instanceId);
2821
- }
2822
- });
2823
- }, 3e4);
2824
- }
2825
- static get() {
2826
- if (!_StateStore.instance) {
2827
- _StateStore.instance = new _StateStore();
2828
- }
2829
- return _StateStore.instance;
2830
- }
2831
- /** 获取实例状态 */
2832
- getState(instanceId) {
2833
- const state = this.state.get(instanceId);
2834
- if (state) {
2835
- return state;
2836
- }
2837
- const newState = {
2838
- instanceId,
2839
- data: {
2840
- props: {},
2841
- state: {}
2842
- },
2843
- lastUpdated: Date.now()
2844
- };
2845
- this.state.set(instanceId, newState);
2846
- return newState;
2847
- }
2848
- /** 设置实例状态 */
2849
- setState(instanceId, state) {
2850
- const instanceState = this.getState(instanceId);
2851
- instanceState.data = Object.assign(instanceState.data.state, state);
2852
- instanceState.lastUpdated = Date.now();
2853
- }
2854
- };
2855
-
2856
- // src/component-system/utils.ts
2857
- var globalIdCounter = 0;
2858
- function generateUniqueId() {
2859
- return `htmx-cid-${globalIdCounter++}`;
2860
- }
2861
- var HTMX_COMPONENT_PREFIX = "/_htmx_components";
2862
-
2863
- // src/component-system/context.tsx
2864
- var RenderContext = class {
2865
- constructor(prefix, instanceId, componentName) {
2866
- this.prefix = prefix;
2867
- this.instanceId = instanceId;
2868
- this.componentName = componentName;
2869
- }
2870
- // 生成唯一 ID(使用全局共享计数器,避免冲突)
2871
- $id() {
2872
- return generateUniqueId();
2873
- }
2874
- setState(state) {
2875
- StateStore.get().setState(this.instanceId, state);
2876
- }
2877
- get state() {
2878
- return StateStore.get().getState(this.instanceId).data.state;
2879
- }
2880
- get props() {
2881
- return StateStore.get().getState(this.instanceId).data.props;
2882
- }
2883
- // 生成方法 URL(使用当前 instanceId)
2884
- url(methodName, params) {
2885
- const baseUrl = `${this.prefix}/${HTMX_COMPONENT_PREFIX}/${this.componentName}/${this.instanceId}/${methodName}`;
2886
- if (params && Object.keys(params).length > 0) {
2887
- const queryString = new URLSearchParams(
2888
- Object.entries(params).reduce(
2889
- (acc, [key, value]) => {
2890
- acc[key] = String(value);
2891
- return acc;
2892
- },
2893
- {}
2894
- )
2895
- ).toString();
2896
- return `${baseUrl}?${queryString}`;
2897
- }
2898
- return baseUrl;
2899
- }
2900
- callMethod(methodName, params) {
2901
- const selectors = Object.entries(params).map(([name, expression]) => `${name}:${expression}`).join(",");
2902
- return {
2903
- "hx-post": this.url(methodName),
2904
- "hx-vals": `js:{_params_:{${selectors}}}`,
2905
- "hx-params": "_state_,_params_,_this_value_"
2906
- };
2907
- }
2908
- };
2909
- var ComponentContext = class extends RenderContext {
2910
- constructor(prefix, ctx, componentName) {
2911
- const routeParams = ctx.req.param();
2912
- const instanceId = String(routeParams.instanceId || "");
2913
- super(prefix, instanceId, componentName);
2914
- this.ctx = ctx;
2915
- }
2916
- // 获取所有参数(统一接口:聚合 query string 和 body)
2917
- async params() {
2918
- const params = {};
2919
- const routeParams = this.ctx.req.param();
2920
- Object.assign(params, routeParams);
2921
- const url = new URL(this.ctx.req.url);
2922
- for (const [key, value] of url.searchParams.entries()) {
2923
- params[key] = value;
2924
- }
2925
- const contentType = this.ctx.req.header("Content-Type") || "";
2926
- if (contentType.includes("application/json")) {
2927
- try {
2928
- const body = await this.ctx.req.json();
2929
- Object.assign(params, body);
2930
- } catch (e) {
2931
- console.warn("[ComponentContext] Failed to parse JSON body:", e);
2932
- }
2933
- } else if (this.ctx.req.method === "POST" || this.ctx.req.method === "PUT" || this.ctx.req.method === "PATCH" || this.ctx.req.method === "DELETE") {
2934
- try {
2935
- const formData = await this.ctx.req.formData();
2936
- for (const [key, value] of formData.entries()) {
2937
- params[key] = value instanceof File ? value : value.toString();
2938
- }
2939
- } catch (e) {
2940
- console.warn("[ComponentContext] Failed to parse form data:", e);
2941
- }
2942
- }
2943
- return params;
2944
- }
2945
- // 获取查询参数
2946
- query() {
2947
- const url = new URL(this.ctx.req.url);
2948
- return Object.fromEntries(url.searchParams.entries());
2949
- }
2950
- // 获取请求体
2951
- async body() {
2952
- const contentType = this.ctx.req.header("Content-Type") || "";
2953
- if (contentType.includes("application/json")) {
2954
- try {
2955
- return await this.ctx.req.json();
2956
- } catch (e) {
2957
- console.warn("[ComponentContext] Failed to parse JSON body:", e);
2958
- return {};
2959
- }
2960
- }
2961
- try {
2962
- const formData = await this.ctx.req.formData();
2963
- const body = {};
2964
- for (const [key, value] of formData.entries()) {
2965
- body[key] = value instanceof File ? value : value.toString();
2966
- }
2967
- return body;
2968
- } catch (e) {
2969
- console.warn("[ComponentContext] Failed to parse form data:", e);
2970
- return {};
2971
- }
2972
- }
2973
- };
2974
-
2975
- // src/component-system/component.tsx
2976
- var METHOD_METADATA_KEY = /* @__PURE__ */ Symbol("htmx:method");
2977
- function Method(config) {
2978
- return function(target, propertyKey, _descriptor) {
2979
- if (!target[METHOD_METADATA_KEY]) {
2980
- target[METHOD_METADATA_KEY] = /* @__PURE__ */ new Map();
2981
- }
2982
- target[METHOD_METADATA_KEY].set(propertyKey, {
2983
- method: config?.method || "get",
2984
- path: config?.path
2985
- });
2986
- };
2987
- }
2988
- var HtmxComponent = class {
2989
- constructor(name) {
2990
- this.name = name;
2991
- }
2992
- prefix;
2993
- // 组件函数
2994
- Component = (props) => {
2995
- const instanceId = generateUniqueId();
2996
- const state = StateStore.get().getState(instanceId).data;
2997
- state.props = props;
2998
- state.state = {};
2999
- const renderCtx = new RenderContext(
3000
- this.prefix,
3001
- instanceId,
3002
- this.name
3003
- );
3004
- return this.render(renderCtx, props);
3005
- };
3006
- // 返回 JSX script 元素
3007
- // 获取所有标记为 @Method() 的方法
3008
- // 注意:handler 不再绑定 this,因为方法会接收 ComponentContext 作为第一个参数
3009
- static getMethods(component) {
3010
- const methods = /* @__PURE__ */ new Map();
3011
- const metadata = component[METHOD_METADATA_KEY];
3012
- if (!metadata) return methods;
3013
- for (const [methodName, config] of metadata.entries()) {
3014
- const handler = component[methodName];
3015
- methods.set(methodName, {
3016
- method: config.method,
3017
- path: config.path,
3018
- handler
3019
- });
3020
- }
3021
- return methods;
3022
- }
3023
- };
3024
- var HtmxComponentHandler = class {
3025
- constructor(hono, prefix, components) {
3026
- this.hono = hono;
3027
- this.prefix = prefix;
3028
- this.components = components;
3029
- for (const component of this.components) {
3030
- component.prefix = this.prefix;
3031
- this.registerHandler(component);
3032
- }
3033
- }
3034
- registerHandler(component) {
3035
- const methods = HtmxComponent.getMethods(component);
3036
- for (const [methodName, methodConfig] of methods) {
3037
- const routePath = `${this.prefix}/${HTMX_COMPONENT_PREFIX}/${component.name}/:instanceId/${methodName}`;
3038
- logger.info(
3039
- `[HtmxComponent] Registering handler ${methodConfig.method} ${routePath}`
3040
- );
3041
- this.hono[methodConfig.method](routePath, async (ctx) => {
3042
- return this.handleComponentMethod(
3043
- ctx,
3044
- component,
3045
- methodConfig.handler.bind(component)
3046
- );
3047
- });
3048
- }
3049
- }
3050
- async handleComponentMethod(ctx, component, handler) {
3051
- const componentContext = new ComponentContext(
3052
- this.prefix,
3053
- ctx,
3054
- component.name
3055
- );
3056
- const result = await handler(componentContext);
3057
- if (result instanceof Object && ("target" in result || "swap" in result || "body" in result || "oobs" in result)) {
3058
- const { target, swap, body, oobs, trigger } = result;
3059
- const headers = {};
3060
- let bodyContent = body;
3061
- if (target) headers["HX-Retarget"] = target;
3062
- if (swap) headers["HX-Reswap"] = swap;
3063
- if (trigger) headers["HX-Trigger"] = trigger;
3064
- if (oobs) {
3065
- oobs.forEach((oob) => {
3066
- oob.props["hx-swap-oob"] = "true";
3067
- });
3068
- if (!body) {
3069
- headers["HX-Reswap"] = "delete";
3070
- }
3071
- bodyContent = /* @__PURE__ */ jsxs(Fragment, { children: [
3072
- body,
3073
- oobs
3074
- ] });
3075
- }
3076
- return ctx.html(bodyContent, 200, headers);
3077
- }
3078
- if (result === null) {
3079
- return ctx.html(/* @__PURE__ */ jsx("div", {}), 200, {
3080
- "HX-Reswap": "none"
3081
- });
3082
- }
3083
- if (result instanceof Response) {
3084
- return result;
3085
- }
3086
- return ctx.html(result, 200);
3087
- }
3088
- };
3089
-
3090
2817
  // src/utils/path.ts
3091
2818
  function modelNameToPath(modelName) {
3092
2819
  return `/${modelName.replace(/([A-Z])/g, "-$1").toLowerCase()}`;
@@ -3098,8 +2825,9 @@ function ErrorAlert(props) {
3098
2825
  type = "error",
3099
2826
  showClose = true,
3100
2827
  className = "",
3101
- autoClose = 5e3
2828
+ autoClose = 5e3,
3102
2829
  // 默认 5 秒自动关闭
2830
+ "hx-swap-oob": hxSwapOob
3103
2831
  } = props;
3104
2832
  const typeClasses = {
3105
2833
  error: "bg-red-50 border-red-200 text-red-800",
@@ -3188,12 +2916,20 @@ function ErrorAlert(props) {
3188
2916
  const now = Date.now();
3189
2917
  this.elapsedTime = now - this.startTime;
3190
2918
  this.progress = Math.max(0, 100 - (this.elapsedTime / this.autoCloseDelay) * 100);
2919
+ // \u66F4\u65B0\u8FDB\u5EA6\u6761\u6837\u5F0F
2920
+ const progressBar = this.$el.querySelector('.progress-bar');
2921
+ if (progressBar) {
2922
+ progressBar.style.width = this.progress + '%';
2923
+ }
3191
2924
  if (this.progress > 0 && this.visible) {
3192
2925
  this.progressTimer = requestAnimationFrame(updateProgress);
3193
2926
  }
3194
2927
  };
3195
2928
  this.progressTimer = requestAnimationFrame(updateProgress);
3196
2929
  },
2930
+ getProgressStyle() {
2931
+ return { width: this.progress + '%' };
2932
+ },
3197
2933
  pauseAutoClose() {
3198
2934
  if (this.autoCloseTimer) {
3199
2935
  clearTimeout(this.autoCloseTimer);
@@ -3222,6 +2958,9 @@ function ErrorAlert(props) {
3222
2958
  cancelAnimationFrame(this.progressTimer);
3223
2959
  this.progressTimer = null;
3224
2960
  }
2961
+ // \u6DFB\u52A0\u6DE1\u51FA\u52A8\u753B
2962
+ this.$el.classList.add('error-alert-exit');
2963
+ // \u52A8\u753B\u7ED3\u675F\u540E\u5B8C\u5168\u79FB\u9664\u5143\u7D20\uFF0C\u4E0D\u7559\u7A7A div
3225
2964
  setTimeout(() => {
3226
2965
  if (this.$el && this.$el.parentNode) {
3227
2966
  this.$el.parentNode.removeChild(this.$el);
@@ -3232,6 +2971,9 @@ function ErrorAlert(props) {
3232
2971
  visible: true,
3233
2972
  close() {
3234
2973
  this.visible = false;
2974
+ // \u6DFB\u52A0\u6DE1\u51FA\u52A8\u753B
2975
+ this.$el.classList.add('error-alert-exit');
2976
+ // \u52A8\u753B\u7ED3\u675F\u540E\u5B8C\u5168\u79FB\u9664\u5143\u7D20\uFF0C\u4E0D\u7559\u7A7A div
3235
2977
  setTimeout(() => {
3236
2978
  if (this.$el && this.$el.parentNode) {
3237
2979
  this.$el.parentNode.removeChild(this.$el);
@@ -3253,18 +2995,26 @@ function ErrorAlert(props) {
3253
2995
  alpineProps["@mouseenter"] = "pauseAutoClose()";
3254
2996
  alpineProps["@mouseleave"] = "resumeAutoClose()";
3255
2997
  }
2998
+ const htmxProps = {};
2999
+ if (hxSwapOob) {
3000
+ htmxProps["hx-swap-oob"] = hxSwapOob;
3001
+ }
3256
3002
  return /* @__PURE__ */ jsxs(
3257
3003
  "div",
3258
3004
  {
3259
3005
  ...alpineProps,
3260
- className: `border rounded-lg p-4 shadow-lg mb-2 ${typeClasses[type]} ${className} relative overflow-hidden`,
3006
+ ...htmxProps,
3007
+ className: `w-full border rounded-lg p-4 shadow-lg ${typeClasses[type]} ${className} relative overflow-hidden pointer-events-auto`,
3261
3008
  role: "alert",
3009
+ style: {
3010
+ animation: "slideInRight 0.3s ease-out"
3011
+ },
3262
3012
  children: [
3263
3013
  autoClose > 0 && /* @__PURE__ */ jsx("div", { className: "absolute top-0 left-0 right-0 h-1 bg-gray-200", children: /* @__PURE__ */ jsx(
3264
3014
  "div",
3265
3015
  {
3266
- className: `h-full ${progressBarColors[type]}`,
3267
- ...{ ":style": "`width: ${progress}%`" }
3016
+ className: `h-full ${progressBarColors[type]} progress-bar`,
3017
+ ...{ "x-bind:style": "getProgressStyle()" }
3268
3018
  }
3269
3019
  ) }),
3270
3020
  /* @__PURE__ */ jsxs("div", { className: "flex items-start", children: [
@@ -3383,6 +3133,9 @@ async function createFeatureContext(ctx, page, feature, user, options) {
3383
3133
  };
3384
3134
  return featureContext;
3385
3135
  }
3136
+ function generateDialogId() {
3137
+ return `dialog-${Date.now()}}`;
3138
+ }
3386
3139
  function Dialog(props) {
3387
3140
  const {
3388
3141
  title,
@@ -3394,6 +3147,7 @@ function Dialog(props) {
3394
3147
  actions = [],
3395
3148
  fixedContentHeight = false
3396
3149
  } = props;
3150
+ const dialogId = generateDialogId();
3397
3151
  const sizeClasses = {
3398
3152
  sm: "max-w-md",
3399
3153
  md: "max-w-lg",
@@ -3401,19 +3155,33 @@ function Dialog(props) {
3401
3155
  xl: "max-w-4xl",
3402
3156
  full: "max-w-7xl"
3403
3157
  };
3404
- const backdropClickHandler = closeOnBackdropClick ? `on click if event.target is me
3405
- add .dialog-exit to me
3406
- add .dialog-content-exit to .dialog-content
3407
- wait 200ms
3408
- set #dialog-container's innerHTML to '' end` : "";
3158
+ const alpineData = `{
3159
+ closeDialog() {
3160
+ const backdrop = this.$el.closest('.dialog-backdrop');
3161
+ if (backdrop) {
3162
+ backdrop.classList.add('dialog-exit');
3163
+ const content = backdrop.querySelector('.dialog-content');
3164
+ if (content) {
3165
+ content.classList.add('dialog-content-exit');
3166
+ }
3167
+ setTimeout(() => {
3168
+ backdrop.remove();
3169
+ }, 200);
3170
+ }
3171
+ }
3172
+ }`;
3409
3173
  return /* @__PURE__ */ jsx(
3410
3174
  "div",
3411
3175
  {
3176
+ id: dialogId,
3412
3177
  className: "fixed inset-0 bg-black bg-opacity-50 z-[100] flex items-center justify-center p-4 dialog-backdrop",
3413
3178
  style: {
3414
3179
  animation: "fadeIn 0.2s ease-out"
3415
3180
  },
3416
- _: backdropClickHandler,
3181
+ "x-data": alpineData,
3182
+ ...closeOnBackdropClick && {
3183
+ "x-on:click": "if ($event.target === $el) closeDialog()"
3184
+ },
3417
3185
  children: /* @__PURE__ */ jsxs(
3418
3186
  "div",
3419
3187
  {
@@ -3421,7 +3189,7 @@ function Dialog(props) {
3421
3189
  style: {
3422
3190
  animation: "slideIn 0.3s ease-out"
3423
3191
  },
3424
- _: "on click call event.stopPropagation()",
3192
+ ...{ "x-on:click.stop": "" },
3425
3193
  children: [
3426
3194
  (title || showClose) && /* @__PURE__ */ jsxs("div", { className: "px-6 py-4 border-b border-gray-200 bg-white flex items-center justify-between", children: [
3427
3195
  title && /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-gray-900", children: title }),
@@ -3429,7 +3197,7 @@ function Dialog(props) {
3429
3197
  "button",
3430
3198
  {
3431
3199
  className: "text-gray-400 hover:text-gray-600 transition-colors",
3432
- _: "on click \r\n add .dialog-exit to .dialog-backdrop\r\n add .dialog-content-exit to .dialog-content\r\n wait 200ms\r\n set #dialog-container's innerHTML to '' end",
3200
+ "x-on:click": "closeDialog()",
3433
3201
  children: /* @__PURE__ */ jsx(
3434
3202
  "svg",
3435
3203
  {
@@ -3526,11 +3294,26 @@ function PermissionDeniedContent(props) {
3526
3294
  Button,
3527
3295
  {
3528
3296
  variant: "secondary",
3529
- _: "on click set #dialog-container's innerHTML to ''",
3297
+ "x-data": `{
3298
+ closeDialog() {
3299
+ const backdrop = this.$el.closest('.dialog-backdrop');
3300
+ if (backdrop) {
3301
+ backdrop.classList.add('dialog-exit');
3302
+ const content = backdrop.querySelector('.dialog-content');
3303
+ if (content) {
3304
+ content.classList.add('dialog-content-exit');
3305
+ }
3306
+ setTimeout(() => {
3307
+ backdrop.remove();
3308
+ }, 200);
3309
+ }
3310
+ }
3311
+ }`,
3312
+ "x-on:click": "closeDialog()",
3530
3313
  children: "\u5173\u95ED"
3531
3314
  }
3532
3315
  ),
3533
- !isDialog && userInfo && /* @__PURE__ */ jsx(Button, { variant: "secondary", _: "on click window.history.back()", children: "\u8FD4\u56DE" })
3316
+ !isDialog && userInfo && /* @__PURE__ */ jsx(Button, { variant: "secondary", onclick: "window.history.back()", children: "\u8FD4\u56DE" })
3534
3317
  ] })
3535
3318
  ] });
3536
3319
  }
@@ -3551,13 +3334,8 @@ var getResourceUrl = (prefix, name) => {
3551
3334
  function globalScripts(prefix) {
3552
3335
  return html`
3553
3336
  <script src=${getResourceUrl(prefix, "htmx")}></script>
3554
- <script src=${getResourceUrl(prefix, "htmx-ext-form-json")}></script>
3555
- <script src=${getResourceUrl(prefix, "hyperscript")}></script>
3556
3337
  <script src=${getResourceUrl(prefix, "tailwindcss")}></script>
3557
3338
  <script src=${getResourceUrl(prefix, "alpinejs")} defer></script>
3558
- <script src=${getResourceUrl(prefix, "sortablejs")}></script>
3559
- <script src=${getResourceUrl(prefix, "idiomorph")}></script>
3560
- <script type="module" src=${getResourceUrl(prefix, "datastar")}></script>
3561
3339
  `;
3562
3340
  }
3563
3341
  function globalStyles() {
@@ -3687,36 +3465,6 @@ function globalStyles() {
3687
3465
  }
3688
3466
  </style>`;
3689
3467
  }
3690
- function sortableScript() {
3691
- html`<script>
3692
- if (typeof htmx !== "undefined" && typeof Sortable !== "undefined") {
3693
- htmx.onLoad(function (content) {
3694
- var sortables = content.querySelectorAll(".sortable");
3695
- for (var i = 0; i < sortables.length; i++) {
3696
- var sortable = sortables[i];
3697
- // 检查是否已经初始化
3698
- if (sortable.sortableInstance) {
3699
- continue;
3700
- }
3701
- var sortableInstance = new Sortable(sortable, {
3702
- animation: 150,
3703
- ghostClass: "sortable-ghost",
3704
- filter: ".htmx-indicator",
3705
- onMove: function (evt) {
3706
- return evt.related.className.indexOf("htmx-indicator") === -1;
3707
- },
3708
- });
3709
- sortable.sortableInstance = sortableInstance;
3710
- sortable.addEventListener("htmx:afterSwap", function () {
3711
- if (sortable.sortableInstance) {
3712
- sortable.sortableInstance.option("disabled", false);
3713
- }
3714
- });
3715
- }
3716
- });
3717
- }
3718
- </script>`;
3719
- }
3720
3468
  function Breadcrumb(props) {
3721
3469
  const { items } = props;
3722
3470
  if (items.length === 0) {
@@ -4072,193 +3820,183 @@ function SortableList(props) {
4072
3820
  function StringArrayEditor(props) {
4073
3821
  const {
4074
3822
  value,
4075
- fieldName,
4076
3823
  placeholder = "\u8BF7\u8F93\u5165\u5185\u5BB9",
4077
3824
  allowEmpty = false,
4078
3825
  rows = 1
4079
3826
  } = props;
3827
+ const name = props.name;
4080
3828
  const initialItems = value || [];
4081
3829
  const initialDataJson = JSON.stringify({
4082
3830
  items: initialItems.map((item) => item || ""),
4083
- fieldName,
3831
+ fieldName: name,
4084
3832
  placeholder,
4085
3833
  allowEmpty,
4086
3834
  rows
4087
3835
  });
4088
- return /* @__PURE__ */ jsxs(
4089
- "div",
4090
- {
4091
- className: "space-y-3",
4092
- "x-data": initialDataJson,
4093
- children: [
4094
- /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end", children: /* @__PURE__ */ jsxs(
4095
- "button",
4096
- {
4097
- type: "button",
4098
- ...{
4099
- "x-on:click": `
3836
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", "x-data": initialDataJson, children: [
3837
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end", children: /* @__PURE__ */ jsxs(
3838
+ "button",
3839
+ {
3840
+ type: "button",
3841
+ ...{
3842
+ "x-on:click": `
4100
3843
  items.push('');
4101
3844
  `
4102
- },
4103
- className: "px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-sm font-medium flex items-center gap-2",
4104
- "data-testid": `${fieldName}-add-button`,
4105
- children: [
4106
- /* @__PURE__ */ jsx(
4107
- "svg",
3845
+ },
3846
+ className: "px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-sm font-medium flex items-center gap-2",
3847
+ "data-testid": `${name}-add-button`,
3848
+ children: [
3849
+ /* @__PURE__ */ jsx(
3850
+ "svg",
3851
+ {
3852
+ className: "w-4 h-4",
3853
+ fill: "none",
3854
+ stroke: "currentColor",
3855
+ viewBox: "0 0 24 24",
3856
+ children: /* @__PURE__ */ jsx(
3857
+ "path",
4108
3858
  {
4109
- className: "w-4 h-4",
4110
- fill: "none",
4111
- stroke: "currentColor",
4112
- viewBox: "0 0 24 24",
4113
- children: /* @__PURE__ */ jsx(
4114
- "path",
4115
- {
4116
- strokeLinecap: "round",
4117
- strokeLinejoin: "round",
4118
- strokeWidth: "2",
4119
- d: "M12 4v16m8-8H4"
4120
- }
4121
- )
3859
+ strokeLinecap: "round",
3860
+ strokeLinejoin: "round",
3861
+ strokeWidth: "2",
3862
+ d: "M12 4v16m8-8H4"
4122
3863
  }
4123
- ),
4124
- "\u6DFB\u52A0\u9879"
4125
- ]
4126
- }
4127
- ) }),
4128
- /* @__PURE__ */ jsx(
4129
- "div",
4130
- {
4131
- "x-show": "items.length > 0",
4132
- "data-testid": `${fieldName}-list-container`,
4133
- ...{
4134
- "@sortable:change.stop": `
3864
+ )
3865
+ }
3866
+ ),
3867
+ "\u6DFB\u52A0\u9879"
3868
+ ]
3869
+ }
3870
+ ) }),
3871
+ /* @__PURE__ */ jsx(
3872
+ "div",
3873
+ {
3874
+ "x-show": "items.length > 0",
3875
+ "data-testid": `${name}-list-container`,
3876
+ ...{
3877
+ "@sortable:change.stop": `
4135
3878
  (function() {
4136
3879
  const { oldIndex, newIndex } = $event.detail;
4137
3880
  [items[oldIndex], items[newIndex]] = [items[newIndex], items[oldIndex]];
4138
3881
  })();
4139
3882
  `
4140
- },
4141
- children: /* @__PURE__ */ jsx(SortableList, { className: "space-y-2", handle: "[data-drag-handle]", children: /* @__PURE__ */ jsx("template", { "x-for": "(item, index) in items", "x-bind:key": "index", children: /* @__PURE__ */ jsx(ArrayItem, { fieldName, rows }) }) })
4142
- }
4143
- ),
4144
- /* @__PURE__ */ jsx(
4145
- "div",
4146
- {
4147
- className: "empty-state text-center py-8 text-gray-400 text-sm border border-dashed border-gray-300 rounded-lg",
4148
- "x-show": "items.length === 0",
4149
- "data-testid": `${fieldName}-empty-state`,
4150
- children: '\u6682\u65E0\u9879\uFF0C\u70B9\u51FB"\u6DFB\u52A0\u9879"\u6309\u94AE\u6DFB\u52A0'
4151
- }
4152
- )
4153
- ]
4154
- }
4155
- );
3883
+ },
3884
+ children: /* @__PURE__ */ jsx(SortableList, { className: "space-y-2", handle: "[data-drag-handle]", children: /* @__PURE__ */ jsx("template", { "x-for": "(item, index) in items", "x-bind:key": "index", children: /* @__PURE__ */ jsx(ArrayItem, { fieldName: name, rows }) }) })
3885
+ }
3886
+ ),
3887
+ /* @__PURE__ */ jsx(
3888
+ "div",
3889
+ {
3890
+ className: "empty-state text-center py-8 text-gray-400 text-sm border border-dashed border-gray-300 rounded-lg",
3891
+ "x-show": "items.length === 0",
3892
+ "data-testid": `${name}-empty-state`,
3893
+ children: '\u6682\u65E0\u9879\uFF0C\u70B9\u51FB"\u6DFB\u52A0\u9879"\u6309\u94AE\u6DFB\u52A0'
3894
+ }
3895
+ )
3896
+ ] });
4156
3897
  }
4157
- function ArrayItem({ fieldName, rows = 1 }) {
4158
- return /* @__PURE__ */ jsxs(
4159
- "div",
4160
- {
4161
- "data-array-item": true,
4162
- className: "flex items-center gap-2 group",
4163
- children: [
4164
- /* @__PURE__ */ jsx(
4165
- "div",
3898
+ function ArrayItem({
3899
+ fieldName,
3900
+ rows = 1
3901
+ }) {
3902
+ return /* @__PURE__ */ jsxs("div", { "data-array-item": true, className: "flex items-center gap-2 group", children: [
3903
+ /* @__PURE__ */ jsx(
3904
+ "div",
3905
+ {
3906
+ className: "flex-shrink-0 cursor-move text-gray-400 hover:text-gray-600 transition-colors p-1",
3907
+ "data-drag-handle": true,
3908
+ "data-testid": `${fieldName}-drag-handle`,
3909
+ title: "\u62D6\u62FD\u6392\u5E8F",
3910
+ children: /* @__PURE__ */ jsx(
3911
+ "svg",
4166
3912
  {
4167
- className: "flex-shrink-0 cursor-move text-gray-400 hover:text-gray-600 transition-colors p-1",
4168
- "data-drag-handle": true,
4169
- "data-testid": `${fieldName}-drag-handle`,
4170
- title: "\u62D6\u62FD\u6392\u5E8F",
3913
+ className: "w-5 h-5",
3914
+ fill: "none",
3915
+ stroke: "currentColor",
3916
+ viewBox: "0 0 24 24",
4171
3917
  children: /* @__PURE__ */ jsx(
4172
- "svg",
3918
+ "path",
4173
3919
  {
4174
- className: "w-5 h-5",
4175
- fill: "none",
4176
- stroke: "currentColor",
4177
- viewBox: "0 0 24 24",
4178
- children: /* @__PURE__ */ jsx(
4179
- "path",
4180
- {
4181
- strokeLinecap: "round",
4182
- strokeLinejoin: "round",
4183
- strokeWidth: "2",
4184
- d: "M4 8h16M4 16h16"
4185
- }
4186
- )
3920
+ strokeLinecap: "round",
3921
+ strokeLinejoin: "round",
3922
+ strokeWidth: "2",
3923
+ d: "M4 8h16M4 16h16"
4187
3924
  }
4188
3925
  )
4189
3926
  }
4190
- ),
4191
- rows === 1 ? /* @__PURE__ */ jsx(
4192
- "input",
4193
- {
4194
- type: "text",
4195
- "x-model": "items[index]",
4196
- "x-bind:placeholder": "placeholder + ' ' + (index + 1)",
4197
- className: "flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent",
4198
- "data-testid": `${fieldName}-input`,
4199
- "x-bind:required": "!allowEmpty"
4200
- }
4201
- ) : /* @__PURE__ */ jsx(
4202
- "textarea",
4203
- {
4204
- "x-model": "items[index]",
4205
- "x-bind:placeholder": "placeholder + ' ' + (index + 1)",
4206
- rows,
4207
- className: "flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-y",
4208
- "data-testid": `${fieldName}-input`,
4209
- "x-bind:required": "!allowEmpty"
4210
- }
4211
- ),
4212
- /* @__PURE__ */ jsx(
4213
- "input",
4214
- {
4215
- type: "hidden",
4216
- "x-bind:name": "fieldName + '[' + index + ']'",
4217
- "x-bind:value": "item"
4218
- }
4219
- ),
4220
- /* @__PURE__ */ jsx(
4221
- "button",
4222
- {
4223
- type: "button",
4224
- ...{
4225
- "x-on:click": `
3927
+ )
3928
+ }
3929
+ ),
3930
+ rows === 1 ? /* @__PURE__ */ jsx(
3931
+ "input",
3932
+ {
3933
+ type: "text",
3934
+ "x-model": "items[index]",
3935
+ "x-bind:placeholder": "placeholder + ' ' + (index + 1)",
3936
+ className: "flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent",
3937
+ "data-testid": `${fieldName}-input`,
3938
+ "x-bind:required": "!allowEmpty"
3939
+ }
3940
+ ) : /* @__PURE__ */ jsx(
3941
+ "textarea",
3942
+ {
3943
+ "x-model": "items[index]",
3944
+ "x-bind:placeholder": "placeholder + ' ' + (index + 1)",
3945
+ rows,
3946
+ className: "flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-y",
3947
+ "data-testid": `${fieldName}-input`,
3948
+ "x-bind:required": "!allowEmpty"
3949
+ }
3950
+ ),
3951
+ /* @__PURE__ */ jsx(
3952
+ "input",
3953
+ {
3954
+ type: "hidden",
3955
+ "x-bind:name": "fieldName + '[' + index + ']'",
3956
+ "x-bind:value": "item"
3957
+ }
3958
+ ),
3959
+ /* @__PURE__ */ jsx(
3960
+ "button",
3961
+ {
3962
+ type: "button",
3963
+ ...{
3964
+ "x-on:click": `
4226
3965
  items.splice(index, 1);
4227
3966
  `
4228
- },
4229
- className: "flex-shrink-0 px-3 py-2 text-sm text-red-600 hover:bg-red-50 rounded-lg transition-colors",
4230
- "data-testid": `${fieldName}-remove-button`,
4231
- title: "\u5220\u9664\u6B64\u9879",
3967
+ },
3968
+ className: "flex-shrink-0 px-3 py-2 text-sm text-red-600 hover:bg-red-50 rounded-lg transition-colors",
3969
+ "data-testid": `${fieldName}-remove-button`,
3970
+ title: "\u5220\u9664\u6B64\u9879",
3971
+ children: /* @__PURE__ */ jsx(
3972
+ "svg",
3973
+ {
3974
+ className: "w-5 h-5",
3975
+ fill: "none",
3976
+ stroke: "currentColor",
3977
+ viewBox: "0 0 24 24",
4232
3978
  children: /* @__PURE__ */ jsx(
4233
- "svg",
3979
+ "path",
4234
3980
  {
4235
- className: "w-5 h-5",
4236
- fill: "none",
4237
- stroke: "currentColor",
4238
- viewBox: "0 0 24 24",
4239
- children: /* @__PURE__ */ jsx(
4240
- "path",
4241
- {
4242
- strokeLinecap: "round",
4243
- strokeLinejoin: "round",
4244
- strokeWidth: "2",
4245
- d: "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
4246
- }
4247
- )
3981
+ strokeLinecap: "round",
3982
+ strokeLinejoin: "round",
3983
+ strokeWidth: "2",
3984
+ d: "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
4248
3985
  }
4249
3986
  )
4250
3987
  }
4251
3988
  )
4252
- ]
4253
- }
4254
- );
3989
+ }
3990
+ )
3991
+ ] });
4255
3992
  }
4256
3993
  function TagsEditor(props) {
4257
- const { value, fieldName, placeholder = "\u8F93\u5165\u6807\u7B7E\u540E\u6309\u56DE\u8F66\u6DFB\u52A0" } = props;
3994
+ const { value, placeholder = "\u8F93\u5165\u6807\u7B7E\u540E\u6309\u56DE\u8F66\u6DFB\u52A0" } = props;
3995
+ const name = props.name;
4258
3996
  const initialTags = value || [];
4259
3997
  const initialDataJson = JSON.stringify({
4260
3998
  tags: initialTags.map((tag) => tag || ""),
4261
- fieldName,
3999
+ fieldName: name,
4262
4000
  newTag: "",
4263
4001
  editingIndex: null,
4264
4002
  editingValue: "",
@@ -4285,7 +4023,7 @@ function TagsEditor(props) {
4285
4023
  placeholder,
4286
4024
  autocomplete: "off",
4287
4025
  className: "w-full px-3 py-1.5 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent",
4288
- "data-testid": `${fieldName}-input`
4026
+ "data-testid": `${name}-input`
4289
4027
  }
4290
4028
  ),
4291
4029
  /* @__PURE__ */ jsx(
@@ -4294,7 +4032,7 @@ function TagsEditor(props) {
4294
4032
  "x-show": "error && editingIndex === null",
4295
4033
  "x-text": "error",
4296
4034
  className: "text-red-600 text-sm p-2",
4297
- id: `${fieldName}-error`
4035
+ id: `${name}-error`
4298
4036
  }
4299
4037
  )
4300
4038
  ] }),
@@ -4302,7 +4040,7 @@ function TagsEditor(props) {
4302
4040
  "div",
4303
4041
  {
4304
4042
  "x-show": "tags.length > 0",
4305
- "data-testid": `${fieldName}-tags-container`,
4043
+ "data-testid": `${name}-tags-container`,
4306
4044
  ...{
4307
4045
  "@sortable:change.stop": `
4308
4046
  (function() {
@@ -4325,8 +4063,8 @@ function TagsEditor(props) {
4325
4063
  "x-bind:value": "editingIndex === index ? editingValue : tag"
4326
4064
  }
4327
4065
  ),
4328
- /* @__PURE__ */ jsx(TagItem, { fieldName }),
4329
- /* @__PURE__ */ jsx(TagItemEdit, { fieldName })
4066
+ /* @__PURE__ */ jsx(TagItem, { fieldName: name }),
4067
+ /* @__PURE__ */ jsx(TagItemEdit, { fieldName: name })
4330
4068
  ] }) })
4331
4069
  }
4332
4070
  )
@@ -4337,7 +4075,7 @@ function TagsEditor(props) {
4337
4075
  {
4338
4076
  className: "empty-state text-center py-4 text-gray-400 text-sm border border-dashed border-gray-300 rounded-md",
4339
4077
  "x-show": "tags.length === 0",
4340
- "data-testid": `${fieldName}-empty-state`,
4078
+ "data-testid": `${name}-empty-state`,
4341
4079
  children: "\u6682\u65E0\u6807\u7B7E\uFF0C\u5728\u4E0A\u65B9\u8F93\u5165\u6846\u4E2D\u8F93\u5165\u6807\u7B7E\u540E\u6309\u56DE\u8F66\u6DFB\u52A0"
4342
4080
  }
4343
4081
  )
@@ -4518,7 +4256,7 @@ function TagItemEdit({ fieldName }) {
4518
4256
  );
4519
4257
  }
4520
4258
  function ObjectEditor(props) {
4521
- const { value, fieldName, objectSchema } = props;
4259
+ const { value, name, objectSchema } = props;
4522
4260
  if (!objectSchema) {
4523
4261
  return /* @__PURE__ */ jsx("div", { className: "p-4 border border-yellow-300 rounded-lg bg-yellow-50 text-yellow-800 text-sm", children: "\u8BF7\u63D0\u4F9B objectSchema \u53C2\u6570\u4EE5\u4F7F\u7528\u5BF9\u8C61\u7F16\u8F91\u5668" });
4524
4262
  }
@@ -4538,7 +4276,7 @@ function ObjectEditor(props) {
4538
4276
  const initialValueJson = JSON.stringify(initialObject);
4539
4277
  JSON.stringify(fields.map((f) => f.name));
4540
4278
  const generateField = (field) => {
4541
- const fieldId = `${fieldName}-${field.name}`;
4279
+ const fieldId = `${String(name)}-${field.name}`;
4542
4280
  const fieldValue = initialObject[field.name];
4543
4281
  const fieldValueStr = fieldValue === void 0 || fieldValue === null ? "" : typeof fieldValue === "object" ? JSON.stringify(fieldValue) : String(fieldValue);
4544
4282
  const requiredAttr = field.required ? "required" : "";
@@ -4548,11 +4286,11 @@ function ObjectEditor(props) {
4548
4286
  <input
4549
4287
  type="text"
4550
4288
  id="${fieldId}"
4551
- name="${fieldName}.${field.name}"
4289
+ name="${name}.${field.name}"
4552
4290
  value="${fieldValueStr}"
4553
4291
  class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
4554
- data-testid="${fieldName}-input-${field.name}"
4555
- oninput="updateObjectField('${fieldName}', '${field.name}', this.value, 'text', ${field.required})"
4292
+ data-testid="${name}-input-${field.name}"
4293
+ oninput="updateObjectField('${name}', '${field.name}', this.value, 'text', ${field.required})"
4556
4294
  ${requiredAttr}
4557
4295
  />
4558
4296
  `;
@@ -4560,11 +4298,11 @@ function ObjectEditor(props) {
4560
4298
  inputElement = html`
4561
4299
  <textarea
4562
4300
  id="${fieldId}"
4563
- name="${fieldName}.${field.name}"
4301
+ name="${name}.${field.name}"
4564
4302
  class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-y"
4565
4303
  rows="4"
4566
- data-testid="${fieldName}-input-${field.name}"
4567
- oninput="updateObjectField('${fieldName}', '${field.name}', this.value, 'text', ${field.required})"
4304
+ data-testid="${name}-input-${field.name}"
4305
+ oninput="updateObjectField('${name}', '${field.name}', this.value, 'text', ${field.required})"
4568
4306
  ${requiredAttr}
4569
4307
  >${fieldValueStr}</textarea>
4570
4308
  `;
@@ -4574,12 +4312,12 @@ function ObjectEditor(props) {
4574
4312
  <input
4575
4313
  type="number"
4576
4314
  id="${fieldId}"
4577
- name="${fieldName}.${field.name}"
4315
+ name="${name}.${field.name}"
4578
4316
  value="${fieldValueStr}"
4579
4317
  step="${step}"
4580
4318
  class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
4581
- data-testid="${fieldName}-input-${field.name}"
4582
- oninput="updateObjectField('${fieldName}', '${field.name}', this.value, 'number', ${field.required})"
4319
+ data-testid="${name}-input-${field.name}"
4320
+ oninput="updateObjectField('${name}', '${field.name}', this.value, 'number', ${field.required})"
4583
4321
  ${requiredAttr}
4584
4322
  />
4585
4323
  `;
@@ -4588,11 +4326,11 @@ function ObjectEditor(props) {
4588
4326
  <input
4589
4327
  type="date"
4590
4328
  id="${fieldId}"
4591
- name="${fieldName}.${field.name}"
4329
+ name="${name}.${field.name}"
4592
4330
  value="${fieldValueStr}"
4593
4331
  class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
4594
- data-testid="${fieldName}-input-${field.name}"
4595
- oninput="updateObjectField('${fieldName}', '${field.name}', this.value, 'date', ${field.required})"
4332
+ data-testid="${name}-input-${field.name}"
4333
+ oninput="updateObjectField('${name}', '${field.name}', this.value, 'date', ${field.required})"
4596
4334
  ${requiredAttr}
4597
4335
  />
4598
4336
  `;
@@ -4601,11 +4339,11 @@ function ObjectEditor(props) {
4601
4339
  <input
4602
4340
  type="email"
4603
4341
  id="${fieldId}"
4604
- name="${fieldName}.${field.name}"
4342
+ name="${name}.${field.name}"
4605
4343
  value="${fieldValueStr}"
4606
4344
  class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
4607
- data-testid="${fieldName}-input-${field.name}"
4608
- oninput="updateObjectField('${fieldName}', '${field.name}', this.value, 'text', ${field.required})"
4345
+ data-testid="${name}-input-${field.name}"
4346
+ oninput="updateObjectField('${name}', '${field.name}', this.value, 'text', ${field.required})"
4609
4347
  ${requiredAttr}
4610
4348
  />
4611
4349
  `;
@@ -4613,10 +4351,10 @@ function ObjectEditor(props) {
4613
4351
  inputElement = html`
4614
4352
  <select
4615
4353
  id="${fieldId}"
4616
- name="${fieldName}.${field.name}"
4354
+ name="${name}.${field.name}"
4617
4355
  class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white"
4618
- data-testid="${fieldName}-select-${field.name}"
4619
- onchange="updateObjectField('${fieldName}', '${field.name}', this.value, 'text', ${field.required})"
4356
+ data-testid="${name}-select-${field.name}"
4357
+ onchange="updateObjectField('${name}', '${field.name}', this.value, 'text', ${field.required})"
4620
4358
  ${requiredAttr}
4621
4359
  >
4622
4360
  ${!field.required ? html`<option value="">请选择</option>` : ""}
@@ -4639,11 +4377,11 @@ function ObjectEditor(props) {
4639
4377
  <input
4640
4378
  type="checkbox"
4641
4379
  id="${fieldId}"
4642
- name="${fieldName}.${field.name}"
4380
+ name="${name}.${field.name}"
4643
4381
  ${checked ? "checked" : ""}
4644
4382
  class="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
4645
- data-testid="${fieldName}-checkbox-${field.name}"
4646
- onchange="updateObjectField('${fieldName}', '${field.name}', this.checked, 'checkbox', ${field.required}')"
4383
+ data-testid="${name}-checkbox-${field.name}"
4384
+ onchange="updateObjectField('${name}', '${field.name}', this.checked, 'checkbox', ${field.required}')"
4647
4385
  />
4648
4386
  <label for="${fieldId}" class="ml-2 text-sm text-gray-700">
4649
4387
  ${field.label}
@@ -4652,12 +4390,12 @@ function ObjectEditor(props) {
4652
4390
  `;
4653
4391
  }
4654
4392
  return html`
4655
- <div class="space-y-2" data-testid="${fieldName}-field-${field.name}">
4393
+ <div class="space-y-2" data-testid="${name}-field-${field.name}">
4656
4394
  ${field.type !== "checkbox" ? html`
4657
4395
  <label
4658
4396
  for="${fieldId}"
4659
4397
  class="block text-sm font-semibold text-gray-700"
4660
- data-testid="${fieldName}-label-${field.name}"
4398
+ data-testid="${name}-label-${field.name}"
4661
4399
  >
4662
4400
  ${field.label}
4663
4401
  ${field.required ? html`<span class="text-red-500 ml-1">*</span>` : ""}
@@ -4669,15 +4407,15 @@ function ObjectEditor(props) {
4669
4407
  };
4670
4408
  return html`
4671
4409
  <div
4672
- id="object-editor-${fieldName}"
4410
+ id="object-editor-${name}"
4673
4411
  class="space-y-4"
4674
4412
  data-initial-value="${initialValueJson}"
4675
4413
  >
4676
4414
  <input
4677
4415
  type="hidden"
4678
- name="${fieldName}"
4416
+ name="${name}"
4679
4417
  value="${initialValueJson}"
4680
- data-testid="hidden-${fieldName}"
4418
+ data-testid="hidden-${name}"
4681
4419
  />
4682
4420
  <div class="space-y-4">
4683
4421
  ${fields.map((field) => generateField(field))}
@@ -4739,14 +4477,13 @@ function BaseLayout(props) {
4739
4477
  <meta name="description" content="${props.description || ""}" />
4740
4478
  ${globalScripts(props.prefix)} ${globalStyles()}
4741
4479
  </head>
4742
- <body hx-ext="morph" className="bg-gray-50" hx-indicator="#loading-bar">
4480
+ <body className="bg-gray-50" hx-indicator="#loading-bar">
4743
4481
  ${LoadingBar()} ${props.children}
4744
4482
  <div
4745
- id="error-container"
4746
- className="fixed top-4 right-4 z-[200] w-full max-w-2xl px-4"
4483
+ id="notification-container"
4484
+ style="position: fixed; top: 1rem; right: 1rem; z-index: 200; display: flex; flex-direction: column; gap: 0.5rem; pointer-events: none; width: 100%; max-width: 28rem; padding-left: 1rem; padding-right: 1rem;"
4747
4485
  ></div>
4748
4486
  <div id="dialog-container"></div>
4749
- ${sortableScript()}
4750
4487
  </body>
4751
4488
  </html>
4752
4489
  `;
@@ -4998,7 +4735,7 @@ async function handlePermissionDenied(ctx, result, options) {
4998
4735
  if (isHtmxRequest) {
4999
4736
  const headers = {
5000
4737
  "HX-Retarget": "#dialog-container",
5001
- "HX-Reswap": "innerHTML",
4738
+ "HX-Reswap": "beforeend",
5002
4739
  "X-Permission-Denied": "true"
5003
4740
  };
5004
4741
  return ctx.html(
@@ -5043,22 +4780,15 @@ function buildNotificationFragments(notifications) {
5043
4780
  if (notifications.length === 0) {
5044
4781
  return null;
5045
4782
  }
5046
- return notifications.map((notification, index) => /* @__PURE__ */ jsx(
5047
- "div",
4783
+ return notifications.map((notification, index) => /* @__PURE__ */ jsx("div", { id: "notification-container", "hx-swap-oob": "beforeend", children: /* @__PURE__ */ jsx(
4784
+ ErrorAlert,
5048
4785
  {
5049
- id: "error-container",
5050
- "hx-swap-oob": "beforeend",
5051
- children: /* @__PURE__ */ jsx(
5052
- ErrorAlert,
5053
- {
5054
- type: notification.type,
5055
- title: notification.title,
5056
- message: notification.message
5057
- }
5058
- )
4786
+ type: notification.type,
4787
+ title: notification.title,
4788
+ message: notification.message
5059
4789
  },
5060
4790
  `notification-${index}`
5061
- ));
4791
+ ) }));
5062
4792
  }
5063
4793
  function isEmptyContent(result) {
5064
4794
  if (result === null || result === void 0) return true;
@@ -5152,7 +4882,6 @@ async function renderResult(ctx, context, result, renderOptions) {
5152
4882
  if (context.isDialog) {
5153
4883
  return ctx.html(
5154
4884
  /* @__PURE__ */ jsxs(Fragment, { children: [
5155
- /* @__PURE__ */ jsx("div", { id: "dialog-container", "hx-swap-oob": "innerHTML" }),
5156
4885
  notificationFragments,
5157
4886
  /* @__PURE__ */ jsx("title", { children: metadata.title })
5158
4887
  ] }),
@@ -5208,7 +4937,7 @@ async function renderResult(ctx, context, result, renderOptions) {
5208
4937
  );
5209
4938
  }
5210
4939
  const target = context.isDialog ? "#dialog-container" : "#main-content";
5211
- const swap = context.isDialog ? "innerHTML" : "outerHTML";
4940
+ const swap = context.isDialog ? "beforeend" : "outerHTML";
5212
4941
  const headers = {
5213
4942
  "HX-Retarget": target,
5214
4943
  "HX-Reswap": swap
@@ -5271,7 +5000,6 @@ async function renderResult(ctx, context, result, renderOptions) {
5271
5000
  useAdminLayout,
5272
5001
  currentPath,
5273
5002
  userInfo: user,
5274
- componentRegistry: renderOptions.componentRegistry,
5275
5003
  breadcrumbs,
5276
5004
  actions,
5277
5005
  children: result
@@ -5307,7 +5035,6 @@ async function renderResult(ctx, context, result, renderOptions) {
5307
5035
  prefix: options.prefix,
5308
5036
  title: dynamicMetadata.title,
5309
5037
  description: dynamicMetadata.description,
5310
- componentRegistry: renderOptions.componentRegistry,
5311
5038
  children: /* @__PURE__ */ jsx(
5312
5039
  AdminLayout,
5313
5040
  {
@@ -5319,7 +5046,6 @@ async function renderResult(ctx, context, result, renderOptions) {
5319
5046
  userInfo: user,
5320
5047
  breadcrumbs,
5321
5048
  actions,
5322
- componentRegistry: renderOptions.componentRegistry,
5323
5049
  children: result
5324
5050
  }
5325
5051
  )
@@ -5334,7 +5060,6 @@ async function renderResult(ctx, context, result, renderOptions) {
5334
5060
  prefix: options.prefix,
5335
5061
  title: dynamicMetadata.title,
5336
5062
  description: dynamicMetadata.description,
5337
- componentRegistry: renderOptions.componentRegistry,
5338
5063
  children: /* @__PURE__ */ jsx(NoLayout, { children: result })
5339
5064
  }
5340
5065
  )
@@ -5423,14 +5148,15 @@ async function handleRequest(ctx, page, feature, handlerOptions) {
5423
5148
  if (isHtmxRequest) {
5424
5149
  return ctx.html(
5425
5150
  /* @__PURE__ */ jsxs(Fragment, { children: [
5426
- /* @__PURE__ */ jsx("div", { id: "error-container", "hx-swap-oob": "beforeend", children: /* @__PURE__ */ jsx(
5151
+ /* @__PURE__ */ jsx(
5427
5152
  ErrorAlert,
5428
5153
  {
5154
+ "hx-swap-oob": "beforeend:#notification-container",
5429
5155
  type: "error",
5430
5156
  title: "\u8BF7\u6C42\u5931\u8D25",
5431
5157
  message: "Feature has no handler or render method"
5432
5158
  }
5433
- ) }),
5159
+ ),
5434
5160
  /* @__PURE__ */ jsx("title", { children: "\u9519\u8BEF" })
5435
5161
  ] }),
5436
5162
  500,
@@ -5447,10 +5173,15 @@ async function handleRequest(ctx, page, feature, handlerOptions) {
5447
5173
  if (isHtmxRequest) {
5448
5174
  const errorMessage = error instanceof Error ? error.message : "Internal server error";
5449
5175
  return ctx.html(
5450
- /* @__PURE__ */ jsxs(Fragment, { children: [
5451
- /* @__PURE__ */ jsx("div", { id: "error-container", "hx-swap-oob": "beforeend", children: /* @__PURE__ */ jsx(ErrorAlert, { type: "error", title: "\u8BF7\u6C42\u5931\u8D25", message: errorMessage }) }),
5452
- /* @__PURE__ */ jsx("title", { children: "\u9519\u8BEF" })
5453
- ] }),
5176
+ /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
5177
+ ErrorAlert,
5178
+ {
5179
+ "hx-swap-oob": "beforeend:#notification-container",
5180
+ type: "error",
5181
+ title: "\u8BF7\u6C42\u5931\u8D25",
5182
+ message: errorMessage
5183
+ }
5184
+ ) }),
5454
5185
  500,
5455
5186
  {
5456
5187
  "HX-Reswap": "none"
@@ -5553,7 +5284,6 @@ var HtmxAdminPlugin = class {
5553
5284
  options;
5554
5285
  serviceName = "";
5555
5286
  pages = /* @__PURE__ */ new Map();
5556
- componentHandler;
5557
5287
  constructor(options) {
5558
5288
  this.options = {
5559
5289
  title: options?.title || "\u7BA1\u7406\u540E\u53F0",
@@ -5562,8 +5292,7 @@ var HtmxAdminPlugin = class {
5562
5292
  homePath: options?.homePath || "",
5563
5293
  navigation: options?.navigation ?? [],
5564
5294
  authProvider: options?.authProvider,
5565
- pages: options?.pages ?? [],
5566
- components: options?.components ?? []
5295
+ pages: options?.pages ?? []
5567
5296
  };
5568
5297
  this.initPages();
5569
5298
  }
@@ -5595,11 +5324,6 @@ var HtmxAdminPlugin = class {
5595
5324
  logger.info(
5596
5325
  `HtmxAdminPlugin initialized${this.serviceName ? ` (service: ${this.serviceName})` : ""}`
5597
5326
  );
5598
- this.componentHandler = new HtmxComponentHandler(
5599
- this.hono,
5600
- this.options.prefix,
5601
- this.options.components
5602
- );
5603
5327
  initializeCdnCache().catch((error) => {
5604
5328
  logger.error("[HtmxAdminPlugin] CDN \u7F13\u5B58\u521D\u59CB\u5316\u5931\u8D25", error);
5605
5329
  });
@@ -5621,4 +5345,4 @@ var HtmxAdminPlugin = class {
5621
5345
  }
5622
5346
  };
5623
5347
 
5624
- export { BaseFeature, ComponentContext, CustomFeature, DefaultCreateFeature, DefaultDeleteFeature, DefaultDetailFeature, DefaultEditFeature, DefaultListFeature, Dialog, ErrorAlert, HtmxAdminPlugin, HtmxComponent, LoadingBar, Method, ObjectEditor, PageModel, RenderContext, SortableList, StringArrayEditor, TagsEditor, checkUserPermission, getUserInfo, modelNameToPath, parseListParams };
5348
+ export { BaseFeature, BaseFormFeature, Breadcrumb, Button, Card, CustomFeature, DefaultCreateFeature, DefaultDeleteFeature, DefaultDetailFeature, DefaultEditFeature, DefaultListFeature, Dialog, EmptyState, ErrorAlert, FilterForm, Header, HtmxAdminPlugin, LoadingBar, ObjectEditor, PageModel, Pagination, PermissionDeniedContent, PermissionDeniedPage, SortableList, StringArrayEditor, Table, TagsEditor, checkUserPermission, getUserInfo, modelNameToPath, parseListParams };