@progressive-development/pd-dialog 0.9.2 → 1.0.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/LICENSE +21 -2
- package/README.md +34 -57
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/pd-confirm-dialog/PdConfirmDialog.d.ts +40 -0
- package/dist/pd-confirm-dialog/PdConfirmDialog.d.ts.map +1 -0
- package/dist/pd-confirm-dialog/PdConfirmDialog.js +108 -0
- package/dist/pd-confirm-dialog/pd-confirm-dialog.d.ts +7 -0
- package/dist/pd-confirm-dialog/pd-confirm-dialog.d.ts.map +1 -0
- package/dist/pd-confirm-dialog/pd-confirm-dialog.stories.d.ts +107 -0
- package/dist/pd-confirm-dialog/pd-confirm-dialog.stories.d.ts.map +1 -0
- package/dist/pd-form-dialog/PdFormDialog.d.ts +54 -0
- package/dist/pd-form-dialog/PdFormDialog.d.ts.map +1 -0
- package/dist/pd-form-dialog/PdFormDialog.js +181 -0
- package/dist/pd-form-dialog/pd-form-dialog.d.ts +6 -0
- package/dist/pd-form-dialog/pd-form-dialog.d.ts.map +1 -0
- package/dist/pd-form-dialog/pd-form-dialog.stories.d.ts +118 -0
- package/dist/pd-form-dialog/pd-form-dialog.stories.d.ts.map +1 -0
- package/dist/pd-popup/PdPopup.d.ts +36 -6
- package/dist/pd-popup/PdPopup.d.ts.map +1 -1
- package/dist/pd-popup/PdPopup.js +98 -27
- package/dist/pd-popup/pd-popup.stories.d.ts +56 -22
- package/dist/pd-popup/pd-popup.stories.d.ts.map +1 -1
- package/dist/pd-popup-dialog/PdPopupDialog.d.ts +36 -9
- package/dist/pd-popup-dialog/PdPopupDialog.d.ts.map +1 -1
- package/dist/pd-popup-dialog/PdPopupDialog.js +90 -47
- package/dist/pd-popup-dialog/pd-popup-dialog.stories.d.ts +52 -18
- package/dist/pd-popup-dialog/pd-popup-dialog.stories.d.ts.map +1 -1
- package/dist/pd-submit-dialog/PdSubmitDialog.d.ts +8 -4
- package/dist/pd-submit-dialog/PdSubmitDialog.d.ts.map +1 -1
- package/dist/pd-submit-dialog/pd-submit-dialog.stories.d.ts +46 -29
- package/dist/pd-submit-dialog/pd-submit-dialog.stories.d.ts.map +1 -1
- package/package.json +9 -6
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { LitElement, css, html } from 'lit';
|
|
2
|
+
import { property, state, query } from 'lit/decorators.js';
|
|
3
|
+
import '@progressive-development/pd-forms/pd-form-container';
|
|
4
|
+
import '../pd-popup-dialog.js';
|
|
5
|
+
import { BUTTON_KEY_CONFIRM, EVENT_CANCEL, BUTTON_KEY_CANCEL } from '../pd-confirm-dialog/PdConfirmDialog.js';
|
|
6
|
+
|
|
7
|
+
var __defProp = Object.defineProperty;
|
|
8
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
9
|
+
var result = void 0 ;
|
|
10
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
11
|
+
if (decorator = decorators[i])
|
|
12
|
+
result = (decorator(target, key, result) ) || result;
|
|
13
|
+
if (result) __defProp(target, key, result);
|
|
14
|
+
return result;
|
|
15
|
+
};
|
|
16
|
+
const DEFAULT_SUBMIT_DISABLE_TIMEOUT = 5e3;
|
|
17
|
+
const EVENT_FORM_SUBMIT = "pd-form-submit";
|
|
18
|
+
class PdFormDialog extends LitElement {
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Lifecycle
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
constructor() {
|
|
23
|
+
super();
|
|
24
|
+
this.saveButtonText = "Save";
|
|
25
|
+
this.cancelButtonText = "Cancel";
|
|
26
|
+
this.showRequiredFieldInfo = true;
|
|
27
|
+
this.submitDisableTimeout = DEFAULT_SUBMIT_DISABLE_TIMEOUT;
|
|
28
|
+
this.closeByEscape = false;
|
|
29
|
+
this.closeByBackdrop = false;
|
|
30
|
+
this.blockScroll = false;
|
|
31
|
+
this._validForm = false;
|
|
32
|
+
this._buttons = [];
|
|
33
|
+
this._initializeButtons();
|
|
34
|
+
}
|
|
35
|
+
static {
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Styles
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
this.styles = css`
|
|
40
|
+
:host {
|
|
41
|
+
display: block;
|
|
42
|
+
--pd-popup-max-width: var(--pd-form-dialog-max-width, 700px);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@media (max-width: 767px) {
|
|
46
|
+
:host {
|
|
47
|
+
--pd-popup-max-width: 95%;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
`;
|
|
51
|
+
}
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Render
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
render() {
|
|
56
|
+
return html`
|
|
57
|
+
<pd-popup-dialog
|
|
58
|
+
@submit-button-clicked="${this._handleButtonClick}"
|
|
59
|
+
title="${this._dialogTitle}"
|
|
60
|
+
.buttons="${this._getButtons()}"
|
|
61
|
+
?closeByEscape="${this.closeByEscape}"
|
|
62
|
+
?closeByBackdrop="${this.closeByBackdrop}"
|
|
63
|
+
?blockScroll="${this.blockScroll}"
|
|
64
|
+
>
|
|
65
|
+
<pd-form-container
|
|
66
|
+
slot="content"
|
|
67
|
+
?requiredFieldInfo="${this.showRequiredFieldInfo}"
|
|
68
|
+
commonError="${this._validationError || ""}"
|
|
69
|
+
@pd-form-change="${this._handleFormChange}"
|
|
70
|
+
>
|
|
71
|
+
${this._renderContent()}
|
|
72
|
+
</pd-form-container>
|
|
73
|
+
</pd-popup-dialog>
|
|
74
|
+
`;
|
|
75
|
+
}
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// Public Methods
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
/** Reset the form to initial state */
|
|
80
|
+
reset() {
|
|
81
|
+
this._formContainer?.reset();
|
|
82
|
+
this._validForm = false;
|
|
83
|
+
this._validationError = void 0;
|
|
84
|
+
}
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// Protected Methods
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
/**
|
|
89
|
+
* Handle button click events.
|
|
90
|
+
* Can be overridden for custom behavior.
|
|
91
|
+
*/
|
|
92
|
+
_handleButtonClick(e) {
|
|
93
|
+
if (e.detail.button === BUTTON_KEY_CONFIRM && this._validForm) {
|
|
94
|
+
const formData = this._formContainer.getValues();
|
|
95
|
+
this._disableSaveButtonTemporarily();
|
|
96
|
+
this.dispatchEvent(
|
|
97
|
+
new CustomEvent(EVENT_FORM_SUBMIT, {
|
|
98
|
+
detail: formData,
|
|
99
|
+
bubbles: true,
|
|
100
|
+
composed: true
|
|
101
|
+
})
|
|
102
|
+
);
|
|
103
|
+
} else {
|
|
104
|
+
this.dispatchEvent(
|
|
105
|
+
new CustomEvent(EVENT_CANCEL, {
|
|
106
|
+
bubbles: true,
|
|
107
|
+
composed: true
|
|
108
|
+
})
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Private Methods
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
_initializeButtons() {
|
|
116
|
+
this._buttons = [
|
|
117
|
+
{ key: BUTTON_KEY_CANCEL, name: this.cancelButtonText },
|
|
118
|
+
{
|
|
119
|
+
key: BUTTON_KEY_CONFIRM,
|
|
120
|
+
name: this.saveButtonText,
|
|
121
|
+
disabled: true,
|
|
122
|
+
primary: true
|
|
123
|
+
}
|
|
124
|
+
];
|
|
125
|
+
}
|
|
126
|
+
_getButtons() {
|
|
127
|
+
return [
|
|
128
|
+
{ ...this._buttons[0], name: this.cancelButtonText },
|
|
129
|
+
{
|
|
130
|
+
...this._buttons[1],
|
|
131
|
+
name: this.saveButtonText,
|
|
132
|
+
disabled: !this._validForm
|
|
133
|
+
}
|
|
134
|
+
];
|
|
135
|
+
}
|
|
136
|
+
_handleFormChange(e) {
|
|
137
|
+
this._validForm = e.detail.overallValidity && this._isValidFormData();
|
|
138
|
+
e.stopPropagation();
|
|
139
|
+
}
|
|
140
|
+
_disableSaveButtonTemporarily() {
|
|
141
|
+
this._validForm = false;
|
|
142
|
+
window.setTimeout(() => {
|
|
143
|
+
this._validForm = true;
|
|
144
|
+
}, this.submitDisableTimeout);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
__decorateClass([
|
|
148
|
+
property({ type: String })
|
|
149
|
+
], PdFormDialog.prototype, "saveButtonText");
|
|
150
|
+
__decorateClass([
|
|
151
|
+
property({ type: String })
|
|
152
|
+
], PdFormDialog.prototype, "cancelButtonText");
|
|
153
|
+
__decorateClass([
|
|
154
|
+
property({ type: Boolean })
|
|
155
|
+
], PdFormDialog.prototype, "showRequiredFieldInfo");
|
|
156
|
+
__decorateClass([
|
|
157
|
+
property({ type: Number })
|
|
158
|
+
], PdFormDialog.prototype, "submitDisableTimeout");
|
|
159
|
+
__decorateClass([
|
|
160
|
+
property({ type: Boolean })
|
|
161
|
+
], PdFormDialog.prototype, "closeByEscape");
|
|
162
|
+
__decorateClass([
|
|
163
|
+
property({ type: Boolean })
|
|
164
|
+
], PdFormDialog.prototype, "closeByBackdrop");
|
|
165
|
+
__decorateClass([
|
|
166
|
+
property({ type: Boolean })
|
|
167
|
+
], PdFormDialog.prototype, "blockScroll");
|
|
168
|
+
__decorateClass([
|
|
169
|
+
state()
|
|
170
|
+
], PdFormDialog.prototype, "_validForm");
|
|
171
|
+
__decorateClass([
|
|
172
|
+
state()
|
|
173
|
+
], PdFormDialog.prototype, "_validationError");
|
|
174
|
+
__decorateClass([
|
|
175
|
+
state()
|
|
176
|
+
], PdFormDialog.prototype, "_buttons");
|
|
177
|
+
__decorateClass([
|
|
178
|
+
query("pd-form-container")
|
|
179
|
+
], PdFormDialog.prototype, "_formContainer");
|
|
180
|
+
|
|
181
|
+
export { EVENT_FORM_SUBMIT, PdFormDialog };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pd-form-dialog.d.ts","sourceRoot":"","sources":["../../src/pd-form-dialog/pd-form-dialog.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { HTMLTemplateResult, CSSResultGroup } from 'lit';
|
|
2
|
+
import { Meta, StoryObj } from '@storybook/web-components-vite';
|
|
3
|
+
import { PdFormDialog } from './PdFormDialog.js';
|
|
4
|
+
/**
|
|
5
|
+
* Example: Contact form data interface
|
|
6
|
+
*/
|
|
7
|
+
interface ContactFormData {
|
|
8
|
+
name: string;
|
|
9
|
+
email: string;
|
|
10
|
+
message: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Example: Contact form dialog
|
|
14
|
+
*/
|
|
15
|
+
declare class ExampleContactForm extends PdFormDialog<ContactFormData> {
|
|
16
|
+
protected _dialogTitle: string;
|
|
17
|
+
protected _formData: ContactFormData;
|
|
18
|
+
static styles: CSSResultGroup;
|
|
19
|
+
protected _isValidFormData(): boolean;
|
|
20
|
+
protected _renderContent(): HTMLTemplateResult;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Example: User profile form data interface
|
|
24
|
+
*/
|
|
25
|
+
interface ProfileFormData {
|
|
26
|
+
displayName: string;
|
|
27
|
+
bio: string;
|
|
28
|
+
language: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Example: User profile form dialog
|
|
32
|
+
*/
|
|
33
|
+
declare class ExampleProfileForm extends PdFormDialog<ProfileFormData> {
|
|
34
|
+
protected _dialogTitle: string;
|
|
35
|
+
protected _formData: ProfileFormData;
|
|
36
|
+
static styles: CSSResultGroup;
|
|
37
|
+
protected _isValidFormData(): boolean;
|
|
38
|
+
protected _renderContent(): HTMLTemplateResult;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Example: Simple feedback form data interface
|
|
42
|
+
*/
|
|
43
|
+
interface FeedbackFormData {
|
|
44
|
+
rating: string;
|
|
45
|
+
feedback: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Example: Feedback form dialog with custom rating UI
|
|
49
|
+
*/
|
|
50
|
+
declare class ExampleFeedbackForm extends PdFormDialog<FeedbackFormData> {
|
|
51
|
+
protected _dialogTitle: string;
|
|
52
|
+
protected _formData: FeedbackFormData;
|
|
53
|
+
static styles: CSSResultGroup;
|
|
54
|
+
protected _isValidFormData(): boolean;
|
|
55
|
+
private _selectRating;
|
|
56
|
+
protected _renderContent(): HTMLTemplateResult;
|
|
57
|
+
}
|
|
58
|
+
declare global {
|
|
59
|
+
interface HTMLElementTagNameMap {
|
|
60
|
+
"example-contact-form": ExampleContactForm;
|
|
61
|
+
"example-profile-form": ExampleProfileForm;
|
|
62
|
+
"example-feedback-form": ExampleFeedbackForm;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Story arguments interface for pd-form-dialog examples.
|
|
67
|
+
* Maps to the example components' properties.
|
|
68
|
+
*/
|
|
69
|
+
interface PdFormDialogArgs {
|
|
70
|
+
/** Save button text */
|
|
71
|
+
saveButtonText: string;
|
|
72
|
+
/** Cancel button text */
|
|
73
|
+
cancelButtonText: string;
|
|
74
|
+
/** Show required field info text */
|
|
75
|
+
showRequiredFieldInfo: boolean;
|
|
76
|
+
/** Double-submit prevention timeout in ms */
|
|
77
|
+
submitDisableTimeout: number;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* ## PdFormDialog<T> (Abstract Base Class)
|
|
81
|
+
*
|
|
82
|
+
* Generic abstract base class for form dialogs with integrated validation.
|
|
83
|
+
* Not used directly -- extend it with a type parameter for your form data.
|
|
84
|
+
*
|
|
85
|
+
* ### Features
|
|
86
|
+
* - Integrated form validation via `pd-form-container`
|
|
87
|
+
* - Save button automatically disabled until form is valid
|
|
88
|
+
* - Double-submit prevention with configurable timeout
|
|
89
|
+
* - Type-safe form data handling via generic `T` parameter
|
|
90
|
+
* - Standardized events: `pd-form-submit` (with typed payload) and `pd-dialog-cancel`
|
|
91
|
+
* - Required field info display (optional)
|
|
92
|
+
*
|
|
93
|
+
* ### How to Extend
|
|
94
|
+
* Implement these abstract members:
|
|
95
|
+
* - `_dialogTitle`: Dialog title string
|
|
96
|
+
* - `_formData`: Your typed form data object
|
|
97
|
+
* - `_isValidFormData()`: Additional validation beyond field-level validation
|
|
98
|
+
* - `_renderContent()`: Returns form fields as `HTMLTemplateResult`
|
|
99
|
+
*
|
|
100
|
+
* ### Validation Flow
|
|
101
|
+
* 1. Each form field validates individually (via pd-form-container)
|
|
102
|
+
* 2. `_isValidFormData()` runs additional cross-field validation
|
|
103
|
+
* 3. Save button enables only when both pass
|
|
104
|
+
*/
|
|
105
|
+
declare const meta: Meta<PdFormDialogArgs>;
|
|
106
|
+
export default meta;
|
|
107
|
+
type Story = StoryObj<PdFormDialogArgs>;
|
|
108
|
+
/** Contact form dialog with name, email, and message fields. Interactive via Controls panel. */
|
|
109
|
+
export declare const Default: Story;
|
|
110
|
+
/** User profile form with display name, bio, and language selection. */
|
|
111
|
+
export declare const ProfileForm: Story;
|
|
112
|
+
/** Feedback form with custom rating buttons and optional text area. */
|
|
113
|
+
export declare const FeedbackForm: Story;
|
|
114
|
+
/** Overview of all three form dialog implementations side by side. */
|
|
115
|
+
export declare const AllForms: Story;
|
|
116
|
+
/** Demonstrates the validation flow: Save button is disabled until form is valid. */
|
|
117
|
+
export declare const ValidationBehavior: Story;
|
|
118
|
+
//# sourceMappingURL=pd-form-dialog.stories.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pd-form-dialog.stories.d.ts","sourceRoot":"","sources":["../../src/pd-form-dialog/pd-form-dialog.stories.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAa,kBAAkB,EAAE,cAAc,EAAE,MAAM,KAAK,CAAC;AAEpE,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAErE,OAAO,4CAA4C,CAAC;AACpD,OAAO,iDAAiD,CAAC;AACzD,OAAO,6CAA6C,CAAC;AAErD,OAAO,EAAE,YAAY,EAAqB,MAAM,mBAAmB,CAAC;AAOpE;;GAEG;AACH,UAAU,eAAe;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,cACM,kBAAmB,SAAQ,YAAY,CAAC,eAAe,CAAC;IAC5D,SAAS,CAAC,YAAY,SAAgB;IAGtC,SAAS,CAAC,SAAS,EAAE,eAAe,CAIlC;IAEF,MAAM,CAAC,MAAM,EASR,cAAc,CAAC;IAEpB,SAAS,CAAC,gBAAgB,IAAI,OAAO;IAIrC,SAAS,CAAC,cAAc,IAAI,kBAAkB;CA4B/C;AAED;;GAEG;AACH,UAAU,eAAe;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,cACM,kBAAmB,SAAQ,YAAY,CAAC,eAAe,CAAC;IAC5D,SAAS,CAAC,YAAY,SAAkB;IAGxC,SAAS,CAAC,SAAS,EAAE,eAAe,CAIlC;IAEF,MAAM,CAAC,MAAM,EASR,cAAc,CAAC;IAEpB,SAAS,CAAC,gBAAgB,IAAI,OAAO;IAIrC,SAAS,CAAC,cAAc,IAAI,kBAAkB;CAgC/C;AAED;;GAEG;AACH,UAAU,gBAAgB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,cACM,mBAAoB,SAAQ,YAAY,CAAC,gBAAgB,CAAC;IAC9D,SAAS,CAAC,YAAY,SAAqB;IAG3C,SAAS,CAAC,SAAS,EAAE,gBAAgB,CAGnC;IAEF,MAAM,CAAC,MAAM,EA6BR,cAAc,CAAC;IAEpB,SAAS,CAAC,gBAAgB,IAAI,OAAO;IAIrC,OAAO,CAAC,aAAa;IAKrB,SAAS,CAAC,cAAc,IAAI,kBAAkB;CAiC/C;AAGD,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,sBAAsB,EAAE,kBAAkB,CAAC;QAC3C,sBAAsB,EAAE,kBAAkB,CAAC;QAC3C,uBAAuB,EAAE,mBAAmB,CAAC;KAC9C;CACF;AAMD;;;GAGG;AACH,UAAU,gBAAgB;IACxB,uBAAuB;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,yBAAyB;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,oCAAoC;IACpC,qBAAqB,EAAE,OAAO,CAAC;IAC/B,6CAA6C;IAC7C,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,gBAAgB,CA0GhC,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,gBAAgB,CAAC,CAAC;AAMxC,gGAAgG;AAChG,eAAO,MAAM,OAAO,EAAE,KASrB,CAAC;AAMF,wEAAwE;AACxE,eAAO,MAAM,WAAW,EAAE,KAoBzB,CAAC;AAMF,uEAAuE;AACvE,eAAO,MAAM,YAAY,EAAE,KAqB1B,CAAC;AAMF,sEAAsE;AACtE,eAAO,MAAM,QAAQ,EAAE,KAwCtB,CAAC;AAMF,qFAAqF;AACrF,eAAO,MAAM,kBAAkB,EAAE,KA6BhC,CAAC"}
|
|
@@ -1,17 +1,44 @@
|
|
|
1
1
|
import { LitElement, CSSResultGroup } from 'lit';
|
|
2
2
|
/**
|
|
3
|
+
* Modal popup triggered by clicking a slot element.
|
|
4
|
+
*
|
|
3
5
|
* @tagname pd-popup
|
|
6
|
+
* @summary Modal popup with trigger element and content slot.
|
|
7
|
+
*
|
|
8
|
+
* @event popup-close - Fired when popup is closed.
|
|
4
9
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
10
|
+
* @slot small-view - Trigger content (e.g., icon, text).
|
|
11
|
+
* @slot content - Content displayed in the popup.
|
|
7
12
|
*
|
|
8
|
-
* @
|
|
9
|
-
* @
|
|
10
|
-
* @
|
|
11
|
-
* @
|
|
13
|
+
* @cssprop --pd-popup-modal-bg-rgba - Modal overlay background. Default: `--pd-modal-overlay-col`.
|
|
14
|
+
* @cssprop --pd-popup-default-display - Initial display state. Default: `none`.
|
|
15
|
+
* @cssprop --pd-popup-z-index - Z-index for modal overlay. Default: `100`.
|
|
16
|
+
* @cssprop --pd-popup-modal-padding - Modal content padding. Default: `--pd-spacing-md`.
|
|
17
|
+
* @cssprop --pd-popup-modal-padding-top - Top padding for vertical positioning. Default: `100px`.
|
|
18
|
+
* @cssprop --pd-popup-modal-padding-bottom - Bottom padding inside content. Default: `130px`.
|
|
19
|
+
* @cssprop --pd-popup-max-width - Maximum popup width. Default: `1200px`.
|
|
20
|
+
* @cssprop --pd-popup-width - Popup width. Default: `80%`.
|
|
21
|
+
* @cssprop --pd-popup-border-radius - Corner radius. Default: `--pd-radius-lg`.
|
|
22
|
+
* @cssprop --pd-popup-modal-slot-max-width - Max width of slot content. Default: `1000px`.
|
|
23
|
+
* @cssprop --pd-popup-modal-slot-margin - Slot content margin. Default: `0 --pd-spacing-lg`.
|
|
24
|
+
* @cssprop --pd-popup-content-bg - Modal content background color. Default: `--pd-default-bg-col`.
|
|
25
|
+
* @cssprop --pd-popup-content-height - Modal content height. Default: `auto`.
|
|
12
26
|
*/
|
|
13
27
|
export declare class PdPopup extends LitElement {
|
|
28
|
+
/** Enable closing with Escape key. */
|
|
14
29
|
closeByEscape: boolean;
|
|
30
|
+
/** Enable closing by clicking the backdrop overlay. */
|
|
31
|
+
closeByBackdrop: boolean;
|
|
32
|
+
/** Hide the built-in close icon. Useful when content provides its own close mechanism. */
|
|
33
|
+
hideCloseIcon: boolean;
|
|
34
|
+
/** Block body scroll when popup is open. */
|
|
35
|
+
blockScroll: boolean;
|
|
36
|
+
/** Accessible label for the popup (used for aria-label). */
|
|
37
|
+
popupLabel: string;
|
|
38
|
+
/** @ignore */
|
|
39
|
+
private _scrollBlocked;
|
|
40
|
+
/** @ignore */
|
|
41
|
+
private _modal;
|
|
15
42
|
static styles: CSSResultGroup;
|
|
16
43
|
connectedCallback(): void;
|
|
17
44
|
disconnectedCallback(): void;
|
|
@@ -27,5 +54,8 @@ export declare class PdPopup extends LitElement {
|
|
|
27
54
|
private _activatePopup;
|
|
28
55
|
private _closePopup;
|
|
29
56
|
private _handleKeyDown;
|
|
57
|
+
private _onBackdropClick;
|
|
58
|
+
private _stopPropagation;
|
|
59
|
+
private _onCloseIconKeyDown;
|
|
30
60
|
}
|
|
31
61
|
//# sourceMappingURL=PdPopup.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PdPopup.d.ts","sourceRoot":"","sources":["../../src/pd-popup/PdPopup.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"PdPopup.d.ts","sourceRoot":"","sources":["../../src/pd-popup/PdPopup.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAsB,cAAc,EAAE,MAAM,KAAK,CAAC;AAKrE,OAAO,0CAA0C,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,OAAQ,SAAQ,UAAU;IACrC,sCAAsC;IAEtC,aAAa,EAAE,OAAO,CAAS;IAE/B,uDAAuD;IAEvD,eAAe,EAAE,OAAO,CAAS;IAEjC,0FAA0F;IAE1F,aAAa,EAAE,OAAO,CAAS;IAE/B,4CAA4C;IAE5C,WAAW,EAAE,OAAO,CAAS;IAE7B,4DAA4D;IAE5D,UAAU,EAAE,MAAM,CAAW;IAE7B,cAAc;IACd,OAAO,CAAC,cAAc,CAAS;IAE/B,cAAc;IAEd,OAAO,CAAC,MAAM,CAAkB;IAEhC,OAAgB,MAAM,EAAE,cAAc,CA0DpC;IAEO,iBAAiB;IAWjB,oBAAoB;IAW7B;;OAEG;IACI,SAAS,IAAI,IAAI;IAIxB;;OAEG;IACI,SAAS,IAAI,IAAI;IAIxB,SAAS,CAAC,MAAM;IAsChB,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,WAAW;IAenB,OAAO,CAAC,cAAc,CAEpB;IAEF,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,mBAAmB;CAM5B"}
|
package/dist/pd-popup/PdPopup.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { LitElement, css, html } from 'lit';
|
|
2
|
-
import { property } from 'lit/decorators.js';
|
|
1
|
+
import { LitElement, css, nothing, html } from 'lit';
|
|
2
|
+
import { property, query } from 'lit/decorators.js';
|
|
3
3
|
import { pdIcons } from '@progressive-development/pd-icon';
|
|
4
4
|
import '@progressive-development/pd-icon/pd-icon';
|
|
5
5
|
|
|
@@ -16,6 +16,12 @@ class PdPopup extends LitElement {
|
|
|
16
16
|
constructor() {
|
|
17
17
|
super(...arguments);
|
|
18
18
|
this.closeByEscape = false;
|
|
19
|
+
this.closeByBackdrop = false;
|
|
20
|
+
this.hideCloseIcon = false;
|
|
21
|
+
this.blockScroll = false;
|
|
22
|
+
this.popupLabel = "Popup";
|
|
23
|
+
/** @ignore */
|
|
24
|
+
this._scrollBlocked = false;
|
|
19
25
|
this._handleKeyDown = (e) => {
|
|
20
26
|
if (e.key === "Escape") this._closePopup();
|
|
21
27
|
};
|
|
@@ -29,7 +35,8 @@ class PdPopup extends LitElement {
|
|
|
29
35
|
|
|
30
36
|
.modal {
|
|
31
37
|
position: fixed;
|
|
32
|
-
|
|
38
|
+
box-sizing: border-box;
|
|
39
|
+
z-index: var(--pd-popup-z-index, 100);
|
|
33
40
|
left: 0;
|
|
34
41
|
top: 0;
|
|
35
42
|
width: 100%;
|
|
@@ -37,34 +44,45 @@ class PdPopup extends LitElement {
|
|
|
37
44
|
overflow: auto;
|
|
38
45
|
background-color: var(
|
|
39
46
|
--pd-popup-modal-bg-rgba,
|
|
40
|
-
|
|
47
|
+
var(--pd-modal-overlay-col)
|
|
41
48
|
);
|
|
42
49
|
display: var(--pd-popup-default-display, none);
|
|
43
|
-
padding-top: 100px;
|
|
50
|
+
padding-top: var(--pd-popup-modal-padding-top, 100px);
|
|
51
|
+
max-width: 100vw;
|
|
52
|
+
overflow-x: hidden;
|
|
44
53
|
}
|
|
45
54
|
|
|
46
55
|
.modal-content {
|
|
47
|
-
background-color: var(--pd-default-bg-col);
|
|
48
|
-
opacity: 1;
|
|
56
|
+
background-color: var(--pd-popup-content-bg, var(--pd-default-bg-col));
|
|
49
57
|
margin: auto;
|
|
50
|
-
padding: var(--pd-popup-modal-padding,
|
|
51
|
-
padding-bottom: 130px;
|
|
52
|
-
border:
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
padding: var(--pd-popup-modal-padding, var(--pd-spacing-md));
|
|
59
|
+
padding-bottom: var(--pd-popup-modal-padding-bottom, 130px);
|
|
60
|
+
border: var(--pd-border-width, 1px) solid var(--pd-default-col);
|
|
61
|
+
border-radius: var(--pd-popup-border-radius, var(--pd-radius-lg));
|
|
62
|
+
box-shadow: var(--pd-shadow-xl);
|
|
63
|
+
width: var(--pd-popup-width, 80%);
|
|
64
|
+
max-width: var(--pd-popup-max-width, 1200px);
|
|
65
|
+
height: var(--pd-popup-content-height, auto);
|
|
55
66
|
position: relative;
|
|
56
67
|
}
|
|
57
68
|
|
|
58
69
|
.modal-content-slot {
|
|
59
70
|
max-width: var(--pd-popup-modal-slot-max-width, 1000px);
|
|
60
|
-
margin: var(--pd-popup-modal-slot-margin, 0
|
|
71
|
+
margin: var(--pd-popup-modal-slot-margin, 0 var(--pd-spacing-lg, 2rem));
|
|
72
|
+
height: 100%;
|
|
61
73
|
}
|
|
62
74
|
|
|
63
75
|
.close-icon {
|
|
64
76
|
position: absolute;
|
|
65
77
|
cursor: pointer;
|
|
66
|
-
right:
|
|
67
|
-
top:
|
|
78
|
+
right: var(--pd-spacing-md, 1rem);
|
|
79
|
+
top: var(--pd-spacing-md, 1rem);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.close-icon:focus {
|
|
83
|
+
outline: 2px solid var(--pd-default-col);
|
|
84
|
+
outline-offset: 2px;
|
|
85
|
+
border-radius: var(--pd-radius-sm, 2px);
|
|
68
86
|
}
|
|
69
87
|
`
|
|
70
88
|
];
|
|
@@ -74,12 +92,20 @@ class PdPopup extends LitElement {
|
|
|
74
92
|
if (this.closeByEscape) {
|
|
75
93
|
document.addEventListener("keydown", this._handleKeyDown);
|
|
76
94
|
}
|
|
95
|
+
if (this.blockScroll) {
|
|
96
|
+
document.body.style.overflow = "hidden";
|
|
97
|
+
this._scrollBlocked = true;
|
|
98
|
+
}
|
|
77
99
|
}
|
|
78
100
|
disconnectedCallback() {
|
|
79
101
|
super.disconnectedCallback();
|
|
80
102
|
if (this.closeByEscape) {
|
|
81
103
|
document.removeEventListener("keydown", this._handleKeyDown);
|
|
82
104
|
}
|
|
105
|
+
if (this._scrollBlocked) {
|
|
106
|
+
document.body.style.overflow = "";
|
|
107
|
+
this._scrollBlocked = false;
|
|
108
|
+
}
|
|
83
109
|
}
|
|
84
110
|
/**
|
|
85
111
|
* Öffnet das Popup programmatisch
|
|
@@ -94,19 +120,33 @@ class PdPopup extends LitElement {
|
|
|
94
120
|
this._closePopup();
|
|
95
121
|
}
|
|
96
122
|
render() {
|
|
123
|
+
const popupId = `${this.id || "popup"}-content`;
|
|
97
124
|
return html`
|
|
98
125
|
<span @click=${this._activatePopup} @keypress=${this._activatePopup}>
|
|
99
126
|
<slot name="small-view"></slot>
|
|
100
127
|
</span>
|
|
101
128
|
|
|
102
|
-
<div
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
129
|
+
<div
|
|
130
|
+
id="modalId"
|
|
131
|
+
class="modal"
|
|
132
|
+
role="dialog"
|
|
133
|
+
aria-modal="true"
|
|
134
|
+
aria-label="${this.popupLabel}"
|
|
135
|
+
@click=${this._onBackdropClick}
|
|
136
|
+
>
|
|
137
|
+
<div class="modal-content" @click=${this._stopPropagation}>
|
|
138
|
+
${this.hideCloseIcon ? nothing : html`
|
|
139
|
+
<pd-icon
|
|
140
|
+
icon=${pdIcons.ICON_CLOSE}
|
|
141
|
+
class="close-icon"
|
|
142
|
+
tabindex="0"
|
|
143
|
+
role="button"
|
|
144
|
+
aria-label="Close popup"
|
|
145
|
+
@click=${this._closePopup}
|
|
146
|
+
@keydown=${this._onCloseIconKeyDown}
|
|
147
|
+
></pd-icon>
|
|
148
|
+
`}
|
|
149
|
+
<div id="${popupId}" class="modal-content-slot">
|
|
110
150
|
<slot name="content"></slot>
|
|
111
151
|
</div>
|
|
112
152
|
</div>
|
|
@@ -114,12 +154,14 @@ class PdPopup extends LitElement {
|
|
|
114
154
|
`;
|
|
115
155
|
}
|
|
116
156
|
_activatePopup() {
|
|
117
|
-
|
|
118
|
-
if (modal) modal.style.display = "block";
|
|
157
|
+
if (this._modal) this._modal.style.display = "block";
|
|
119
158
|
}
|
|
120
159
|
_closePopup() {
|
|
121
|
-
|
|
122
|
-
if (
|
|
160
|
+
if (this._modal) this._modal.style.display = "none";
|
|
161
|
+
if (this._scrollBlocked) {
|
|
162
|
+
document.body.style.overflow = "";
|
|
163
|
+
this._scrollBlocked = false;
|
|
164
|
+
}
|
|
123
165
|
this.dispatchEvent(
|
|
124
166
|
new CustomEvent("popup-close", {
|
|
125
167
|
bubbles: true,
|
|
@@ -127,9 +169,38 @@ class PdPopup extends LitElement {
|
|
|
127
169
|
})
|
|
128
170
|
);
|
|
129
171
|
}
|
|
172
|
+
_onBackdropClick() {
|
|
173
|
+
if (this.closeByBackdrop) {
|
|
174
|
+
this._closePopup();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
_stopPropagation(e) {
|
|
178
|
+
e.stopPropagation();
|
|
179
|
+
}
|
|
180
|
+
_onCloseIconKeyDown(e) {
|
|
181
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
182
|
+
e.preventDefault();
|
|
183
|
+
this._closePopup();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
130
186
|
}
|
|
131
187
|
__decorateClass([
|
|
132
188
|
property({ type: Boolean })
|
|
133
189
|
], PdPopup.prototype, "closeByEscape");
|
|
190
|
+
__decorateClass([
|
|
191
|
+
property({ type: Boolean })
|
|
192
|
+
], PdPopup.prototype, "closeByBackdrop");
|
|
193
|
+
__decorateClass([
|
|
194
|
+
property({ type: Boolean })
|
|
195
|
+
], PdPopup.prototype, "hideCloseIcon");
|
|
196
|
+
__decorateClass([
|
|
197
|
+
property({ type: Boolean })
|
|
198
|
+
], PdPopup.prototype, "blockScroll");
|
|
199
|
+
__decorateClass([
|
|
200
|
+
property({ type: String })
|
|
201
|
+
], PdPopup.prototype, "popupLabel");
|
|
202
|
+
__decorateClass([
|
|
203
|
+
query("#modalId")
|
|
204
|
+
], PdPopup.prototype, "_modal");
|
|
134
205
|
|
|
135
206
|
export { PdPopup };
|
|
@@ -1,24 +1,58 @@
|
|
|
1
|
-
import { StoryObj } from '@storybook/web-components';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/web-components-vite';
|
|
2
|
+
/**
|
|
3
|
+
* Story arguments interface for pd-popup component.
|
|
4
|
+
* Maps to the component's public API.
|
|
5
|
+
*/
|
|
6
|
+
interface PdPopupArgs {
|
|
7
|
+
/** Content for the trigger slot */
|
|
8
|
+
smallViewSlot: string;
|
|
9
|
+
/** Content displayed in the popup */
|
|
10
|
+
contentSlot: string;
|
|
11
|
+
/** Enable closing with Escape key */
|
|
12
|
+
closeByEscape: boolean;
|
|
13
|
+
/** Enable closing by clicking the backdrop overlay */
|
|
14
|
+
closeByBackdrop: boolean;
|
|
15
|
+
/** Hide the built-in close icon */
|
|
16
|
+
hideCloseIcon: boolean;
|
|
17
|
+
/** Block body scroll when popup is open */
|
|
18
|
+
blockScroll: boolean;
|
|
19
|
+
/** Accessible label for the popup */
|
|
20
|
+
popupLabel: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* ## pd-popup
|
|
24
|
+
*
|
|
25
|
+
* A modal popup component triggered by clicking a slot element.
|
|
26
|
+
*
|
|
27
|
+
* ### Features
|
|
28
|
+
* - Trigger element via `small-view` slot (click to open)
|
|
29
|
+
* - Full-screen modal overlay with centered content
|
|
30
|
+
* - Built-in close icon (keyboard accessible, can be hidden)
|
|
31
|
+
* - Optional close on Escape key press (`closeByEscape`)
|
|
32
|
+
* - Optional close on backdrop click (`closeByBackdrop`)
|
|
33
|
+
* - Programmatic control via `showPopup()` and `hidePopup()` methods
|
|
34
|
+
* - Body scroll blocking when open (`blockScroll`)
|
|
35
|
+
* - Accessibility: `role="dialog"`, `aria-modal="true"`, `aria-label`
|
|
36
|
+
*
|
|
37
|
+
* ### Accessibility
|
|
38
|
+
* - Uses `role="dialog"` and `aria-modal="true"` on the modal container
|
|
39
|
+
* - `aria-label` set via `popupLabel` property
|
|
40
|
+
* - Close icon is keyboard focusable with `role="button"` and `aria-label`
|
|
41
|
+
* - Supports Escape key and backdrop click for closing
|
|
42
|
+
*/
|
|
43
|
+
declare const meta: Meta<PdPopupArgs>;
|
|
19
44
|
export default meta;
|
|
20
|
-
type Story = StoryObj
|
|
21
|
-
|
|
22
|
-
export declare const
|
|
23
|
-
|
|
45
|
+
type Story = StoryObj<PdPopupArgs>;
|
|
46
|
+
/** Default popup with simple text content. Interactive via Controls panel. */
|
|
47
|
+
export declare const Default: Story;
|
|
48
|
+
/** Popup with structured HTML content including headings, text, and lists. */
|
|
49
|
+
export declare const RichContent: Story;
|
|
50
|
+
/** Demonstrates the three closing mechanisms: close icon, Escape key, and backdrop click. */
|
|
51
|
+
export declare const ClosingOptions: Story;
|
|
52
|
+
/** Popup with the built-in close icon hidden. Content provides its own close mechanism. */
|
|
53
|
+
export declare const HiddenCloseIcon: Story;
|
|
54
|
+
/** Demonstrates opening and closing the popup programmatically via `showPopup()` / `hidePopup()`. */
|
|
55
|
+
export declare const ProgrammaticControl: Story;
|
|
56
|
+
/** CSS Custom Properties -- Branded and Redesigned variants. */
|
|
57
|
+
export declare const CustomStyling: Story;
|
|
24
58
|
//# sourceMappingURL=pd-popup.stories.d.ts.map
|