@openmfp/webcomponents 0.6.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.
Files changed (140) hide show
  1. package/.github/workflows/pipeline.yaml +41 -0
  2. package/.storybook/main.ts +34 -0
  3. package/.storybook/preview.ts +29 -0
  4. package/.storybook/tsconfig.json +11 -0
  5. package/AGENTS.md +153 -0
  6. package/CODEOWNERS +6 -0
  7. package/CONTRIBUTING.md +95 -0
  8. package/LICENSE +201 -0
  9. package/LICENSES/Apache-2.0.txt +73 -0
  10. package/README.md +91 -0
  11. package/REUSE.toml +9 -0
  12. package/angular.json +157 -0
  13. package/docs/dashboard.md +358 -0
  14. package/docs/declarative-form.md +178 -0
  15. package/docs/declarative-table-card.md +235 -0
  16. package/docs/declarative-table.md +315 -0
  17. package/eslint.config.js +41 -0
  18. package/package.json +73 -0
  19. package/projects/ngx/cards/favorites/favorites.component.html +12 -0
  20. package/projects/ngx/cards/favorites/favorites.component.scss +50 -0
  21. package/projects/ngx/cards/favorites/favorites.component.ts +19 -0
  22. package/projects/ngx/cards/public-api.ts +4 -0
  23. package/projects/ngx/cards/service-status/service-status-card.component.html +15 -0
  24. package/projects/ngx/cards/service-status/service-status-card.component.scss +87 -0
  25. package/projects/ngx/cards/service-status/service-status-card.component.ts +36 -0
  26. package/projects/ngx/cards/stories/visited-service-card.stories.ts +149 -0
  27. package/projects/ngx/cards/visited-service-card/visited-service-card.component.html +17 -0
  28. package/projects/ngx/cards/visited-service-card/visited-service-card.component.scss +34 -0
  29. package/projects/ngx/cards/visited-service-card/visited-service-card.component.ts +22 -0
  30. package/projects/ngx/cards/whats-new/whats-new.component.html +10 -0
  31. package/projects/ngx/cards/whats-new/whats-new.component.scss +25 -0
  32. package/projects/ngx/cards/whats-new/whats-new.component.ts +46 -0
  33. package/projects/ngx/declarative-ui/dashboard/add-card-dialog/add-card-dialog.component.html +28 -0
  34. package/projects/ngx/declarative-ui/dashboard/add-card-dialog/add-card-dialog.component.scss +44 -0
  35. package/projects/ngx/declarative-ui/dashboard/add-card-dialog/add-card-dialog.component.spec.ts +85 -0
  36. package/projects/ngx/declarative-ui/dashboard/add-card-dialog/add-card-dialog.component.ts +58 -0
  37. package/projects/ngx/declarative-ui/dashboard/card/dashboard-card.component.html +29 -0
  38. package/projects/ngx/declarative-ui/dashboard/card/dashboard-card.component.scss +63 -0
  39. package/projects/ngx/declarative-ui/dashboard/card/dashboard-card.component.spec.ts +255 -0
  40. package/projects/ngx/declarative-ui/dashboard/card/dashboard-card.component.ts +75 -0
  41. package/projects/ngx/declarative-ui/dashboard/card/utils/dashboard-card-registry.spec.ts +76 -0
  42. package/projects/ngx/declarative-ui/dashboard/card/utils/dashboard-card-registry.ts +109 -0
  43. package/projects/ngx/declarative-ui/dashboard/card/utils/index.ts +4 -0
  44. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-angular-card.spec.ts +141 -0
  45. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-angular-card.ts +44 -0
  46. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-sap-card.spec.ts +142 -0
  47. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-sap-card.ts +52 -0
  48. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-wc-card.spec.ts +107 -0
  49. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-wc-card.ts +22 -0
  50. package/projects/ngx/declarative-ui/dashboard/dashboard/dashboard.component.html +134 -0
  51. package/projects/ngx/declarative-ui/dashboard/dashboard/dashboard.component.scss +88 -0
  52. package/projects/ngx/declarative-ui/dashboard/dashboard/dashboard.component.spec.ts +354 -0
  53. package/projects/ngx/declarative-ui/dashboard/dashboard/dashboard.component.ts +238 -0
  54. package/projects/ngx/declarative-ui/dashboard/dashboard/index.ts +1 -0
  55. package/projects/ngx/declarative-ui/dashboard/index.ts +5 -0
  56. package/projects/ngx/declarative-ui/dashboard/models/constants.ts +2 -0
  57. package/projects/ngx/declarative-ui/dashboard/models/dashboard.model.ts +50 -0
  58. package/projects/ngx/declarative-ui/dashboard/models/index.ts +1 -0
  59. package/projects/ngx/declarative-ui/dashboard/section/dashboard-section.component.html +28 -0
  60. package/projects/ngx/declarative-ui/dashboard/section/dashboard-section.component.scss +85 -0
  61. package/projects/ngx/declarative-ui/dashboard/section/dashboard-section.component.spec.ts +104 -0
  62. package/projects/ngx/declarative-ui/dashboard/section/dashboard-section.component.ts +23 -0
  63. package/projects/ngx/declarative-ui/form/declarative-form/declarative-form.component.html +62 -0
  64. package/projects/ngx/declarative-ui/form/declarative-form/declarative-form.component.scss +12 -0
  65. package/projects/ngx/declarative-ui/form/declarative-form/declarative-form.component.spec.ts +301 -0
  66. package/projects/ngx/declarative-ui/form/declarative-form/declarative-form.component.ts +166 -0
  67. package/projects/ngx/declarative-ui/form/declarative-form/index.ts +1 -0
  68. package/projects/ngx/declarative-ui/form/index.ts +2 -0
  69. package/projects/ngx/declarative-ui/form/models/form-field-definition.ts +15 -0
  70. package/projects/ngx/declarative-ui/form/models/index.ts +1 -0
  71. package/projects/ngx/declarative-ui/form/utils/set-property-by-path.ts +30 -0
  72. package/projects/ngx/declarative-ui/models/index.ts +2 -0
  73. package/projects/ngx/declarative-ui/models/resource.ts +5 -0
  74. package/projects/ngx/declarative-ui/models/ui-definition.ts +95 -0
  75. package/projects/ngx/declarative-ui/public-api.ts +4 -0
  76. package/projects/ngx/declarative-ui/stories/add-card-dialog.stories.ts +91 -0
  77. package/projects/ngx/declarative-ui/stories/background-lightblue.png +0 -0
  78. package/projects/ngx/declarative-ui/stories/dashboard.cards.ts +107 -0
  79. package/projects/ngx/declarative-ui/stories/dashboard.stories.ts +296 -0
  80. package/projects/ngx/declarative-ui/stories/declarative-form.stories.ts +149 -0
  81. package/projects/ngx/declarative-ui/stories/declarative-table-card.stories.ts +358 -0
  82. package/projects/ngx/declarative-ui/stories/declarative-table.stories.ts +363 -0
  83. package/projects/ngx/declarative-ui/stories/pods-table.config.ts +188 -0
  84. package/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.html +138 -0
  85. package/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.scss +21 -0
  86. package/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.spec.ts +345 -0
  87. package/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.ts +61 -0
  88. package/projects/ngx/declarative-ui/table/declarative-table/index.ts +1 -0
  89. package/projects/ngx/declarative-ui/table/index.ts +2 -0
  90. package/projects/ngx/declarative-ui/table/models/index.ts +14 -0
  91. package/projects/ngx/declarative-ui/table/models/table.model.ts +17 -0
  92. package/projects/ngx/declarative-ui/table/utils/cssRules.engine.spec.ts +146 -0
  93. package/projects/ngx/declarative-ui/table/utils/cssRules.engine.ts +69 -0
  94. package/projects/ngx/declarative-ui/table/utils/field-definition.utils.spec.ts +70 -0
  95. package/projects/ngx/declarative-ui/table/utils/field-definition.utils.ts +13 -0
  96. package/projects/ngx/declarative-ui/table/utils/proccess-fields.spec.ts +511 -0
  97. package/projects/ngx/declarative-ui/table/utils/proccess-fields.ts +71 -0
  98. package/projects/ngx/declarative-ui/table/utils/resource-field-by-path.spec.ts +372 -0
  99. package/projects/ngx/declarative-ui/table/utils/resource-field-by-path.ts +98 -0
  100. package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-cell.constants.ts +5 -0
  101. package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-value.component.html +1 -0
  102. package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-value.component.scss +0 -0
  103. package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-value.component.spec.ts +119 -0
  104. package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-value.component.ts +35 -0
  105. package/projects/ngx/declarative-ui/table/value-cell/link-value/link-value.component.html +7 -0
  106. package/projects/ngx/declarative-ui/table/value-cell/link-value/link-value.component.scss +0 -0
  107. package/projects/ngx/declarative-ui/table/value-cell/link-value/link-value.component.spec.ts +114 -0
  108. package/projects/ngx/declarative-ui/table/value-cell/link-value/link-value.component.ts +19 -0
  109. package/projects/ngx/declarative-ui/table/value-cell/secret-value/secret-value.component.html +7 -0
  110. package/projects/ngx/declarative-ui/table/value-cell/secret-value/secret-value.component.scss +10 -0
  111. package/projects/ngx/declarative-ui/table/value-cell/secret-value/secret-value.component.spec.ts +188 -0
  112. package/projects/ngx/declarative-ui/table/value-cell/secret-value/secret-value.component.ts +16 -0
  113. package/projects/ngx/declarative-ui/table/value-cell/value-cell.component.html +59 -0
  114. package/projects/ngx/declarative-ui/table/value-cell/value-cell.component.scss +33 -0
  115. package/projects/ngx/declarative-ui/table/value-cell/value-cell.component.spec.ts +316 -0
  116. package/projects/ngx/declarative-ui/table/value-cell/value-cell.component.ts +115 -0
  117. package/projects/ngx/declarative-ui/table-card/declarative-table-card.component.html +156 -0
  118. package/projects/ngx/declarative-ui/table-card/declarative-table-card.component.scss +123 -0
  119. package/projects/ngx/declarative-ui/table-card/declarative-table-card.component.spec.ts +786 -0
  120. package/projects/ngx/declarative-ui/table-card/declarative-table-card.component.ts +286 -0
  121. package/projects/ngx/declarative-ui/table-card/index.ts +2 -0
  122. package/projects/ngx/declarative-ui/table-card/models/configs.ts +46 -0
  123. package/projects/ngx/declarative-ui/tsconfig.lib.json +11 -0
  124. package/projects/ngx/declarative-ui/tsconfig.lib.prod.json +9 -0
  125. package/projects/ngx/declarative-ui/tsconfig.spec.json +9 -0
  126. package/projects/ngx/ng-package.json +8 -0
  127. package/projects/ngx/package.json +22 -0
  128. package/projects/ngx/public-api.ts +2 -0
  129. package/projects/ngx/tsconfig.lib.json +11 -0
  130. package/projects/ngx/tsconfig.lib.prod.json +9 -0
  131. package/projects/webcomponents/main.ts +92 -0
  132. package/projects/webcomponents/tsconfig.app.json +9 -0
  133. package/projects/webcomponents-dashboard/main.ts +15 -0
  134. package/projects/webcomponents-dashboard/tsconfig.app.json +9 -0
  135. package/renovate.json +6 -0
  136. package/scripts/bundle-wc.mjs +79 -0
  137. package/tsconfig.json +37 -0
  138. package/tsconfig.spec.json +8 -0
  139. package/tsconfig.storybook.json +16 -0
  140. package/vitest.config.ts +26 -0
@@ -0,0 +1,142 @@
1
+ import { EffectCleanupRegisterFn, ViewContainerRef } from '@angular/core';
2
+ import { mountSapCard } from './mount-sap-card';
3
+ import { CardConfig } from '../../models';
4
+
5
+ function makeCleanup(): {
6
+ onCleanup: EffectCleanupRegisterFn;
7
+ runCleanup: () => void;
8
+ } {
9
+ let registered: (() => void) | undefined;
10
+ return {
11
+ onCleanup: (fn) => {
12
+ registered = fn;
13
+ },
14
+ runCleanup: () => registered?.(),
15
+ };
16
+ }
17
+
18
+ function makeContainer(): { container: ViewContainerRef; el: HTMLElement } {
19
+ const el = document.createElement('div');
20
+ document.body.appendChild(el);
21
+ const container = { element: { nativeElement: el } } as unknown as ViewContainerRef;
22
+ return { container, el };
23
+ }
24
+
25
+ function makeCfg(overrides: Partial<CardConfig> = {}): CardConfig {
26
+ return { id: 'card-1', component: 'my.sap.App', type: 'sap-ui', ...overrides };
27
+ }
28
+
29
+ describe('mountSapCard', () => {
30
+ let placeAt: ReturnType<typeof vi.fn>;
31
+ let destroy: ReturnType<typeof vi.fn>;
32
+ let ComponentContainer: ReturnType<typeof vi.fn>;
33
+ let sapRequire: ReturnType<typeof vi.fn>;
34
+
35
+ beforeEach(() => {
36
+ placeAt = vi.fn();
37
+ destroy = vi.fn();
38
+ ComponentContainer = vi.fn(function (this: Record<string, unknown>) {
39
+ this['placeAt'] = placeAt;
40
+ this['destroy'] = destroy;
41
+ });
42
+ sapRequire = vi
43
+ .fn()
44
+ .mockImplementation((_deps: unknown, cb: (ctor: unknown) => void) => {
45
+ cb(ComponentContainer);
46
+ });
47
+
48
+ (window as unknown as Record<string, unknown>)['sap'] = {
49
+ ui: { require: sapRequire },
50
+ };
51
+ });
52
+
53
+ afterEach(() => {
54
+ delete (window as unknown as Record<string, unknown>)['sap'];
55
+ vi.restoreAllMocks();
56
+ });
57
+
58
+ it('calls sap.ui.require with ComponentContainer dependency', () => {
59
+ const { onCleanup } = makeCleanup();
60
+ mountSapCard(makeCfg(), makeContainer().container, onCleanup);
61
+
62
+ expect(sapRequire).toHaveBeenCalledWith(
63
+ ['sap/ui/core/ComponentContainer'],
64
+ expect.any(Function),
65
+ );
66
+ });
67
+
68
+ it('instantiates ComponentContainer with the correct config', () => {
69
+ const { onCleanup } = makeCleanup();
70
+ mountSapCard(
71
+ makeCfg({ componentInputs: { env: 'prod' } }),
72
+ makeContainer().container,
73
+ onCleanup,
74
+ );
75
+
76
+ expect(ComponentContainer).toHaveBeenCalledWith({
77
+ name: 'my.sap.App',
78
+ manifest: true,
79
+ async: true,
80
+ settings: { env: 'prod' },
81
+ });
82
+ });
83
+
84
+ it('falls back to empty settings when componentInputs is omitted', () => {
85
+ const { onCleanup } = makeCleanup();
86
+ mountSapCard(makeCfg(), makeContainer().container, onCleanup);
87
+
88
+ expect(ComponentContainer).toHaveBeenCalledWith(
89
+ expect.objectContaining({ settings: {} }),
90
+ );
91
+ });
92
+
93
+ it('calls placeAt with the host element', () => {
94
+ const { onCleanup } = makeCleanup();
95
+ const { container, el } = makeContainer();
96
+ mountSapCard(makeCfg(), container, onCleanup);
97
+
98
+ expect(placeAt).toHaveBeenCalledWith(el);
99
+ });
100
+
101
+ it('destroys the container and clears the host on cleanup', () => {
102
+ const { onCleanup, runCleanup } = makeCleanup();
103
+ const { container, el } = makeContainer();
104
+ el.innerHTML = '<span>old</span>';
105
+ mountSapCard(makeCfg(), container, onCleanup);
106
+
107
+ runCleanup();
108
+
109
+ expect(destroy).toHaveBeenCalledTimes(1);
110
+ expect(el.innerHTML).toBe('');
111
+ });
112
+
113
+ it('does not mount the container when cleanup runs before the require callback fires', () => {
114
+ let deferred: ((ctor: unknown) => void) | undefined;
115
+ sapRequire.mockImplementationOnce(
116
+ (_deps: unknown, cb: (ctor: unknown) => void) => {
117
+ deferred = cb;
118
+ },
119
+ );
120
+
121
+ const { onCleanup, runCleanup } = makeCleanup();
122
+ mountSapCard(makeCfg(), makeContainer().container, onCleanup);
123
+
124
+ runCleanup();
125
+ deferred?.(ComponentContainer);
126
+
127
+ expect(ComponentContainer).not.toHaveBeenCalled();
128
+ expect(placeAt).not.toHaveBeenCalled();
129
+ });
130
+
131
+ it('logs an error when window.sap is not available', () => {
132
+ delete (window as unknown as Record<string, unknown>)['sap'];
133
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(vi.fn());
134
+ const { onCleanup } = makeCleanup();
135
+
136
+ mountSapCard(makeCfg(), makeContainer().container, onCleanup);
137
+
138
+ expect(consoleSpy).toHaveBeenCalledWith(
139
+ '[DashboardCard] SAP UI5 is not available on window.sap',
140
+ );
141
+ });
142
+ });
@@ -0,0 +1,52 @@
1
+ import { CardConfig } from '../../models';
2
+ import { EffectCleanupRegisterFn, ViewContainerRef } from '@angular/core';
3
+
4
+ export type SapUiRequire = (
5
+ deps: string[],
6
+ cb: (
7
+ ComponentContainer: new (cfg: {
8
+ name: string;
9
+ manifest: boolean;
10
+ async: boolean;
11
+ settings: Record<string, unknown>;
12
+ }) => { placeAt(el: HTMLElement): void; destroy(): void },
13
+ ) => void,
14
+ ) => void;
15
+
16
+ export function mountSapCard(
17
+ cfg: CardConfig,
18
+ container: ViewContainerRef,
19
+ onCleanup: EffectCleanupRegisterFn,
20
+ ): void {
21
+ const host = container.element.nativeElement;
22
+
23
+ let sapContainer: { destroy(): void } | null = null;
24
+ let isDestroyed = false;
25
+ const sapRequire = (
26
+ window as unknown as { sap?: { ui: { require: SapUiRequire } } }
27
+ ).sap?.ui?.require;
28
+
29
+ if (sapRequire) {
30
+ sapRequire(['sap/ui/core/ComponentContainer'], (ComponentContainer) => {
31
+ if (isDestroyed) return;
32
+
33
+ const container = new ComponentContainer({
34
+ name: cfg.component,
35
+ manifest: true,
36
+ async: true,
37
+ settings: cfg.componentInputs ?? {},
38
+ });
39
+
40
+ container.placeAt(host);
41
+ sapContainer = container;
42
+ });
43
+ } else {
44
+ console.error('[DashboardCard] SAP UI5 is not available on window.sap');
45
+ }
46
+
47
+ onCleanup(() => {
48
+ isDestroyed = true;
49
+ sapContainer?.destroy();
50
+ host.innerHTML = '';
51
+ });
52
+ }
@@ -0,0 +1,107 @@
1
+ import { EffectCleanupRegisterFn, Renderer2, ViewContainerRef } from '@angular/core';
2
+ import { mountWcCard } from './mount-wc-card';
3
+ import { CardConfig } from '../../models';
4
+
5
+ function makeCleanup(): {
6
+ onCleanup: EffectCleanupRegisterFn;
7
+ runCleanup: () => void;
8
+ } {
9
+ let registered: (() => void) | undefined;
10
+ return {
11
+ onCleanup: (fn) => {
12
+ registered = fn;
13
+ },
14
+ runCleanup: () => registered?.(),
15
+ };
16
+ }
17
+
18
+ function makeContainer(): { container: ViewContainerRef; el: HTMLElement } {
19
+ const el = document.createElement('div');
20
+ document.body.appendChild(el);
21
+ const container = { element: { nativeElement: el } } as unknown as ViewContainerRef;
22
+ return { container, el };
23
+ }
24
+
25
+ function makeRenderer(): Renderer2 {
26
+ return {
27
+ createElement: vi.fn((tag: string) => document.createElement(tag)),
28
+ setProperty: vi.fn((el: Record<string, unknown>, key: string, value: unknown) => {
29
+ el[key] = value;
30
+ }),
31
+ appendChild: vi.fn((parent: HTMLElement, child: HTMLElement) => {
32
+ parent.appendChild(child);
33
+ }),
34
+ } as unknown as Renderer2;
35
+ }
36
+
37
+ function makeCfg(overrides: Partial<CardConfig> = {}): CardConfig {
38
+ return { id: 'card-1', component: 'demo-widget', ...overrides };
39
+ }
40
+
41
+ describe('mountWcCard', () => {
42
+ it('appends the web component element to the host', () => {
43
+ const { onCleanup } = makeCleanup();
44
+ const { container, el } = makeContainer();
45
+ mountWcCard(makeCfg(), container, onCleanup, makeRenderer());
46
+
47
+ expect(el.querySelector('demo-widget')).not.toBeNull();
48
+ });
49
+
50
+ it('applies componentInputs as properties on the element', () => {
51
+ const { onCleanup } = makeCleanup();
52
+ const { container, el } = makeContainer();
53
+ mountWcCard(
54
+ makeCfg({ componentInputs: { title: 'Pods', count: 5 } }),
55
+ container,
56
+ onCleanup,
57
+ makeRenderer(),
58
+ );
59
+
60
+ const wc = el.querySelector('demo-widget') as HTMLElement & {
61
+ title?: string;
62
+ count?: number;
63
+ };
64
+ expect(wc?.title).toBe('Pods');
65
+ expect(wc?.count).toBe(5);
66
+ });
67
+
68
+ it('renders without errors when componentInputs is omitted', () => {
69
+ const { onCleanup } = makeCleanup();
70
+ expect(() => {
71
+ mountWcCard(makeCfg(), makeContainer().container, onCleanup, makeRenderer());
72
+ }).not.toThrow();
73
+ });
74
+
75
+ it('clears the host innerHTML on cleanup', () => {
76
+ const { onCleanup, runCleanup } = makeCleanup();
77
+ const { container, el } = makeContainer();
78
+ mountWcCard(makeCfg(), container, onCleanup, makeRenderer());
79
+
80
+ runCleanup();
81
+
82
+ expect(el.innerHTML).toBe('');
83
+ });
84
+
85
+ it('calls renderer.createElement, setProperty and appendChild in order', () => {
86
+ const { onCleanup } = makeCleanup();
87
+ const { container, el } = makeContainer();
88
+ const renderer = makeRenderer();
89
+ mountWcCard(
90
+ makeCfg({ componentInputs: { label: 'test' } }),
91
+ container,
92
+ onCleanup,
93
+ renderer,
94
+ );
95
+
96
+ expect(renderer.createElement).toHaveBeenCalledWith('demo-widget');
97
+ expect(renderer.setProperty).toHaveBeenCalledWith(
98
+ expect.any(HTMLElement),
99
+ 'label',
100
+ 'test',
101
+ );
102
+ expect(renderer.appendChild).toHaveBeenCalledWith(
103
+ el,
104
+ expect.any(HTMLElement),
105
+ );
106
+ });
107
+ });
@@ -0,0 +1,22 @@
1
+ import { EffectCleanupRegisterFn, Renderer2, ViewContainerRef } from '@angular/core';
2
+ import { CardConfig } from '../../models';
3
+
4
+ export function mountWcCard(
5
+ cfg: CardConfig,
6
+ container: ViewContainerRef,
7
+ onCleanup: EffectCleanupRegisterFn,
8
+ renderer: Renderer2,
9
+ ): void {
10
+ const host = container.element.nativeElement;
11
+ const element = renderer.createElement(cfg.component);
12
+
13
+ for (const [key, value] of Object.entries(cfg.componentInputs ?? {})) {
14
+ renderer.setProperty(element, key, value);
15
+ }
16
+
17
+ renderer.appendChild(host, element);
18
+
19
+ onCleanup(() => {
20
+ host.innerHTML = '';
21
+ });
22
+ }
@@ -0,0 +1,134 @@
1
+ <div class="mfp-dashboard">
2
+ <div class="mfp-dashboard__topbar">
3
+ <div class="mfp-dashboard__topbar-row">
4
+ <div class="mfp-dashboard__header">
5
+ @if (config().title) {
6
+ <ui5-title level="H2" size="H2" wrapping-type="Normal">{{
7
+ config().title
8
+ }}</ui5-title>
9
+ }
10
+ @if (config().description) {
11
+ <ui5-text>{{ config().description }}</ui5-text>
12
+ }
13
+ </div>
14
+
15
+ <div class="mfp-dashboard__toolbar">
16
+ @if (editMode()) {
17
+ <ui5-button
18
+ id="add-card-btn"
19
+ [design]="addCardButton().design"
20
+ [icon]="addCardButton().icon"
21
+ [tooltip]="addCardButton().tooltip"
22
+ (click)="openCardPanel()"
23
+ >{{ addCardButton().text }}</ui5-button
24
+ >
25
+ } @else if (compactToolbar()) {
26
+ <ui5-button
27
+ #menuBtn
28
+ design="Transparent"
29
+ icon="menu2"
30
+ tooltip="Actions"
31
+ (click)="toolbarMenuOpen.update((v) => !v)"
32
+ ></ui5-button>
33
+ <ui5-menu
34
+ [open]="toolbarMenuOpen()"
35
+ [opener]="menuBtn.element"
36
+ (ui5Open)="toolbarMenuOpen.set(true)"
37
+ (ui5Close)="toolbarMenuOpen.set(false)"
38
+ (ui5ItemClick)="
39
+ onMenuItemClick(
40
+ $any($event).detail.item.dataset['action'],
41
+ $event
42
+ )
43
+ "
44
+ >
45
+ @for (action of customActions(); track action.action) {
46
+ <ui5-menu-item
47
+ [icon]="action.icon ?? ''"
48
+ [text]="action.text ?? ''"
49
+ [attr.data-action]="action.action"
50
+ ></ui5-menu-item>
51
+ }
52
+ @if (config().editable) {
53
+ <ui5-menu-separator></ui5-menu-separator>
54
+ <ui5-menu-item
55
+ [icon]="editViewButton().icon"
56
+ [text]="editViewButton().text || 'Edit View'"
57
+ data-action="edit-view"
58
+ ></ui5-menu-item>
59
+ }
60
+ </ui5-menu>
61
+ } @else {
62
+ @for (action of customActions(); track action.action) {
63
+ <ui5-button
64
+ [design]="action.design ?? 'Default'"
65
+ [icon]="action.icon ?? ''"
66
+ [endIcon]="action?.endIcon"
67
+ [tooltip]="action.tooltip ?? ''"
68
+ (click)="
69
+ actionButtonClick.emit({ event: $event, action: action })
70
+ "
71
+ >{{ action.text }}</ui5-button
72
+ >
73
+ }
74
+ @if (config().editable) {
75
+ <ui5-button
76
+ [design]="editViewButton().design"
77
+ [icon]="editViewButton().icon"
78
+ [tooltip]="editViewButton().tooltip"
79
+ (click)="enterEditMode()"
80
+ >{{ editViewButton().text }}</ui5-button
81
+ >
82
+ }
83
+ }
84
+ </div>
85
+ </div>
86
+
87
+ <div class="mfp-dashboard__subheader">
88
+ <slot name="dashboard-subheader"></slot>
89
+ </div>
90
+ </div>
91
+ <div class="mfp-sections-container">
92
+ @for (section of sections(); track section.id) {
93
+ <mfp-dashboard-section
94
+ [section]="section"
95
+ [cards]="sectionCards()(section.id)"
96
+ [columns]="12"
97
+ [editMode]="editMode()"
98
+ (removeSection)="removeSection(section.id)"
99
+ (removeCard)="removeCard($event)"
100
+ />
101
+ }
102
+ </div>
103
+ <gridstack #grid (changeCB)="onOrderChange($event)" [options]="gridOptions()">
104
+ @for (card of looseCards(); track card.id) {
105
+ <gridstack-item
106
+ [style.cursor]="editMode() ? 'pointer' : 'auto'"
107
+ [options]="card"
108
+ >
109
+ <mfp-dashboard-card
110
+ [card]="card"
111
+ [editMode]="editMode()"
112
+ (removeCard)="removeCard($any(card.id))"
113
+ />
114
+ </gridstack-item>
115
+ }
116
+ </gridstack>
117
+
118
+ @if (editMode()) {
119
+ <div class="mfp-dashboard__edit-bar">
120
+ <ui5-button design="Emphasized" (click)="saveEdit()">Save</ui5-button>
121
+ <ui5-button design="Transparent" (click)="cancelEdit()"
122
+ >Cancel</ui5-button
123
+ >
124
+ </div>
125
+ }
126
+ </div>
127
+
128
+ <mfp-add-card-dialog
129
+ [open]="cardDialogOpen()"
130
+ [availableCards]="availableCards()"
131
+ [addedCardsIds]="addedCardsIds()"
132
+ (confirm)="onCardsAdded($event)"
133
+ (cancel)="closeCardPanel()"
134
+ />
@@ -0,0 +1,88 @@
1
+ @import 'gridstack/dist/gridstack.min.css';
2
+
3
+ mfp-dashboard,
4
+ mfp-wc-dashboard {
5
+ display: block;
6
+ margin: 0;
7
+ padding: 0;
8
+ min-height: 100%;
9
+ background: var(--sapShellColor, #354a5e);
10
+ background-size: cover;
11
+ background-position: center;
12
+ background-repeat: no-repeat;
13
+ background-attachment: local;
14
+ }
15
+
16
+ .mfp-dashboard {
17
+ padding: 2rem;
18
+ }
19
+
20
+ .mfp-sections-container {
21
+ display: grid;
22
+ grid-template-columns: repeat(12, 1fr);
23
+ grid-auto-rows: auto;
24
+ align-content: start;
25
+ gap: 1rem;
26
+
27
+ @media (max-width: 726px) {
28
+ grid-template-columns: 1fr;
29
+ }
30
+
31
+ @media (min-width: 727px) and (max-width: 1200px) {
32
+ grid-template-columns: repeat(8, 1fr);
33
+ }
34
+ }
35
+
36
+ .mfp-dashboard__topbar {
37
+ grid-column: 1 / -1;
38
+ align-self: start;
39
+ display: flex;
40
+ flex-direction: column;
41
+ gap: 1rem;
42
+ padding-bottom: 3rem;
43
+ width: 100%;
44
+ min-width: 0;
45
+ }
46
+
47
+ .mfp-dashboard__topbar-row {
48
+ display: flex;
49
+ align-items: flex-start;
50
+ justify-content: space-between;
51
+ gap: 1rem;
52
+ }
53
+
54
+ .mfp-dashboard__header {
55
+ display: flex;
56
+ flex-direction: column;
57
+ gap: 0.25rem;
58
+ }
59
+
60
+ .mfp-dashboard__toolbar {
61
+ flex-shrink: 0;
62
+ display: flex;
63
+ gap: 0.5rem;
64
+ }
65
+
66
+ .mfp-dashboard__subheader {
67
+ display: flex;
68
+ align-items: center;
69
+ gap: 1rem;
70
+ }
71
+
72
+ .mfp-dashboard__edit-bar {
73
+ position: fixed;
74
+ bottom: 20px;
75
+ left: 16px;
76
+ right: 16px;
77
+ z-index: 1000;
78
+ display: flex;
79
+ justify-content: flex-end;
80
+ align-items: center;
81
+ gap: 8px;
82
+ padding: 7px 16px;
83
+ background: var(--sapPageFooter_Background, white);
84
+ border-radius: var(--sapElement_BorderCornerRadius, 12px);
85
+ box-shadow: 0px 0px 0px 1px rgba(34, 53, 72, 0.48), 0px 2px 8px 0px rgba(34, 53, 72, 0.3);
86
+ box-sizing: border-box;
87
+ overflow: hidden;
88
+ }