@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.
Files changed (75) hide show
  1. package/dist/base/pd-base-ui-input.d.ts +6 -0
  2. package/dist/base/pd-base-ui-input.d.ts.map +1 -1
  3. package/dist/base/pd-base-ui-input.js +46 -1
  4. package/dist/index.d.ts +4 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +9 -4
  7. package/dist/pd-button/PdButton.d.ts +8 -0
  8. package/dist/pd-button/PdButton.d.ts.map +1 -1
  9. package/dist/pd-button/PdButton.js +23 -0
  10. package/dist/pd-color-picker/PdColorPicker.d.ts +25 -0
  11. package/dist/pd-color-picker/PdColorPicker.d.ts.map +1 -0
  12. package/dist/pd-color-picker/PdColorPicker.js +238 -0
  13. package/dist/pd-color-picker/pd-color-picker.d.ts +3 -0
  14. package/dist/pd-color-picker/pd-color-picker.d.ts.map +1 -0
  15. package/dist/pd-color-picker.d.ts +2 -0
  16. package/dist/pd-color-picker.js +8 -0
  17. package/dist/pd-generic-form/PdGenericForm.d.ts +1 -0
  18. package/dist/pd-generic-form/PdGenericForm.d.ts.map +1 -1
  19. package/dist/pd-generic-form/PdGenericForm.js +12 -2
  20. package/dist/pd-generic-form/pd-generic-form.styles.d.ts.map +1 -1
  21. package/dist/pd-generic-form/pd-generic-form.styles.js +12 -5
  22. package/dist/pd-input-image/PdInputImage.d.ts +79 -0
  23. package/dist/pd-input-image/PdInputImage.d.ts.map +1 -0
  24. package/dist/pd-input-image/PdInputImage.js +553 -0
  25. package/dist/pd-input-image/pd-input-image.d.ts +4 -0
  26. package/dist/pd-input-image/pd-input-image.d.ts.map +1 -0
  27. package/dist/pd-input-image/pd-input-image.styles.d.ts +6 -0
  28. package/dist/pd-input-image/pd-input-image.styles.d.ts.map +1 -0
  29. package/dist/pd-input-image/pd-input-image.styles.js +300 -0
  30. package/dist/pd-input-image.d.ts +2 -0
  31. package/dist/pd-input-image.js +8 -0
  32. package/dist/pd-range/PdRange.d.ts +2 -0
  33. package/dist/pd-range/PdRange.d.ts.map +1 -1
  34. package/dist/pd-range/PdRange.js +11 -7
  35. package/package.json +6 -4
  36. package/dist/pd-button/pd-button.stories.d.ts +0 -84
  37. package/dist/pd-button/pd-button.stories.d.ts.map +0 -1
  38. package/dist/pd-button-group/pd-button-group.stories.d.ts +0 -45
  39. package/dist/pd-button-group/pd-button-group.stories.d.ts.map +0 -1
  40. package/dist/pd-button-select-group/pd-button-select-group.stories.d.ts +0 -46
  41. package/dist/pd-button-select-group/pd-button-select-group.stories.d.ts.map +0 -1
  42. package/dist/pd-checkbox/pd-checkbox.stories.d.ts +0 -45
  43. package/dist/pd-checkbox/pd-checkbox.stories.d.ts.map +0 -1
  44. package/dist/pd-form-container/pd-form-container.stories.d.ts +0 -49
  45. package/dist/pd-form-container/pd-form-container.stories.d.ts.map +0 -1
  46. package/dist/pd-form-field/pd-form-field.stories.d.ts +0 -40
  47. package/dist/pd-form-field/pd-form-field.stories.d.ts.map +0 -1
  48. package/dist/pd-form-fieldset/pd-form-fieldset.stories.d.ts +0 -38
  49. package/dist/pd-form-fieldset/pd-form-fieldset.stories.d.ts.map +0 -1
  50. package/dist/pd-form-row/pd-form-row.stories.d.ts +0 -43
  51. package/dist/pd-form-row/pd-form-row.stories.d.ts.map +0 -1
  52. package/dist/pd-generic-form/pd-generic-form.stories.d.ts +0 -35
  53. package/dist/pd-generic-form/pd-generic-form.stories.d.ts.map +0 -1
  54. package/dist/pd-hover-box/pd-hover-box.stories.d.ts +0 -31
  55. package/dist/pd-hover-box/pd-hover-box.stories.d.ts.map +0 -1
  56. package/dist/pd-input/pd-input.stories.d.ts +0 -73
  57. package/dist/pd-input/pd-input.stories.d.ts.map +0 -1
  58. package/dist/pd-input-area/pd-input-area.stories.d.ts +0 -67
  59. package/dist/pd-input-area/pd-input-area.stories.d.ts.map +0 -1
  60. package/dist/pd-input-file/pd-input-file.stories.d.ts +0 -53
  61. package/dist/pd-input-file/pd-input-file.stories.d.ts.map +0 -1
  62. package/dist/pd-input-time/pd-input-time.stories.d.ts +0 -94
  63. package/dist/pd-input-time/pd-input-time.stories.d.ts.map +0 -1
  64. package/dist/pd-panel-button/pd-panel-button.stories.d.ts +0 -57
  65. package/dist/pd-panel-button/pd-panel-button.stories.d.ts.map +0 -1
  66. package/dist/pd-radio-group/pd-radio-group.stories.d.ts +0 -39
  67. package/dist/pd-radio-group/pd-radio-group.stories.d.ts.map +0 -1
  68. package/dist/pd-range/pd-range.stories.d.ts +0 -51
  69. package/dist/pd-range/pd-range.stories.d.ts.map +0 -1
  70. package/dist/pd-select/pd-select.stories.d.ts +0 -58
  71. package/dist/pd-select/pd-select.stories.d.ts.map +0 -1
  72. package/dist/pd-suggestion-box/pd-suggestion-box.stories.d.ts +0 -79
  73. package/dist/pd-suggestion-box/pd-suggestion-box.stories.d.ts.map +0 -1
  74. package/dist/stories/pd-forms-overview.stories.d.ts +0 -48
  75. 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: center;
24
- justify-content: space-between;
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
- white-space: normal;
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,4 @@
1
+ import { PdInputImage } from './PdInputImage.js';
2
+ export { PdInputImage };
3
+ export type { ImageSelection } from './PdInputImage.js';
4
+ //# sourceMappingURL=pd-input-image.d.ts.map
@@ -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"}