@progressive-development/pd-forms 1.0.1 → 1.0.3
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/dist/base/pd-base-ui-input.d.ts +6 -0
- package/dist/base/pd-base-ui-input.d.ts.map +1 -1
- package/dist/base/pd-base-ui-input.js +46 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -4
- package/dist/pd-button/PdButton.d.ts +8 -0
- package/dist/pd-button/PdButton.d.ts.map +1 -1
- package/dist/pd-button/PdButton.js +23 -0
- package/dist/pd-color-picker/PdColorPicker.d.ts +25 -0
- package/dist/pd-color-picker/PdColorPicker.d.ts.map +1 -0
- package/dist/pd-color-picker/PdColorPicker.js +238 -0
- package/dist/pd-color-picker/pd-color-picker.d.ts +3 -0
- package/dist/pd-color-picker/pd-color-picker.d.ts.map +1 -0
- package/dist/pd-color-picker.d.ts +2 -0
- package/dist/pd-color-picker.js +8 -0
- package/dist/pd-generic-form/PdGenericForm.d.ts +1 -0
- package/dist/pd-generic-form/PdGenericForm.d.ts.map +1 -1
- package/dist/pd-generic-form/PdGenericForm.js +12 -2
- package/dist/pd-generic-form/pd-generic-form.styles.d.ts.map +1 -1
- package/dist/pd-generic-form/pd-generic-form.styles.js +12 -5
- package/dist/pd-input-image/PdInputImage.d.ts +79 -0
- package/dist/pd-input-image/PdInputImage.d.ts.map +1 -0
- package/dist/pd-input-image/PdInputImage.js +553 -0
- package/dist/pd-input-image/pd-input-image.d.ts +4 -0
- package/dist/pd-input-image/pd-input-image.d.ts.map +1 -0
- package/dist/pd-input-image/pd-input-image.styles.d.ts +6 -0
- package/dist/pd-input-image/pd-input-image.styles.d.ts.map +1 -0
- package/dist/pd-input-image/pd-input-image.styles.js +300 -0
- package/dist/pd-input-image.d.ts +2 -0
- package/dist/pd-input-image.js +8 -0
- package/dist/pd-range/PdRange.d.ts +2 -0
- package/dist/pd-range/PdRange.d.ts.map +1 -1
- package/dist/pd-range/PdRange.js +11 -7
- package/package.json +6 -4
- package/dist/pd-button/pd-button.stories.d.ts +0 -84
- package/dist/pd-button/pd-button.stories.d.ts.map +0 -1
- package/dist/pd-button-group/pd-button-group.stories.d.ts +0 -45
- package/dist/pd-button-group/pd-button-group.stories.d.ts.map +0 -1
- package/dist/pd-button-select-group/pd-button-select-group.stories.d.ts +0 -46
- package/dist/pd-button-select-group/pd-button-select-group.stories.d.ts.map +0 -1
- package/dist/pd-checkbox/pd-checkbox.stories.d.ts +0 -45
- package/dist/pd-checkbox/pd-checkbox.stories.d.ts.map +0 -1
- package/dist/pd-form-container/pd-form-container.stories.d.ts +0 -49
- package/dist/pd-form-container/pd-form-container.stories.d.ts.map +0 -1
- package/dist/pd-form-field/pd-form-field.stories.d.ts +0 -40
- package/dist/pd-form-field/pd-form-field.stories.d.ts.map +0 -1
- package/dist/pd-form-fieldset/pd-form-fieldset.stories.d.ts +0 -38
- package/dist/pd-form-fieldset/pd-form-fieldset.stories.d.ts.map +0 -1
- package/dist/pd-form-row/pd-form-row.stories.d.ts +0 -43
- package/dist/pd-form-row/pd-form-row.stories.d.ts.map +0 -1
- package/dist/pd-generic-form/pd-generic-form.stories.d.ts +0 -35
- package/dist/pd-generic-form/pd-generic-form.stories.d.ts.map +0 -1
- package/dist/pd-hover-box/pd-hover-box.stories.d.ts +0 -31
- package/dist/pd-hover-box/pd-hover-box.stories.d.ts.map +0 -1
- package/dist/pd-input/pd-input.stories.d.ts +0 -73
- package/dist/pd-input/pd-input.stories.d.ts.map +0 -1
- package/dist/pd-input-area/pd-input-area.stories.d.ts +0 -67
- package/dist/pd-input-area/pd-input-area.stories.d.ts.map +0 -1
- package/dist/pd-input-file/pd-input-file.stories.d.ts +0 -53
- package/dist/pd-input-file/pd-input-file.stories.d.ts.map +0 -1
- package/dist/pd-input-time/pd-input-time.stories.d.ts +0 -94
- package/dist/pd-input-time/pd-input-time.stories.d.ts.map +0 -1
- package/dist/pd-panel-button/pd-panel-button.stories.d.ts +0 -57
- package/dist/pd-panel-button/pd-panel-button.stories.d.ts.map +0 -1
- package/dist/pd-radio-group/pd-radio-group.stories.d.ts +0 -39
- package/dist/pd-radio-group/pd-radio-group.stories.d.ts.map +0 -1
- package/dist/pd-range/pd-range.stories.d.ts +0 -51
- package/dist/pd-range/pd-range.stories.d.ts.map +0 -1
- package/dist/pd-select/pd-select.stories.d.ts +0 -58
- package/dist/pd-select/pd-select.stories.d.ts.map +0 -1
- package/dist/pd-suggestion-box/pd-suggestion-box.stories.d.ts +0 -79
- package/dist/pd-suggestion-box/pd-suggestion-box.stories.d.ts.map +0 -1
- package/dist/stories/pd-forms-overview.stories.d.ts +0 -48
- package/dist/stories/pd-forms-overview.stories.d.ts.map +0 -1
|
@@ -20,8 +20,8 @@ const pdGenericFormStyles = css`
|
|
|
20
20
|
|
|
21
21
|
.view-item {
|
|
22
22
|
display: flex;
|
|
23
|
-
align-items:
|
|
24
|
-
|
|
23
|
+
align-items: baseline;
|
|
24
|
+
gap: 1rem;
|
|
25
25
|
background: var(
|
|
26
26
|
--pd-generic-form-view-item-bg,
|
|
27
27
|
var(--pd-default-bg-light-col, #f8f9fc)
|
|
@@ -38,6 +38,8 @@ const pdGenericFormStyles = css`
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
.view-label {
|
|
41
|
+
flex: 0 0 auto;
|
|
42
|
+
max-width: 40%;
|
|
41
43
|
font-weight: var(--pd-generic-form-view-label-weight, 500);
|
|
42
44
|
font-size: var(
|
|
43
45
|
--pd-generic-form-view-label-size,
|
|
@@ -51,13 +53,16 @@ const pdGenericFormStyles = css`
|
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
.view-value {
|
|
56
|
+
flex: 1 1 0;
|
|
57
|
+
min-width: 0;
|
|
58
|
+
text-align: right;
|
|
59
|
+
word-break: break-word;
|
|
54
60
|
font-weight: var(--pd-generic-form-view-value-weight, 600);
|
|
55
61
|
color: var(
|
|
56
62
|
--pd-generic-form-view-value-col,
|
|
57
63
|
var(--pd-default-col, #067394)
|
|
58
64
|
);
|
|
59
65
|
font-family: var(--pd-default-font-content-family);
|
|
60
|
-
white-space: nowrap;
|
|
61
66
|
}
|
|
62
67
|
|
|
63
68
|
.view-value a {
|
|
@@ -99,10 +104,12 @@ const pdGenericFormStyles = css`
|
|
|
99
104
|
flex-direction: column;
|
|
100
105
|
align-items: flex-start;
|
|
101
106
|
}
|
|
107
|
+
.view-label {
|
|
108
|
+
max-width: none;
|
|
109
|
+
}
|
|
102
110
|
.view-value {
|
|
103
111
|
margin-top: 0.25rem;
|
|
104
|
-
|
|
105
|
-
word-break: break-word;
|
|
112
|
+
text-align: left;
|
|
106
113
|
}
|
|
107
114
|
}
|
|
108
115
|
`;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { CSSResultGroup, PropertyValues } from 'lit';
|
|
2
|
+
import { PdBaseUIInput } from '../base/pd-base-ui-input.js';
|
|
3
|
+
export interface ImageSelection {
|
|
4
|
+
/** Bild-URL (Data-URL oder externe URL) */
|
|
5
|
+
src: string;
|
|
6
|
+
/** Optionaler Kommentar zum Bild (Phase 2) */
|
|
7
|
+
comment?: string;
|
|
8
|
+
/** Whether this image is selected (only used when selectable mode is enabled) */
|
|
9
|
+
selected?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Multi-image input component with file upload and optional AI image generation.
|
|
13
|
+
*
|
|
14
|
+
* @tagname pd-input-image
|
|
15
|
+
* @summary Multi-image picker with thumbnail grid, file upload, and AI integration.
|
|
16
|
+
*
|
|
17
|
+
* @event pd-form-element-change - Fired when the images array changes.
|
|
18
|
+
* @event pd-form-element-register - Fired on first render for form integration.
|
|
19
|
+
* @event pd-form-element-unregister - Fired on disconnect for form cleanup.
|
|
20
|
+
* @event pd-ai-trigger - Fired when AI sparkle icon is clicked (requires aiOptions).
|
|
21
|
+
*
|
|
22
|
+
* @cssprop --pd-input-field-width - Container width. Default: `300px`.
|
|
23
|
+
* @cssprop --pd-input-field-bg-col - Background color. Default: `var(--pd-default-bg-col)`.
|
|
24
|
+
* @cssprop --pd-input-field-border - Border style. Default: `1px solid var(--pd-default-light-col)`.
|
|
25
|
+
* @cssprop --pd-input-field-border-bottom - Bottom border. Default: `2px solid var(--pd-default-col)`.
|
|
26
|
+
* @cssprop --pd-input-image-grid-gap - Gap between thumbnails. Default: `var(--pd-spacing-sm, 8px)`.
|
|
27
|
+
* @cssprop --pd-input-image-thumb-size - Thumbnail tile size. Default: `120px`.
|
|
28
|
+
* @cssprop --pd-input-image-thumb-radius - Thumbnail border-radius. Default: `var(--pd-radius-md)`.
|
|
29
|
+
* @cssprop --pd-input-image-remove-bg - Remove button background. Default: `rgba(0,0,0,0.5)`.
|
|
30
|
+
* @cssprop --pd-input-image-remove-size - Remove button size. Default: `24px`.
|
|
31
|
+
*/
|
|
32
|
+
export declare class PdInputImage extends PdBaseUIInput {
|
|
33
|
+
/** Accepted file types for the file picker. */
|
|
34
|
+
accept: string;
|
|
35
|
+
/** Placeholder text shown when no image is set. */
|
|
36
|
+
placeholder: string;
|
|
37
|
+
/** Maximum number of images allowed. */
|
|
38
|
+
max: number;
|
|
39
|
+
/** Enable single-select mode: user can click a thumbnail to select it. */
|
|
40
|
+
selectable: boolean;
|
|
41
|
+
/** Internal images array – primary data source. */
|
|
42
|
+
private _images;
|
|
43
|
+
/** Index of the image currently shown in the preview overlay, or null. */
|
|
44
|
+
private _previewIndex;
|
|
45
|
+
/** Whether the URL input row is visible. */
|
|
46
|
+
private _showUrlInput;
|
|
47
|
+
/** Current value of the URL input field. */
|
|
48
|
+
private _urlInputValue;
|
|
49
|
+
static styles: CSSResultGroup;
|
|
50
|
+
private _boundHandleKeyDown;
|
|
51
|
+
connectedCallback(): void;
|
|
52
|
+
disconnectedCallback(): void;
|
|
53
|
+
get value(): ImageSelection[];
|
|
54
|
+
set value(val: ImageSelection[]);
|
|
55
|
+
private _updateImages;
|
|
56
|
+
protected _dispatchChange(_value: string, eventName?: string): void;
|
|
57
|
+
protected _registerAtContainer(valid: boolean): void;
|
|
58
|
+
protected _getParsedValue(): ImageSelection[];
|
|
59
|
+
aiAdopt(value: string): void;
|
|
60
|
+
reset(): void;
|
|
61
|
+
clear(): void;
|
|
62
|
+
update(changedProps: PropertyValues<this>): void;
|
|
63
|
+
render(): import('lit').TemplateResult<1>;
|
|
64
|
+
private _onPlaceholderClick;
|
|
65
|
+
private _onSelectClick;
|
|
66
|
+
private _openFilePicker;
|
|
67
|
+
private _onContainerKeyDown;
|
|
68
|
+
private _onFileSelect;
|
|
69
|
+
private _openPreview;
|
|
70
|
+
private _closePreview;
|
|
71
|
+
private _onBackdropClick;
|
|
72
|
+
private _handleGlobalKeyDown;
|
|
73
|
+
private _removeImage;
|
|
74
|
+
private _selectImage;
|
|
75
|
+
private _getSelectedImage;
|
|
76
|
+
private _onUrlAddClick;
|
|
77
|
+
private _onUrlConfirm;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=PdInputImage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PdInputImage.d.ts","sourceRoot":"","sources":["../../src/pd-input-image/PdInputImage.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAiB,cAAc,EAAE,cAAc,EAAE,MAAM,KAAK,CAAC;AAMpE,OAAO,0CAA0C,CAAC;AAElD,OAAO,2BAA2B,CAAC;AACnC,OAAO,yBAAyB,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAI5D,MAAM,WAAW,cAAc;IAC7B,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,8CAA8C;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,YAAa,SAAQ,aAAa;IAC7C,+CAA+C;IAE/C,MAAM,SAAa;IAEnB,mDAAmD;IAEnD,WAAW,SAAoB;IAE/B,wCAAwC;IAExC,GAAG,SAAY;IAEf,0EAA0E;IAE1E,UAAU,UAAS;IAEnB,mDAAmD;IAEnD,OAAO,CAAC,OAAO,CAAwB;IAEvC,0EAA0E;IAE1E,OAAO,CAAC,aAAa,CAAuB;IAE5C,4CAA4C;IAE5C,OAAO,CAAC,aAAa,CAAS;IAE9B,4CAA4C;IAE5C,OAAO,CAAC,cAAc,CAAM;IAE5B,OAAgB,MAAM,EAAE,cAAc,CAIpC;IAEF,OAAO,CAAC,mBAAmB,CAAwC;IAE1D,iBAAiB,IAAI,IAAI;IAkBzB,oBAAoB,IAAI,IAAI;IASrC,IAAI,KAAK,IAAI,cAAc,EAAE,CAE5B;IAGD,IAAI,KAAK,CAAC,GAAG,EAAE,cAAc,EAAE,EAE9B;IAID,OAAO,CAAC,aAAa;cAaF,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;cAyBzD,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;cAyB1C,eAAe,IAAI,cAAc,EAAE;IAItC,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAM5B,KAAK,IAAI,IAAI;IAqCb,KAAK,IAAI,IAAI;IAMpB,MAAM,CAAC,YAAY,EAAE,cAAc,CAAC,IAAI,CAAC,GAAG,IAAI;IAoChD,MAAM;IAkQf,OAAO,CAAC,mBAAmB;IAK3B,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,aAAa;IA2BrB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,gBAAgB;IAOxB,OAAO,CAAC,oBAAoB;IAM5B,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,aAAa;CAiBtB"}
|
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
import { nothing, html } from 'lit';
|
|
2
|
+
import { property, state } from 'lit/decorators.js';
|
|
3
|
+
import { classMap } from 'lit/directives/class-map.js';
|
|
4
|
+
import { msg } from '@lit/localize';
|
|
5
|
+
import { pdIcons } from '@progressive-development/pd-icon';
|
|
6
|
+
import '@progressive-development/pd-icon/pd-icon';
|
|
7
|
+
import '../pd-button.js';
|
|
8
|
+
import '../pd-input.js';
|
|
9
|
+
import { PdBaseUIInput } from '../base/pd-base-ui-input.js';
|
|
10
|
+
import { SharedInputStyles } from '../styles/shared-input-styles.js';
|
|
11
|
+
import { pdInputImageStyles } from './pd-input-image.styles.js';
|
|
12
|
+
|
|
13
|
+
var __defProp = Object.defineProperty;
|
|
14
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
15
|
+
var result = void 0 ;
|
|
16
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
17
|
+
if (decorator = decorators[i])
|
|
18
|
+
result = (decorator(target, key, result) ) || result;
|
|
19
|
+
if (result) __defProp(target, key, result);
|
|
20
|
+
return result;
|
|
21
|
+
};
|
|
22
|
+
class PdInputImage extends PdBaseUIInput {
|
|
23
|
+
constructor() {
|
|
24
|
+
super(...arguments);
|
|
25
|
+
this.accept = "image/*";
|
|
26
|
+
this.placeholder = "Bild hochladen";
|
|
27
|
+
this.max = Infinity;
|
|
28
|
+
this.selectable = false;
|
|
29
|
+
this._images = [];
|
|
30
|
+
this._previewIndex = null;
|
|
31
|
+
this._showUrlInput = false;
|
|
32
|
+
this._urlInputValue = "";
|
|
33
|
+
this._boundHandleKeyDown = this._handleGlobalKeyDown.bind(this);
|
|
34
|
+
}
|
|
35
|
+
static {
|
|
36
|
+
this.styles = [
|
|
37
|
+
PdBaseUIInput.styles,
|
|
38
|
+
SharedInputStyles,
|
|
39
|
+
pdInputImageStyles
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
connectedCallback() {
|
|
43
|
+
super.connectedCallback();
|
|
44
|
+
this._validators.push((value) => {
|
|
45
|
+
if (!this.required) return null;
|
|
46
|
+
if (this._images.length < 1) {
|
|
47
|
+
return msg("Eingabe erforderlich", { id: "pd.form.field.required" });
|
|
48
|
+
}
|
|
49
|
+
if (this.selectable && !this._images.some((img) => img.selected)) {
|
|
50
|
+
return msg("Bitte ein Bild auswählen", {
|
|
51
|
+
id: "pd.form.field.selection-required"
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
});
|
|
56
|
+
document.addEventListener("keydown", this._boundHandleKeyDown);
|
|
57
|
+
}
|
|
58
|
+
disconnectedCallback() {
|
|
59
|
+
super.disconnectedCallback();
|
|
60
|
+
document.removeEventListener("keydown", this._boundHandleKeyDown);
|
|
61
|
+
}
|
|
62
|
+
// -- Value getter/setter overrides --
|
|
63
|
+
// Type intentionally widened from base class string to ImageSelection[] for multi-image support.
|
|
64
|
+
// @ts-expect-error – value type changed from string to ImageSelection[] (no backwards compat needed)
|
|
65
|
+
get value() {
|
|
66
|
+
return [...this._images];
|
|
67
|
+
}
|
|
68
|
+
// @ts-expect-error – value type changed from string to ImageSelection[] (no backwards compat needed)
|
|
69
|
+
set value(val) {
|
|
70
|
+
this._updateImages(val);
|
|
71
|
+
}
|
|
72
|
+
// -- Core update method --
|
|
73
|
+
_updateImages(newImages) {
|
|
74
|
+
this._images = [...newImages];
|
|
75
|
+
this._value = JSON.stringify(this._images);
|
|
76
|
+
this._pristine = false;
|
|
77
|
+
this._dirty = true;
|
|
78
|
+
this.runValidators().then(() => {
|
|
79
|
+
this._dispatchChange(this._value);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// -- Overrides for form integration --
|
|
83
|
+
_dispatchChange(_value, eventName) {
|
|
84
|
+
this.dispatchEvent(
|
|
85
|
+
new CustomEvent(eventName || "pd-form-element-change", {
|
|
86
|
+
detail: {
|
|
87
|
+
value: this._images,
|
|
88
|
+
name: this.valueName || this.id || "field",
|
|
89
|
+
parsedValue: this._images,
|
|
90
|
+
...this.selectable ? { selectedImage: this._getSelectedImage() } : {},
|
|
91
|
+
state: {
|
|
92
|
+
pristine: this._pristine,
|
|
93
|
+
dirty: this._dirty,
|
|
94
|
+
touched: this._touched,
|
|
95
|
+
focused: this._focused,
|
|
96
|
+
valid: this._valid,
|
|
97
|
+
invalid: this._invalid
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
bubbles: true,
|
|
101
|
+
composed: true
|
|
102
|
+
})
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
_registerAtContainer(valid) {
|
|
106
|
+
this.dispatchEvent(
|
|
107
|
+
new CustomEvent("pd-form-element-register", {
|
|
108
|
+
detail: {
|
|
109
|
+
name: this.valueName || this.id || "field",
|
|
110
|
+
value: this._images,
|
|
111
|
+
parsedValue: this._images,
|
|
112
|
+
...this.selectable ? { selectedImage: this._getSelectedImage() } : {},
|
|
113
|
+
state: {
|
|
114
|
+
pristine: this._pristine,
|
|
115
|
+
dirty: this._dirty,
|
|
116
|
+
touched: this._touched,
|
|
117
|
+
focused: this._focused,
|
|
118
|
+
valid,
|
|
119
|
+
invalid: !valid
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
bubbles: true,
|
|
123
|
+
composed: true
|
|
124
|
+
})
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
_getParsedValue() {
|
|
128
|
+
return this._images;
|
|
129
|
+
}
|
|
130
|
+
aiAdopt(value) {
|
|
131
|
+
if (this._images.length < this.max) {
|
|
132
|
+
this._updateImages([...this._images, { src: value }]);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
reset() {
|
|
136
|
+
this._images = [];
|
|
137
|
+
this._value = "[]";
|
|
138
|
+
this._errorMsg = "";
|
|
139
|
+
this._valid = false;
|
|
140
|
+
this._invalid = false;
|
|
141
|
+
this._pristine = true;
|
|
142
|
+
this._dirty = false;
|
|
143
|
+
this._touched = false;
|
|
144
|
+
this._focused = false;
|
|
145
|
+
this.runValidators(true).then((result) => {
|
|
146
|
+
this.dispatchEvent(
|
|
147
|
+
new CustomEvent("pd-form-element-change", {
|
|
148
|
+
detail: {
|
|
149
|
+
value: this._images,
|
|
150
|
+
parsedValue: this._images,
|
|
151
|
+
name: this.valueName || this.id || "field",
|
|
152
|
+
...this.selectable ? { selectedImage: this._getSelectedImage() } : {},
|
|
153
|
+
state: {
|
|
154
|
+
pristine: this._pristine,
|
|
155
|
+
dirty: this._dirty,
|
|
156
|
+
touched: this._touched,
|
|
157
|
+
focused: this._focused,
|
|
158
|
+
valid: result,
|
|
159
|
+
invalid: !result
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
bubbles: true,
|
|
163
|
+
composed: true
|
|
164
|
+
})
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
clear() {
|
|
169
|
+
this.reset();
|
|
170
|
+
}
|
|
171
|
+
// -- Handle initValue changes --
|
|
172
|
+
update(changedProps) {
|
|
173
|
+
if (changedProps.has("initValue") && this.initValue) {
|
|
174
|
+
let parsed = [];
|
|
175
|
+
try {
|
|
176
|
+
const json = JSON.parse(this.initValue);
|
|
177
|
+
if (Array.isArray(json)) {
|
|
178
|
+
parsed = json.map(
|
|
179
|
+
(item) => typeof item === "string" ? { src: item } : item
|
|
180
|
+
);
|
|
181
|
+
} else {
|
|
182
|
+
parsed = [{ src: this.initValue }];
|
|
183
|
+
}
|
|
184
|
+
} catch {
|
|
185
|
+
if (this.initValue) {
|
|
186
|
+
parsed = [{ src: this.initValue }];
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (this.selectable) {
|
|
190
|
+
let foundSelected = false;
|
|
191
|
+
parsed = parsed.map((img) => {
|
|
192
|
+
if (img.selected && !foundSelected) {
|
|
193
|
+
foundSelected = true;
|
|
194
|
+
return img;
|
|
195
|
+
}
|
|
196
|
+
return { ...img, selected: false };
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
this._images = parsed;
|
|
200
|
+
this._value = JSON.stringify(this._images);
|
|
201
|
+
}
|
|
202
|
+
super.update(changedProps);
|
|
203
|
+
}
|
|
204
|
+
// -- Render --
|
|
205
|
+
render() {
|
|
206
|
+
const inputId = `${this.id}Input`;
|
|
207
|
+
const errorId = `${this.id}Error`;
|
|
208
|
+
const hasImages = this._images.length > 0;
|
|
209
|
+
const isFull = this._images.length >= this.max;
|
|
210
|
+
const maxIsFinite = isFinite(this.max);
|
|
211
|
+
return html`
|
|
212
|
+
${this._renderLabel(inputId)}
|
|
213
|
+
<div class="image-area">
|
|
214
|
+
${hasImages ? html`
|
|
215
|
+
<div
|
|
216
|
+
class="${classMap(
|
|
217
|
+
this.getClassmap({
|
|
218
|
+
"image-container": true,
|
|
219
|
+
"has-images": true
|
|
220
|
+
})
|
|
221
|
+
)}"
|
|
222
|
+
>
|
|
223
|
+
<div class="thumb-grid">
|
|
224
|
+
${this._images.map(
|
|
225
|
+
(img, i) => html`
|
|
226
|
+
<div
|
|
227
|
+
class="${classMap({
|
|
228
|
+
"thumb-item": true,
|
|
229
|
+
selectable: this.selectable,
|
|
230
|
+
selected: !!img.selected
|
|
231
|
+
})}"
|
|
232
|
+
@click="${this.selectable ? (e) => {
|
|
233
|
+
e.stopPropagation();
|
|
234
|
+
this._selectImage(i);
|
|
235
|
+
} : nothing}"
|
|
236
|
+
>
|
|
237
|
+
<img src="${img.src}" alt="Bild ${i + 1}" />
|
|
238
|
+
${this.selectable ? html`
|
|
239
|
+
<pd-icon
|
|
240
|
+
class="thumb-preview-btn round"
|
|
241
|
+
icon="${pdIcons.ICON_DETAIL}"
|
|
242
|
+
activeIcon
|
|
243
|
+
tabindex="0"
|
|
244
|
+
role="button"
|
|
245
|
+
aria-label="Bild ${i + 1} vergrößern"
|
|
246
|
+
@click="${(e) => {
|
|
247
|
+
e.stopPropagation();
|
|
248
|
+
this._openPreview(i);
|
|
249
|
+
}}"
|
|
250
|
+
@keydown="${(e) => {
|
|
251
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
252
|
+
e.preventDefault();
|
|
253
|
+
e.stopPropagation();
|
|
254
|
+
this._openPreview(i);
|
|
255
|
+
}
|
|
256
|
+
}}"
|
|
257
|
+
></pd-icon>
|
|
258
|
+
<div class="thumb-select-indicator">
|
|
259
|
+
${img.selected ? html`<pd-icon
|
|
260
|
+
icon="${pdIcons.ICON_CHECKBOX_ONLY_CHECK}"
|
|
261
|
+
class="check-icon"
|
|
262
|
+
></pd-icon>` : nothing}
|
|
263
|
+
</div>
|
|
264
|
+
` : html`
|
|
265
|
+
<div
|
|
266
|
+
class="thumb-overlay"
|
|
267
|
+
@click="${(e) => {
|
|
268
|
+
e.stopPropagation();
|
|
269
|
+
this._openPreview(i);
|
|
270
|
+
}}"
|
|
271
|
+
role="button"
|
|
272
|
+
tabindex="0"
|
|
273
|
+
aria-label="Bild ${i + 1} vergrößern"
|
|
274
|
+
@keydown="${(e) => {
|
|
275
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
276
|
+
e.preventDefault();
|
|
277
|
+
e.stopPropagation();
|
|
278
|
+
this._openPreview(i);
|
|
279
|
+
}
|
|
280
|
+
}}"
|
|
281
|
+
>
|
|
282
|
+
<pd-icon
|
|
283
|
+
icon="${pdIcons.ICON_DETAIL}"
|
|
284
|
+
class="preview-icon"
|
|
285
|
+
></pd-icon>
|
|
286
|
+
</div>
|
|
287
|
+
`}
|
|
288
|
+
${!img.src.startsWith("data:") ? html`<span class="thumb-url-badge">URL</span>` : nothing}
|
|
289
|
+
${!this.disabled && !this.readonly ? html`
|
|
290
|
+
<pd-icon
|
|
291
|
+
class="thumb-remove round"
|
|
292
|
+
icon="${pdIcons.ICON_DELETE_NEW}"
|
|
293
|
+
activeIcon
|
|
294
|
+
tabindex="0"
|
|
295
|
+
role="button"
|
|
296
|
+
aria-label="Bild ${i + 1} entfernen"
|
|
297
|
+
@click="${(e) => {
|
|
298
|
+
e.stopPropagation();
|
|
299
|
+
this._removeImage(i);
|
|
300
|
+
}}"
|
|
301
|
+
@keydown="${(e) => {
|
|
302
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
303
|
+
e.preventDefault();
|
|
304
|
+
e.stopPropagation();
|
|
305
|
+
this._removeImage(i);
|
|
306
|
+
}
|
|
307
|
+
}}"
|
|
308
|
+
></pd-icon>
|
|
309
|
+
` : nothing}
|
|
310
|
+
</div>
|
|
311
|
+
`
|
|
312
|
+
)}
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
` : html`
|
|
316
|
+
<div
|
|
317
|
+
class="${classMap(
|
|
318
|
+
this.getClassmap({ "image-container": true })
|
|
319
|
+
)}"
|
|
320
|
+
aria-disabled="${this.disabled || this.readonly}"
|
|
321
|
+
@click="${this._onPlaceholderClick}"
|
|
322
|
+
@keydown="${this._onContainerKeyDown}"
|
|
323
|
+
tabindex="0"
|
|
324
|
+
role="button"
|
|
325
|
+
aria-label="${this.placeholder}"
|
|
326
|
+
>
|
|
327
|
+
<div class="placeholder">
|
|
328
|
+
<pd-icon
|
|
329
|
+
icon="${pdIcons.ICON_CAMERA}"
|
|
330
|
+
class="placeholder-icon"
|
|
331
|
+
></pd-icon>
|
|
332
|
+
<span>${this.placeholder}</span>
|
|
333
|
+
</div>
|
|
334
|
+
</div>
|
|
335
|
+
`}
|
|
336
|
+
</div>
|
|
337
|
+
<div class="action-row">
|
|
338
|
+
${!isFull ? html`
|
|
339
|
+
<pd-button
|
|
340
|
+
text="Dateien auswählen"
|
|
341
|
+
size="sm"
|
|
342
|
+
?disabled="${this.disabled || this.readonly}"
|
|
343
|
+
@button-clicked="${this._onSelectClick}"
|
|
344
|
+
></pd-button>
|
|
345
|
+
<pd-button
|
|
346
|
+
text="URL hinzufügen"
|
|
347
|
+
size="sm"
|
|
348
|
+
?disabled="${this.disabled || this.readonly}"
|
|
349
|
+
@button-clicked="${this._onUrlAddClick}"
|
|
350
|
+
></pd-button>
|
|
351
|
+
` : nothing}
|
|
352
|
+
${maxIsFinite ? html`
|
|
353
|
+
<span class="image-count">
|
|
354
|
+
${this._images.length} von ${this.max} Bildern
|
|
355
|
+
</span>
|
|
356
|
+
` : nothing}
|
|
357
|
+
${isFull && maxIsFinite ? html`
|
|
358
|
+
<span class="max-hint">
|
|
359
|
+
Entferne ein Bild um weitere hinzuzufügen.
|
|
360
|
+
</span>
|
|
361
|
+
` : nothing}
|
|
362
|
+
</div>
|
|
363
|
+
${this._showUrlInput ? html`
|
|
364
|
+
<div class="url-input-row">
|
|
365
|
+
<pd-input
|
|
366
|
+
placeholder="https://..."
|
|
367
|
+
.value="${this._urlInputValue}"
|
|
368
|
+
@pd-form-element-change="${(e) => {
|
|
369
|
+
this._urlInputValue = e.detail.value;
|
|
370
|
+
}}"
|
|
371
|
+
@enter-pressed="${this._onUrlConfirm}"
|
|
372
|
+
></pd-input>
|
|
373
|
+
<pd-button
|
|
374
|
+
text="OK"
|
|
375
|
+
size="sm"
|
|
376
|
+
primary
|
|
377
|
+
@button-clicked="${this._onUrlConfirm}"
|
|
378
|
+
></pd-button>
|
|
379
|
+
<pd-button
|
|
380
|
+
text="X"
|
|
381
|
+
size="sm"
|
|
382
|
+
@button-clicked="${() => {
|
|
383
|
+
this._showUrlInput = false;
|
|
384
|
+
this._urlInputValue = "";
|
|
385
|
+
}}"
|
|
386
|
+
></pd-button>
|
|
387
|
+
</div>
|
|
388
|
+
` : nothing}
|
|
389
|
+
<input
|
|
390
|
+
id="${inputId}"
|
|
391
|
+
type="file"
|
|
392
|
+
multiple
|
|
393
|
+
accept="${this.accept}"
|
|
394
|
+
class="file-input-hidden"
|
|
395
|
+
@change="${this._onFileSelect}"
|
|
396
|
+
/>
|
|
397
|
+
${this._renderErrorMsg(errorId)}
|
|
398
|
+
${this._previewIndex !== null && this._images[this._previewIndex] ? html`
|
|
399
|
+
<div
|
|
400
|
+
class="image-preview-backdrop"
|
|
401
|
+
role="dialog"
|
|
402
|
+
aria-modal="true"
|
|
403
|
+
aria-label="Bildvorschau"
|
|
404
|
+
@click="${this._onBackdropClick}"
|
|
405
|
+
>
|
|
406
|
+
<div class="image-preview-container">
|
|
407
|
+
<div class="image-preview-main">
|
|
408
|
+
<img
|
|
409
|
+
src="${this._images[this._previewIndex].src}"
|
|
410
|
+
alt="Bild ${this._previewIndex + 1}"
|
|
411
|
+
/>
|
|
412
|
+
</div>
|
|
413
|
+
<pd-icon
|
|
414
|
+
class="image-preview-close round"
|
|
415
|
+
icon="${pdIcons.ICON_XCLOSE}"
|
|
416
|
+
activeIcon
|
|
417
|
+
role="button"
|
|
418
|
+
tabindex="0"
|
|
419
|
+
aria-label="Vorschau schließen"
|
|
420
|
+
@click="${(e) => {
|
|
421
|
+
e.stopPropagation();
|
|
422
|
+
this._closePreview();
|
|
423
|
+
}}"
|
|
424
|
+
@keydown="${(e) => {
|
|
425
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
426
|
+
e.preventDefault();
|
|
427
|
+
e.stopPropagation();
|
|
428
|
+
this._closePreview();
|
|
429
|
+
}
|
|
430
|
+
}}"
|
|
431
|
+
></pd-icon>
|
|
432
|
+
</div>
|
|
433
|
+
</div>
|
|
434
|
+
` : nothing}
|
|
435
|
+
`;
|
|
436
|
+
}
|
|
437
|
+
// -- Event handlers --
|
|
438
|
+
_onPlaceholderClick() {
|
|
439
|
+
if (this.disabled || this.readonly) return;
|
|
440
|
+
this._openFilePicker();
|
|
441
|
+
}
|
|
442
|
+
_onSelectClick(e) {
|
|
443
|
+
e.stopPropagation();
|
|
444
|
+
if (this.disabled || this.readonly) return;
|
|
445
|
+
this._openFilePicker();
|
|
446
|
+
}
|
|
447
|
+
_openFilePicker() {
|
|
448
|
+
const fileInput = this.shadowRoot?.querySelector(".file-input-hidden");
|
|
449
|
+
fileInput?.click();
|
|
450
|
+
}
|
|
451
|
+
_onContainerKeyDown(e) {
|
|
452
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
453
|
+
e.preventDefault();
|
|
454
|
+
this._onPlaceholderClick();
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
_onFileSelect(e) {
|
|
458
|
+
const input = e.target;
|
|
459
|
+
const files = Array.from(input.files || []);
|
|
460
|
+
if (files.length === 0) return;
|
|
461
|
+
const slotsAvailable = this.max - this._images.length;
|
|
462
|
+
const filesToProcess = files.slice(0, Math.max(0, slotsAvailable));
|
|
463
|
+
if (filesToProcess.length === 0) return;
|
|
464
|
+
const readPromises = filesToProcess.map(
|
|
465
|
+
(file) => new Promise((resolve) => {
|
|
466
|
+
const reader = new FileReader();
|
|
467
|
+
reader.onload = () => resolve(reader.result);
|
|
468
|
+
reader.readAsDataURL(file);
|
|
469
|
+
})
|
|
470
|
+
);
|
|
471
|
+
Promise.all(readPromises).then((dataUrls) => {
|
|
472
|
+
const newSelections = dataUrls.map((url) => ({ src: url }));
|
|
473
|
+
this._updateImages([...this._images, ...newSelections]);
|
|
474
|
+
});
|
|
475
|
+
input.value = "";
|
|
476
|
+
}
|
|
477
|
+
_openPreview(index) {
|
|
478
|
+
this._previewIndex = index;
|
|
479
|
+
}
|
|
480
|
+
_closePreview() {
|
|
481
|
+
this._previewIndex = null;
|
|
482
|
+
}
|
|
483
|
+
_onBackdropClick(e) {
|
|
484
|
+
if (e.target === e.currentTarget) {
|
|
485
|
+
this._closePreview();
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
_handleGlobalKeyDown(e) {
|
|
489
|
+
if (e.key === "Escape" && this._previewIndex !== null) {
|
|
490
|
+
this._closePreview();
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
_removeImage(index) {
|
|
494
|
+
const newImages = this._images.filter((_, i) => i !== index);
|
|
495
|
+
this._updateImages(newImages);
|
|
496
|
+
}
|
|
497
|
+
_selectImage(index) {
|
|
498
|
+
if (!this.selectable) return;
|
|
499
|
+
const newImages = this._images.map((img, i) => ({
|
|
500
|
+
...img,
|
|
501
|
+
selected: i === index
|
|
502
|
+
}));
|
|
503
|
+
this._updateImages(newImages);
|
|
504
|
+
}
|
|
505
|
+
_getSelectedImage() {
|
|
506
|
+
return this._images.find((img) => img.selected) ?? null;
|
|
507
|
+
}
|
|
508
|
+
_onUrlAddClick() {
|
|
509
|
+
if (this.disabled || this.readonly) return;
|
|
510
|
+
this._showUrlInput = true;
|
|
511
|
+
this._urlInputValue = "";
|
|
512
|
+
}
|
|
513
|
+
_onUrlConfirm() {
|
|
514
|
+
const url = this._urlInputValue.trim();
|
|
515
|
+
if (!url) return;
|
|
516
|
+
try {
|
|
517
|
+
new URL(url);
|
|
518
|
+
} catch {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
if (this._images.length < this.max) {
|
|
522
|
+
this._updateImages([...this._images, { src: url }]);
|
|
523
|
+
}
|
|
524
|
+
this._showUrlInput = false;
|
|
525
|
+
this._urlInputValue = "";
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
__decorateClass([
|
|
529
|
+
property({ type: String })
|
|
530
|
+
], PdInputImage.prototype, "accept");
|
|
531
|
+
__decorateClass([
|
|
532
|
+
property({ type: String })
|
|
533
|
+
], PdInputImage.prototype, "placeholder");
|
|
534
|
+
__decorateClass([
|
|
535
|
+
property({ type: Number })
|
|
536
|
+
], PdInputImage.prototype, "max");
|
|
537
|
+
__decorateClass([
|
|
538
|
+
property({ type: Boolean })
|
|
539
|
+
], PdInputImage.prototype, "selectable");
|
|
540
|
+
__decorateClass([
|
|
541
|
+
state()
|
|
542
|
+
], PdInputImage.prototype, "_images");
|
|
543
|
+
__decorateClass([
|
|
544
|
+
state()
|
|
545
|
+
], PdInputImage.prototype, "_previewIndex");
|
|
546
|
+
__decorateClass([
|
|
547
|
+
state()
|
|
548
|
+
], PdInputImage.prototype, "_showUrlInput");
|
|
549
|
+
__decorateClass([
|
|
550
|
+
state()
|
|
551
|
+
], PdInputImage.prototype, "_urlInputValue");
|
|
552
|
+
|
|
553
|
+
export { PdInputImage };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pd-input-image.d.ts","sourceRoot":"","sources":["../../src/pd-input-image/pd-input-image.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAOjD,OAAO,EAAE,YAAY,EAAE,CAAC;AACxB,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC"}
|