@progressive-development/pd-content 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/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -5
- package/dist/pd-badge-order/DragController.d.ts +41 -0
- package/dist/pd-badge-order/DragController.d.ts.map +1 -0
- package/dist/pd-badge-order/DragController.js +239 -0
- package/dist/pd-badge-order/PdBadgeItem.d.ts +31 -0
- package/dist/pd-badge-order/PdBadgeItem.d.ts.map +1 -0
- package/dist/pd-badge-order/PdBadgeItem.js +320 -0
- package/dist/pd-badge-order/PdBadgeOrder.d.ts +68 -0
- package/dist/pd-badge-order/PdBadgeOrder.d.ts.map +1 -0
- package/dist/pd-badge-order/PdBadgeOrder.js +550 -0
- package/dist/pd-badge-order/flip-animator.d.ts +30 -0
- package/dist/pd-badge-order/flip-animator.d.ts.map +1 -0
- package/dist/pd-badge-order/flip-animator.js +39 -0
- package/dist/pd-badge-order/pd-badge-item.d.ts +3 -0
- package/dist/pd-badge-order/pd-badge-item.d.ts.map +1 -0
- package/dist/pd-badge-order/pd-badge-item.js +8 -0
- package/dist/pd-badge-order/pd-badge-order.d.ts +3 -0
- package/dist/pd-badge-order/pd-badge-order.d.ts.map +1 -0
- package/dist/pd-badge-order/types.d.ts +25 -0
- package/dist/pd-badge-order/types.d.ts.map +1 -0
- package/dist/pd-badge-order/types.js +3 -0
- package/dist/pd-badge-order.d.ts +2 -0
- package/dist/pd-badge-order.js +8 -0
- package/dist/pd-gallery/PdGallery.d.ts +72 -0
- package/dist/pd-gallery/PdGallery.d.ts.map +1 -0
- package/dist/pd-gallery/PdGallery.js +660 -0
- package/dist/pd-gallery/PdGalleryLightbox.d.ts +53 -0
- package/dist/pd-gallery/PdGalleryLightbox.d.ts.map +1 -0
- package/dist/pd-gallery/PdGalleryLightbox.js +530 -0
- package/dist/pd-gallery/index.d.ts +4 -0
- package/dist/pd-gallery/index.d.ts.map +1 -0
- package/dist/pd-gallery/pd-gallery-lightbox.d.ts +3 -0
- package/dist/pd-gallery/pd-gallery-lightbox.d.ts.map +1 -0
- package/dist/pd-gallery/pd-gallery.d.ts +3 -0
- package/dist/pd-gallery/pd-gallery.d.ts.map +1 -0
- package/dist/pd-gallery/types.d.ts +23 -0
- package/dist/pd-gallery/types.d.ts.map +1 -0
- package/dist/pd-gallery-lightbox.d.ts +2 -0
- package/dist/pd-gallery-lightbox.js +8 -0
- package/dist/pd-gallery.d.ts +2 -0
- package/dist/pd-gallery.js +8 -0
- package/dist/pd-loading-state/PdLoadingState.d.ts +25 -9
- package/dist/pd-loading-state/PdLoadingState.d.ts.map +1 -1
- package/dist/pd-loading-state/PdLoadingState.js +228 -83
- package/dist/pd-loading-state/pd-loading-state.d.ts.map +1 -1
- package/dist/pd-loading-state/pd-logo-loader.d.ts +25 -0
- package/dist/pd-loading-state/pd-logo-loader.d.ts.map +1 -0
- package/dist/pd-loading-state/pd-logo-loader.js +63 -0
- package/dist/pd-loading-state/register-pd-logo-loader.d.ts +6 -0
- package/dist/pd-loading-state/register-pd-logo-loader.d.ts.map +1 -0
- package/dist/pd-loading-state/register-pd-logo-loader.js +6 -0
- package/dist/pd-loading-state.js +8 -1
- package/dist/pd-panel-viewer/PdPanelViewer.d.ts.map +1 -1
- package/dist/pd-panel-viewer/PdPanelViewer.js +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -3
- package/dist/pd-box-view/pd-box-view.stories.d.ts +0 -43
- package/dist/pd-box-view/pd-box-view.stories.d.ts.map +0 -1
- package/dist/pd-code-snippet/pd-code-snippet.stories.d.ts +0 -55
- package/dist/pd-code-snippet/pd-code-snippet.stories.d.ts.map +0 -1
- package/dist/pd-collapse/pd-collapse.stories.d.ts +0 -51
- package/dist/pd-collapse/pd-collapse.stories.d.ts.map +0 -1
- package/dist/pd-collapse-group/pd-collapse-group.stories.d.ts +0 -40
- package/dist/pd-collapse-group/pd-collapse-group.stories.d.ts.map +0 -1
- package/dist/pd-edit-content/pd-edit-content.stories.d.ts +0 -55
- package/dist/pd-edit-content/pd-edit-content.stories.d.ts.map +0 -1
- package/dist/pd-loading-state/pd-loading-state.stories.d.ts +0 -48
- package/dist/pd-loading-state/pd-loading-state.stories.d.ts.map +0 -1
- package/dist/pd-more-info/pd-more-info.stories.d.ts +0 -42
- package/dist/pd-more-info/pd-more-info.stories.d.ts.map +0 -1
- package/dist/pd-notice-box/pd-notice-box.stories.d.ts +0 -58
- package/dist/pd-notice-box/pd-notice-box.stories.d.ts.map +0 -1
- package/dist/pd-panel-viewer/pd-panel-viewer.stories.d.ts +0 -46
- package/dist/pd-panel-viewer/pd-panel-viewer.stories.d.ts.map +0 -1
- package/dist/pd-resize-content/pd-resize-content.stories.d.ts +0 -37
- package/dist/pd-resize-content/pd-resize-content.stories.d.ts.map +0 -1
- package/dist/pd-tab/pd-tab.stories.d.ts +0 -53
- package/dist/pd-tab/pd-tab.stories.d.ts.map +0 -1
- package/dist/pd-timeline/pd-timeline.stories.d.ts +0 -56
- package/dist/pd-timeline/pd-timeline.stories.d.ts.map +0 -1
- package/dist/pd-timeline-wizard/pd-timeline-wizard.stories.d.ts +0 -54
- package/dist/pd-timeline-wizard/pd-timeline-wizard.stories.d.ts.map +0 -1
|
@@ -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 @@
|
|
|
1
|
+
{"version":3,"file":"pd-badge-item.d.ts","sourceRoot":"","sources":["../../src/pd-badge-order/pd-badge-item.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAO/C,OAAO,EAAE,WAAW,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pd-badge-order.d.ts","sourceRoot":"","sources":["../../src/pd-badge-order/pd-badge-order.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAOjD,OAAO,EAAE,YAAY,EAAE,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface PdBadge {
|
|
2
|
+
/** Unique badge ID */
|
|
3
|
+
id: string;
|
|
4
|
+
/** Display title (max TEXT_LIMIT_TITLE characters) */
|
|
5
|
+
title: string;
|
|
6
|
+
/** Optional description below title (max TEXT_LIMIT_DESC characters) */
|
|
7
|
+
description?: string;
|
|
8
|
+
/** Optional color (hex or CSS color name) */
|
|
9
|
+
color?: string;
|
|
10
|
+
/** Optional icon name from pd-icon library */
|
|
11
|
+
icon?: string;
|
|
12
|
+
/** Marks user-created badges (only these are deletable) */
|
|
13
|
+
custom?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface PdBadgeChangeDetail {
|
|
16
|
+
value: string[];
|
|
17
|
+
badges: PdBadge[];
|
|
18
|
+
}
|
|
19
|
+
export interface PdBadgeReorderDetail {
|
|
20
|
+
value: string[];
|
|
21
|
+
}
|
|
22
|
+
export declare const TEXT_LIMIT_TITLE = 40;
|
|
23
|
+
export declare const TEXT_LIMIT_DESC = 100;
|
|
24
|
+
export declare const DEFAULT_BADGE_COLOR = "#9E9E9E";
|
|
25
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/pd-badge-order/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,OAAO;IACtB,sBAAsB;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,EAAE,OAAO,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,eAAO,MAAM,eAAe,MAAM,CAAC;AAEnC,eAAO,MAAM,mBAAmB,YAAY,CAAC"}
|