@exmg/exm-form 1.1.36 → 1.1.37-alpha.31
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/.rollup.cache/root/repo/packages/exm-form/dist/exm-form-base.d.ts +56 -0
- package/.rollup.cache/root/repo/packages/exm-form/dist/exm-form-base.js +190 -0
- package/.rollup.cache/root/repo/packages/exm-form/dist/exm-form-dirty-mixin.d.ts +10 -0
- package/.rollup.cache/root/repo/packages/exm-form/dist/exm-form-dirty-mixin.js +92 -0
- package/.rollup.cache/root/repo/packages/exm-form/dist/exm-form-validate-mixin.d.ts +10 -0
- package/.rollup.cache/root/repo/packages/exm-form/dist/exm-form-validate-mixin.js +66 -0
- package/.rollup.cache/root/repo/packages/exm-form/dist/exm-form.d.ts +10 -0
- package/.rollup.cache/root/repo/packages/exm-form/dist/exm-form.js +15 -0
- package/.rollup.cache/root/repo/packages/exm-form/dist/index.d.ts +6 -0
- package/.rollup.cache/root/repo/packages/exm-form/dist/index.js +7 -0
- package/.rollup.cache/root/repo/packages/exm-form/dist/styles/exm-form-base-css.d.ts +1 -0
- package/.rollup.cache/root/repo/packages/exm-form/dist/styles/exm-form-base-css.js +91 -0
- package/.rollup.cache/root/repo/packages/exm-form/dist/styles/exm-form-css.d.ts +1 -0
- package/.rollup.cache/root/repo/packages/exm-form/dist/styles/exm-form-css.js +59 -0
- package/.rollup.cache/root/repo/packages/exm-form/dist/utils/serializeFormData.d.ts +1 -0
- package/.rollup.cache/root/repo/packages/exm-form/dist/utils/serializeFormData.js +64 -0
- package/dist/exm-form-base.js +6 -3
- package/dist/exm-form-dirty-mixin.js +7 -6
- package/dist/exm-form-validate-mixin.js +8 -7
- package/dist/exm-form.js +4 -2
- package/dist/index.js +1 -1
- package/dist/styles/exm-form-base-css.js +5 -2
- package/dist/styles/exm-form-css.js +5 -2
- package/dist/utils/serializeFormData.js +4 -2
- package/package.json +3 -3
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import '@exmg/exm-button/exm-text-button.js';
|
|
2
|
+
import '@material/web/button/outlined-button.js';
|
|
3
|
+
import '@material/web/button/filled-button.js';
|
|
4
|
+
import '@material/web/divider/divider.js';
|
|
5
|
+
import { ExmgElement } from '@exmg/lit-base';
|
|
6
|
+
declare const ExmFormBase_base: import("./exm-form-dirty-mixin.js").Constructor<import("./exm-form-dirty-mixin.js").FormDirtyMixinInterface> & import("./exm-form-validate-mixin.js").Constructor<import("./exm-form-validate-mixin.js").FormValidateMixinInterface> & typeof ExmgElement;
|
|
7
|
+
export declare class ExmFormBase extends ExmFormBase_base {
|
|
8
|
+
/**
|
|
9
|
+
* Submit button copy
|
|
10
|
+
*/
|
|
11
|
+
submitBtn: string;
|
|
12
|
+
/**
|
|
13
|
+
* Cancel button copy
|
|
14
|
+
*/
|
|
15
|
+
cancelBtn: string;
|
|
16
|
+
/**
|
|
17
|
+
* Hide cancel button
|
|
18
|
+
*/
|
|
19
|
+
hideCancelButton: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Show reset button
|
|
22
|
+
*/
|
|
23
|
+
showResetButton: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Disabled the submit,
|
|
26
|
+
*/
|
|
27
|
+
disableSubmitButton?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Internall used to show button spinner.
|
|
30
|
+
*/
|
|
31
|
+
submitting: boolean;
|
|
32
|
+
boundHandleBlur?: (e: Event) => void;
|
|
33
|
+
boundHandleKeyup?: (e: Event) => void;
|
|
34
|
+
hasDescription: boolean;
|
|
35
|
+
hasHeader: boolean;
|
|
36
|
+
errorMessage?: string | null;
|
|
37
|
+
reset(): void;
|
|
38
|
+
submit(): void;
|
|
39
|
+
/**
|
|
40
|
+
* Action method that needs to be implemented
|
|
41
|
+
* @param {CustomEvent} e
|
|
42
|
+
*/
|
|
43
|
+
doAction?(formData: unknown): Promise<void> | void;
|
|
44
|
+
protected handleSubmit(): Promise<void>;
|
|
45
|
+
showError(message: string): void;
|
|
46
|
+
slotHasContent(slot: HTMLSlotElement): boolean;
|
|
47
|
+
handleDescriptionSlotChange(event: CustomEvent): void;
|
|
48
|
+
handleHeaderSlotChange(event: CustomEvent): void;
|
|
49
|
+
protected renderToolbar(): import("lit-html").TemplateResult<1>;
|
|
50
|
+
renderFormContent(): import("lit-html").TemplateResult<1>;
|
|
51
|
+
protected renderDescription(): import("lit-html").TemplateResult<1>;
|
|
52
|
+
protected renderError(): import("lit-html").TemplateResult<1>;
|
|
53
|
+
protected renderActions(): import("lit-html").TemplateResult<1>;
|
|
54
|
+
protected render(): import("lit-html").TemplateResult<1>;
|
|
55
|
+
}
|
|
56
|
+
export {};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { html, nothing } from 'lit';
|
|
3
|
+
import { property, state } from 'lit/decorators.js';
|
|
4
|
+
import '@exmg/exm-button/exm-text-button.js';
|
|
5
|
+
import '@material/web/button/outlined-button.js';
|
|
6
|
+
import '@material/web/button/filled-button.js';
|
|
7
|
+
import '@material/web/divider/divider.js';
|
|
8
|
+
import { ExmgElement } from '@exmg/lit-base';
|
|
9
|
+
import { classMap } from 'lit/directives/class-map.js';
|
|
10
|
+
import { ExmFormValidateMixin } from './exm-form-validate-mixin.js';
|
|
11
|
+
import { serializeFormData } from './utils/serializeFormData.js';
|
|
12
|
+
import { ExmFormDirtyMixin } from './exm-form-dirty-mixin.js';
|
|
13
|
+
export class ExmFormBase extends ExmFormDirtyMixin(ExmFormValidateMixin(ExmgElement)) {
|
|
14
|
+
constructor() {
|
|
15
|
+
super(...arguments);
|
|
16
|
+
/**
|
|
17
|
+
* Submit button copy
|
|
18
|
+
*/
|
|
19
|
+
this.submitBtn = 'Save';
|
|
20
|
+
/**
|
|
21
|
+
* Cancel button copy
|
|
22
|
+
*/
|
|
23
|
+
this.cancelBtn = 'Cancel';
|
|
24
|
+
/**
|
|
25
|
+
* Hide cancel button
|
|
26
|
+
*/
|
|
27
|
+
this.hideCancelButton = false;
|
|
28
|
+
/**
|
|
29
|
+
* Show reset button
|
|
30
|
+
*/
|
|
31
|
+
this.showResetButton = false;
|
|
32
|
+
/**
|
|
33
|
+
* Disabled the submit,
|
|
34
|
+
*/
|
|
35
|
+
this.disableSubmitButton = false;
|
|
36
|
+
/**
|
|
37
|
+
* Internall used to show button spinner.
|
|
38
|
+
*/
|
|
39
|
+
this.submitting = false;
|
|
40
|
+
this.hasDescription = false;
|
|
41
|
+
this.hasHeader = false;
|
|
42
|
+
}
|
|
43
|
+
reset() {
|
|
44
|
+
const form = this.getForm();
|
|
45
|
+
form === null || form === void 0 ? void 0 : form.reset();
|
|
46
|
+
}
|
|
47
|
+
submit() {
|
|
48
|
+
this.handleSubmit();
|
|
49
|
+
}
|
|
50
|
+
async handleSubmit() {
|
|
51
|
+
this.errorMessage = null;
|
|
52
|
+
const form = this.getForm();
|
|
53
|
+
// Check form validity
|
|
54
|
+
this.checkFormValidity();
|
|
55
|
+
// Return when there are invalid fields
|
|
56
|
+
if (!this.formValid) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// Serialize form data
|
|
60
|
+
const data = serializeFormData(form);
|
|
61
|
+
if (this.doAction) {
|
|
62
|
+
try {
|
|
63
|
+
this.submitting = true;
|
|
64
|
+
await this.doAction(data);
|
|
65
|
+
this.fire('form-success', null, true);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
this.showError(error instanceof Error ? error.message : 'Unknown error');
|
|
69
|
+
this.fire('form-error', { message: error instanceof Error ? error.message : 'Unknown error' }, true);
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
this.submitting = false;
|
|
73
|
+
this.fire('form-submit-finished', { success: !this.errorMessage });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
this.fire('form-submit', data, true);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
showError(message) {
|
|
81
|
+
this.errorMessage = message;
|
|
82
|
+
}
|
|
83
|
+
slotHasContent(slot) {
|
|
84
|
+
const nodes = slot.assignedNodes({ flatten: true });
|
|
85
|
+
return nodes.length > 0;
|
|
86
|
+
}
|
|
87
|
+
handleDescriptionSlotChange(event) {
|
|
88
|
+
this.hasDescription = this.slotHasContent(event.target);
|
|
89
|
+
}
|
|
90
|
+
handleHeaderSlotChange(event) {
|
|
91
|
+
this.hasHeader = this.slotHasContent(event.target);
|
|
92
|
+
}
|
|
93
|
+
renderToolbar() {
|
|
94
|
+
return html `
|
|
95
|
+
<header class="toolbar-container">
|
|
96
|
+
<slot name="toolbar" @slotchange="${this.handleHeaderSlotChange}"></slot>
|
|
97
|
+
</header>
|
|
98
|
+
`;
|
|
99
|
+
}
|
|
100
|
+
renderFormContent() {
|
|
101
|
+
return html `
|
|
102
|
+
<section class="content">
|
|
103
|
+
<slot></slot>
|
|
104
|
+
</section>
|
|
105
|
+
`;
|
|
106
|
+
}
|
|
107
|
+
renderDescription() {
|
|
108
|
+
return html `
|
|
109
|
+
<section class="description">
|
|
110
|
+
<slot name="description" @slotchange="${this.handleDescriptionSlotChange}"></slot>
|
|
111
|
+
</section>
|
|
112
|
+
`;
|
|
113
|
+
}
|
|
114
|
+
renderError() {
|
|
115
|
+
return html `
|
|
116
|
+
<div class="form-error">
|
|
117
|
+
<div>${this.errorMessage}</div>
|
|
118
|
+
</div>
|
|
119
|
+
`;
|
|
120
|
+
}
|
|
121
|
+
renderActions() {
|
|
122
|
+
return html `
|
|
123
|
+
<footer class="actions">
|
|
124
|
+
${this.showResetButton
|
|
125
|
+
? html `
|
|
126
|
+
<md-icon-button ?disabled=${!this.dirty} @click=${this.reset}>
|
|
127
|
+
<md-icon>restart_alt</md-icon>
|
|
128
|
+
</md-icon-button>
|
|
129
|
+
`
|
|
130
|
+
: nothing}
|
|
131
|
+
${this.hideCancelButton
|
|
132
|
+
? nothing
|
|
133
|
+
: html `
|
|
134
|
+
<md-outlined-button dialogFocus @click=${() => this.fire('form-cancel')}>
|
|
135
|
+
${this.cancelBtn}
|
|
136
|
+
</md-outlined-button>
|
|
137
|
+
`}
|
|
138
|
+
<md-filled-button
|
|
139
|
+
type="button"
|
|
140
|
+
@click=${this.handleSubmit}
|
|
141
|
+
?disabled=${this.disableSubmitButton || this.submitting || !this.formValid}
|
|
142
|
+
?loading=${this.submitting}
|
|
143
|
+
>
|
|
144
|
+
${this.submitBtn}
|
|
145
|
+
</md-filled-button>
|
|
146
|
+
</footer>
|
|
147
|
+
`;
|
|
148
|
+
}
|
|
149
|
+
render() {
|
|
150
|
+
const classNames = {
|
|
151
|
+
'has-description': this.hasDescription,
|
|
152
|
+
'has-header': this.hasHeader,
|
|
153
|
+
'has-error': !!this.errorMessage,
|
|
154
|
+
};
|
|
155
|
+
return html `
|
|
156
|
+
<article class="form-container ${classMap(classNames)}">
|
|
157
|
+
${this.renderToolbar()} ${this.renderDescription()} ${this.errorMessage ? this.renderError() : nothing}
|
|
158
|
+
${this.renderFormContent()} ${this.renderActions()}
|
|
159
|
+
</article>
|
|
160
|
+
`;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
__decorate([
|
|
164
|
+
property({ type: String })
|
|
165
|
+
], ExmFormBase.prototype, "submitBtn", void 0);
|
|
166
|
+
__decorate([
|
|
167
|
+
property({ type: String })
|
|
168
|
+
], ExmFormBase.prototype, "cancelBtn", void 0);
|
|
169
|
+
__decorate([
|
|
170
|
+
property({ type: Boolean, attribute: 'hide-cancel-button' })
|
|
171
|
+
], ExmFormBase.prototype, "hideCancelButton", void 0);
|
|
172
|
+
__decorate([
|
|
173
|
+
property({ type: Boolean, attribute: 'show-reset-button' })
|
|
174
|
+
], ExmFormBase.prototype, "showResetButton", void 0);
|
|
175
|
+
__decorate([
|
|
176
|
+
property({ type: Boolean, attribute: 'disable-submit-button' })
|
|
177
|
+
], ExmFormBase.prototype, "disableSubmitButton", void 0);
|
|
178
|
+
__decorate([
|
|
179
|
+
property({ type: Boolean })
|
|
180
|
+
], ExmFormBase.prototype, "submitting", void 0);
|
|
181
|
+
__decorate([
|
|
182
|
+
state()
|
|
183
|
+
], ExmFormBase.prototype, "hasDescription", void 0);
|
|
184
|
+
__decorate([
|
|
185
|
+
state()
|
|
186
|
+
], ExmFormBase.prototype, "hasHeader", void 0);
|
|
187
|
+
__decorate([
|
|
188
|
+
property({ type: String })
|
|
189
|
+
], ExmFormBase.prototype, "errorMessage", void 0);
|
|
190
|
+
//# sourceMappingURL=exm-form-base.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
export type Constructor<T> = new (...args: any[]) => T;
|
|
3
|
+
export declare class FormDirtyMixinClass extends LitElement {
|
|
4
|
+
}
|
|
5
|
+
export declare class FormDirtyMixinInterface {
|
|
6
|
+
dirty: boolean;
|
|
7
|
+
getForm(): HTMLFormElement | null;
|
|
8
|
+
takeFormDataSnapshot(): void;
|
|
9
|
+
}
|
|
10
|
+
export declare const ExmFormDirtyMixin: <T extends Constructor<LitElement & FormDirtyMixinClass>>(superClass: T) => Constructor<FormDirtyMixinInterface> & T;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { LitElement } from 'lit';
|
|
3
|
+
import { state } from 'lit/decorators.js';
|
|
4
|
+
export class FormDirtyMixinClass extends LitElement {
|
|
5
|
+
}
|
|
6
|
+
export const ExmFormDirtyMixin = (superClass) => {
|
|
7
|
+
class FormDirtyMixinElement extends superClass {
|
|
8
|
+
constructor(...args) {
|
|
9
|
+
super(...args);
|
|
10
|
+
this.dirty = false;
|
|
11
|
+
this.onFormChanged = this.updateDirtyState.bind(this);
|
|
12
|
+
}
|
|
13
|
+
getForm() {
|
|
14
|
+
var _a, _b;
|
|
15
|
+
return (_b = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('form')) !== null && _b !== void 0 ? _b : null;
|
|
16
|
+
}
|
|
17
|
+
getCurrentFormData() {
|
|
18
|
+
const form = this.getForm();
|
|
19
|
+
if (!form)
|
|
20
|
+
return;
|
|
21
|
+
return new FormData(form);
|
|
22
|
+
}
|
|
23
|
+
// NOTE: We have to compare formdata like this... we can't do a direct comparison unfortunately
|
|
24
|
+
formDataEquals(formData1, formData2) {
|
|
25
|
+
if (!formData1 || !formData2)
|
|
26
|
+
return false;
|
|
27
|
+
return JSON.stringify([...formData1]) === JSON.stringify([...formData2]);
|
|
28
|
+
}
|
|
29
|
+
fireDirtyChanged() {
|
|
30
|
+
this.dispatchEvent(new CustomEvent('form-dirty-changed', {
|
|
31
|
+
detail: this.dirty,
|
|
32
|
+
bubbles: true,
|
|
33
|
+
composed: true,
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
async updateDirtyState() {
|
|
37
|
+
await this.updateComplete;
|
|
38
|
+
const current = this.getCurrentFormData();
|
|
39
|
+
if (!current)
|
|
40
|
+
return;
|
|
41
|
+
if (this.formDataEquals(current, this.formDataSnapshot)) {
|
|
42
|
+
this.resetDirtyState();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (!this.dirty) {
|
|
46
|
+
this.dirty = true;
|
|
47
|
+
this.fireDirtyChanged();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
resetDirtyState() {
|
|
51
|
+
if (this.dirty) {
|
|
52
|
+
this.dirty = false;
|
|
53
|
+
this.fireDirtyChanged();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// NOTE: Sometimes we want to make a snapshot from a form on for example drawer open instead of on firstUpdated
|
|
57
|
+
takeFormDataSnapshot() {
|
|
58
|
+
const form = this.getForm();
|
|
59
|
+
if (!form)
|
|
60
|
+
return;
|
|
61
|
+
this.formDataSnapshot = new FormData(form);
|
|
62
|
+
}
|
|
63
|
+
async firstUpdated() {
|
|
64
|
+
var _a;
|
|
65
|
+
// @ts-ignore
|
|
66
|
+
(_a = super.firstUpdated) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
67
|
+
await this.updateComplete;
|
|
68
|
+
const form = this.getForm();
|
|
69
|
+
if (!form)
|
|
70
|
+
return;
|
|
71
|
+
this.takeFormDataSnapshot();
|
|
72
|
+
form.addEventListener('input', this.onFormChanged);
|
|
73
|
+
form.addEventListener('change', this.onFormChanged);
|
|
74
|
+
form.addEventListener('reset', this.onFormChanged);
|
|
75
|
+
}
|
|
76
|
+
async disconnectedCallback() {
|
|
77
|
+
var _a;
|
|
78
|
+
const form = this.getForm();
|
|
79
|
+
if (form) {
|
|
80
|
+
form.removeEventListener('input', this.onFormChanged);
|
|
81
|
+
form.removeEventListener('change', this.onFormChanged);
|
|
82
|
+
form.removeEventListener('reset', this.onFormChanged);
|
|
83
|
+
}
|
|
84
|
+
(_a = super.disconnectedCallback) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
__decorate([
|
|
88
|
+
state()
|
|
89
|
+
], FormDirtyMixinElement.prototype, "dirty", void 0);
|
|
90
|
+
return FormDirtyMixinElement;
|
|
91
|
+
};
|
|
92
|
+
//# sourceMappingURL=exm-form-dirty-mixin.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
export type Constructor<T> = new (...args: any[]) => T;
|
|
3
|
+
export declare class FormValidateMixinClass extends LitElement {
|
|
4
|
+
}
|
|
5
|
+
export declare class FormValidateMixinInterface {
|
|
6
|
+
formValid: boolean;
|
|
7
|
+
getForm(): HTMLFormElement | null;
|
|
8
|
+
checkFormValidity(): void;
|
|
9
|
+
}
|
|
10
|
+
export declare const ExmFormValidateMixin: <T extends Constructor<LitElement & FormValidateMixinClass>>(superClass: T) => Constructor<FormValidateMixinInterface> & T;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { LitElement } from 'lit';
|
|
3
|
+
import { async, debounce } from '@exmg/lit-base/index.js';
|
|
4
|
+
import { state } from 'lit/decorators.js';
|
|
5
|
+
export class FormValidateMixinClass extends LitElement {
|
|
6
|
+
}
|
|
7
|
+
export const ExmFormValidateMixin = (superClass) => {
|
|
8
|
+
class FormvalidateMixinElement extends superClass {
|
|
9
|
+
constructor() {
|
|
10
|
+
super(...arguments);
|
|
11
|
+
this.formValid = false;
|
|
12
|
+
}
|
|
13
|
+
getForm() {
|
|
14
|
+
return this === null || this === void 0 ? void 0 : this.shadowRoot.querySelector('form');
|
|
15
|
+
}
|
|
16
|
+
checkFormValidity() {
|
|
17
|
+
const form = this.getForm();
|
|
18
|
+
if (!form) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const formElements = form === null || form === void 0 ? void 0 : form.elements;
|
|
22
|
+
let allValid = true;
|
|
23
|
+
for (const el of formElements || []) {
|
|
24
|
+
let isValid = true;
|
|
25
|
+
// @ts-ignore
|
|
26
|
+
if (typeof el.reportValidity === 'function') {
|
|
27
|
+
// @ts-ignore
|
|
28
|
+
isValid = el.checkValidity();
|
|
29
|
+
}
|
|
30
|
+
if (!isValid) {
|
|
31
|
+
allValid = false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
this.formValid = allValid;
|
|
35
|
+
}
|
|
36
|
+
_handleInputChange(e) {
|
|
37
|
+
const target = e.target;
|
|
38
|
+
// Only check validation every 200ms max
|
|
39
|
+
this.debouncer = debounce.Debouncer.debounce(this.debouncer, async.timeOut.after(200), () => {
|
|
40
|
+
// @ts-ignore
|
|
41
|
+
typeof target.reportValidity === 'function' && target.reportValidity();
|
|
42
|
+
this.checkFormValidity();
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
async firstUpdated() {
|
|
46
|
+
const form = this.getForm();
|
|
47
|
+
if (!form) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
this.boundHandleChange = this._handleInputChange.bind(this);
|
|
51
|
+
form.addEventListener('keyup', this.boundHandleChange, true);
|
|
52
|
+
form.addEventListener('input', this.boundHandleChange, true);
|
|
53
|
+
}
|
|
54
|
+
async disconnectedCallback() {
|
|
55
|
+
const form = this.getForm();
|
|
56
|
+
this.boundHandleChange && form.addEventListener('keyup', this.boundHandleChange, true);
|
|
57
|
+
this.boundHandleChange && form.addEventListener('input', this.boundHandleChange, true);
|
|
58
|
+
super.disconnectedCallback();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
__decorate([
|
|
62
|
+
state()
|
|
63
|
+
], FormvalidateMixinElement.prototype, "formValid", void 0);
|
|
64
|
+
return FormvalidateMixinElement;
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=exm-form-validate-mixin.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ExmFormBase } from './exm-form-base.js';
|
|
2
|
+
export declare class ExmForm extends ExmFormBase {
|
|
3
|
+
static styles: import("lit").CSSResult[];
|
|
4
|
+
getForm(): HTMLFormElement | null;
|
|
5
|
+
}
|
|
6
|
+
declare global {
|
|
7
|
+
interface HTMLElementTagNameMap {
|
|
8
|
+
'exm-form': ExmForm;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { customElement } from 'lit/decorators.js';
|
|
3
|
+
import { style } from './styles/exm-form-base-css.js';
|
|
4
|
+
import { ExmFormBase } from './exm-form-base.js';
|
|
5
|
+
let ExmForm = class ExmForm extends ExmFormBase {
|
|
6
|
+
getForm() {
|
|
7
|
+
return this.querySelector('form');
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
ExmForm.styles = [style];
|
|
11
|
+
ExmForm = __decorate([
|
|
12
|
+
customElement('exm-form')
|
|
13
|
+
], ExmForm);
|
|
14
|
+
export { ExmForm };
|
|
15
|
+
//# sourceMappingURL=exm-form.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { ExmForm } from './exm-form.js';
|
|
2
|
+
export { ExmFormValidateMixin, Constructor } from './exm-form-validate-mixin.js';
|
|
3
|
+
export { ExmFormBase } from './exm-form-base.js';
|
|
4
|
+
export { serializeFormData } from './utils/serializeFormData.js';
|
|
5
|
+
export { style as formStyles } from './styles/exm-form-css.js';
|
|
6
|
+
export { style as formBaseStyles } from './styles/exm-form-base-css.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { ExmForm } from './exm-form.js';
|
|
2
|
+
export { ExmFormValidateMixin } from './exm-form-validate-mixin.js';
|
|
3
|
+
export { ExmFormBase } from './exm-form-base.js';
|
|
4
|
+
export { serializeFormData } from './utils/serializeFormData.js';
|
|
5
|
+
export { style as formStyles } from './styles/exm-form-css.js';
|
|
6
|
+
export { style as formBaseStyles } from './styles/exm-form-base-css.js';
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const style: import("lit").CSSResult;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { css } from 'lit';
|
|
2
|
+
export const style = css `
|
|
3
|
+
:host {
|
|
4
|
+
display: block;
|
|
5
|
+
--_exm-form-content-margin-left: var(--exm-form-content-margin-left, 0);
|
|
6
|
+
--_exm-form-container-margin-top: var(--exm-form-container-margin-top, 1rem);
|
|
7
|
+
--_exm-form-container-margin-bottom: var(--exm-form-container-margin-bottom, 1rem);
|
|
8
|
+
--_exm-form-divider-margin-top: var(--exm-form-divider-margin-top, 2rem);
|
|
9
|
+
--_exm-form-aside-font-size: var(--exm-form-aside-font-size, 0.875rem);
|
|
10
|
+
--_exm-form-aside-line-height: var(--exm-form-aside-line-height, 1.25rem);
|
|
11
|
+
--_exm-form-aside-letter-spacing: var(--exm-form-aside-letter-spacing, 0.0142857143em);
|
|
12
|
+
--_exm-form-aside-font-weight: var(--exm-form-aside-font-weight, 400);
|
|
13
|
+
--_exm-form-aside-margin-left: var(--exm-form-aside-margin-left, 1rem);
|
|
14
|
+
--_exm-form-aside-margin-right: var(--exm-form-aside-margin-right, 5rem);
|
|
15
|
+
--_exm-form-aside-min-width: var(--exm-form-aside-min-width, 180px);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.form-container {
|
|
19
|
+
display: grid;
|
|
20
|
+
grid-template-rows: auto auto auto 1fr auto;
|
|
21
|
+
height: 100%;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
::slotted(.toolbar) {
|
|
25
|
+
grid-area: 1/1/2/2;
|
|
26
|
+
display: none;
|
|
27
|
+
align-items: center;
|
|
28
|
+
padding: 0.5rem 1rem;
|
|
29
|
+
min-height: 3.5rem;
|
|
30
|
+
box-sizing: border-box;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
::slotted(.toolbar) .title {
|
|
34
|
+
line-height: 1.5rem;
|
|
35
|
+
font-size: 1rem;
|
|
36
|
+
letter-spacing: 0.00625em;
|
|
37
|
+
font-weight: 500;
|
|
38
|
+
color: var(--md-sys-color-on-surface);
|
|
39
|
+
box-sizing: border-box;
|
|
40
|
+
overflow-wrap: break-word;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.has-header ::slotted(.toolbar) {
|
|
44
|
+
display: flex;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.description {
|
|
48
|
+
grid-area: 2/1/3/2;
|
|
49
|
+
display: none;
|
|
50
|
+
padding: 0.5rem 1rem;
|
|
51
|
+
line-height: var(--_exm-form-aside-line-height);
|
|
52
|
+
font-size: var(--_exm-form-aside-font-size);
|
|
53
|
+
letter-spacing: var(--_exm-form-aside-letter-spacing);
|
|
54
|
+
font-weight: var(--_exm-form-aside-font-weight);
|
|
55
|
+
color: var(--_exm-form-aside-color, var(--md-sys-color-on-surface));
|
|
56
|
+
height: 100%;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.has-description .description {
|
|
60
|
+
display: initial;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.content {
|
|
64
|
+
grid-area: 4/1/5/2;
|
|
65
|
+
padding: 0.5rem 1rem;
|
|
66
|
+
overflow: auto;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.actions {
|
|
70
|
+
grid-area: 5/1/6/2;
|
|
71
|
+
display: flex;
|
|
72
|
+
padding: 1rem;
|
|
73
|
+
margin-top: 1rem;
|
|
74
|
+
gap: 0.5rem;
|
|
75
|
+
justify-content: flex-end;
|
|
76
|
+
border-top: 1px solid var(--md-sys-color-outline-variant);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.form-error {
|
|
80
|
+
grid-area: 3/1/4/2;
|
|
81
|
+
padding: 0.5rem 1rem;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.form-error > div {
|
|
85
|
+
padding: 1rem;
|
|
86
|
+
border-radius: 1rem;
|
|
87
|
+
background-color: var(--_exm-form-error-background, var(--md-sys-color-error-container, red));
|
|
88
|
+
color: var(--_exm-form-error-color, var(--md-sys-color-on-error-container, white));
|
|
89
|
+
}
|
|
90
|
+
`;
|
|
91
|
+
//# sourceMappingURL=exm-form-base-css.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const style: import("lit").CSSResult;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { css } from 'lit';
|
|
2
|
+
export const style = css `
|
|
3
|
+
.section-title {
|
|
4
|
+
line-height: 1.5rem;
|
|
5
|
+
font-size: 1rem;
|
|
6
|
+
letter-spacing: 0.00625em;
|
|
7
|
+
font-weight: 500;
|
|
8
|
+
color: var(--_exm-form-title-color, var(--md-sys-color-on-surface));
|
|
9
|
+
padding-bottom: 8px;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
form {
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
gap: 0.75rem;
|
|
16
|
+
width: 100%;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
form > * {
|
|
20
|
+
display: contents;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
form .row {
|
|
24
|
+
display: flex;
|
|
25
|
+
gap: 0.75rem;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
form .row > * {
|
|
29
|
+
flex: 1;
|
|
30
|
+
height: fit-content;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.no-flex {
|
|
34
|
+
display: block;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.extra-margin {
|
|
38
|
+
margin-top: 2rem;
|
|
39
|
+
display: block;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
label.checkbox {
|
|
43
|
+
display: flex;
|
|
44
|
+
justify-content: flex-start;
|
|
45
|
+
align-items: center;
|
|
46
|
+
font-size: 1rem;
|
|
47
|
+
font-weight: 500;
|
|
48
|
+
margin: 0.5rem 1rem;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
label.checkbox md-checkbox {
|
|
52
|
+
margin-right: 1rem;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
a {
|
|
56
|
+
color: var(--md-sys-color-primary);
|
|
57
|
+
}
|
|
58
|
+
`;
|
|
59
|
+
//# sourceMappingURL=exm-form-css.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const serializeFormData: (form: HTMLFormElement) => {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export const serializeFormData = (form) => {
|
|
2
|
+
const obj = {};
|
|
3
|
+
const formElements = form === null || form === void 0 ? void 0 : form.elements;
|
|
4
|
+
const formElementsArray = Array.from(formElements);
|
|
5
|
+
/**
|
|
6
|
+
* Create list of checkbox elements. If no value is set the return value of the checkbox for 'on' will be a boolean
|
|
7
|
+
*/
|
|
8
|
+
const checkboxNames = formElementsArray
|
|
9
|
+
.filter((input) => {
|
|
10
|
+
return input.value === 'on' && (input.type === 'checkbox' || input.tagName.toLowerCase().includes('checkbox'));
|
|
11
|
+
})
|
|
12
|
+
.map((input) => input.name);
|
|
13
|
+
/* Same for the radio items */
|
|
14
|
+
const radioNames = formElementsArray
|
|
15
|
+
.filter((input) => {
|
|
16
|
+
return input.type === 'radio' || input.tagName.toLowerCase().includes('radio');
|
|
17
|
+
})
|
|
18
|
+
.map((input) => input.name);
|
|
19
|
+
const numberNames = formElementsArray
|
|
20
|
+
.filter((input) => {
|
|
21
|
+
return input.type === 'number';
|
|
22
|
+
})
|
|
23
|
+
.map((input) => input.name);
|
|
24
|
+
const formData = new FormData(form);
|
|
25
|
+
for (const [key, value] of formData.entries()) {
|
|
26
|
+
if (Object.hasOwnProperty.call(obj, key)) {
|
|
27
|
+
if (!Array.isArray(obj[key])) {
|
|
28
|
+
obj[key] = [obj[key]];
|
|
29
|
+
}
|
|
30
|
+
obj[key].push(value);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (numberNames.includes(key)) {
|
|
34
|
+
obj[key] = value ? parseFloat(`${value}`) : undefined;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
// When set to on convert to boolean return value
|
|
38
|
+
// @ts-ignore
|
|
39
|
+
if (checkboxNames.includes(key)) {
|
|
40
|
+
obj[key] = value === 'on';
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
// Check for a default value of on for radio items to set to true, if not, set to the value
|
|
44
|
+
if (radioNames.includes(key)) {
|
|
45
|
+
if (value === 'on') {
|
|
46
|
+
obj[key] = value === 'on';
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
obj[key] = value;
|
|
50
|
+
}
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
obj[key] = formData.get(key);
|
|
54
|
+
}
|
|
55
|
+
// All checkboxes that are not checked will not be included in the form data and need to return false
|
|
56
|
+
for (const name of checkboxNames) {
|
|
57
|
+
// check for
|
|
58
|
+
if (!Object.hasOwnProperty.call(obj, name)) {
|
|
59
|
+
obj[name] = false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return obj;
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=serializeFormData.js.map
|
package/dist/exm-form-base.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { __decorate } from
|
|
1
|
+
import { __decorate } from 'tslib';
|
|
2
2
|
import { html, nothing } from 'lit';
|
|
3
3
|
import { property, state } from 'lit/decorators.js';
|
|
4
4
|
import '@exmg/exm-button/exm-text-button.js';
|
|
@@ -10,7 +10,8 @@ import { classMap } from 'lit/directives/class-map.js';
|
|
|
10
10
|
import { ExmFormValidateMixin } from './exm-form-validate-mixin.js';
|
|
11
11
|
import { serializeFormData } from './utils/serializeFormData.js';
|
|
12
12
|
import { ExmFormDirtyMixin } from './exm-form-dirty-mixin.js';
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
class ExmFormBase extends ExmFormDirtyMixin(ExmFormValidateMixin(ExmgElement)) {
|
|
14
15
|
constructor() {
|
|
15
16
|
super(...arguments);
|
|
16
17
|
/**
|
|
@@ -187,4 +188,6 @@ __decorate([
|
|
|
187
188
|
__decorate([
|
|
188
189
|
property({ type: String })
|
|
189
190
|
], ExmFormBase.prototype, "errorMessage", void 0);
|
|
190
|
-
|
|
191
|
+
|
|
192
|
+
export { ExmFormBase };
|
|
193
|
+
//# sourceMappingURL=exm-form-base.js.map
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { __decorate } from
|
|
2
|
-
import
|
|
1
|
+
import { __decorate } from 'tslib';
|
|
2
|
+
import 'lit';
|
|
3
3
|
import { state } from 'lit/decorators.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export const ExmFormDirtyMixin = (superClass) => {
|
|
4
|
+
|
|
5
|
+
const ExmFormDirtyMixin = (superClass) => {
|
|
7
6
|
class FormDirtyMixinElement extends superClass {
|
|
8
7
|
constructor(...args) {
|
|
9
8
|
super(...args);
|
|
@@ -89,4 +88,6 @@ export const ExmFormDirtyMixin = (superClass) => {
|
|
|
89
88
|
], FormDirtyMixinElement.prototype, "dirty", void 0);
|
|
90
89
|
return FormDirtyMixinElement;
|
|
91
90
|
};
|
|
92
|
-
|
|
91
|
+
|
|
92
|
+
export { ExmFormDirtyMixin };
|
|
93
|
+
//# sourceMappingURL=exm-form-dirty-mixin.js.map
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { __decorate } from
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
1
|
+
import { __decorate } from 'tslib';
|
|
2
|
+
import 'lit';
|
|
3
|
+
import { debounce, async } from '@exmg/lit-base/index.js';
|
|
4
4
|
import { state } from 'lit/decorators.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export const ExmFormValidateMixin = (superClass) => {
|
|
5
|
+
|
|
6
|
+
const ExmFormValidateMixin = (superClass) => {
|
|
8
7
|
class FormvalidateMixinElement extends superClass {
|
|
9
8
|
constructor() {
|
|
10
9
|
super(...arguments);
|
|
@@ -63,4 +62,6 @@ export const ExmFormValidateMixin = (superClass) => {
|
|
|
63
62
|
], FormvalidateMixinElement.prototype, "formValid", void 0);
|
|
64
63
|
return FormvalidateMixinElement;
|
|
65
64
|
};
|
|
66
|
-
|
|
65
|
+
|
|
66
|
+
export { ExmFormValidateMixin };
|
|
67
|
+
//# sourceMappingURL=exm-form-validate-mixin.js.map
|
package/dist/exm-form.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { __decorate } from
|
|
1
|
+
import { __decorate } from 'tslib';
|
|
2
2
|
import { customElement } from 'lit/decorators.js';
|
|
3
3
|
import { style } from './styles/exm-form-base-css.js';
|
|
4
4
|
import { ExmFormBase } from './exm-form-base.js';
|
|
5
|
+
|
|
5
6
|
let ExmForm = class ExmForm extends ExmFormBase {
|
|
6
7
|
getForm() {
|
|
7
8
|
return this.querySelector('form');
|
|
@@ -11,5 +12,6 @@ ExmForm.styles = [style];
|
|
|
11
12
|
ExmForm = __decorate([
|
|
12
13
|
customElement('exm-form')
|
|
13
14
|
], ExmForm);
|
|
15
|
+
|
|
14
16
|
export { ExmForm };
|
|
15
|
-
//# sourceMappingURL=exm-form.js.map
|
|
17
|
+
//# sourceMappingURL=exm-form.js.map
|
package/dist/index.js
CHANGED
|
@@ -4,4 +4,4 @@ export { ExmFormBase } from './exm-form-base.js';
|
|
|
4
4
|
export { serializeFormData } from './utils/serializeFormData.js';
|
|
5
5
|
export { style as formStyles } from './styles/exm-form-css.js';
|
|
6
6
|
export { style as formBaseStyles } from './styles/exm-form-base-css.js';
|
|
7
|
-
//# sourceMappingURL=index.js.map
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { css } from 'lit';
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
const style = css `
|
|
3
4
|
:host {
|
|
4
5
|
display: block;
|
|
5
6
|
--_exm-form-content-margin-left: var(--exm-form-content-margin-left, 0);
|
|
@@ -88,4 +89,6 @@ export const style = css `
|
|
|
88
89
|
color: var(--_exm-form-error-color, var(--md-sys-color-on-error-container, white));
|
|
89
90
|
}
|
|
90
91
|
`;
|
|
91
|
-
|
|
92
|
+
|
|
93
|
+
export { style };
|
|
94
|
+
//# sourceMappingURL=exm-form-base-css.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { css } from 'lit';
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
const style = css `
|
|
3
4
|
.section-title {
|
|
4
5
|
line-height: 1.5rem;
|
|
5
6
|
font-size: 1rem;
|
|
@@ -56,4 +57,6 @@ export const style = css `
|
|
|
56
57
|
color: var(--md-sys-color-primary);
|
|
57
58
|
}
|
|
58
59
|
`;
|
|
59
|
-
|
|
60
|
+
|
|
61
|
+
export { style };
|
|
62
|
+
//# sourceMappingURL=exm-form-css.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
const serializeFormData = (form) => {
|
|
2
2
|
const obj = {};
|
|
3
3
|
const formElements = form === null || form === void 0 ? void 0 : form.elements;
|
|
4
4
|
const formElementsArray = Array.from(formElements);
|
|
@@ -61,4 +61,6 @@ export const serializeFormData = (form) => {
|
|
|
61
61
|
}
|
|
62
62
|
return obj;
|
|
63
63
|
};
|
|
64
|
-
|
|
64
|
+
|
|
65
|
+
export { serializeFormData };
|
|
66
|
+
//# sourceMappingURL=serializeFormData.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exmg/exm-form",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.37-alpha.31+513140a",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"license": "MIT",
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@exmg/exm-button": "^1.1.
|
|
34
|
+
"@exmg/exm-button": "^1.1.37-alpha.31+513140a"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"lit": "^3.2.1",
|
|
@@ -41,5 +41,5 @@
|
|
|
41
41
|
"publishConfig": {
|
|
42
42
|
"access": "public"
|
|
43
43
|
},
|
|
44
|
-
"gitHead": "
|
|
44
|
+
"gitHead": "513140a59e3a5a9a0fa572147ba6c0cf9801816e"
|
|
45
45
|
}
|