@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.
- package/components/steps-form/index.js +3 -0
- package/components/steps-form/src/DileStepForm.js +202 -0
- package/components/steps-form/src/DileStepNextPrev.js +134 -0
- package/components/steps-form/src/DileStepsForm.js +347 -0
- package/components/steps-form/step-form.js +5 -0
- package/components/steps-form/step-next-prev.js +5 -0
- package/components/steps-form/steps-form.js +5 -0
- package/lib/i18n/en.js +6 -1
- package/lib/i18n/es.js +6 -1
- package/package.json +3 -3
- package/components/action/src/DileCrudSingleActions.spec.js +0 -126
- 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
- package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-initialData-is-cleared-after-being-applied-1.png +0 -0
- package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-initialData-is-only-applied-once-and-cleared-after-opening-1.png +0 -0
- package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-triggerAction-behavior-matches-original-when-no-initialData-1.png +0 -0
- package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-triggerAction-opens-confirm-dialog-1.png +0 -0
- package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-triggerAction-with-initialData-sets-form-values-1.png +0 -0
- package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-triggerAction-with-initialData-sets-form-values-before-opening-dialog-1.png +0 -0
- package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-triggerAction-without-initialData-resets-form-1.png +0 -0
- package/components/action/src/__screenshots__/DileCrudSingleActions.spec.js/DileCrudSingleActions-with-triggerAction-triggerAction-without-initialData-resets-form-to-empty-1.png +0 -0
- package/components/ajax/ajax.spec.js +0 -35
- package/components/ajax-select-crud/ajax-select-crud-overlay.spec.js +0 -186
- 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
- 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
- package/components/crud/crud.spec.js +0 -453
- package/components/file-uploader/file-uploader.spec.js +0 -32
- 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
- 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
- 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
- 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
- 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
- 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
- package/components/list/crud-data-grid.spec.js +0 -207
- package/components/list/crud-item-actions.spec.js +0 -122
- package/components/list/crud-list.spec.js +0 -455
- package/components/many-relation/many-relation.spec.js +0 -154
- package/components/select-ajax-simple/select-ajax-simple.spec.js +0 -133
- package/components/single/src/DileCrudSingle.spec.js +0 -151
- package/components/single/src/__screenshots__/DileCrudSingle.spec.js/DileCrudSingle-triggerAction-and-triggerDirectAction-triggerAction-delegates-to-singleActionsElement-triggerAction-1.png +0 -0
- package/components/single/src/__screenshots__/DileCrudSingle.spec.js/DileCrudSingle-triggerAction-and-triggerDirectAction-triggerAction-without-initialData-works-1.png +0 -0
- package/components/single/src/__screenshots__/DileCrudSingle.spec.js/DileCrudSingle-triggerAction-and-triggerDirectAction-triggerDirectAction-delegates-to-singleActionsElement-triggerAction-1.png +0 -0
- package/components/single/src/__screenshots__/DileCrudSingle.spec.js/DileCrudSingle-triggerAction-and-triggerDirectAction-triggerDirectAction-without-initialData-still-works--backward-compatibility--1.png +0 -0
- 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
- 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
- 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
- package/components/ui/crud-sort-form.spec.js +0 -132
- package/lib/CrudConfigBuilder.spec.js +0 -32
- package/lib/deepMerge.spec.js +0 -45
- package/lib/listViewMode.spec.js +0 -73
|
@@ -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
|
+
}
|