@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.
package/dist/index.es.js CHANGED
@@ -1353,7 +1353,26 @@ const Slot = props => {
1353
1353
  const fillsAndSeparators = useMemo(() => {
1354
1354
  return buildFills(groups, fillRoot, separatorFn);
1355
1355
  }, [groups, fillRoot, separatorFn]);
1356
- return fillsAndSeparators;
1356
+
1357
+ // Framework-agnostic fills from SlotFillManager
1358
+ const editorContext = useContext(FormEditorContext);
1359
+ const slotFillManager = editorContext ? editorContext.getService('slotFillManager', false) : null;
1360
+ const eventBus = editorContext ? editorContext.getService('eventBus', false) : null;
1361
+ const [managerFills, setManagerFills] = useState([]);
1362
+ useEffect(() => {
1363
+ if (!eventBus || !slotFillManager) {
1364
+ return;
1365
+ }
1366
+ setManagerFills(slotFillManager.getFills(name));
1367
+ const onChange = () => setManagerFills(slotFillManager.getFills(name));
1368
+ eventBus.on('slotFillManager.changed', onChange);
1369
+ return () => eventBus.off('slotFillManager.changed', onChange);
1370
+ }, [eventBus, slotFillManager, name]);
1371
+ return jsxs(Fragment, {
1372
+ children: [fillsAndSeparators, managerFills.map(fill => jsx(FillContainer, {
1373
+ fill: fill
1374
+ }, fill.fillId))]
1375
+ });
1357
1376
  };
1358
1377
 
1359
1378
  /**
@@ -1366,6 +1385,34 @@ const FillFragment = fill => jsx(Fragment, {
1366
1385
  children: fill.children
1367
1386
  }, fill.id);
1368
1387
 
1388
+ /**
1389
+ * Mounts a single SlotFillManager fill's render callback into a DOM container.
1390
+ */
1391
+ function FillContainer({
1392
+ fill
1393
+ }) {
1394
+ const containerRef = useRef(null);
1395
+ const cleanupRef = useRef(null);
1396
+ useEffect(() => {
1397
+ const container = containerRef.current;
1398
+ if (!container) {
1399
+ return;
1400
+ }
1401
+ cleanupRef.current = fill.render(container) || null;
1402
+ return () => {
1403
+ if (typeof cleanupRef.current === 'function') {
1404
+ cleanupRef.current();
1405
+ cleanupRef.current = null;
1406
+ }
1407
+ container.innerHTML = '';
1408
+ };
1409
+ }, [fill]);
1410
+ return jsx("div", {
1411
+ ref: containerRef,
1412
+ "data-slot-fill": fill.fillId
1413
+ });
1414
+ }
1415
+
1369
1416
  /**
1370
1417
  * Creates an array of fills, with separators inserted between groups.
1371
1418
  *
@@ -2244,6 +2291,8 @@ function EmptyForm() {
2244
2291
  children: "Drag and drop components here to start designing."
2245
2292
  }), jsx("span", {
2246
2293
  children: "Use the preview window to test your form."
2294
+ }), jsx(Slot, {
2295
+ name: "editor-empty-state__footer"
2247
2296
  })]
2248
2297
  })
2249
2298
  });
@@ -2549,7 +2598,8 @@ function FormEditor$1() {
2549
2598
  Element: Element$1,
2550
2599
  Empty,
2551
2600
  Row,
2552
- hoverInfo: {}
2601
+ hoverInfo: {},
2602
+ applyVisibilityConditions: false
2553
2603
  }), []);
2554
2604
  const formContext = useMemo(() => ({
2555
2605
  getService(type, strict = true) {
@@ -14249,9 +14299,162 @@ class RenderInjector extends SectionModuleBase {
14249
14299
  }
14250
14300
  RenderInjector.$inject = ['eventBus'];
14251
14301
 
14302
+ /**
14303
+ * Framework-agnostic service for managing slot fills.
14304
+ *
14305
+ * Fills are registered as render callbacks: `(container: HTMLElement) => (() => void) | void`.
14306
+ * The optional return value is a cleanup function called when the fill is removed or the slot unmounts.
14307
+ *
14308
+ * @example
14309
+ *
14310
+ * // Via config (simplest):
14311
+ * new FormEditor({
14312
+ * slots: {
14313
+ * 'editor-empty-state__footer': (container) => {
14314
+ * container.textContent = 'Hello from vanilla JS';
14315
+ * }
14316
+ * }
14317
+ * });
14318
+ *
14319
+ * // Via config (multiple fills per slot):
14320
+ * new FormEditor({
14321
+ * slots: {
14322
+ * 'editor-empty-state__footer': [
14323
+ * (container) => { container.textContent = 'First'; },
14324
+ * { render: (container) => { container.textContent = 'Second'; }, priority: 10 }
14325
+ * ]
14326
+ * }
14327
+ * });
14328
+ *
14329
+ * // Via service (runtime):
14330
+ * const slotFillManager = editor.get('slotFillManager');
14331
+ * slotFillManager.addFill('editor-empty-state__footer', 'my-fill', {
14332
+ * render: (container) => { ... },
14333
+ * priority: 10,
14334
+ * group: 'custom'
14335
+ * });
14336
+ */
14337
+ class SlotFillManager {
14338
+ /**
14339
+ * @param {Object} slotsConfig
14340
+ * @param {import('../../core/EventBus').EventBus} eventBus
14341
+ */
14342
+ constructor(slotsConfig, eventBus) {
14343
+ this._eventBus = eventBus;
14344
+
14345
+ /** @type {Array<{ slotName: string, fillId: string, render: Function, priority: number, group: string }>} */
14346
+ this._fills = [];
14347
+ this._populateFromConfig(slotsConfig);
14348
+ }
14349
+
14350
+ /**
14351
+ * Register a fill for a named slot.
14352
+ *
14353
+ * @param {string} slotName - The slot to fill.
14354
+ * @param {string} fillId - Unique identifier for this fill.
14355
+ * @param {Function|Object} options - A render callback `(container) => cleanup`, or `{ render, priority?, group? }`.
14356
+ */
14357
+ addFill(slotName, fillId, options) {
14358
+ const fill = normalizeFill(slotName, fillId, options);
14359
+ this._fills = [...this._fills.filter(f => f.fillId !== fillId), fill];
14360
+ this._eventBus.fire('slotFillManager.changed');
14361
+ }
14362
+
14363
+ /**
14364
+ * Remove a fill by its ID.
14365
+ *
14366
+ * @param {string} fillId
14367
+ */
14368
+ removeFill(fillId) {
14369
+ const remaining = this._fills.filter(f => f.fillId !== fillId);
14370
+ if (remaining.length === this._fills.length) {
14371
+ return;
14372
+ }
14373
+ this._fills = remaining;
14374
+ this._eventBus.fire('slotFillManager.changed');
14375
+ }
14376
+
14377
+ /**
14378
+ * Get fills for a given slot, sorted by group (alphabetical) then priority (descending).
14379
+ *
14380
+ * @param {string} slotName
14381
+ * @returns {Array<{ slotName: string, fillId: string, render: Function, priority: number, group: string }>}
14382
+ */
14383
+ getFills(slotName) {
14384
+ const matching = this._fills.filter(f => f.slotName === slotName);
14385
+ return sortFills(matching);
14386
+ }
14387
+
14388
+ /**
14389
+ * @private
14390
+ */
14391
+ _populateFromConfig(slotsConfig) {
14392
+ Object.entries(slotsConfig || {}).forEach(([slotName, value]) => {
14393
+ if (Array.isArray(value)) {
14394
+ value.forEach((entry, index) => {
14395
+ this.addFill(slotName, `config__${slotName}_${index}`, entry);
14396
+ });
14397
+ } else {
14398
+ this.addFill(slotName, `config__${slotName}`, value);
14399
+ }
14400
+ });
14401
+ }
14402
+ }
14403
+ SlotFillManager.$inject = ['config.slots', 'eventBus'];
14404
+
14405
+ // helpers //////////
14406
+
14407
+ /**
14408
+ * @param {string} slotName
14409
+ * @param {string} fillId
14410
+ * @param {Function|Object} options
14411
+ * @returns {{ slotName: string, fillId: string, render: Function, priority: number, group: string }}
14412
+ */
14413
+ function normalizeFill(slotName, fillId, options) {
14414
+ if (typeof options === 'function') {
14415
+ return {
14416
+ slotName,
14417
+ fillId,
14418
+ render: options,
14419
+ priority: 0,
14420
+ group: 'z_default'
14421
+ };
14422
+ }
14423
+ const {
14424
+ render,
14425
+ priority = 0,
14426
+ group = 'z_default'
14427
+ } = options;
14428
+ return {
14429
+ slotName,
14430
+ fillId,
14431
+ render,
14432
+ priority,
14433
+ group
14434
+ };
14435
+ }
14436
+
14437
+ /**
14438
+ * Sort fills by group (alphabetical) then by priority (descending) within each group.
14439
+ */
14440
+ function sortFills(fills) {
14441
+ const grouped = groupBy(fills, f => f.group);
14442
+ return Object.keys(grouped).sort().flatMap(key => grouped[key].toSorted((a, b) => b.priority - a.priority));
14443
+ }
14444
+ function groupBy(items, keyFn) {
14445
+ return items.reduce((groups, item) => {
14446
+ const key = keyFn(item);
14447
+ return {
14448
+ ...groups,
14449
+ [key]: [...(groups[key] || []), item]
14450
+ };
14451
+ }, {});
14452
+ }
14453
+
14252
14454
  const RenderInjectionModule = {
14253
- __init__: ['renderInjector'],
14254
- renderInjector: ['type', RenderInjector]
14455
+ __init__: ['renderInjector', 'slotFillManager'],
14456
+ renderInjector: ['type', RenderInjector],
14457
+ slotFillManager: ['type', SlotFillManager]
14255
14458
  };
14256
14459
 
14257
14460
  var _path;