@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.
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- 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,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 };
|