@progressive-development/pd-content 1.0.2 → 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 (85) hide show
  1. package/dist/index.d.ts +5 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +10 -1
  4. package/dist/pd-badge-order/DragController.d.ts +41 -0
  5. package/dist/pd-badge-order/DragController.d.ts.map +1 -0
  6. package/dist/pd-badge-order/DragController.js +239 -0
  7. package/dist/pd-badge-order/PdBadgeItem.d.ts +31 -0
  8. package/dist/pd-badge-order/PdBadgeItem.d.ts.map +1 -0
  9. package/dist/pd-badge-order/PdBadgeItem.js +320 -0
  10. package/dist/pd-badge-order/PdBadgeOrder.d.ts +68 -0
  11. package/dist/pd-badge-order/PdBadgeOrder.d.ts.map +1 -0
  12. package/dist/pd-badge-order/PdBadgeOrder.js +550 -0
  13. package/dist/pd-badge-order/flip-animator.d.ts +30 -0
  14. package/dist/pd-badge-order/flip-animator.d.ts.map +1 -0
  15. package/dist/pd-badge-order/flip-animator.js +39 -0
  16. package/dist/pd-badge-order/pd-badge-item.d.ts +3 -0
  17. package/dist/pd-badge-order/pd-badge-item.d.ts.map +1 -0
  18. package/dist/pd-badge-order/pd-badge-item.js +8 -0
  19. package/dist/pd-badge-order/pd-badge-order.d.ts +3 -0
  20. package/dist/pd-badge-order/pd-badge-order.d.ts.map +1 -0
  21. package/dist/pd-badge-order/types.d.ts +25 -0
  22. package/dist/pd-badge-order/types.d.ts.map +1 -0
  23. package/dist/pd-badge-order/types.js +3 -0
  24. package/dist/pd-badge-order.d.ts +2 -0
  25. package/dist/pd-badge-order.js +8 -0
  26. package/dist/pd-gallery/PdGallery.d.ts +72 -0
  27. package/dist/pd-gallery/PdGallery.d.ts.map +1 -0
  28. package/dist/pd-gallery/PdGallery.js +660 -0
  29. package/dist/pd-gallery/PdGalleryLightbox.d.ts +53 -0
  30. package/dist/pd-gallery/PdGalleryLightbox.d.ts.map +1 -0
  31. package/dist/pd-gallery/PdGalleryLightbox.js +530 -0
  32. package/dist/pd-gallery/index.d.ts +4 -0
  33. package/dist/pd-gallery/index.d.ts.map +1 -0
  34. package/dist/pd-gallery/pd-gallery-lightbox.d.ts +3 -0
  35. package/dist/pd-gallery/pd-gallery-lightbox.d.ts.map +1 -0
  36. package/dist/pd-gallery/pd-gallery.d.ts +3 -0
  37. package/dist/pd-gallery/pd-gallery.d.ts.map +1 -0
  38. package/dist/pd-gallery/types.d.ts +23 -0
  39. package/dist/pd-gallery/types.d.ts.map +1 -0
  40. package/dist/pd-gallery-lightbox.d.ts +2 -0
  41. package/dist/pd-gallery-lightbox.js +8 -0
  42. package/dist/pd-gallery.d.ts +2 -0
  43. package/dist/pd-gallery.js +8 -0
  44. package/dist/pd-loading-state/PdLoadingState.d.ts +25 -9
  45. package/dist/pd-loading-state/PdLoadingState.d.ts.map +1 -1
  46. package/dist/pd-loading-state/PdLoadingState.js +228 -83
  47. package/dist/pd-loading-state/pd-loading-state.d.ts.map +1 -1
  48. package/dist/pd-loading-state/pd-logo-loader.d.ts +25 -0
  49. package/dist/pd-loading-state/pd-logo-loader.d.ts.map +1 -0
  50. package/dist/pd-loading-state/pd-logo-loader.js +63 -0
  51. package/dist/pd-loading-state/register-pd-logo-loader.d.ts +6 -0
  52. package/dist/pd-loading-state/register-pd-logo-loader.d.ts.map +1 -0
  53. package/dist/pd-loading-state/register-pd-logo-loader.js +6 -0
  54. package/dist/pd-loading-state.js +8 -1
  55. package/dist/pd-panel-viewer/PdPanelViewer.d.ts.map +1 -1
  56. package/dist/pd-panel-viewer/PdPanelViewer.js +1 -1
  57. package/dist/types.d.ts +3 -0
  58. package/dist/types.d.ts.map +1 -1
  59. package/package.json +8 -3
  60. package/dist/pd-box-view/pd-box-view.stories.d.ts +0 -43
  61. package/dist/pd-box-view/pd-box-view.stories.d.ts.map +0 -1
  62. package/dist/pd-code-snippet/pd-code-snippet.stories.d.ts +0 -55
  63. package/dist/pd-code-snippet/pd-code-snippet.stories.d.ts.map +0 -1
  64. package/dist/pd-collapse/pd-collapse.stories.d.ts +0 -51
  65. package/dist/pd-collapse/pd-collapse.stories.d.ts.map +0 -1
  66. package/dist/pd-collapse-group/pd-collapse-group.stories.d.ts +0 -40
  67. package/dist/pd-collapse-group/pd-collapse-group.stories.d.ts.map +0 -1
  68. package/dist/pd-edit-content/pd-edit-content.stories.d.ts +0 -55
  69. package/dist/pd-edit-content/pd-edit-content.stories.d.ts.map +0 -1
  70. package/dist/pd-loading-state/pd-loading-state.stories.d.ts +0 -48
  71. package/dist/pd-loading-state/pd-loading-state.stories.d.ts.map +0 -1
  72. package/dist/pd-more-info/pd-more-info.stories.d.ts +0 -42
  73. package/dist/pd-more-info/pd-more-info.stories.d.ts.map +0 -1
  74. package/dist/pd-notice-box/pd-notice-box.stories.d.ts +0 -58
  75. package/dist/pd-notice-box/pd-notice-box.stories.d.ts.map +0 -1
  76. package/dist/pd-panel-viewer/pd-panel-viewer.stories.d.ts +0 -46
  77. package/dist/pd-panel-viewer/pd-panel-viewer.stories.d.ts.map +0 -1
  78. package/dist/pd-resize-content/pd-resize-content.stories.d.ts +0 -37
  79. package/dist/pd-resize-content/pd-resize-content.stories.d.ts.map +0 -1
  80. package/dist/pd-tab/pd-tab.stories.d.ts +0 -53
  81. package/dist/pd-tab/pd-tab.stories.d.ts.map +0 -1
  82. package/dist/pd-timeline/pd-timeline.stories.d.ts +0 -56
  83. package/dist/pd-timeline/pd-timeline.stories.d.ts.map +0 -1
  84. package/dist/pd-timeline-wizard/pd-timeline-wizard.stories.d.ts +0 -54
  85. package/dist/pd-timeline-wizard/pd-timeline-wizard.stories.d.ts.map +0 -1
@@ -0,0 +1,68 @@
1
+ import { CSSResultGroup, PropertyValues } from 'lit';
2
+ import { PdBaseUIInput } from '@progressive-development/pd-forms';
3
+ import { PdBadge } from './types.js';
4
+ /**
5
+ * Badge order component for selecting and sorting badges.
6
+ * Two areas: active (sorted, left/bottom) and available (pool, right/top).
7
+ *
8
+ * @tagname pd-badge-order
9
+ *
10
+ * @event pd-change - Fired when order or selection changes. Detail: { value: string[], badges: PdBadge[] }
11
+ * @event pd-reorder - Fired when only the order changes. Detail: { value: string[] }
12
+ * @event pd-form-element-change - Standard PD form event.
13
+ *
14
+ * @cssprop --pd-badge-order-gap - Gap between the two areas. Default: 1rem.
15
+ * @cssprop --pd-badge-order-min-height - Minimum height of badge lists. Default: 200px.
16
+ * @cssprop --pd-badge-transition-duration - FLIP animation duration. Default: 200ms.
17
+ */
18
+ export declare class PdBadgeOrder extends PdBaseUIInput {
19
+ /** All available badges (the full pool). Set from outside. */
20
+ badges: PdBadge[];
21
+ /** Minimum number of active badges */
22
+ min?: number;
23
+ /** Maximum number of active badges */
24
+ max?: number;
25
+ /** Show position numbers in active list */
26
+ numbered: boolean;
27
+ /** Label for the active area */
28
+ activeLabel: string;
29
+ /** Label for the available area */
30
+ availableLabel: string;
31
+ /** Placeholder text when active list is empty */
32
+ emptyActiveText: string;
33
+ /** Placeholder text when available list is empty */
34
+ emptyAvailableText: string;
35
+ private _dragController;
36
+ private _activeListEl;
37
+ private _availableListEl;
38
+ /** Positions captured before DOM change for FLIP animation */
39
+ private _oldPositions;
40
+ static styles: CSSResultGroup;
41
+ aiAdopt(value: string): void;
42
+ /** @ts-expect-error Widening return type from string to string[] */
43
+ get value(): string[];
44
+ /** @ts-expect-error Widening param type from string to string[] | string */
45
+ set value(val: string[] | string);
46
+ _getParsedValue(): string[];
47
+ protected _getInitialValue(reset?: boolean): string;
48
+ connectedCallback(): void;
49
+ private _setupValidators;
50
+ private get _activeBadges();
51
+ private get _availableBadges();
52
+ private _captureBeforeChange;
53
+ private _addBadge;
54
+ private _removeBadge;
55
+ private _deleteBadge;
56
+ /** Reorder: move badge from oldIndex to newIndex within active list */
57
+ reorderBadge(oldIndex: number, newIndex: number): void;
58
+ /** Insert a badge from available into active list at a specific index */
59
+ insertBadgeAt(badgeId: string, index: number): void;
60
+ /** Move a badge from active to available (at drop) */
61
+ deactivateBadge(badgeId: string): void;
62
+ private _fireChangeEvents;
63
+ private _onBadgeAction;
64
+ updated(changedProps: PropertyValues): void;
65
+ render(): import('lit').TemplateResult<1>;
66
+ private _renderGhost;
67
+ }
68
+ //# sourceMappingURL=PdBadgeOrder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PdBadgeOrder.d.ts","sourceRoot":"","sources":["../../src/pd-badge-order/PdBadgeOrder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,cAAc,EAAW,cAAc,EAAE,MAAM,KAAK,CAAC;AAIzE,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAGlE,OAAO,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,OAAO,EAAuB,MAAM,YAAY,CAAC;AAM/D;;;;;;;;;;;;;GAaG;AACH,qBAAa,YAAa,SAAQ,aAAa;IAC7C,8DAA8D;IAE9D,MAAM,EAAE,OAAO,EAAE,CAAM;IAEvB,sCAAsC;IAEtC,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,sCAAsC;IAEtC,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,2CAA2C;IAE3C,QAAQ,UAAQ;IAEhB,gCAAgC;IAEhC,WAAW,SAAgB;IAE3B,mCAAmC;IAEnC,cAAc,SAAe;IAE7B,iDAAiD;IAEjD,eAAe,SAA2B;IAE1C,oDAAoD;IAEpD,kBAAkB,SAA2B;IAG7C,OAAO,CAAC,eAAe,CAA4B;IAGnD,OAAO,CAAC,aAAa,CAAe;IAGpC,OAAO,CAAC,gBAAgB,CAAe;IAEvC,8DAA8D;IAC9D,OAAO,CAAC,aAAa,CAAqC;IAE1D,OAAgB,MAAM,EAAE,cAAc,CAsHpC;IAMc,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAgC5C,oEAAoE;IACpE,IAAa,KAAK,IAAI,MAAM,EAAE,CAO7B;IAED,4EAA4E;IAC5E,IAAa,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAUxC;IAEQ,eAAe,IAAI,MAAM,EAAE;cAIjB,gBAAgB,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM;IAUnD,iBAAiB,IAAI,IAAI;IAKlC,OAAO,CAAC,gBAAgB;IA8CxB,OAAO,KAAK,aAAa,GAIxB;IAED,OAAO,KAAK,gBAAgB,GAG3B;IAMD,OAAO,CAAC,oBAAoB;IAmB5B,OAAO,CAAC,SAAS;IAWjB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,YAAY;IA0BpB,uEAAuE;IACvE,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IA0BtD,yEAAyE;IACzE,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAenD,sDAAsD;IACtD,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAItC,OAAO,CAAC,iBAAiB;IAkBzB,OAAO,CAAC,cAAc,CAepB;IAMO,OAAO,CAAC,YAAY,EAAE,cAAc,GAAG,IAAI;IAuB3C,MAAM;IAmHf,OAAO,CAAC,YAAY;CAwBrB"}
@@ -0,0 +1,550 @@
1
+ import { css, nothing, html } from 'lit';
2
+ import { property, state, query } from 'lit/decorators.js';
3
+ import { classMap } from 'lit/directives/class-map.js';
4
+ import { PdBaseUIInput } from '@progressive-development/pd-forms';
5
+ import './pd-badge-item.js';
6
+ import { capturePositions, animateFlip } from './flip-animator.js';
7
+ import { DragController } from './DragController.js';
8
+
9
+ var __defProp = Object.defineProperty;
10
+ var __decorateClass = (decorators, target, key, kind) => {
11
+ var result = void 0 ;
12
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
13
+ if (decorator = decorators[i])
14
+ result = (decorator(target, key, result) ) || result;
15
+ if (result) __defProp(target, key, result);
16
+ return result;
17
+ };
18
+ const BADGE_SELECTOR = "pd-badge-item";
19
+ class PdBadgeOrder extends PdBaseUIInput {
20
+ constructor() {
21
+ super(...arguments);
22
+ this.badges = [];
23
+ this.numbered = true;
24
+ this.activeLabel = "Ausgewählt";
25
+ this.availableLabel = "Verfügbar";
26
+ this.emptyActiveText = "Badges hierher ziehen";
27
+ this.emptyAvailableText = "Keine weiteren Badges";
28
+ this._dragController = new DragController(this);
29
+ /** Positions captured before DOM change for FLIP animation */
30
+ this._oldPositions = null;
31
+ // ---------------------------------------------------------------------------
32
+ // Event handlers
33
+ // ---------------------------------------------------------------------------
34
+ this._onBadgeAction = (e) => {
35
+ const { action, badgeId } = e.detail;
36
+ switch (action) {
37
+ case "add":
38
+ this._addBadge(badgeId);
39
+ break;
40
+ case "remove":
41
+ this._removeBadge(badgeId);
42
+ break;
43
+ case "delete":
44
+ this._deleteBadge(badgeId);
45
+ break;
46
+ }
47
+ };
48
+ }
49
+ static {
50
+ this.styles = [
51
+ PdBaseUIInput.styles,
52
+ css`
53
+ :host {
54
+ display: block;
55
+ --pd-input-field-width: 100%;
56
+ }
57
+
58
+ .badge-order-container {
59
+ display: grid;
60
+ grid-template-columns: 1fr 1fr;
61
+ gap: var(--pd-badge-order-gap, 1rem);
62
+ width: 100%;
63
+ box-sizing: border-box;
64
+ }
65
+
66
+ .list-section {
67
+ display: flex;
68
+ flex-direction: column;
69
+ min-height: var(--pd-badge-order-min-height, 200px);
70
+ }
71
+
72
+ .section-header {
73
+ display: flex;
74
+ align-items: center;
75
+ justify-content: space-between;
76
+ padding: 0.5rem 0;
77
+ font-family: var(--pd-default-font-title-family, sans-serif);
78
+ font-size: 0.8rem;
79
+ font-weight: 600;
80
+ color: var(--pd-default-font-title-col, #374151);
81
+ }
82
+
83
+ .section-count {
84
+ font-weight: 400;
85
+ color: var(--pd-default-disabled-col, #9ca3af);
86
+ font-size: 0.75rem;
87
+ }
88
+
89
+ .badge-list {
90
+ display: flex;
91
+ flex-direction: column;
92
+ gap: 0.4rem;
93
+ flex: 1;
94
+ padding: 0.5rem;
95
+ border: 1px solid var(--pd-default-disabled-light-col, #e5e7eb);
96
+ border-radius: var(--pd-radius-md, 6px);
97
+ background: var(--pd-default-lightest-col, #fafafa);
98
+ overflow-y: auto;
99
+ position: relative;
100
+ transition: background-color 0.2s;
101
+ }
102
+
103
+ .badge-list.drag-over {
104
+ background: color-mix(
105
+ in srgb,
106
+ var(--pd-badge-drop-highlight, var(--pd-default-col, #3b82f6)) 8%,
107
+ var(--pd-default-lightest-col, #fafafa)
108
+ );
109
+ }
110
+
111
+ .empty-placeholder {
112
+ display: flex;
113
+ align-items: center;
114
+ justify-content: center;
115
+ flex: 1;
116
+ min-height: 80px;
117
+ color: var(--pd-default-disabled-col, #9ca3af);
118
+ font-size: 0.8rem;
119
+ font-style: italic;
120
+ text-align: center;
121
+ padding: 1rem;
122
+ }
123
+
124
+ .drop-indicator {
125
+ height: 2px;
126
+ background: var(
127
+ --pd-badge-drop-highlight,
128
+ var(--pd-default-col, #3b82f6)
129
+ );
130
+ border-radius: 1px;
131
+ transition: opacity 0.15s;
132
+ flex-shrink: 0;
133
+ }
134
+
135
+ /* Ghost element for drag */
136
+ .drag-ghost {
137
+ position: fixed;
138
+ pointer-events: none;
139
+ z-index: 9999;
140
+ opacity: 0.9;
141
+ transform: scale(var(--pd-badge-drag-scale, 1.03));
142
+ box-shadow: var(--pd-badge-drag-shadow, 0 8px 24px rgba(0, 0, 0, 0.15));
143
+ will-change: transform;
144
+ width: var(--_ghost-width, 300px);
145
+ }
146
+
147
+ /* Disabled state */
148
+ :host([disabled]) .badge-order-container {
149
+ opacity: 0.5;
150
+ pointer-events: none;
151
+ }
152
+
153
+ :host([readonly]) .badge-order-container {
154
+ pointer-events: none;
155
+ }
156
+
157
+ /* Mobile: stacked layout, available on top */
158
+ @media (max-width: 767px) {
159
+ .badge-order-container {
160
+ grid-template-columns: 1fr;
161
+ }
162
+
163
+ .list-section.available-section {
164
+ order: -1;
165
+ }
166
+ }
167
+ `
168
+ ];
169
+ }
170
+ // ---------------------------------------------------------------------------
171
+ // AI adoption: supports enriched format { value: string[], badges: PdBadge[] }
172
+ // ---------------------------------------------------------------------------
173
+ aiAdopt(value) {
174
+ try {
175
+ const parsed = JSON.parse(value);
176
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && Array.isArray(parsed.value)) {
177
+ if (Array.isArray(parsed.badges) && parsed.badges.length > 0) {
178
+ const existingMap = new Map(this.badges.map((b) => [b.id, b]));
179
+ for (const badge of parsed.badges) {
180
+ existingMap.set(badge.id, badge);
181
+ }
182
+ this.badges = [...existingMap.values()];
183
+ }
184
+ this.value = parsed.value;
185
+ this._fireChangeEvents();
186
+ return;
187
+ }
188
+ } catch {
189
+ }
190
+ super.aiAdopt(value);
191
+ }
192
+ // ---------------------------------------------------------------------------
193
+ // Value handling: string[] serialized as JSON string
194
+ // ---------------------------------------------------------------------------
195
+ /** @ts-expect-error Widening return type from string to string[] */
196
+ get value() {
197
+ try {
198
+ const parsed = JSON.parse(this._value);
199
+ return Array.isArray(parsed) ? parsed : [];
200
+ } catch {
201
+ return [];
202
+ }
203
+ }
204
+ /** @ts-expect-error Widening param type from string to string[] | string */
205
+ set value(val) {
206
+ const ids = Array.isArray(val) ? val : typeof val === "string" && val.startsWith("[") ? JSON.parse(val) : [];
207
+ const jsonVal = JSON.stringify(ids);
208
+ if (this._value !== jsonVal) {
209
+ this._handleChangedValue(jsonVal, void 0, true);
210
+ }
211
+ }
212
+ _getParsedValue() {
213
+ return this.value;
214
+ }
215
+ _getInitialValue(reset) {
216
+ return reset ? this.initValue || "[]" : this.initValue || this._value || "[]";
217
+ }
218
+ // ---------------------------------------------------------------------------
219
+ // Validators
220
+ // ---------------------------------------------------------------------------
221
+ connectedCallback() {
222
+ super.connectedCallback();
223
+ this._setupValidators();
224
+ }
225
+ _setupValidators() {
226
+ this._validators = [];
227
+ const requiredBadgeValidator = (val) => {
228
+ if (!this.required) return null;
229
+ try {
230
+ const ids = JSON.parse(val);
231
+ return Array.isArray(ids) && ids.length === 0 ? "Mindestens eine Badge auswählen" : null;
232
+ } catch {
233
+ return "Mindestens eine Badge auswählen";
234
+ }
235
+ };
236
+ const minValidator = (val) => {
237
+ if (this.min === void 0) return null;
238
+ try {
239
+ const ids = JSON.parse(val);
240
+ return Array.isArray(ids) && ids.length < this.min ? `Mindestens ${this.min} Badges auswählen` : null;
241
+ } catch {
242
+ return null;
243
+ }
244
+ };
245
+ const maxValidator = (val) => {
246
+ if (this.max === void 0) return null;
247
+ try {
248
+ const ids = JSON.parse(val);
249
+ return Array.isArray(ids) && ids.length > this.max ? `Maximal ${this.max} Badges erlaubt` : null;
250
+ } catch {
251
+ return null;
252
+ }
253
+ };
254
+ this._validators.push(requiredBadgeValidator, minValidator, maxValidator);
255
+ }
256
+ // ---------------------------------------------------------------------------
257
+ // Computed properties
258
+ // ---------------------------------------------------------------------------
259
+ get _activeBadges() {
260
+ const ids = this.value;
261
+ const badgeMap = new Map(this.badges.map((b) => [b.id, b]));
262
+ return ids.map((id) => badgeMap.get(id)).filter((b) => !!b);
263
+ }
264
+ get _availableBadges() {
265
+ const activeIds = new Set(this.value);
266
+ return this.badges.filter((b) => !activeIds.has(b.id));
267
+ }
268
+ // ---------------------------------------------------------------------------
269
+ // Actions
270
+ // ---------------------------------------------------------------------------
271
+ _captureBeforeChange() {
272
+ if (this._activeListEl) {
273
+ this._oldPositions = capturePositions(this._activeListEl, BADGE_SELECTOR);
274
+ }
275
+ if (this._availableListEl) {
276
+ const availPositions = capturePositions(
277
+ this._availableListEl,
278
+ BADGE_SELECTOR
279
+ );
280
+ if (this._oldPositions) {
281
+ availPositions.forEach(
282
+ (rect, key) => this._oldPositions.set(key, rect)
283
+ );
284
+ } else {
285
+ this._oldPositions = availPositions;
286
+ }
287
+ }
288
+ }
289
+ _addBadge(badgeId) {
290
+ if (this.disabled || this.readonly) return;
291
+ const ids = this.value;
292
+ if (ids.includes(badgeId)) return;
293
+ if (this.max !== void 0 && ids.length >= this.max) return;
294
+ this._captureBeforeChange();
295
+ this.value = [...ids, badgeId];
296
+ this._fireChangeEvents();
297
+ }
298
+ _removeBadge(badgeId) {
299
+ if (this.disabled || this.readonly) return;
300
+ const ids = this.value;
301
+ this._captureBeforeChange();
302
+ this.value = ids.filter((id) => id !== badgeId);
303
+ this._fireChangeEvents();
304
+ }
305
+ _deleteBadge(badgeId) {
306
+ if (this.disabled || this.readonly) return;
307
+ const badge = this.badges.find((b) => b.id === badgeId);
308
+ if (!badge?.custom) return;
309
+ this._captureBeforeChange();
310
+ const ids = this.value;
311
+ if (ids.includes(badgeId)) {
312
+ this.value = ids.filter((id) => id !== badgeId);
313
+ }
314
+ this.badges = this.badges.filter((b) => b.id !== badgeId);
315
+ this.dispatchEvent(
316
+ new CustomEvent("pd-badge-deleted", {
317
+ detail: { badge },
318
+ bubbles: true,
319
+ composed: true
320
+ })
321
+ );
322
+ this._fireChangeEvents();
323
+ }
324
+ /** Reorder: move badge from oldIndex to newIndex within active list */
325
+ reorderBadge(oldIndex, newIndex) {
326
+ const ids = [...this.value];
327
+ if (oldIndex < 0 || oldIndex >= ids.length || newIndex < 0 || newIndex >= ids.length)
328
+ return;
329
+ this._captureBeforeChange();
330
+ const [moved] = ids.splice(oldIndex, 1);
331
+ ids.splice(newIndex, 0, moved);
332
+ this.value = ids;
333
+ this.dispatchEvent(
334
+ new CustomEvent("pd-reorder", {
335
+ detail: { value: ids },
336
+ bubbles: true,
337
+ composed: true
338
+ })
339
+ );
340
+ this._fireChangeEvents();
341
+ }
342
+ /** Insert a badge from available into active list at a specific index */
343
+ insertBadgeAt(badgeId, index) {
344
+ if (this.disabled || this.readonly) return;
345
+ const ids = this.value;
346
+ if (ids.includes(badgeId)) return;
347
+ if (this.max !== void 0 && ids.length >= this.max) return;
348
+ this._captureBeforeChange();
349
+ const newIds = [...ids];
350
+ const insertIdx = Math.max(0, Math.min(index, newIds.length));
351
+ newIds.splice(insertIdx, 0, badgeId);
352
+ this.value = newIds;
353
+ this._fireChangeEvents();
354
+ }
355
+ /** Move a badge from active to available (at drop) */
356
+ deactivateBadge(badgeId) {
357
+ this._removeBadge(badgeId);
358
+ }
359
+ _fireChangeEvents() {
360
+ const detail = {
361
+ value: this.value,
362
+ badges: this.badges
363
+ };
364
+ this.dispatchEvent(
365
+ new CustomEvent("pd-change", {
366
+ detail,
367
+ bubbles: true,
368
+ composed: true
369
+ })
370
+ );
371
+ }
372
+ // ---------------------------------------------------------------------------
373
+ // Lifecycle
374
+ // ---------------------------------------------------------------------------
375
+ updated(changedProps) {
376
+ super.updated(changedProps);
377
+ if (this._oldPositions) {
378
+ const oldPos = this._oldPositions;
379
+ this._oldPositions = null;
380
+ requestAnimationFrame(() => {
381
+ if (this._activeListEl) {
382
+ animateFlip(this._activeListEl, BADGE_SELECTOR, oldPos);
383
+ }
384
+ if (this._availableListEl) {
385
+ animateFlip(this._availableListEl, BADGE_SELECTOR, oldPos);
386
+ }
387
+ });
388
+ }
389
+ }
390
+ // ---------------------------------------------------------------------------
391
+ // Render
392
+ // ---------------------------------------------------------------------------
393
+ render() {
394
+ const activeBadges = this._activeBadges;
395
+ const availableBadges = this._availableBadges;
396
+ const drag = this._dragController;
397
+ return html`
398
+ ${this._renderLabel("badge-order")}
399
+
400
+ <div
401
+ class="badge-order-container"
402
+ part="container"
403
+ @badge-action=${this._onBadgeAction}
404
+ >
405
+ <!-- Active list -->
406
+ <div class="list-section active-section">
407
+ <div class="section-header">
408
+ <span>
409
+ <slot name="active-header">${this.activeLabel}</slot>
410
+ <span class="section-count">(${activeBadges.length})</span>
411
+ </span>
412
+ </div>
413
+ <div
414
+ id="active-list"
415
+ class=${classMap({
416
+ "badge-list": true,
417
+ "drag-over": drag.isDragging && drag.dropTarget === "active"
418
+ })}
419
+ part="active-list"
420
+ role="listbox"
421
+ aria-label="${this.activeLabel} (${activeBadges.length})"
422
+ data-area="active"
423
+ @pointerdown=${drag.onPointerDown}
424
+ >
425
+ ${activeBadges.length > 0 ? activeBadges.map(
426
+ (badge, i) => html`
427
+ ${drag.isDragging && drag.dropTarget === "active" && drag.insertIndex === i ? html`<div class="drop-indicator"></div>` : nothing}
428
+ <pd-badge-item
429
+ .badge=${badge}
430
+ .index=${i + 1}
431
+ ?numbered=${this.numbered}
432
+ ?active=${true}
433
+ ?disabled=${this.disabled}
434
+ ?readonly=${this.readonly}
435
+ ?ghost=${drag.isDragging && drag.draggedId === badge.id}
436
+ data-badge-id=${badge.id}
437
+ role="option"
438
+ aria-label="${badge.title}, Position ${i + 1}"
439
+ ></pd-badge-item>
440
+ `
441
+ ) : html`
442
+ <div class="empty-placeholder">
443
+ <slot name="empty-active">${this.emptyActiveText}</slot>
444
+ </div>
445
+ `}
446
+ ${drag.isDragging && drag.dropTarget === "active" && drag.insertIndex === activeBadges.length ? html`<div class="drop-indicator"></div>` : nothing}
447
+ </div>
448
+ </div>
449
+
450
+ <!-- Available list -->
451
+ <div class="list-section available-section">
452
+ <div class="section-header">
453
+ <span>
454
+ <slot name="available-header">${this.availableLabel}</slot>
455
+ <span class="section-count">(${availableBadges.length})</span>
456
+ </span>
457
+ </div>
458
+ <div
459
+ id="available-list"
460
+ class=${classMap({
461
+ "badge-list": true,
462
+ "drag-over": drag.isDragging && drag.dropTarget === "available"
463
+ })}
464
+ part="available-list"
465
+ role="listbox"
466
+ aria-label="${this.availableLabel} (${availableBadges.length})"
467
+ data-area="available"
468
+ >
469
+ ${availableBadges.length > 0 ? availableBadges.map(
470
+ (badge) => html`
471
+ <pd-badge-item
472
+ .badge=${badge}
473
+ ?disabled=${this.disabled}
474
+ ?readonly=${this.readonly}
475
+ data-badge-id=${badge.id}
476
+ role="option"
477
+ aria-label="${badge.title}"
478
+ ></pd-badge-item>
479
+ `
480
+ ) : html`
481
+ <div class="empty-placeholder">
482
+ <slot name="empty-available"
483
+ >${this.emptyAvailableText}</slot
484
+ >
485
+ </div>
486
+ `}
487
+ </div>
488
+ </div>
489
+ </div>
490
+
491
+ ${this._renderGhost()} ${this._renderErrorMsg()}
492
+ `;
493
+ }
494
+ _renderGhost() {
495
+ const drag = this._dragController;
496
+ if (!drag.isDragging || !drag.draggedId) return nothing;
497
+ const badge = this.badges.find((b) => b.id === drag.draggedId);
498
+ if (!badge) return nothing;
499
+ const idx = this.value.indexOf(drag.draggedId);
500
+ return html`
501
+ <div
502
+ class="drag-ghost"
503
+ style="left: ${drag.ghostX}px; top: ${drag.ghostY}px;"
504
+ >
505
+ <pd-badge-item
506
+ .badge=${badge}
507
+ .index=${idx >= 0 ? idx + 1 : 0}
508
+ ?numbered=${this.numbered && idx >= 0}
509
+ ?active=${idx >= 0}
510
+ ?dragging=${true}
511
+ ></pd-badge-item>
512
+ </div>
513
+ `;
514
+ }
515
+ }
516
+ __decorateClass([
517
+ property({ type: Array, attribute: false })
518
+ ], PdBadgeOrder.prototype, "badges");
519
+ __decorateClass([
520
+ property({ type: Number })
521
+ ], PdBadgeOrder.prototype, "min");
522
+ __decorateClass([
523
+ property({ type: Number })
524
+ ], PdBadgeOrder.prototype, "max");
525
+ __decorateClass([
526
+ property({ type: Boolean })
527
+ ], PdBadgeOrder.prototype, "numbered");
528
+ __decorateClass([
529
+ property({ type: String })
530
+ ], PdBadgeOrder.prototype, "activeLabel");
531
+ __decorateClass([
532
+ property({ type: String })
533
+ ], PdBadgeOrder.prototype, "availableLabel");
534
+ __decorateClass([
535
+ property({ type: String })
536
+ ], PdBadgeOrder.prototype, "emptyActiveText");
537
+ __decorateClass([
538
+ property({ type: String })
539
+ ], PdBadgeOrder.prototype, "emptyAvailableText");
540
+ __decorateClass([
541
+ state()
542
+ ], PdBadgeOrder.prototype, "_dragController");
543
+ __decorateClass([
544
+ query("#active-list")
545
+ ], PdBadgeOrder.prototype, "_activeListEl");
546
+ __decorateClass([
547
+ query("#available-list")
548
+ ], PdBadgeOrder.prototype, "_availableListEl");
549
+
550
+ export { PdBadgeOrder };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * FLIP animation utility for smooth badge reordering.
3
+ *
4
+ * FLIP = First, Last, Invert, Play
5
+ * 1. First: Capture positions before DOM change (capturePositions)
6
+ * 2. Last: DOM change happens (Lit re-render)
7
+ * 3. Invert: Calculate deltas, apply inverse transform
8
+ * 4. Play: Animate transform back to none
9
+ */
10
+ /**
11
+ * Capture current positions of all matching elements in a container.
12
+ * Call this BEFORE the DOM change.
13
+ *
14
+ * @returns Map of badge-id → DOMRect
15
+ */
16
+ export declare function capturePositions(container: HTMLElement, selector: string): Map<string, DOMRect>;
17
+ /**
18
+ * Animate elements from their old positions to their new positions using FLIP.
19
+ * Call this AFTER the DOM change (e.g., in updated() or requestAnimationFrame).
20
+ *
21
+ * @param container - The container element holding the badges
22
+ * @param selector - CSS selector for badge elements
23
+ * @param oldPositions - Positions captured before the DOM change
24
+ * @param options - Animation options
25
+ */
26
+ export declare function animateFlip(container: HTMLElement, selector: string, oldPositions: Map<string, DOMRect>, options?: {
27
+ duration?: number;
28
+ easing?: string;
29
+ }): void;
30
+ //# sourceMappingURL=flip-animator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flip-animator.d.ts","sourceRoot":"","sources":["../../src/pd-badge-order/flip-animator.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,WAAW,EACtB,QAAQ,EAAE,MAAM,GACf,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAYtB;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CACzB,SAAS,EAAE,WAAW,EACtB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,OAAO,GAAE;IACP,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACZ,GACL,IAAI,CAkCN"}
@@ -0,0 +1,39 @@
1
+ function capturePositions(container, selector) {
2
+ const positions = /* @__PURE__ */ new Map();
3
+ const elements = container.querySelectorAll(selector);
4
+ elements.forEach((el) => {
5
+ const id = el.dataset.badgeId;
6
+ if (id) {
7
+ positions.set(id, el.getBoundingClientRect());
8
+ }
9
+ });
10
+ return positions;
11
+ }
12
+ function animateFlip(container, selector, oldPositions, options = {}) {
13
+ const { duration = 200, easing = "ease-in-out" } = options;
14
+ const elements = container.querySelectorAll(selector);
15
+ elements.forEach((el) => {
16
+ const htmlEl = el;
17
+ const id = htmlEl.dataset.badgeId;
18
+ if (!id) return;
19
+ const oldPos = oldPositions.get(id);
20
+ if (!oldPos) return;
21
+ const newPos = htmlEl.getBoundingClientRect();
22
+ const deltaX = oldPos.left - newPos.left;
23
+ const deltaY = oldPos.top - newPos.top;
24
+ if (Math.abs(deltaX) < 1 && Math.abs(deltaY) < 1) return;
25
+ htmlEl.animate(
26
+ [
27
+ { transform: `translate(${deltaX}px, ${deltaY}px)` },
28
+ { transform: "translate(0, 0)" }
29
+ ],
30
+ {
31
+ duration,
32
+ easing,
33
+ fill: "none"
34
+ }
35
+ );
36
+ });
37
+ }
38
+
39
+ export { animateFlip, capturePositions };
@@ -0,0 +1,3 @@
1
+ import { PdBadgeItem } from './PdBadgeItem.js';
2
+ export { PdBadgeItem };
3
+ //# sourceMappingURL=pd-badge-item.d.ts.map