@dile/crud 2.9.5 → 2.10.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 (49) hide show
  1. package/components/steps-form/index.js +3 -0
  2. package/components/steps-form/src/DileStepForm.js +202 -0
  3. package/components/steps-form/src/DileStepNextPrev.js +134 -0
  4. package/components/steps-form/src/DileStepsForm.js +347 -0
  5. package/components/steps-form/step-form.js +5 -0
  6. package/components/steps-form/step-next-prev.js +5 -0
  7. package/components/steps-form/steps-form.js +5 -0
  8. package/lib/i18n/en.js +6 -1
  9. package/lib/i18n/es.js +6 -1
  10. package/package.json +3 -3
  11. package/components/action/src/DileCrudSingleActions.spec.js +0 -126
  12. package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-closing-dialog-with-ESC-and-reopening-without-data-shows-reset-form-1.png +0 -0
  13. package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-initialData-is-cleared-after-being-applied-1.png +0 -0
  14. package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-initialData-is-only-applied-once-and-cleared-after-opening-1.png +0 -0
  15. package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-triggerAction-behavior-matches-original-when-no-initialData-1.png +0 -0
  16. package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-triggerAction-opens-confirm-dialog-1.png +0 -0
  17. package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-triggerAction-with-initialData-sets-form-values-1.png +0 -0
  18. package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-triggerAction-with-initialData-sets-form-values-before-opening-dialog-1.png +0 -0
  19. package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-triggerAction-without-initialData-resets-form-1.png +0 -0
  20. package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-triggerAction-without-initialData-resets-form-to-empty-1.png +0 -0
  21. package/components/ajax/ajax.spec.js +0 -35
  22. package/components/ajax-select-crud/ajax-select-crud-overlay.spec.js +0 -186
  23. package/components/crud/__screenshots__/crud.spec.js/dile-crud-list-grid-view-switch-defaults-to-the-item-template-and-toggles-to-the-grid-on-click-when-both-views-are-configured-1.png +0 -0
  24. package/components/crud/__screenshots__/crud.spec.js/dile-crud-list-grid-view-switch-does-not-render-the-switch-button-when-customization-disableListWiewSwitch-is-true-1.png +0 -0
  25. package/components/crud/crud.spec.js +0 -453
  26. package/components/file-uploader/file-uploader.spec.js +0 -32
  27. package/components/list/__screenshots__/crud-data-grid.spec.js/dile-crud-data-grid-always-forces-sort-mode-to-external-regardless-of-config-1.png +0 -0
  28. package/components/list/__screenshots__/crud-data-grid.spec.js/dile-crud-data-grid-forwards-stickyFirstColumn--striped--responsiveMode-and-emptyMessage-with-sensible-defaults-1.png +0 -0
  29. package/components/list/__screenshots__/crud-data-grid.spec.js/dile-crud-data-grid-leaves-computeRowId-unset-when-config-computeItemId-is-not-a-function-1.png +0 -0
  30. package/components/list/__screenshots__/crud-list.spec.js/dile-crud-list-scope-changes-and-page-reset-keeps-the-current-page-on-refresh---and-goes-back-to-page-1-on-refresh---resetPage--true----1.png +0 -0
  31. package/components/list/__screenshots__/crud-list.spec.js/dile-crud-list-scope-changes-and-page-reset-reloads-from-the-first-page-with-the-new-scope-when-belongsTo-and-relationId-change-1.png +0 -0
  32. package/components/list/__screenshots__/crud-list.spec.js/dile-crud-list-scope-changes-and-page-reset-reloads-when-only-relationId-changes-1.png +0 -0
  33. package/components/list/crud-data-grid.spec.js +0 -207
  34. package/components/list/crud-item-actions.spec.js +0 -122
  35. package/components/list/crud-list.spec.js +0 -455
  36. package/components/many-relation/many-relation.spec.js +0 -154
  37. package/components/select-ajax-simple/select-ajax-simple.spec.js +0 -133
  38. package/components/single/src/DileCrudSingle.spec.js +0 -151
  39. package/components/single/src/__screenshots__/DileCrudSingle.spec.js/DileCrudSingle-triggerAction-and-triggerDirectAction-triggerAction-delegates-to-singleActionsElement-triggerAction-1.png +0 -0
  40. package/components/single/src/__screenshots__/DileCrudSingle.spec.js/DileCrudSingle-triggerAction-and-triggerDirectAction-triggerAction-without-initialData-works-1.png +0 -0
  41. package/components/single/src/__screenshots__/DileCrudSingle.spec.js/DileCrudSingle-triggerAction-and-triggerDirectAction-triggerDirectAction-delegates-to-singleActionsElement-triggerAction-1.png +0 -0
  42. package/components/single/src/__screenshots__/DileCrudSingle.spec.js/DileCrudSingle-triggerAction-and-triggerDirectAction-triggerDirectAction-without-initialData-still-works--backward-compatibility--1.png +0 -0
  43. package/components/ui/__screenshots__/crud-sort-form.spec.js/dile-crud-sort-form-clicking-the-label-of-an-inactive-option-switches-to-it-using-its-own-configured-direction--without-flipping-it-1.png +0 -0
  44. package/components/ui/__screenshots__/crud-sort-form.spec.js/dile-crud-sort-form-clicking-the-label-of-the-already-active-option-toggles-its-direction-instead-of-doing-nothing-1.png +0 -0
  45. package/components/ui/__screenshots__/crud-sort-form.spec.js/dile-crud-sort-form-syncSort-suppresses-exactly-one-radio-group-echo--preserving-the-synced-direction-instead-of-falling-back-to-the-option-static-default-1.png +0 -0
  46. package/components/ui/crud-sort-form.spec.js +0 -132
  47. package/lib/CrudConfigBuilder.spec.js +0 -32
  48. package/lib/deepMerge.spec.js +0 -45
  49. package/lib/listViewMode.spec.js +0 -73
@@ -0,0 +1,3 @@
1
+ export { DileStepsForm } from './src/DileStepsForm.js';
2
+ export { DileStepForm } from './src/DileStepForm.js';
3
+ export { DileStepNextPrev } from './src/DileStepNextPrev.js';
@@ -0,0 +1,202 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import "../../ajax/ajax.js";
3
+ import { ResponseApiAdapter } from "../../../lib/ResponseApiAdapter.js";
4
+ import "../step-next-prev.js";
5
+
6
+ export class DileStepForm extends LitElement {
7
+ static styles = [
8
+ css`
9
+ :host {
10
+ display: block;
11
+ }
12
+ :host(:not([active])) {
13
+ display: none;
14
+ }
15
+ `,
16
+ ];
17
+
18
+ static get properties() {
19
+ return {
20
+ /** Label of the step, shown in the steps indicator */
21
+ label: { type: String },
22
+ /** The step is the visible one. Managed by dile-steps-form */
23
+ active: { type: Boolean, reflect: true },
24
+ /** Endpoint where the step data is saved */
25
+ endpoint: { type: String },
26
+ /** HTTP method used to save the step */
27
+ method: { type: String },
28
+ /** Zero-based index of the step, set by dile-steps-form */
29
+ step: { type: Number },
30
+ /** Labels of all the steps, set by dile-steps-form */
31
+ steps: { type: Array },
32
+ /** Shows the alternative exit link in this step */
33
+ alternativeExit: { type: Boolean },
34
+ alternativeExitLabel: { type: String },
35
+ /** Name of the field added to the payload when the alternative exit is used. Empty to not send it */
36
+ alternativeExitField: { type: String },
37
+ prevLabel: { type: String },
38
+ nextLabel: { type: String },
39
+ endLabel: { type: String },
40
+ /** Default labels, set by dile-steps-form: { prev, next, end } */
41
+ defaultLabels: { type: Object },
42
+ /** The form of this step receives the data returned when another step is saved */
43
+ refreshOnSave: { type: Boolean },
44
+ sendDataAsFormData: { type: Boolean },
45
+ responseAdapter: { type: Object },
46
+ language: { type: String },
47
+ loading: { type: Boolean },
48
+ };
49
+ }
50
+
51
+ constructor() {
52
+ super();
53
+ this.active = false;
54
+ this.method = "patch";
55
+ this.steps = [];
56
+ this.alternativeExit = false;
57
+ this.alternativeExitField = "alternativeExit";
58
+ this.defaultLabels = {};
59
+ this.refreshOnSave = false;
60
+ this.sendDataAsFormData = false;
61
+ this.language = "en";
62
+ this.loading = false;
63
+ this.alternativeExitRequested = false;
64
+ this.defaultResponseAdapter = new ResponseApiAdapter();
65
+ }
66
+
67
+ get elajax() {
68
+ return this.shadowRoot.getElementById("ajaxsave");
69
+ }
70
+
71
+ get form() {
72
+ return this.querySelector('[slot="form"]');
73
+ }
74
+
75
+ get adapter() {
76
+ return this.responseAdapter ?? this.defaultResponseAdapter;
77
+ }
78
+
79
+ updated(changedProperties) {
80
+ if (
81
+ changedProperties.has("label") &&
82
+ changedProperties.get("label") !== undefined
83
+ ) {
84
+ this.dispatchEvent(
85
+ new CustomEvent("dile-step-form-label-change", {
86
+ bubbles: true,
87
+ detail: { label: this.label },
88
+ }),
89
+ );
90
+ }
91
+ }
92
+
93
+ render() {
94
+ return html`
95
+ <dile-ajax
96
+ id="ajaxsave"
97
+ method="${this.method}"
98
+ url="${this.endpoint}"
99
+ language="${this.language}"
100
+ ?sendDataAsFormData=${this.sendDataAsFormData}
101
+ @ajax-success="${this.doSuccessSave}"
102
+ @ajax-error="${this.doErrorSave}"
103
+ ></dile-ajax>
104
+ <slot name="form"></slot>
105
+ <dile-step-next-prev
106
+ .current=${this.step}
107
+ .steps=${this.steps}
108
+ ?alternativeExit=${this.alternativeExit}
109
+ .alternativeExitLabel=${this.alternativeExitLabel}
110
+ .prevLabel=${this.prevLabel || this.defaultLabels?.prev}
111
+ .nextLabel=${this.nextLabel || this.defaultLabels?.next}
112
+ .endLabel=${this.endLabel || this.defaultLabels?.end}
113
+ language="${this.language}"
114
+ ?loading=${this.loading}
115
+ @dile-step-next-click=${this.next}
116
+ @dile-step-alternative-exit=${this.alternativeNext}
117
+ @dile-step-prev-click=${this.navigatePrev}
118
+ ></dile-step-next-prev>
119
+ `;
120
+ }
121
+
122
+ next() {
123
+ this.submit(false);
124
+ }
125
+
126
+ alternativeNext() {
127
+ this.submit(true);
128
+ }
129
+
130
+ submit(alternativeExit) {
131
+ const form = this.form;
132
+ if (!form) {
133
+ throw new Error(
134
+ `[dile-step-form] No element with slot="form" was found in step ${this.step + 1}. The form element must have the slot="form" attribute.`,
135
+ );
136
+ }
137
+ const data = form.getData();
138
+ if (alternativeExit && this.alternativeExitField) {
139
+ data[this.alternativeExitField] = true;
140
+ }
141
+ this.alternativeExitRequested = alternativeExit;
142
+ this.setLoading(true);
143
+ this.elajax.data = data;
144
+ this.elajax.generateRequest();
145
+ }
146
+
147
+ setLoading(loading) {
148
+ this.loading = loading;
149
+ this.dispatchEvent(
150
+ new CustomEvent("dile-step-form-loading", {
151
+ bubbles: true,
152
+ composed: true,
153
+ detail: { loading },
154
+ }),
155
+ );
156
+ }
157
+
158
+ doSuccessSave(e) {
159
+ const alternativeExit = this.alternativeExitRequested;
160
+ this.alternativeExitRequested = false;
161
+ this.setLoading(false);
162
+ this.adapter.setResponse(e.detail);
163
+ const data = this.adapter.getData();
164
+ this.dispatchEvent(
165
+ new CustomEvent("dile-step-form-success", {
166
+ bubbles: true,
167
+ composed: true,
168
+ detail: { message: this.adapter.getMessage(), data },
169
+ }),
170
+ );
171
+ this.dispatchEvent(
172
+ new CustomEvent("dile-step-next", {
173
+ bubbles: true,
174
+ detail: { data, alternativeExit },
175
+ }),
176
+ );
177
+ }
178
+
179
+ doErrorSave(e) {
180
+ this.alternativeExitRequested = false;
181
+ this.setLoading(false);
182
+ this.adapter.setResponse(e.detail);
183
+ const errors = this.adapter.getValidationErrors();
184
+ this.dispatchEvent(
185
+ new CustomEvent("dile-step-form-error", {
186
+ bubbles: true,
187
+ composed: true,
188
+ detail: { message: e.detail.message, errors, status: e.detail.status },
189
+ }),
190
+ );
191
+ this.form?.showErrors(errors);
192
+ }
193
+
194
+ navigatePrev() {
195
+ this.dispatchEvent(
196
+ new CustomEvent("dile-step-prev", {
197
+ bubbles: true,
198
+ detail: { index: this.step - 1 },
199
+ }),
200
+ );
201
+ }
202
+ }
@@ -0,0 +1,134 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import "@dile/ui/components/button/button.js";
3
+ import { DileI18nMixin } from "../../../lib/DileI18nMixin.js";
4
+
5
+ export class DileStepNextPrev extends DileI18nMixin(LitElement) {
6
+ static styles = [
7
+ css`
8
+ :host {
9
+ display: block;
10
+ container-type: inline-size;
11
+ margin-top: var(--dile-step-next-prev-margin-top, 1.5rem);
12
+ }
13
+ section {
14
+ display: flex;
15
+ flex-direction: column;
16
+ justify-content: space-between;
17
+ gap: 0.25rem;
18
+ align-items: center;
19
+ }
20
+ @container (min-width: 420px) {
21
+ section {
22
+ flex-direction: row;
23
+ gap: 1rem;
24
+ }
25
+ }
26
+ .alternative-exit {
27
+ flex-grow: 1;
28
+ display: flex;
29
+ justify-content: flex-end;
30
+ }
31
+ .alternative-exit button {
32
+ color: var(--dile-step-alternative-exit-color, var(--dile-link-color));
33
+ background: none;
34
+ border: none;
35
+ padding: 0;
36
+ font: inherit;
37
+ cursor: pointer;
38
+ text-decoration: none;
39
+ }
40
+ .alternative-exit button:disabled {
41
+ cursor: default;
42
+ opacity: 0.5;
43
+ }
44
+ `,
45
+ ];
46
+
47
+ static get properties() {
48
+ return {
49
+ current: { type: Number },
50
+ steps: { type: Array },
51
+ prevLabel: { type: String },
52
+ nextLabel: { type: String },
53
+ endLabel: { type: String },
54
+ alternativeExit: { type: Boolean },
55
+ alternativeExitLabel: { type: String },
56
+ loading: { type: Boolean },
57
+ };
58
+ }
59
+
60
+ constructor() {
61
+ super();
62
+ this.current = 0;
63
+ this.steps = [];
64
+ this.alternativeExit = false;
65
+ this.loading = false;
66
+ }
67
+
68
+ get isLast() {
69
+ return this.current >= this.steps.length - 1;
70
+ }
71
+
72
+ get computedPrevLabel() {
73
+ return this.prevLabel || this.translations?.step_prev_label || "Previous";
74
+ }
75
+
76
+ get computedNextLabel() {
77
+ return this.nextLabel || this.translations?.step_next_label || "Next";
78
+ }
79
+
80
+ get computedEndLabel() {
81
+ return this.endLabel || this.translations?.step_end_label || "Finish";
82
+ }
83
+
84
+ render() {
85
+ return html`
86
+ <section>
87
+ <div class="prev">
88
+ ${this.current > 0
89
+ ? html`<dile-button ?disabled=${this.loading} @click=${this.prev}
90
+ >${this.computedPrevLabel}</dile-button
91
+ >`
92
+ : ""}
93
+ </div>
94
+ ${this.alternativeExit && this.alternativeExitLabel
95
+ ? html`<div class="alternative-exit">
96
+ <button
97
+ type="button"
98
+ ?disabled=${this.loading}
99
+ @click=${this.alternativeExitClick}
100
+ >
101
+ ${this.alternativeExitLabel}
102
+ </button>
103
+ </div>`
104
+ : ""}
105
+ <div class="next">
106
+ <dile-button ?loading=${this.loading} @click=${this.next}>
107
+ ${this.isLast ? this.computedEndLabel : this.computedNextLabel}
108
+ </dile-button>
109
+ </div>
110
+ </section>
111
+ `;
112
+ }
113
+
114
+ dispatchNavigationEvent(eventName) {
115
+ this.dispatchEvent(
116
+ new CustomEvent(eventName, {
117
+ detail: {
118
+ current: this.current,
119
+ },
120
+ }),
121
+ );
122
+ }
123
+
124
+ next() {
125
+ this.dispatchNavigationEvent("dile-step-next-click");
126
+ }
127
+ prev() {
128
+ this.dispatchNavigationEvent("dile-step-prev-click");
129
+ }
130
+
131
+ alternativeExitClick() {
132
+ this.dispatchNavigationEvent("dile-step-alternative-exit");
133
+ }
134
+ }
@@ -0,0 +1,347 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import "@dile/ui/components/steps/steps.js";
3
+ import "../../ajax/ajax.js";
4
+ import "@dile/ui/components/inline-feedback/inline-feedback.js";
5
+ import { ResponseApiAdapter } from "../../../lib/ResponseApiAdapter.js";
6
+ import { DileI18nMixin } from "../../../lib/DileI18nMixin.js";
7
+
8
+ export class DileStepsForm extends DileI18nMixin(LitElement) {
9
+ static styles = [
10
+ css`
11
+ :host {
12
+ display: block;
13
+ }
14
+ dile-steps {
15
+ margin-bottom: 1rem;
16
+ }
17
+ .header {
18
+ margin-bottom: 1.5rem;
19
+ font-size: var(--dile-steps-form-header-font-size, 1rem);
20
+ color: var(--dile-steps-form-header-color, var(--dile-on-background-color, #303030));
21
+ }
22
+ .title {
23
+ font-weight: var(--dile-steps-form-title-font-weight, 600);
24
+ color: var(--dile-on-background-color, #303030);
25
+ }
26
+ .separator {
27
+ margin: 0 0.4rem;
28
+ opacity: 0.5;
29
+ }
30
+ .step-label {
31
+ opacity: 0.75;
32
+ color: var(--dile-on-background-color, #303030);
33
+ }
34
+ .header:focus-visible {
35
+ outline: 2px solid var(--dile-step-active-border-color, var(--dile-primary-dark-color, #0039cb));
36
+ outline-offset: 2px;
37
+ }
38
+ dile-inline-feedback {
39
+ margin-top: var(--dile-steps-form-feedback-margin-top, 1rem);
40
+ }
41
+ :host([hidefeedback]) dile-inline-feedback {
42
+ display: none;
43
+ }
44
+ `,
45
+ ];
46
+
47
+ static get properties() {
48
+ return {
49
+ /** Labels of the steps. If empty, the label attribute of each dile-step-form is used */
50
+ steps: { type: Array },
51
+ title: { type: String },
52
+ /** Endpoint to load the data of every step form */
53
+ endpoint: { type: String },
54
+ current: { type: Number },
55
+ reached: { type: Number },
56
+ freeNavigation: { type: Boolean },
57
+ vertical: { type: Boolean },
58
+ /** Default labels for the navigation buttons of every step */
59
+ prevLabel: { type: String },
60
+ nextLabel: { type: String },
61
+ endLabel: { type: String },
62
+ /** Response adapter for the initial load and, if set, for every step */
63
+ responseAdapter: { type: Object },
64
+ /** Hides the inline feedback messages */
65
+ hideFeedback: { type: Boolean, reflect: true },
66
+ };
67
+ }
68
+
69
+ constructor() {
70
+ super();
71
+ this.steps = [];
72
+ this.current = 0;
73
+ this.reached = 0;
74
+ this.freeNavigation = false;
75
+ this.vertical = false;
76
+ this.hideFeedback = false;
77
+ this.defaultResponseAdapter = new ResponseApiAdapter();
78
+ }
79
+
80
+ get adapter() {
81
+ return this.responseAdapter ?? this.defaultResponseAdapter;
82
+ }
83
+
84
+ connectedCallback() {
85
+ super.connectedCallback();
86
+ this.addEventListener("dile-step-next", this.onStepNext);
87
+ this.addEventListener("dile-step-prev", this.onStepPrev);
88
+ this.addEventListener("dile-step-form-loading", this.onStepLoading);
89
+ this.addEventListener("dile-step-form-success", this.onStepSuccess);
90
+ this.addEventListener("dile-step-form-error", this.onStepError);
91
+ this.addEventListener("dile-step-form-label-change", this.syncStepForms);
92
+ }
93
+
94
+ disconnectedCallback() {
95
+ super.disconnectedCallback();
96
+ this.removeEventListener("dile-step-next", this.onStepNext);
97
+ this.removeEventListener("dile-step-prev", this.onStepPrev);
98
+ this.removeEventListener("dile-step-form-loading", this.onStepLoading);
99
+ this.removeEventListener("dile-step-form-success", this.onStepSuccess);
100
+ this.removeEventListener("dile-step-form-error", this.onStepError);
101
+ this.removeEventListener("dile-step-form-label-change", this.syncStepForms);
102
+ }
103
+
104
+ updated(changedProperties) {
105
+ if (
106
+ ["steps", "current", "prevLabel", "nextLabel", "endLabel", "responseAdapter", "language"].some(
107
+ (property) => changedProperties.has(property),
108
+ )
109
+ ) {
110
+ this.syncStepForms();
111
+ }
112
+ if (changedProperties.has("endpoint") && this.endpoint) {
113
+ this.fetchData();
114
+ }
115
+ if (
116
+ changedProperties.has("current") &&
117
+ changedProperties.get("current") !== undefined
118
+ ) {
119
+ this.focusHeader();
120
+ }
121
+ }
122
+
123
+ render() {
124
+ return html`
125
+ ${this.endpoint
126
+ ? html`
127
+ <dile-ajax
128
+ id="ajaxget"
129
+ method="get"
130
+ url="${this.endpoint}"
131
+ language="${this.language}"
132
+ @ajax-success="${this.onFetchSuccess}"
133
+ @ajax-error="${this.onFetchError}"
134
+ ></dile-ajax>
135
+ `
136
+ : ""}
137
+ ${this.headerTemplate}
138
+ <dile-steps
139
+ .steps=${this.stepLabels}
140
+ current="${this.current}"
141
+ reached="${this.reached}"
142
+ ?freeNavigation=${this.freeNavigation}
143
+ ?vertical=${this.vertical}
144
+ @dile-steps-select=${this.onStepSelect}
145
+ ></dile-steps>
146
+ <slot @slotchange=${this.syncStepForms}></slot>
147
+ <dile-inline-feedback id="feedback"></dile-inline-feedback>
148
+ `;
149
+ }
150
+
151
+ get feedback() {
152
+ return this.shadowRoot.getElementById("feedback");
153
+ }
154
+
155
+ get stepForms() {
156
+ return [...this.querySelectorAll(":scope > dile-step-form")];
157
+ }
158
+
159
+ get stepLabels() {
160
+ if (this.steps.length) {
161
+ return this.steps;
162
+ }
163
+ return this.stepForms.map((stepForm) => stepForm.label ?? "");
164
+ }
165
+
166
+ get formElements() {
167
+ return this.stepForms
168
+ .map((stepForm) => stepForm.form)
169
+ .filter((form) => form);
170
+ }
171
+
172
+ get currentStepLabel() {
173
+ return this.stepLabels[this.current] ?? "";
174
+ }
175
+
176
+ get headerTemplate() {
177
+ if (!this.title && !this.currentStepLabel) return "";
178
+ return html`
179
+ <div class="header" tabindex="-1" role="status">
180
+ ${this.title
181
+ ? html`<span class="title">${this.title}</span
182
+ ><span class="separator">›</span
183
+ ><span class="step-label">${this.currentStepLabel}</span>`
184
+ : html`<span class="step-label">${this.currentStepLabel}</span>`}
185
+ </div>
186
+ `;
187
+ }
188
+
189
+ focusHeader() {
190
+ this.shadowRoot.querySelector(".header")?.focus();
191
+ }
192
+
193
+ syncStepForms() {
194
+ const forms = this.stepForms;
195
+ const labels = this.stepLabels;
196
+ forms.forEach((form, index) => {
197
+ form.step = index;
198
+ form.steps = labels;
199
+ form.active = index === this.current;
200
+ form.language = this.language;
201
+ form.defaultLabels = {
202
+ prev: this.prevLabel,
203
+ next: this.nextLabel,
204
+ end: this.endLabel,
205
+ };
206
+ if (this.responseAdapter) {
207
+ form.responseAdapter = this.responseAdapter;
208
+ }
209
+ });
210
+ if (this.steps.length && forms.length && forms.length !== this.steps.length) {
211
+ console.warn(
212
+ `[dile-steps-form] The number of <dile-step-form> elements (${forms.length}) does not match steps.length (${this.steps.length}).`,
213
+ );
214
+ }
215
+ this.requestUpdate();
216
+ }
217
+
218
+ dispatchLoading(loading) {
219
+ this.dispatchEvent(
220
+ new CustomEvent("dile-steps-form-loading", {
221
+ bubbles: true,
222
+ composed: true,
223
+ detail: { loading },
224
+ }),
225
+ );
226
+ }
227
+
228
+ fetchData() {
229
+ this.dispatchLoading(true);
230
+ this.shadowRoot.getElementById("ajaxget")?.generateRequest();
231
+ }
232
+
233
+ async onFetchSuccess(e) {
234
+ this.adapter.setResponse(e.detail);
235
+ const data = this.adapter.getData();
236
+ await Promise.all(this.formElements.map((form) => form.updateComplete));
237
+ this.setData(data);
238
+ this.dispatchLoading(false);
239
+ this.dispatchEvent(
240
+ new CustomEvent("dile-steps-form-loaded", {
241
+ bubbles: true,
242
+ composed: true,
243
+ detail: { data },
244
+ }),
245
+ );
246
+ }
247
+
248
+ onFetchError(e) {
249
+ this.dispatchLoading(false);
250
+ this.dispatchEvent(
251
+ new CustomEvent("dile-steps-form-error", {
252
+ bubbles: true,
253
+ composed: true,
254
+ detail: {
255
+ message: e.detail.message,
256
+ data: e.detail.data,
257
+ errors: e.detail.errors,
258
+ status: e.detail.status,
259
+ },
260
+ }),
261
+ );
262
+ }
263
+
264
+ /** Loads again the data of every step form from the endpoint */
265
+ reload() {
266
+ if (this.endpoint) {
267
+ this.fetchData();
268
+ }
269
+ }
270
+
271
+ /** Sets the same data in the form of every step */
272
+ setData(data) {
273
+ this.formElements.forEach((form) => form.setData(data));
274
+ }
275
+
276
+ onStepLoading(e) {
277
+ if (e.detail.loading) {
278
+ this.feedback?.clear();
279
+ }
280
+ }
281
+
282
+ onStepSuccess(e) {
283
+ this.stepForms
284
+ .filter((stepForm) => stepForm.refreshOnSave && !e.composedPath().includes(stepForm))
285
+ .forEach((stepForm) => stepForm.form?.setData(e.detail.data));
286
+ this.feedback?.positiveFeedbackWithDelay(
287
+ e.detail.message || this.translations?.step_success || "Data saved",
288
+ 5000,
289
+ );
290
+ }
291
+
292
+ onStepError(e) {
293
+ this.feedback?.negativeFeedbackWithDelay(
294
+ e.detail.message || this.translations?.step_error || "Data could not be saved",
295
+ 5000,
296
+ );
297
+ }
298
+
299
+ onStepNext(e) {
300
+ e.stopPropagation();
301
+ const next = this.current + 1;
302
+ if (next < this.stepForms.length && !e.detail.alternativeExit) {
303
+ this.navigateTo(next);
304
+ } else {
305
+ this.dispatchEvent(
306
+ new CustomEvent("dile-steps-form-complete", {
307
+ bubbles: true,
308
+ composed: true,
309
+ detail: {
310
+ data: e.detail.data,
311
+ alternativeExit: Boolean(e.detail.alternativeExit),
312
+ },
313
+ }),
314
+ );
315
+ }
316
+ }
317
+
318
+ onStepPrev(e) {
319
+ e.stopPropagation();
320
+ this.feedback?.clear();
321
+ this.navigateTo(this.current - 1);
322
+ }
323
+
324
+ onStepSelect(e) {
325
+ e.stopPropagation();
326
+ this.feedback?.clear();
327
+ this.navigateTo(e.detail.index);
328
+ }
329
+
330
+ /** Shows the step at the given zero-based index */
331
+ navigateTo(index) {
332
+ if (index < 0 || index >= this.stepForms.length) return;
333
+ this.current = index;
334
+ if (index > this.reached) this.reached = index;
335
+ this.stepForms.forEach((stepForm, i) => {
336
+ stepForm.active = i === index;
337
+ });
338
+ }
339
+
340
+ next() {
341
+ this.navigateTo(this.current + 1);
342
+ }
343
+
344
+ prev() {
345
+ this.navigateTo(this.current - 1);
346
+ }
347
+ }
@@ -0,0 +1,5 @@
1
+ import { DileStepForm } from './src/DileStepForm.js';
2
+
3
+ if (!customElements.get('dile-step-form')) {
4
+ customElements.define('dile-step-form', DileStepForm);
5
+ }
@@ -0,0 +1,5 @@
1
+ import { DileStepNextPrev } from './src/DileStepNextPrev.js';
2
+
3
+ if (!customElements.get('dile-step-next-prev')) {
4
+ customElements.define('dile-step-next-prev', DileStepNextPrev);
5
+ }
@@ -0,0 +1,5 @@
1
+ import { DileStepsForm } from './src/DileStepsForm.js';
2
+
3
+ if (!customElements.get('dile-steps-form')) {
4
+ customElements.define('dile-steps-form', DileStepsForm);
5
+ }