@btst/stack 2.5.4 → 2.5.6

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.
@@ -20,6 +20,7 @@ const card = require('../../../components/card.cjs');
20
20
  const breakpointClassnameControl = require('../../../components/ui-builder/internal/form-fields/classname-control/breakpoint-classname-control.cjs');
21
21
  const label = require('../../../components/label.cjs');
22
22
  const badge = require('../../../components/badge.cjs');
23
+ const select = require('../../../components/select.cjs');
23
24
 
24
25
  const classNameFieldOverrides = (layer) => {
25
26
  return {
@@ -167,6 +168,210 @@ const commonFieldOverrides = (allowBinding = true) => {
167
168
  memoizedCommonFieldOverrides.set(allowBinding, overrides);
168
169
  return overrides;
169
170
  };
171
+ function FunctionPropField({
172
+ propName,
173
+ label,
174
+ isRequired,
175
+ fieldConfigItem
176
+ }) {
177
+ const selectedLayerId = layerStore.useLayerStore((state) => state.selectedLayerId);
178
+ const findLayerById = layerStore.useLayerStore((state) => state.findLayerById);
179
+ const updateLayer = layerStore.useLayerStore((state) => state.updateLayer);
180
+ const incrementRevision = editorStore.useEditorStore((state) => state.incrementRevision);
181
+ const functionRegistry = editorStore.useEditorStore((state) => state.functionRegistry);
182
+ const unbindPropFromVariable = layerStore.useLayerStore(
183
+ (state) => state.unbindPropFromVariable
184
+ );
185
+ const selectedLayer = findLayerById(selectedLayerId);
186
+ if (!selectedLayer || !functionRegistry) {
187
+ return /* @__PURE__ */ jsxRuntime.jsx(
188
+ FormFieldWrapper,
189
+ {
190
+ label,
191
+ isRequired,
192
+ fieldConfigItem,
193
+ children: /* @__PURE__ */ jsxRuntime.jsx(select.Select, { disabled: true, children: /* @__PURE__ */ jsxRuntime.jsx(select.SelectTrigger, { children: /* @__PURE__ */ jsxRuntime.jsx(select.SelectValue, { placeholder: "No function registry available" }) }) })
194
+ }
195
+ );
196
+ }
197
+ const functionEntries = Object.entries(functionRegistry);
198
+ const getCurrentFunctionId = () => {
199
+ const directFuncId = selectedLayer.props[`__function_${propName}`];
200
+ if (typeof directFuncId === "string") {
201
+ return directFuncId;
202
+ }
203
+ return "";
204
+ };
205
+ const handleValueChange = (value) => {
206
+ if (value === "__none__") {
207
+ unbindPropFromVariable(selectedLayer.id, propName);
208
+ updateLayer(selectedLayer.id, {
209
+ [`__function_${propName}`]: void 0,
210
+ [propName]: void 0
211
+ });
212
+ incrementRevision();
213
+ return;
214
+ }
215
+ const funcDef = functionRegistry[value];
216
+ if (funcDef) {
217
+ unbindPropFromVariable(selectedLayer.id, propName);
218
+ updateLayer(selectedLayer.id, {
219
+ [propName]: funcDef.fn,
220
+ [`__function_${propName}`]: value
221
+ });
222
+ incrementRevision();
223
+ }
224
+ };
225
+ const currentFunctionId = getCurrentFunctionId();
226
+ const getDisplayText = () => {
227
+ if (currentFunctionId) {
228
+ const funcDef = functionRegistry[currentFunctionId];
229
+ return funcDef?.name || currentFunctionId;
230
+ }
231
+ return "Select a function...";
232
+ };
233
+ return /* @__PURE__ */ jsxRuntime.jsx(
234
+ FormFieldWrapper,
235
+ {
236
+ label,
237
+ isRequired,
238
+ fieldConfigItem,
239
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
240
+ select.Select,
241
+ {
242
+ value: currentFunctionId,
243
+ onValueChange: handleValueChange,
244
+ children: [
245
+ /* @__PURE__ */ jsxRuntime.jsx(select.SelectTrigger, { className: "w-full mb-0", children: /* @__PURE__ */ jsxRuntime.jsx(select.SelectValue, { placeholder: "Select a function...", children: getDisplayText() }) }),
246
+ /* @__PURE__ */ jsxRuntime.jsxs(select.SelectContent, { children: [
247
+ /* @__PURE__ */ jsxRuntime.jsx(select.SelectItem, { value: "__none__", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "None (clear)" }) }),
248
+ functionEntries.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: functionEntries.map(([id, funcDef]) => /* @__PURE__ */ jsxRuntime.jsx(select.SelectItem, { value: id, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
249
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: funcDef.name }),
250
+ funcDef.description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: funcDef.description })
251
+ ] }) }, id)) })
252
+ ] })
253
+ ]
254
+ }
255
+ )
256
+ }
257
+ );
258
+ }
259
+ const functionPropFieldOverrides = (propName) => {
260
+ return {
261
+ renderParent: ({ children }) => /* @__PURE__ */ jsxRuntime.jsx(VariableBindingWrapper, { propName, isFunctionProp: true, children }),
262
+ fieldType: (props) => /* @__PURE__ */ jsxRuntime.jsx(FunctionPropField, { propName, ...props })
263
+ };
264
+ };
265
+ function VariableBindingWrapper({
266
+ propName,
267
+ children,
268
+ isFunctionProp = false
269
+ }) {
270
+ const variables = layerStore.useLayerStore((state) => state.variables);
271
+ const selectedLayerId = layerStore.useLayerStore((state) => state.selectedLayerId);
272
+ const findLayerById = layerStore.useLayerStore((state) => state.findLayerById);
273
+ const isBindingImmutable = layerStore.useLayerStore((state) => state.isBindingImmutable);
274
+ const incrementRevision = editorStore.useEditorStore((state) => state.incrementRevision);
275
+ const functionRegistry = editorStore.useEditorStore((state) => state.functionRegistry);
276
+ const unbindPropFromVariable = layerStore.useLayerStore(
277
+ (state) => state.unbindPropFromVariable
278
+ );
279
+ const bindPropToVariable = layerStore.useLayerStore((state) => state.bindPropToVariable);
280
+ const selectedLayer = findLayerById(selectedLayerId);
281
+ if (!selectedLayer) {
282
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
283
+ }
284
+ const filteredVariables = variables.filter(
285
+ (v) => isFunctionProp ? v.type === "function" : v.type !== "function"
286
+ );
287
+ const currentValue = selectedLayer.props[propName];
288
+ const isCurrentlyBound = types.isVariableReference(currentValue);
289
+ const boundVariable = isCurrentlyBound ? variables.find((v) => v.id === currentValue.__variableRef) : null;
290
+ const isImmutable = isBindingImmutable(selectedLayer.id, propName);
291
+ const getFunctionDisplayValue = (variable) => {
292
+ if (variable.type === "function" && functionRegistry) {
293
+ const funcId = String(variable.defaultValue);
294
+ const funcDef = functionRegistry[funcId];
295
+ return funcDef ? funcDef.name : funcId;
296
+ }
297
+ return String(variable.defaultValue);
298
+ };
299
+ const handleBindToVariable = (variableId) => {
300
+ bindPropToVariable(selectedLayer.id, propName, variableId);
301
+ incrementRevision();
302
+ };
303
+ const handleUnbind = () => {
304
+ unbindPropFromVariable(selectedLayer.id, propName);
305
+ incrementRevision();
306
+ };
307
+ const emptyMessage = isFunctionProp ? "No function variables defined" : "No variables defined";
308
+ const bindLabel = isFunctionProp ? "Bind to Function Variable" : "Bind to Variable";
309
+ const tooltipLabel = isFunctionProp ? "Bind Function" : "Bind Variable";
310
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex w-full gap-2 items-end", children: isCurrentlyBound && boundVariable ? (
311
+ // Bound state - show variable info and unbind button
312
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 w-full", children: [
313
+ /* @__PURE__ */ jsxRuntime.jsx(label.Label, { children: propName.charAt(0).toUpperCase() + propName.slice(1) }),
314
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-end gap-2 w-full", children: [
315
+ /* @__PURE__ */ jsxRuntime.jsx(card.Card, { className: "w-full overflow-hidden min-w-0", children: /* @__PURE__ */ jsxRuntime.jsx(card.CardContent, { className: "py-1 px-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 w-full", children: [
316
+ /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Link, { className: "h-4 w-4 flex-shrink-0" }),
317
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col min-w-0 flex-1", children: [
318
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 w-full", children: [
319
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: boundVariable.name }),
320
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-1.5 py-0.5 bg-muted rounded text-xs font-mono", children: boundVariable.type }),
321
+ isImmutable && /* @__PURE__ */ jsxRuntime.jsx(badge.Badge, { "data-testid": "immutable-badge", className: "rounded", children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.LockKeyhole, { strokeWidth: 3, className: "w-3 h-3" }) })
322
+ ] }),
323
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground truncate", children: getFunctionDisplayValue(boundVariable) })
324
+ ] })
325
+ ] }) }) }),
326
+ !isImmutable && /* @__PURE__ */ jsxRuntime.jsxs(tooltip.Tooltip, { children: [
327
+ /* @__PURE__ */ jsxRuntime.jsx(tooltip.TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
328
+ button.Button,
329
+ {
330
+ variant: "outline",
331
+ onClick: handleUnbind,
332
+ className: "px-3 h-10",
333
+ "data-testid": "unbind-variable-button",
334
+ children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Unlink, { className: "h-4 w-4" })
335
+ }
336
+ ) }),
337
+ /* @__PURE__ */ jsxRuntime.jsx(tooltip.TooltipContent, { children: "Unbind Variable" })
338
+ ] })
339
+ ] })
340
+ ] })
341
+ ) : (
342
+ // Unbound state - show normal field with bind button
343
+ /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
344
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children }),
345
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsxs(dropdownMenu.DropdownMenu, { children: [
346
+ /* @__PURE__ */ jsxRuntime.jsxs(tooltip.Tooltip, { children: [
347
+ /* @__PURE__ */ jsxRuntime.jsx(dropdownMenu.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(tooltip.TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(button.Button, { variant: "outline", size: "sm", className: "px-3 h-10", "data-testid": "bind-variable-button", children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Link, { className: "h-4 w-4 my-1" }) }) }) }),
348
+ /* @__PURE__ */ jsxRuntime.jsx(tooltip.TooltipContent, { children: tooltipLabel })
349
+ ] }),
350
+ /* @__PURE__ */ jsxRuntime.jsxs(dropdownMenu.DropdownMenuContent, { align: "end", className: "w-56", children: [
351
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 py-1.5 text-xs font-medium text-muted-foreground border-b", children: bindLabel }),
352
+ filteredVariables.length > 0 ? filteredVariables.map((variable) => /* @__PURE__ */ jsxRuntime.jsx(
353
+ dropdownMenu.DropdownMenuItem,
354
+ {
355
+ onClick: () => handleBindToVariable(variable.id),
356
+ className: "flex flex-col items-start p-3",
357
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 w-full", children: [
358
+ /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Link, { className: "h-4 w-4 flex-shrink-0" }),
359
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col min-w-0 flex-1", children: [
360
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 ", children: [
361
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: variable.name }),
362
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-1.5 py-0.5 bg-muted rounded text-xs font-mono", children: variable.type })
363
+ ] }),
364
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground truncate", children: getFunctionDisplayValue(variable) })
365
+ ] })
366
+ ] })
367
+ },
368
+ variable.id
369
+ )) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-2 text-xs text-muted-foreground", children: emptyMessage })
370
+ ] })
371
+ ] }) })
372
+ ] })
373
+ ) });
374
+ }
170
375
  function FormFieldWrapper({
171
376
  label,
172
377
  isRequired,
@@ -278,9 +483,11 @@ function ChildrenVariableBindingWrapper({
278
483
 
279
484
  exports.ChildrenVariableBindingWrapper = ChildrenVariableBindingWrapper;
280
485
  exports.FormFieldWrapper = FormFieldWrapper;
486
+ exports.VariableBindingWrapper = VariableBindingWrapper;
281
487
  exports.childrenAsTextareaFieldOverrides = childrenAsTextareaFieldOverrides;
282
488
  exports.childrenAsTipTapFieldOverrides = childrenAsTipTapFieldOverrides;
283
489
  exports.childrenFieldOverrides = childrenFieldOverrides;
284
490
  exports.classNameFieldOverrides = classNameFieldOverrides;
285
491
  exports.commonFieldOverrides = commonFieldOverrides;
492
+ exports.functionPropFieldOverrides = functionPropFieldOverrides;
286
493
  exports.iconNameFieldOverrides = iconNameFieldOverrides;
@@ -18,6 +18,7 @@ import { Card, CardContent } from '../../../components/card.mjs';
18
18
  import { BreakpointClassNameControl } from '../../../components/ui-builder/internal/form-fields/classname-control/breakpoint-classname-control.mjs';
19
19
  import { Label } from '../../../components/label.mjs';
20
20
  import { Badge } from '../../../components/badge.mjs';
21
+ import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '../../../components/select.mjs';
21
22
 
22
23
  const classNameFieldOverrides = (layer) => {
23
24
  return {
@@ -165,6 +166,210 @@ const commonFieldOverrides = (allowBinding = true) => {
165
166
  memoizedCommonFieldOverrides.set(allowBinding, overrides);
166
167
  return overrides;
167
168
  };
169
+ function FunctionPropField({
170
+ propName,
171
+ label,
172
+ isRequired,
173
+ fieldConfigItem
174
+ }) {
175
+ const selectedLayerId = useLayerStore((state) => state.selectedLayerId);
176
+ const findLayerById = useLayerStore((state) => state.findLayerById);
177
+ const updateLayer = useLayerStore((state) => state.updateLayer);
178
+ const incrementRevision = useEditorStore((state) => state.incrementRevision);
179
+ const functionRegistry = useEditorStore((state) => state.functionRegistry);
180
+ const unbindPropFromVariable = useLayerStore(
181
+ (state) => state.unbindPropFromVariable
182
+ );
183
+ const selectedLayer = findLayerById(selectedLayerId);
184
+ if (!selectedLayer || !functionRegistry) {
185
+ return /* @__PURE__ */ jsx(
186
+ FormFieldWrapper,
187
+ {
188
+ label,
189
+ isRequired,
190
+ fieldConfigItem,
191
+ children: /* @__PURE__ */ jsx(Select, { disabled: true, children: /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "No function registry available" }) }) })
192
+ }
193
+ );
194
+ }
195
+ const functionEntries = Object.entries(functionRegistry);
196
+ const getCurrentFunctionId = () => {
197
+ const directFuncId = selectedLayer.props[`__function_${propName}`];
198
+ if (typeof directFuncId === "string") {
199
+ return directFuncId;
200
+ }
201
+ return "";
202
+ };
203
+ const handleValueChange = (value) => {
204
+ if (value === "__none__") {
205
+ unbindPropFromVariable(selectedLayer.id, propName);
206
+ updateLayer(selectedLayer.id, {
207
+ [`__function_${propName}`]: void 0,
208
+ [propName]: void 0
209
+ });
210
+ incrementRevision();
211
+ return;
212
+ }
213
+ const funcDef = functionRegistry[value];
214
+ if (funcDef) {
215
+ unbindPropFromVariable(selectedLayer.id, propName);
216
+ updateLayer(selectedLayer.id, {
217
+ [propName]: funcDef.fn,
218
+ [`__function_${propName}`]: value
219
+ });
220
+ incrementRevision();
221
+ }
222
+ };
223
+ const currentFunctionId = getCurrentFunctionId();
224
+ const getDisplayText = () => {
225
+ if (currentFunctionId) {
226
+ const funcDef = functionRegistry[currentFunctionId];
227
+ return funcDef?.name || currentFunctionId;
228
+ }
229
+ return "Select a function...";
230
+ };
231
+ return /* @__PURE__ */ jsx(
232
+ FormFieldWrapper,
233
+ {
234
+ label,
235
+ isRequired,
236
+ fieldConfigItem,
237
+ children: /* @__PURE__ */ jsxs(
238
+ Select,
239
+ {
240
+ value: currentFunctionId,
241
+ onValueChange: handleValueChange,
242
+ children: [
243
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "w-full mb-0", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Select a function...", children: getDisplayText() }) }),
244
+ /* @__PURE__ */ jsxs(SelectContent, { children: [
245
+ /* @__PURE__ */ jsx(SelectItem, { value: "__none__", children: /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "None (clear)" }) }),
246
+ functionEntries.length > 0 && /* @__PURE__ */ jsx(Fragment, { children: functionEntries.map(([id, funcDef]) => /* @__PURE__ */ jsx(SelectItem, { value: id, children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
247
+ /* @__PURE__ */ jsx("span", { children: funcDef.name }),
248
+ funcDef.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: funcDef.description })
249
+ ] }) }, id)) })
250
+ ] })
251
+ ]
252
+ }
253
+ )
254
+ }
255
+ );
256
+ }
257
+ const functionPropFieldOverrides = (propName) => {
258
+ return {
259
+ renderParent: ({ children }) => /* @__PURE__ */ jsx(VariableBindingWrapper, { propName, isFunctionProp: true, children }),
260
+ fieldType: (props) => /* @__PURE__ */ jsx(FunctionPropField, { propName, ...props })
261
+ };
262
+ };
263
+ function VariableBindingWrapper({
264
+ propName,
265
+ children,
266
+ isFunctionProp = false
267
+ }) {
268
+ const variables = useLayerStore((state) => state.variables);
269
+ const selectedLayerId = useLayerStore((state) => state.selectedLayerId);
270
+ const findLayerById = useLayerStore((state) => state.findLayerById);
271
+ const isBindingImmutable = useLayerStore((state) => state.isBindingImmutable);
272
+ const incrementRevision = useEditorStore((state) => state.incrementRevision);
273
+ const functionRegistry = useEditorStore((state) => state.functionRegistry);
274
+ const unbindPropFromVariable = useLayerStore(
275
+ (state) => state.unbindPropFromVariable
276
+ );
277
+ const bindPropToVariable = useLayerStore((state) => state.bindPropToVariable);
278
+ const selectedLayer = findLayerById(selectedLayerId);
279
+ if (!selectedLayer) {
280
+ return /* @__PURE__ */ jsx(Fragment, { children });
281
+ }
282
+ const filteredVariables = variables.filter(
283
+ (v) => isFunctionProp ? v.type === "function" : v.type !== "function"
284
+ );
285
+ const currentValue = selectedLayer.props[propName];
286
+ const isCurrentlyBound = isVariableReference(currentValue);
287
+ const boundVariable = isCurrentlyBound ? variables.find((v) => v.id === currentValue.__variableRef) : null;
288
+ const isImmutable = isBindingImmutable(selectedLayer.id, propName);
289
+ const getFunctionDisplayValue = (variable) => {
290
+ if (variable.type === "function" && functionRegistry) {
291
+ const funcId = String(variable.defaultValue);
292
+ const funcDef = functionRegistry[funcId];
293
+ return funcDef ? funcDef.name : funcId;
294
+ }
295
+ return String(variable.defaultValue);
296
+ };
297
+ const handleBindToVariable = (variableId) => {
298
+ bindPropToVariable(selectedLayer.id, propName, variableId);
299
+ incrementRevision();
300
+ };
301
+ const handleUnbind = () => {
302
+ unbindPropFromVariable(selectedLayer.id, propName);
303
+ incrementRevision();
304
+ };
305
+ const emptyMessage = isFunctionProp ? "No function variables defined" : "No variables defined";
306
+ const bindLabel = isFunctionProp ? "Bind to Function Variable" : "Bind to Variable";
307
+ const tooltipLabel = isFunctionProp ? "Bind Function" : "Bind Variable";
308
+ return /* @__PURE__ */ jsx("div", { className: "flex w-full gap-2 items-end", children: isCurrentlyBound && boundVariable ? (
309
+ // Bound state - show variable info and unbind button
310
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 w-full", children: [
311
+ /* @__PURE__ */ jsx(Label, { children: propName.charAt(0).toUpperCase() + propName.slice(1) }),
312
+ /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-2 w-full", children: [
313
+ /* @__PURE__ */ jsx(Card, { className: "w-full overflow-hidden min-w-0", children: /* @__PURE__ */ jsx(CardContent, { className: "py-1 px-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 w-full", children: [
314
+ /* @__PURE__ */ jsx(Link, { className: "h-4 w-4 flex-shrink-0" }),
315
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col min-w-0 flex-1", children: [
316
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 w-full", children: [
317
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: boundVariable.name }),
318
+ /* @__PURE__ */ jsx("span", { className: "px-1.5 py-0.5 bg-muted rounded text-xs font-mono", children: boundVariable.type }),
319
+ isImmutable && /* @__PURE__ */ jsx(Badge, { "data-testid": "immutable-badge", className: "rounded", children: /* @__PURE__ */ jsx(LockKeyhole, { strokeWidth: 3, className: "w-3 h-3" }) })
320
+ ] }),
321
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground truncate", children: getFunctionDisplayValue(boundVariable) })
322
+ ] })
323
+ ] }) }) }),
324
+ !isImmutable && /* @__PURE__ */ jsxs(Tooltip, { children: [
325
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
326
+ Button,
327
+ {
328
+ variant: "outline",
329
+ onClick: handleUnbind,
330
+ className: "px-3 h-10",
331
+ "data-testid": "unbind-variable-button",
332
+ children: /* @__PURE__ */ jsx(Unlink, { className: "h-4 w-4" })
333
+ }
334
+ ) }),
335
+ /* @__PURE__ */ jsx(TooltipContent, { children: "Unbind Variable" })
336
+ ] })
337
+ ] })
338
+ ] })
339
+ ) : (
340
+ // Unbound state - show normal field with bind button
341
+ /* @__PURE__ */ jsxs(Fragment, { children: [
342
+ /* @__PURE__ */ jsx("div", { className: "flex-1", children }),
343
+ /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [
344
+ /* @__PURE__ */ jsxs(Tooltip, { children: [
345
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", className: "px-3 h-10", "data-testid": "bind-variable-button", children: /* @__PURE__ */ jsx(Link, { className: "h-4 w-4 my-1" }) }) }) }),
346
+ /* @__PURE__ */ jsx(TooltipContent, { children: tooltipLabel })
347
+ ] }),
348
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", className: "w-56", children: [
349
+ /* @__PURE__ */ jsx("div", { className: "px-2 py-1.5 text-xs font-medium text-muted-foreground border-b", children: bindLabel }),
350
+ filteredVariables.length > 0 ? filteredVariables.map((variable) => /* @__PURE__ */ jsx(
351
+ DropdownMenuItem,
352
+ {
353
+ onClick: () => handleBindToVariable(variable.id),
354
+ className: "flex flex-col items-start p-3",
355
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 w-full", children: [
356
+ /* @__PURE__ */ jsx(Link, { className: "h-4 w-4 flex-shrink-0" }),
357
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col min-w-0 flex-1", children: [
358
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 ", children: [
359
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: variable.name }),
360
+ /* @__PURE__ */ jsx("span", { className: "px-1.5 py-0.5 bg-muted rounded text-xs font-mono", children: variable.type })
361
+ ] }),
362
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground truncate", children: getFunctionDisplayValue(variable) })
363
+ ] })
364
+ ] })
365
+ },
366
+ variable.id
367
+ )) : /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-xs text-muted-foreground", children: emptyMessage })
368
+ ] })
369
+ ] }) })
370
+ ] })
371
+ ) });
372
+ }
168
373
  function FormFieldWrapper({
169
374
  label,
170
375
  isRequired,
@@ -274,4 +479,4 @@ function ChildrenVariableBindingWrapper({
274
479
  ) });
275
480
  }
276
481
 
277
- export { ChildrenVariableBindingWrapper, FormFieldWrapper, childrenAsTextareaFieldOverrides, childrenAsTipTapFieldOverrides, childrenFieldOverrides, classNameFieldOverrides, commonFieldOverrides, iconNameFieldOverrides };
482
+ export { ChildrenVariableBindingWrapper, FormFieldWrapper, VariableBindingWrapper, childrenAsTextareaFieldOverrides, childrenAsTipTapFieldOverrides, childrenFieldOverrides, classNameFieldOverrides, commonFieldOverrides, functionPropFieldOverrides, iconNameFieldOverrides };
@@ -4,7 +4,7 @@ import react__default, { ComponentType } from 'react';
4
4
  import * as _btst_yar from '@btst/yar';
5
5
  import { QueryClient } from '@tanstack/react-query';
6
6
  import { c as UIBuilderClientHooks } from '../../../shared/stack.Cl7ok_cY.cjs';
7
- import { a as ComponentRegistry, b as FieldConfigFunction, C as ComponentLayer } from '../../../shared/stack.C5ZSOJGJ.cjs';
7
+ import { a as ComponentRegistry, b as FieldConfigFunction, C as ComponentLayer, F as FunctionRegistry } from '../../../shared/stack.C5ZSOJGJ.cjs';
8
8
  import * as react_jsx_runtime from 'react/jsx-runtime';
9
9
  import { A as AutoFormInputComponentProps, F as FieldConfigItem } from '../../../shared/stack.8nldKomx.cjs';
10
10
  export { PageBuilderPage, PageListPage, PageRenderer, PageRendererProps, SuspensePageRenderer } from './components/index.cjs';
@@ -342,6 +342,11 @@ interface UIBuilderPluginOverrides {
342
342
  * Component registry for the UI Builder
343
343
  */
344
344
  componentRegistry?: ComponentRegistry;
345
+ /**
346
+ * Function registry for resolving bindable event handlers (onClick, onSubmit, etc.)
347
+ * in the preview modal and layer renderer.
348
+ */
349
+ functionRegistry?: FunctionRegistry;
345
350
  /**
346
351
  * Base path for UI Builder admin pages (default: /pages/ui-builder)
347
352
  */
@@ -4,7 +4,7 @@ import react__default, { ComponentType } from 'react';
4
4
  import * as _btst_yar from '@btst/yar';
5
5
  import { QueryClient } from '@tanstack/react-query';
6
6
  import { c as UIBuilderClientHooks } from '../../../shared/stack.VMmQdbsJ.mjs';
7
- import { a as ComponentRegistry, b as FieldConfigFunction, C as ComponentLayer } from '../../../shared/stack.Dq4qVr1F.mjs';
7
+ import { a as ComponentRegistry, b as FieldConfigFunction, C as ComponentLayer, F as FunctionRegistry } from '../../../shared/stack.Dq4qVr1F.mjs';
8
8
  import * as react_jsx_runtime from 'react/jsx-runtime';
9
9
  import { A as AutoFormInputComponentProps, F as FieldConfigItem } from '../../../shared/stack.8nldKomx.mjs';
10
10
  export { PageBuilderPage, PageListPage, PageRenderer, PageRendererProps, SuspensePageRenderer } from './components/index.mjs';
@@ -342,6 +342,11 @@ interface UIBuilderPluginOverrides {
342
342
  * Component registry for the UI Builder
343
343
  */
344
344
  componentRegistry?: ComponentRegistry;
345
+ /**
346
+ * Function registry for resolving bindable event handlers (onClick, onSubmit, etc.)
347
+ * in the preview modal and layer renderer.
348
+ */
349
+ functionRegistry?: FunctionRegistry;
345
350
  /**
346
351
  * Base path for UI Builder admin pages (default: /pages/ui-builder)
347
352
  */
@@ -4,7 +4,7 @@ import react__default, { ComponentType } from 'react';
4
4
  import * as _btst_yar from '@btst/yar';
5
5
  import { QueryClient } from '@tanstack/react-query';
6
6
  import { c as UIBuilderClientHooks } from '../../../shared/stack.B2DwzF3r.js';
7
- import { a as ComponentRegistry, b as FieldConfigFunction, C as ComponentLayer } from '../../../shared/stack.D0QupDcQ.js';
7
+ import { a as ComponentRegistry, b as FieldConfigFunction, C as ComponentLayer, F as FunctionRegistry } from '../../../shared/stack.D0QupDcQ.js';
8
8
  import * as react_jsx_runtime from 'react/jsx-runtime';
9
9
  import { A as AutoFormInputComponentProps, F as FieldConfigItem } from '../../../shared/stack.8nldKomx.js';
10
10
  export { PageBuilderPage, PageListPage, PageRenderer, PageRendererProps, SuspensePageRenderer } from './components/index.js';
@@ -342,6 +342,11 @@ interface UIBuilderPluginOverrides {
342
342
  * Component registry for the UI Builder
343
343
  */
344
344
  componentRegistry?: ComponentRegistry;
345
+ /**
346
+ * Function registry for resolving bindable event handlers (onClick, onSubmit, etc.)
347
+ * in the preview modal and layer renderer.
348
+ */
349
+ functionRegistry?: FunctionRegistry;
345
350
  /**
346
351
  * Base path for UI Builder admin pages (default: /pages/ui-builder)
347
352
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btst/stack",
3
- "version": "2.5.4",
3
+ "version": "2.5.6",
4
4
  "description": "A composable, plugin-based library for building full-stack applications.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -215,6 +215,7 @@ function PageBuilderPageContent({
215
215
  navigate,
216
216
  Link,
217
217
  componentRegistry: customRegistry,
218
+ functionRegistry,
218
219
  } = usePluginOverrides<UIBuilderPluginOverrides>("ui-builder");
219
220
  const basePath = useBasePath();
220
221
 
@@ -463,6 +464,7 @@ function PageBuilderPageContent({
463
464
  }
464
465
  onVariablesChange={handleVariablesChange}
465
466
  componentRegistry={componentRegistry}
467
+ functionRegistry={functionRegistry}
466
468
  persistLayerStore={false}
467
469
  allowVariableEditing={true}
468
470
  allowPagesCreation={false}
@@ -1,5 +1,8 @@
1
1
  import type { ComponentType } from "react";
2
- import type { ComponentRegistry } from "@workspace/ui/components/ui-builder/types";
2
+ import type {
3
+ ComponentRegistry,
4
+ FunctionRegistry,
5
+ } from "@workspace/ui/components/ui-builder/types";
3
6
  import type { UIBuilderClientHooks } from "../types";
4
7
 
5
8
  /**
@@ -63,6 +66,12 @@ export interface UIBuilderPluginOverrides {
63
66
  */
64
67
  componentRegistry?: ComponentRegistry;
65
68
 
69
+ /**
70
+ * Function registry for resolving bindable event handlers (onClick, onSubmit, etc.)
71
+ * in the preview modal and layer renderer.
72
+ */
73
+ functionRegistry?: FunctionRegistry;
74
+
66
75
  /**
67
76
  * Base path for UI Builder admin pages (default: /pages/ui-builder)
68
77
  */