@bpmn-io/form-js-editor 1.20.1 → 1.21.1

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.
@@ -304,6 +304,7 @@
304
304
  display: flex;
305
305
  flex-direction: column;
306
306
  padding: 20px;
307
+ gap: 4px;
307
308
  }
308
309
 
309
310
  .fjs-editor-container .fjs-drop-container-horizontal {
@@ -281,6 +281,7 @@
281
281
  display: flex;
282
282
  flex-direction: column;
283
283
  padding: 20px;
284
+ gap: 4px;
284
285
  }
285
286
 
286
287
  .fjs-editor-container .fjs-drop-container-horizontal {
package/dist/index.cjs CHANGED
@@ -1373,7 +1373,26 @@ const Slot = props => {
1373
1373
  const fillsAndSeparators = hooks.useMemo(() => {
1374
1374
  return buildFills(groups, fillRoot, separatorFn);
1375
1375
  }, [groups, fillRoot, separatorFn]);
1376
- return fillsAndSeparators;
1376
+
1377
+ // Framework-agnostic fills from SlotFillManager
1378
+ const editorContext = hooks.useContext(FormEditorContext);
1379
+ const slotFillManager = editorContext ? editorContext.getService('slotFillManager', false) : null;
1380
+ const eventBus = editorContext ? editorContext.getService('eventBus', false) : null;
1381
+ const [managerFills, setManagerFills] = hooks.useState([]);
1382
+ hooks.useEffect(() => {
1383
+ if (!eventBus || !slotFillManager) {
1384
+ return;
1385
+ }
1386
+ setManagerFills(slotFillManager.getFills(name));
1387
+ const onChange = () => setManagerFills(slotFillManager.getFills(name));
1388
+ eventBus.on('slotFillManager.changed', onChange);
1389
+ return () => eventBus.off('slotFillManager.changed', onChange);
1390
+ }, [eventBus, slotFillManager, name]);
1391
+ return jsxRuntime.jsxs(preact.Fragment, {
1392
+ children: [fillsAndSeparators, managerFills.map(fill => jsxRuntime.jsx(FillContainer, {
1393
+ fill: fill
1394
+ }, fill.fillId))]
1395
+ });
1377
1396
  };
1378
1397
 
1379
1398
  /**
@@ -1386,6 +1405,34 @@ const FillFragment = fill => jsxRuntime.jsx(preact.Fragment, {
1386
1405
  children: fill.children
1387
1406
  }, fill.id);
1388
1407
 
1408
+ /**
1409
+ * Mounts a single SlotFillManager fill's render callback into a DOM container.
1410
+ */
1411
+ function FillContainer({
1412
+ fill
1413
+ }) {
1414
+ const containerRef = hooks.useRef(null);
1415
+ const cleanupRef = hooks.useRef(null);
1416
+ hooks.useEffect(() => {
1417
+ const container = containerRef.current;
1418
+ if (!container) {
1419
+ return;
1420
+ }
1421
+ cleanupRef.current = fill.render(container) || null;
1422
+ return () => {
1423
+ if (typeof cleanupRef.current === 'function') {
1424
+ cleanupRef.current();
1425
+ cleanupRef.current = null;
1426
+ }
1427
+ container.innerHTML = '';
1428
+ };
1429
+ }, [fill]);
1430
+ return jsxRuntime.jsx("div", {
1431
+ ref: containerRef,
1432
+ "data-slot-fill": fill.fillId
1433
+ });
1434
+ }
1435
+
1389
1436
  /**
1390
1437
  * Creates an array of fills, with separators inserted between groups.
1391
1438
  *
@@ -2264,6 +2311,8 @@ function EmptyForm() {
2264
2311
  children: "Drag and drop components here to start designing."
2265
2312
  }), jsxRuntime.jsx("span", {
2266
2313
  children: "Use the preview window to test your form."
2314
+ }), jsxRuntime.jsx(Slot, {
2315
+ name: "editor-empty-state__footer"
2267
2316
  })]
2268
2317
  })
2269
2318
  });
@@ -2569,7 +2618,8 @@ function FormEditor$1() {
2569
2618
  Element: Element$1,
2570
2619
  Empty,
2571
2620
  Row,
2572
- hoverInfo: {}
2621
+ hoverInfo: {},
2622
+ applyVisibilityConditions: false
2573
2623
  }), []);
2574
2624
  const formContext = hooks.useMemo(() => ({
2575
2625
  getService(type, strict = true) {
@@ -14269,9 +14319,162 @@ class RenderInjector extends SectionModuleBase {
14269
14319
  }
14270
14320
  RenderInjector.$inject = ['eventBus'];
14271
14321
 
14322
+ /**
14323
+ * Framework-agnostic service for managing slot fills.
14324
+ *
14325
+ * Fills are registered as render callbacks: `(container: HTMLElement) => (() => void) | void`.
14326
+ * The optional return value is a cleanup function called when the fill is removed or the slot unmounts.
14327
+ *
14328
+ * @example
14329
+ *
14330
+ * // Via config (simplest):
14331
+ * new FormEditor({
14332
+ * slots: {
14333
+ * 'editor-empty-state__footer': (container) => {
14334
+ * container.textContent = 'Hello from vanilla JS';
14335
+ * }
14336
+ * }
14337
+ * });
14338
+ *
14339
+ * // Via config (multiple fills per slot):
14340
+ * new FormEditor({
14341
+ * slots: {
14342
+ * 'editor-empty-state__footer': [
14343
+ * (container) => { container.textContent = 'First'; },
14344
+ * { render: (container) => { container.textContent = 'Second'; }, priority: 10 }
14345
+ * ]
14346
+ * }
14347
+ * });
14348
+ *
14349
+ * // Via service (runtime):
14350
+ * const slotFillManager = editor.get('slotFillManager');
14351
+ * slotFillManager.addFill('editor-empty-state__footer', 'my-fill', {
14352
+ * render: (container) => { ... },
14353
+ * priority: 10,
14354
+ * group: 'custom'
14355
+ * });
14356
+ */
14357
+ class SlotFillManager {
14358
+ /**
14359
+ * @param {Object} slotsConfig
14360
+ * @param {import('../../core/EventBus').EventBus} eventBus
14361
+ */
14362
+ constructor(slotsConfig, eventBus) {
14363
+ this._eventBus = eventBus;
14364
+
14365
+ /** @type {Array<{ slotName: string, fillId: string, render: Function, priority: number, group: string }>} */
14366
+ this._fills = [];
14367
+ this._populateFromConfig(slotsConfig);
14368
+ }
14369
+
14370
+ /**
14371
+ * Register a fill for a named slot.
14372
+ *
14373
+ * @param {string} slotName - The slot to fill.
14374
+ * @param {string} fillId - Unique identifier for this fill.
14375
+ * @param {Function|Object} options - A render callback `(container) => cleanup`, or `{ render, priority?, group? }`.
14376
+ */
14377
+ addFill(slotName, fillId, options) {
14378
+ const fill = normalizeFill(slotName, fillId, options);
14379
+ this._fills = [...this._fills.filter(f => f.fillId !== fillId), fill];
14380
+ this._eventBus.fire('slotFillManager.changed');
14381
+ }
14382
+
14383
+ /**
14384
+ * Remove a fill by its ID.
14385
+ *
14386
+ * @param {string} fillId
14387
+ */
14388
+ removeFill(fillId) {
14389
+ const remaining = this._fills.filter(f => f.fillId !== fillId);
14390
+ if (remaining.length === this._fills.length) {
14391
+ return;
14392
+ }
14393
+ this._fills = remaining;
14394
+ this._eventBus.fire('slotFillManager.changed');
14395
+ }
14396
+
14397
+ /**
14398
+ * Get fills for a given slot, sorted by group (alphabetical) then priority (descending).
14399
+ *
14400
+ * @param {string} slotName
14401
+ * @returns {Array<{ slotName: string, fillId: string, render: Function, priority: number, group: string }>}
14402
+ */
14403
+ getFills(slotName) {
14404
+ const matching = this._fills.filter(f => f.slotName === slotName);
14405
+ return sortFills(matching);
14406
+ }
14407
+
14408
+ /**
14409
+ * @private
14410
+ */
14411
+ _populateFromConfig(slotsConfig) {
14412
+ Object.entries(slotsConfig || {}).forEach(([slotName, value]) => {
14413
+ if (Array.isArray(value)) {
14414
+ value.forEach((entry, index) => {
14415
+ this.addFill(slotName, `config__${slotName}_${index}`, entry);
14416
+ });
14417
+ } else {
14418
+ this.addFill(slotName, `config__${slotName}`, value);
14419
+ }
14420
+ });
14421
+ }
14422
+ }
14423
+ SlotFillManager.$inject = ['config.slots', 'eventBus'];
14424
+
14425
+ // helpers //////////
14426
+
14427
+ /**
14428
+ * @param {string} slotName
14429
+ * @param {string} fillId
14430
+ * @param {Function|Object} options
14431
+ * @returns {{ slotName: string, fillId: string, render: Function, priority: number, group: string }}
14432
+ */
14433
+ function normalizeFill(slotName, fillId, options) {
14434
+ if (typeof options === 'function') {
14435
+ return {
14436
+ slotName,
14437
+ fillId,
14438
+ render: options,
14439
+ priority: 0,
14440
+ group: 'z_default'
14441
+ };
14442
+ }
14443
+ const {
14444
+ render,
14445
+ priority = 0,
14446
+ group = 'z_default'
14447
+ } = options;
14448
+ return {
14449
+ slotName,
14450
+ fillId,
14451
+ render,
14452
+ priority,
14453
+ group
14454
+ };
14455
+ }
14456
+
14457
+ /**
14458
+ * Sort fills by group (alphabetical) then by priority (descending) within each group.
14459
+ */
14460
+ function sortFills(fills) {
14461
+ const grouped = groupBy(fills, f => f.group);
14462
+ return Object.keys(grouped).sort().flatMap(key => grouped[key].toSorted((a, b) => b.priority - a.priority));
14463
+ }
14464
+ function groupBy(items, keyFn) {
14465
+ return items.reduce((groups, item) => {
14466
+ const key = keyFn(item);
14467
+ return {
14468
+ ...groups,
14469
+ [key]: [...(groups[key] || []), item]
14470
+ };
14471
+ }, {});
14472
+ }
14473
+
14272
14474
  const RenderInjectionModule = {
14273
- __init__: ['renderInjector'],
14274
- renderInjector: ['type', RenderInjector]
14475
+ __init__: ['renderInjector', 'slotFillManager'],
14476
+ renderInjector: ['type', RenderInjector],
14477
+ slotFillManager: ['type', SlotFillManager]
14275
14478
  };
14276
14479
 
14277
14480
  var _path;