@acorex/platform 21.0.0-next.88 → 21.0.0-next.90

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.
Files changed (55) hide show
  1. package/fesm2022/acorex-platform-auth.mjs +8 -2
  2. package/fesm2022/acorex-platform-auth.mjs.map +1 -1
  3. package/fesm2022/{acorex-platform-common-common-settings.provider-VfmZct9H.mjs → acorex-platform-common-common-settings.provider-DilV4dql.mjs} +15 -42
  4. package/fesm2022/acorex-platform-common-common-settings.provider-DilV4dql.mjs.map +1 -0
  5. package/fesm2022/acorex-platform-common.mjs +3299 -3200
  6. package/fesm2022/acorex-platform-common.mjs.map +1 -1
  7. package/fesm2022/acorex-platform-contracts.mjs +1 -0
  8. package/fesm2022/acorex-platform-contracts.mjs.map +1 -1
  9. package/fesm2022/acorex-platform-core.mjs +643 -30
  10. package/fesm2022/acorex-platform-core.mjs.map +1 -1
  11. package/fesm2022/acorex-platform-layout-builder.mjs +98 -189
  12. package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
  13. package/fesm2022/{acorex-platform-layout-components-binding-expression-editor-popup.component-CXEdvDTf.mjs → acorex-platform-layout-components-binding-expression-editor-popup.component-RttFwaw9.mjs} +17 -4
  14. package/fesm2022/acorex-platform-layout-components-binding-expression-editor-popup.component-RttFwaw9.mjs.map +1 -0
  15. package/fesm2022/acorex-platform-layout-components.mjs +125 -26
  16. package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
  17. package/fesm2022/acorex-platform-layout-designer.mjs +4 -4
  18. package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
  19. package/fesm2022/acorex-platform-layout-entity-contracts.mjs +106 -12
  20. package/fesm2022/acorex-platform-layout-entity-contracts.mjs.map +1 -1
  21. package/fesm2022/acorex-platform-layout-entity.mjs +512 -155
  22. package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
  23. package/fesm2022/acorex-platform-layout-views.mjs +921 -173
  24. package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
  25. package/fesm2022/acorex-platform-layout-widget-core.mjs +380 -103
  26. package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
  27. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-edit-popup.component-D4MOHEpI.mjs → acorex-platform-layout-widgets-tabular-data-edit-popup.component-BvFykMTH.mjs} +2 -2
  28. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-edit-popup.component-D4MOHEpI.mjs.map → acorex-platform-layout-widgets-tabular-data-edit-popup.component-BvFykMTH.mjs.map} +1 -1
  29. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-view-popup.component-gX-3Kx9I.mjs → acorex-platform-layout-widgets-tabular-data-view-popup.component-CntopmDS.mjs} +2 -2
  30. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-view-popup.component-gX-3Kx9I.mjs.map → acorex-platform-layout-widgets-tabular-data-view-popup.component-CntopmDS.mjs.map} +1 -1
  31. package/fesm2022/acorex-platform-layout-widgets.mjs +575 -239
  32. package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
  33. package/fesm2022/acorex-platform-themes-default.mjs +232 -25
  34. package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
  35. package/fesm2022/{acorex-platform-themes-shared-settings.provider-BUwPqwUS.mjs → acorex-platform-themes-shared-settings.provider-UyKdkeyk.mjs} +48 -1
  36. package/fesm2022/acorex-platform-themes-shared-settings.provider-UyKdkeyk.mjs.map +1 -0
  37. package/fesm2022/acorex-platform-themes-shared.mjs +103 -24
  38. package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
  39. package/package.json +1 -1
  40. package/types/acorex-platform-auth.d.ts +1 -0
  41. package/types/acorex-platform-common.d.ts +202 -151
  42. package/types/acorex-platform-contracts.d.ts +1 -0
  43. package/types/acorex-platform-core.d.ts +162 -7
  44. package/types/acorex-platform-layout-builder.d.ts +10 -20
  45. package/types/acorex-platform-layout-components.d.ts +14 -1
  46. package/types/acorex-platform-layout-entity-contracts.d.ts +31 -2
  47. package/types/acorex-platform-layout-entity.d.ts +91 -16
  48. package/types/acorex-platform-layout-views.d.ts +107 -20
  49. package/types/acorex-platform-layout-widget-core.d.ts +71 -9
  50. package/types/acorex-platform-layout-widgets.d.ts +48 -26
  51. package/types/acorex-platform-themes-default.d.ts +60 -6
  52. package/types/acorex-platform-themes-shared.d.ts +4 -0
  53. package/fesm2022/acorex-platform-common-common-settings.provider-VfmZct9H.mjs.map +0 -1
  54. package/fesm2022/acorex-platform-layout-components-binding-expression-editor-popup.component-CXEdvDTf.mjs.map +0 -1
  55. package/fesm2022/acorex-platform-themes-shared-settings.provider-BUwPqwUS.mjs.map +0 -1
@@ -1,11 +1,12 @@
1
1
  import * as i5 from '@angular/common';
2
2
  import { CommonModule, DOCUMENT } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
- import { Injectable, inject, input, model, signal, computed, effect, output, viewChild, ChangeDetectionStrategy, Component, NgModule, EventEmitter, ElementRef, DestroyRef, Output } from '@angular/core';
4
+ import { Injectable, inject, input, model, signal, computed, effect, output, viewChild, ChangeDetectionStrategy, Component, NgModule, EventEmitter, ElementRef, DestroyRef, untracked, Output } from '@angular/core';
5
5
  import { provideCommandSetups, AXPCommandService } from '@acorex/platform/runtime';
6
6
  import { AXPopupService } from '@acorex/components/popup';
7
7
  import * as i4 from '@acorex/platform/core';
8
- import { normalizeKeyboardShortcuts, AXPHookService, findOverlayContainerAncestor, getNestedVisibleOverlayPanes, getTopVisibleOverlayContainer, AXPExpressionEvaluatorService, AXPComponentSlotModule, AXPContextStore } from '@acorex/platform/core';
8
+ import { AXPHookService, resolveDialogActionShortcuts, DEFAULT_CANCEL_DIALOG_ACTION_SHORTCUTS, DEFAULT_SUBMIT_DIALOG_ACTION_SHORTCUTS, findOverlayContainerAncestor, getNestedVisibleOverlayPanes, getTopVisibleOverlayContainer, AXPExpressionEvaluatorService, AXPKeyboardShortcutRegistry, AXPKeyboardShortcutPriority, normalizeDialogActionShortcutChord, isDialogShortcutAllowedInEditableField, resolveConfiguredFooterActionShortcuts, AXPComponentSlotModule, AXPContextStore } from '@acorex/platform/core';
9
+ export { DEFAULT_CANCEL_DIALOG_ACTION_SHORTCUTS, DEFAULT_SUBMIT_DIALOG_ACTION_SHORTCUTS } from '@acorex/platform/core';
9
10
  import * as i1 from '@acorex/platform/layout/widget-core';
10
11
  import { AXPWidgetSerializationHelper, AXPWidgetContainerComponent, AXPPageStatus, AXPWidgetCoreModule, AXPWidgetRegistryService } from '@acorex/platform/layout/widget-core';
11
12
  import { cloneDeep, isNil, set, isEqual, merge } from 'lodash-es';
@@ -26,150 +27,7 @@ import { AXTranslationService, AXTranslationModule } from '@acorex/core/translat
26
27
  import { AXPUnsavedChangesConfirmService } from '@acorex/platform/common';
27
28
  import { AXP_ENTITY_DEFINITION_CRUD_SERVICE } from '@acorex/platform/domain';
28
29
 
29
- //#region ---- Dialog Action Shortcut Utilities ----
30
- const DEFAULT_CANCEL_DIALOG_ACTION_SHORTCUTS = ['Esc'];
31
- const DEFAULT_SUBMIT_DIALOG_ACTION_SHORTCUTS = ['Enter', 'ctrl+s'];
32
- const PRIMARY_DIALOG_ACTION_COMMANDS = new Set(['submit', 'create', 'entity-form-done']);
33
- /**
34
- * Parses a shortcut string such as `Enter`, `Escape`, or `ctrl+shift+s`.
35
- */
36
- function parseDialogActionShortcut(shortcut) {
37
- const parts = shortcut
38
- .trim()
39
- .toLowerCase()
40
- .split('+')
41
- .map((part) => part.trim())
42
- .filter(Boolean);
43
- const key = normalizeShortcutToken(parts.pop() ?? '');
44
- return {
45
- ctrl: parts.includes('ctrl') || parts.includes('control'),
46
- shift: parts.includes('shift'),
47
- alt: parts.includes('alt') || parts.includes('option'),
48
- meta: parts.includes('meta') || parts.includes('cmd') || parts.includes('command'),
49
- key,
50
- };
51
- }
52
- /**
53
- * Returns true when the keyboard event matches the given shortcut definition.
54
- */
55
- function matchesDialogActionShortcut(event, shortcut) {
56
- const parsed = parseDialogActionShortcut(shortcut);
57
- if (parsed.ctrl !== event.ctrlKey) {
58
- return false;
59
- }
60
- if (parsed.shift !== event.shiftKey) {
61
- return false;
62
- }
63
- if (parsed.alt !== event.altKey) {
64
- return false;
65
- }
66
- if (parsed.meta !== event.metaKey) {
67
- return false;
68
- }
69
- return normalizeShortcutKey(event) === normalizeShortcutToken(parsed.key);
70
- }
71
- /**
72
- * Resolves footer action shortcuts: defaults when omitted, merge when extras are provided, none when `[]`.
73
- */
74
- function resolveDialogActionShortcuts(defaults, overrides) {
75
- if (overrides !== undefined) {
76
- if (overrides.length === 0) {
77
- return undefined;
78
- }
79
- return flattenDialogActionShortcutChords([...defaults, ...overrides]);
80
- }
81
- return flattenDialogActionShortcutChords(defaults);
82
- }
83
- /**
84
- * Applies built-in footer shortcuts when actions are declared without `shortcuts`
85
- * (e.g. workflow `show-layout-popup` raw action config).
86
- */
87
- function resolveConfiguredFooterActionShortcuts(command, explicit) {
88
- if (explicit !== undefined) {
89
- return resolveDialogActionShortcuts([], explicit);
90
- }
91
- if (command === 'cancel') {
92
- return resolveDialogActionShortcuts(DEFAULT_CANCEL_DIALOG_ACTION_SHORTCUTS, undefined);
93
- }
94
- if (command && PRIMARY_DIALOG_ACTION_COMMANDS.has(command)) {
95
- return resolveDialogActionShortcuts(DEFAULT_SUBMIT_DIALOG_ACTION_SHORTCUTS, undefined);
96
- }
97
- return undefined;
98
- }
99
- function flattenDialogActionShortcutChords(shortcuts) {
100
- const chords = normalizeKeyboardShortcuts(shortcuts).flatMap((shortcut) => shortcut.keys);
101
- const deduped = dedupeDialogActionShortcuts(chords);
102
- return deduped.length ? deduped : undefined;
103
- }
104
- function dedupeDialogActionShortcuts(shortcuts) {
105
- const seen = new Set();
106
- const result = [];
107
- for (const shortcut of shortcuts) {
108
- const token = normalizeShortcutToken(shortcut);
109
- if (!token || seen.has(token)) {
110
- continue;
111
- }
112
- seen.add(token);
113
- result.push(shortcut.trim());
114
- }
115
- return result;
116
- }
117
- function normalizeShortcutToken(key) {
118
- const normalized = key.trim().toLowerCase();
119
- if (normalized === 'esc') {
120
- return 'escape';
121
- }
122
- return normalized;
123
- }
124
- /**
125
- * Whether a shortcut should fire given the current focus target.
126
- * Enter without modifiers is suppressed in multiline / rich-text fields.
127
- */
128
- function shouldTriggerDialogActionShortcut(event, shortcut) {
129
- const parsed = parseDialogActionShortcut(shortcut);
130
- const isPlainEnter = parsed.key === 'enter' && !parsed.ctrl && !parsed.shift && !parsed.alt && !parsed.meta;
131
- if (!isPlainEnter) {
132
- return true;
133
- }
134
- return !isKeyboardTargetMultilineEditable(event.target);
135
- }
136
- function normalizeShortcutKey(event) {
137
- if (event.key === 'Enter') {
138
- return 'enter';
139
- }
140
- if (event.key === 'Escape') {
141
- return 'escape';
142
- }
143
- if (event.key === ' ') {
144
- return 'space';
145
- }
146
- if (event.key.length === 1) {
147
- return event.key.toLowerCase();
148
- }
149
- const codeMatch = event.code.match(/^Key([A-Z])$/);
150
- if (codeMatch) {
151
- return codeMatch[1].toLowerCase();
152
- }
153
- const digitMatch = event.code.match(/^Digit([0-9])$/);
154
- if (digitMatch) {
155
- return digitMatch[1];
156
- }
157
- return event.key.toLowerCase();
158
- }
159
- function isKeyboardTargetMultilineEditable(target) {
160
- if (!(target instanceof HTMLElement)) {
161
- return false;
162
- }
163
- if (target.isContentEditable) {
164
- return true;
165
- }
166
- const textarea = target.closest('textarea');
167
- if (textarea && !textarea.readOnly && !textarea.disabled) {
168
- return true;
169
- }
170
- const richText = target.closest('[contenteditable="true"]');
171
- return richText instanceof HTMLElement;
172
- }
30
+ //#region ---- Dialog Action Shortcut Utilities (re-export) ----
173
31
  //#endregion
174
32
 
175
33
  /** Fallback {@link AXPDialogRef} when the popup is dismissed without a footer action (e.g. header close). */
@@ -2658,6 +2516,7 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
2658
2516
  this.translationService = inject(AXTranslationService);
2659
2517
  this.document = inject(DOCUMENT);
2660
2518
  this.host = inject((ElementRef));
2519
+ this.shortcutRegistry = inject(AXPKeyboardShortcutRegistry);
2661
2520
  /** Ensures `show()` resolves once when the dialog closes (footer action or header close). */
2662
2521
  this.callbackInvoked = false;
2663
2522
  /** Skips dirty confirmation on the next {@link onClosing} (successful submit / programmatic close). */
@@ -2679,44 +2538,15 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
2679
2538
  ? crypto.randomUUID()
2680
2539
  : `layout-dialog-ctx-${Date.now()}-${Math.random().toString(36).slice(2)}`;
2681
2540
  /**
2682
- * Capture-phase footer shortcuts runs before widget editors (select, date, text) that stop keydown bubbling.
2541
+ * Registers footer shortcuts on the platform registry at modal priority so they win over page
2542
+ * bindings (e.g. Ctrl+S on entity details) and stop propagation.
2683
2543
  */
2684
- this.onDialogShortcutCapture = (event) => {
2685
- if (this.isDialogLoading()) {
2686
- return;
2687
- }
2688
- // Unsaved-changes confirm is modal — parent footer shortcuts must not run underneath it.
2689
- if (this.unsavedChangesConfirm.isConfirmPending()) {
2690
- return;
2691
- }
2692
- if (shouldDeferDialogShortcutsToNestedOverlay(this.document, this.host.nativeElement, this.overlayService)) {
2693
- return;
2694
- }
2695
- if (event.key === 'Escape' && this.config?.confirmCloseWhenDirty?.enabled) {
2696
- event.preventDefault();
2697
- event.stopImmediatePropagation();
2698
- this.requestDismiss();
2699
- return;
2700
- }
2701
- const actions = [...this.footerPrefixActions(), ...this.footerSuffixActions()];
2702
- for (const action of actions) {
2703
- if (action.disabled || action.hidden || !action.shortcuts?.length) {
2704
- continue;
2705
- }
2706
- for (const shortcut of action.shortcuts) {
2707
- if (event.key === 'Escape' && this.config?.confirmCloseWhenDirty?.enabled) {
2708
- continue;
2709
- }
2710
- if (!matchesDialogActionShortcut(event, shortcut) || !shouldTriggerDialogActionShortcut(event, shortcut)) {
2711
- continue;
2712
- }
2713
- event.preventDefault();
2714
- event.stopImmediatePropagation();
2715
- void this.executeAction(action);
2716
- return;
2717
- }
2718
- }
2719
- };
2544
+ this.#dialogShortcutsEffect = effect(() => {
2545
+ this.footerPrefix();
2546
+ this.footerSuffix();
2547
+ this.isDialogLoading();
2548
+ untracked(() => this.refreshDialogKeyboardShortcuts());
2549
+ }, ...(ngDevMode ? [{ debugName: "#dialogShortcutsEffect" }] : /* istanbul ignore next */ []));
2720
2550
  //#endregion
2721
2551
  //#region ---- View Accessors ----
2722
2552
  // Access the internal layout renderer to reach the widgets container injector
@@ -2738,18 +2568,92 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
2738
2568
  }, DIALOG_WIDGET_COUNT_STABLE_DELAY_MS);
2739
2569
  }, ...(ngDevMode ? [{ debugName: "#widgetActionsEffect" }] : /* istanbul ignore next */ []));
2740
2570
  }
2571
+ /**
2572
+ * Registers footer shortcuts on the platform registry at modal priority so they win over page
2573
+ * bindings (e.g. Ctrl+S on entity details) and stop propagation.
2574
+ */
2575
+ #dialogShortcutsEffect;
2741
2576
  //#endregion
2742
2577
  //#region ---- Lifecycle ----
2743
2578
  ngOnInit() {
2744
2579
  const initialContext = this.config?.context || {};
2745
2580
  this.context.set(initialContext);
2746
- this.document.addEventListener('keydown', this.onDialogShortcutCapture, true);
2747
- this.destroyRef.onDestroy(() => {
2748
- this.document.removeEventListener('keydown', this.onDialogShortcutCapture, true);
2749
- });
2750
2581
  void this.invokeLayoutContextChangedHooks();
2751
2582
  }
2752
2583
  //#endregion
2584
+ //#region ---- Keyboard Shortcuts ----
2585
+ refreshDialogKeyboardShortcuts() {
2586
+ const shortcuts = this.buildDialogFooterShortcutBindings();
2587
+ const dialogTitle = this.config?.title;
2588
+ const scope = typeof dialogTitle === 'string' && dialogTitle.startsWith('@')
2589
+ ? dialogTitle
2590
+ : '@general:keyboard-shortcuts.groups.dialog';
2591
+ this.shortcutRegistry.register({
2592
+ owner: this.destroyRef,
2593
+ elementRef: this.host,
2594
+ id: 'layout-builder-dialog-footer',
2595
+ priority: AXPKeyboardShortcutPriority.Modal,
2596
+ scope,
2597
+ when: () => this.isDialogShortcutRegistrationActive(),
2598
+ shortcuts,
2599
+ });
2600
+ }
2601
+ isDialogShortcutRegistrationActive() {
2602
+ if (this.isDialogLoading()) {
2603
+ return false;
2604
+ }
2605
+ if (this.unsavedChangesConfirm.isConfirmPending()) {
2606
+ return false;
2607
+ }
2608
+ return !shouldDeferDialogShortcutsToNestedOverlay(this.document, this.host.nativeElement, this.overlayService);
2609
+ }
2610
+ buildDialogFooterShortcutBindings() {
2611
+ const shortcuts = [];
2612
+ const group = '@general:keyboard-shortcuts.groups.dialog';
2613
+ const actions = [...this.footerPrefixActions(), ...this.footerSuffixActions()];
2614
+ const confirmDirty = !!this.config?.confirmCloseWhenDirty?.enabled;
2615
+ for (const action of actions) {
2616
+ if (action.disabled || action.hidden || !action.shortcuts?.length) {
2617
+ continue;
2618
+ }
2619
+ for (const shortcut of action.shortcuts) {
2620
+ const chord = normalizeDialogActionShortcutChord(shortcut);
2621
+ if (confirmDirty && chord === 'escape') {
2622
+ continue;
2623
+ }
2624
+ shortcuts.push({
2625
+ keys: [chord],
2626
+ title: action.title ?? '',
2627
+ group,
2628
+ allowInEditableFields: isDialogShortcutAllowedInEditableField(shortcut),
2629
+ when: () => this.isDialogFooterShortcutActive(action),
2630
+ handler: () => {
2631
+ void this.executeAction(action);
2632
+ },
2633
+ });
2634
+ }
2635
+ }
2636
+ if (confirmDirty) {
2637
+ shortcuts.push({
2638
+ keys: ['escape'],
2639
+ title: '@general:actions.discard.title',
2640
+ group,
2641
+ allowInEditableFields: true,
2642
+ when: () => this.isDialogShortcutRegistrationActive(),
2643
+ handler: () => {
2644
+ this.requestDismiss();
2645
+ },
2646
+ });
2647
+ }
2648
+ return shortcuts;
2649
+ }
2650
+ isDialogFooterShortcutActive(action) {
2651
+ if (!this.isDialogShortcutRegistrationActive()) {
2652
+ return false;
2653
+ }
2654
+ return !action.disabled && !action.hidden;
2655
+ }
2656
+ //#endregion
2753
2657
  //#region ---- Popup Close Gate ----
2754
2658
  /**
2755
2659
  * Popup shell hook — handles header **X** and **Esc** (when `closeButton` is enabled).
@@ -3099,7 +3003,12 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
3099
3003
  try {
3100
3004
  const widget = this.widgetCoreService.getWidget(name);
3101
3005
  const widgetActs = widget?.actions?.() ?? [];
3102
- actions.push(...widgetActs);
3006
+ for (const action of widgetActs) {
3007
+ actions.push({
3008
+ ...action,
3009
+ shortcuts: resolveConfiguredFooterActionShortcuts(this.resolveActionCommandName(action.command), action.shortcuts),
3010
+ });
3011
+ }
3103
3012
  }
3104
3013
  catch { }
3105
3014
  }
@@ -3527,5 +3436,5 @@ var previewWidgetField_command = /*#__PURE__*/Object.freeze({
3527
3436
  * Generated bundle index. Do not edit.
3528
3437
  */
3529
3438
 
3530
- export { AXPDialogRendererComponent, AXPLayoutBuilderService, AXPLayoutConversionService, AXPLayoutRendererComponent, AXPPreviewWidgetFieldCommand, AXP_LAYOUT_BUILDER_DIALOG_BEFORE_OPEN_HOOK_KEY, AXP_LAYOUT_BUILDER_DIALOG_CONFIG_HOOK_KEY, AXP_LAYOUT_BUILDER_DIALOG_CONTEXT_CHANGED_HOOK_KEY, DEFAULT_CANCEL_DIALOG_ACTION_SHORTCUTS, DEFAULT_SUBMIT_DIALOG_ACTION_SHORTCUTS, LayoutBuilderModule, createDismissedDialogRef };
3439
+ export { AXPDialogRendererComponent, AXPLayoutBuilderService, AXPLayoutConversionService, AXPLayoutRendererComponent, AXPPreviewWidgetFieldCommand, AXP_LAYOUT_BUILDER_DIALOG_BEFORE_OPEN_HOOK_KEY, AXP_LAYOUT_BUILDER_DIALOG_CONFIG_HOOK_KEY, AXP_LAYOUT_BUILDER_DIALOG_CONTEXT_CHANGED_HOOK_KEY, LayoutBuilderModule, createDismissedDialogRef };
3531
3440
  //# sourceMappingURL=acorex-platform-layout-builder.mjs.map