juxscript 1.0.62 → 1.0.64
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/bin/cli.js +161 -293
- package/docs/v2comps/HEADLESS.md +83 -0
- package/docs/v2comps/ISOMORPHISM.md +10 -0
- package/juxconfig.example.js +63 -58
- package/lib/componentsv2/base/BaseEngine.js +258 -0
- package/lib/componentsv2/base/BaseEngine.js.map +1 -0
- package/lib/componentsv2/base/BaseEngine.ts +303 -0
- package/lib/componentsv2/base/BaseSkin.js +108 -0
- package/lib/componentsv2/base/BaseSkin.js.map +1 -0
- package/lib/componentsv2/base/BaseSkin.ts +137 -0
- package/lib/componentsv2/base/GlobalBus.js +56 -0
- package/lib/componentsv2/base/GlobalBus.js.map +1 -0
- package/lib/componentsv2/base/GlobalBus.ts +60 -0
- package/lib/componentsv2/base/State.js +68 -0
- package/lib/componentsv2/base/State.js.map +1 -0
- package/lib/componentsv2/base/State.ts +62 -0
- package/lib/componentsv2/grid/component.js +41 -0
- package/lib/componentsv2/grid/component.js.map +1 -0
- package/lib/componentsv2/grid/component.ts +67 -0
- package/lib/componentsv2/grid/engine.js +73 -0
- package/lib/componentsv2/grid/engine.js.map +1 -0
- package/lib/componentsv2/grid/engine.ts +110 -0
- package/lib/componentsv2/grid/skin.js +95 -0
- package/lib/componentsv2/grid/skin.js.map +1 -0
- package/lib/componentsv2/grid/skin.ts +105 -0
- package/lib/componentsv2/grid/structure.css +58 -0
- package/lib/componentsv2/index.js +218 -0
- package/lib/componentsv2/index.js.map +1 -0
- package/lib/componentsv2/index.ts +253 -0
- package/lib/componentsv2/input/component.js +21 -0
- package/lib/componentsv2/input/component.js.map +1 -0
- package/lib/componentsv2/input/component.ts +28 -0
- package/lib/componentsv2/input/engine.js +50 -0
- package/lib/componentsv2/input/engine.js.map +1 -0
- package/lib/componentsv2/input/engine.ts +76 -0
- package/lib/componentsv2/input/skin.js +91 -0
- package/lib/componentsv2/input/skin.js.map +1 -0
- package/lib/componentsv2/input/skin.ts +91 -0
- package/lib/componentsv2/input/structure.css +47 -0
- package/lib/componentsv2/list/component.js +83 -0
- package/lib/componentsv2/list/component.js.map +1 -0
- package/lib/componentsv2/list/component.ts +97 -0
- package/lib/componentsv2/list/engine.js +261 -0
- package/lib/componentsv2/list/engine.js.map +1 -0
- package/lib/componentsv2/list/engine.ts +345 -0
- package/lib/componentsv2/list/skin.js +343 -0
- package/lib/componentsv2/list/skin.js.map +1 -0
- package/lib/componentsv2/list/skin.ts +367 -0
- package/lib/componentsv2/list/structure.css +359 -0
- package/lib/componentsv2/plugins/ClientSQLitePlugin.js +130 -0
- package/lib/componentsv2/plugins/ClientSQLitePlugin.js.map +1 -0
- package/lib/componentsv2/plugins/ClientSQLitePlugin.ts +154 -0
- package/lib/componentsv2/plugins/IndexedDBPlugin.js +75 -0
- package/lib/componentsv2/plugins/IndexedDBPlugin.js.map +1 -0
- package/lib/componentsv2/plugins/IndexedDBPlugin.ts +96 -0
- package/lib/componentsv2/plugins/LocalStoragePlugin.js +65 -0
- package/lib/componentsv2/plugins/LocalStoragePlugin.js.map +1 -0
- package/lib/componentsv2/plugins/LocalStoragePlugin.ts +86 -0
- package/lib/componentsv2/plugins/ServerSQLitePlugin.js +70 -0
- package/lib/componentsv2/plugins/ServerSQLitePlugin.js.map +1 -0
- package/lib/componentsv2/plugins/ServerSQLitePlugin.ts +99 -0
- package/lib/componentsv2/stubs/ComponentComposition.ts.stub +32 -0
- package/lib/componentsv2/stubs/ComponentEngine.ts.stub +36 -0
- package/lib/componentsv2/stubs/ComponentSkin.ts.stub +34 -0
- package/lib/componentsv2/stubs/ComponentStructure.css.stub +13 -0
- package/lib/componentsv2/tools/CreateSkin.js +62 -0
- package/lib/componentsv2/tools/DocSpam.js +134 -0
- package/lib/componentsv2/tools/FluencyAudit.js +141 -0
- package/lib/componentsv2/tools/OptionsAudit.js +177 -0
- package/lib/componentsv2/tools/Scaffold.js +140 -0
- package/lib/utils/fetch.js +428 -0
- package/lib/utils/fetch.js.map +1 -0
- package/machinery/build.js +2 -1
- package/machinery/compiler.js +200 -37
- package/machinery/config.js +93 -6
- package/machinery/diagnose.js +72 -0
- package/machinery/jux-module-pattern.md +118 -0
- package/machinery/server.js +23 -7
- package/machinery/verifier.js +143 -0
- package/machinery/watcher.js +53 -64
- package/package.json +11 -2
- package/lib/components/alert.ts +0 -200
- package/lib/components/app.ts +0 -258
- package/lib/components/badge.ts +0 -101
- package/lib/components/base/BaseComponent.ts +0 -417
- package/lib/components/base/FormInput.ts +0 -227
- package/lib/components/button.ts +0 -178
- package/lib/components/card.ts +0 -173
- package/lib/components/chart.ts +0 -231
- package/lib/components/checkbox.ts +0 -242
- package/lib/components/code.ts +0 -123
- package/lib/components/container.ts +0 -140
- package/lib/components/data.ts +0 -135
- package/lib/components/datepicker.ts +0 -234
- package/lib/components/dialog.ts +0 -172
- package/lib/components/divider.ts +0 -100
- package/lib/components/dropdown.ts +0 -186
- package/lib/components/element.ts +0 -267
- package/lib/components/error-handler.ts +0 -285
- package/lib/components/fileupload.ts +0 -309
- package/lib/components/grid.ts +0 -291
- package/lib/components/guard.ts +0 -92
- package/lib/components/heading.ts +0 -96
- package/lib/components/helpers.ts +0 -41
- package/lib/components/hero.ts +0 -224
- package/lib/components/icon.ts +0 -160
- package/lib/components/icons.ts +0 -175
- package/lib/components/include.ts +0 -440
- package/lib/components/input.ts +0 -457
- package/lib/components/list.ts +0 -419
- package/lib/components/loading.ts +0 -100
- package/lib/components/menu.ts +0 -260
- package/lib/components/modal.ts +0 -239
- package/lib/components/nav.ts +0 -257
- package/lib/components/paragraph.ts +0 -97
- package/lib/components/progress.ts +0 -139
- package/lib/components/radio.ts +0 -278
- package/lib/components/req.ts +0 -302
- package/lib/components/script.ts +0 -43
- package/lib/components/select.ts +0 -252
- package/lib/components/sidebar.ts +0 -167
- package/lib/components/style.ts +0 -43
- package/lib/components/switch.ts +0 -246
- package/lib/components/table.ts +0 -1249
- package/lib/components/tabs.ts +0 -250
- package/lib/components/theme-toggle.ts +0 -300
- package/lib/components/token-calculator.ts +0 -313
- package/lib/components/tooltip.ts +0 -144
- package/lib/components/view.ts +0 -190
- package/lib/components/write.ts +0 -272
- package/lib/jux.ts +0 -365
- package/lib/layouts/default.css +0 -260
- package/lib/layouts/figma.css +0 -334
- package/lib/reactivity/state.ts +0 -78
- package/machinery/bundleAssets.js +0 -0
- package/machinery/bundleJux.js +0 -0
- package/machinery/bundleVendors.js +0 -0
- package/presets/default/all.jux +0 -343
- package/presets/default/index.jux +0 -90
- package/presets/default/layout.jux +0 -57
- package/presets/default/style.css +0 -1612
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import { BaseSkin } from '../base/BaseSkin.js';
|
|
2
|
+
import { ListEngine, ListState, ListItem } from './engine.js';
|
|
3
|
+
|
|
4
|
+
export class ListSkin extends BaseSkin<ListState, ListEngine> {
|
|
5
|
+
// UI Feature Flags
|
|
6
|
+
#showSearch = false;
|
|
7
|
+
#showAdd = false;
|
|
8
|
+
#showDelete = false;
|
|
9
|
+
#showEdit = false;
|
|
10
|
+
#showMove = false;
|
|
11
|
+
#showSort = false;
|
|
12
|
+
|
|
13
|
+
// Internal Drag State
|
|
14
|
+
#dragSourceIndex: number | null = null;
|
|
15
|
+
|
|
16
|
+
// DOM References (Stable elements to prevent re-render thrashing)
|
|
17
|
+
#toolbar: HTMLElement | null = null;
|
|
18
|
+
#listContainer: HTMLElement | null = null;
|
|
19
|
+
|
|
20
|
+
// Toolbar Componenets
|
|
21
|
+
#searchInput: HTMLInputElement | null = null;
|
|
22
|
+
#addBtn: HTMLButtonElement | null = null;
|
|
23
|
+
#sortBtn: HTMLButtonElement | null = null;
|
|
24
|
+
|
|
25
|
+
constructor(engine: ListEngine) {
|
|
26
|
+
super(engine);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ✅ Implements Contract: Points to ./structure.css at runtime
|
|
30
|
+
protected get structureCss(): string {
|
|
31
|
+
return new URL('./structure.css', import.meta.url).href;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// --- Public UI API ---
|
|
35
|
+
enableSearch(enabled: boolean): void { this.#showSearch = enabled; this.#renderToolbar(); }
|
|
36
|
+
enableAdd(enabled: boolean): void { this.#showAdd = enabled; this.#renderToolbar(); }
|
|
37
|
+
enableDelete(enabled: boolean): void { this.#showDelete = enabled; this.updateSkin(this.engine.state); }
|
|
38
|
+
enableMove(enabled: boolean): void { this.#showMove = enabled; this.updateSkin(this.engine.state); }
|
|
39
|
+
enableSort(enabled: boolean): void { this.#showSort = enabled; this.#renderToolbar(); }
|
|
40
|
+
enableNoItems(message: string = 'No items found.'): void { this.engine.enableNoItems(message); }
|
|
41
|
+
|
|
42
|
+
enableEdit(enabled: boolean): void {
|
|
43
|
+
this.#showEdit = enabled;
|
|
44
|
+
this.updateSkin(this.engine.state);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// --- Template Skin Implementation ---
|
|
48
|
+
|
|
49
|
+
protected bindEvents(root: HTMLElement): void {
|
|
50
|
+
// 1. Delegation: Toolbar Events (Handled via direct element ref where possible now, or delegation fallback)
|
|
51
|
+
|
|
52
|
+
// We attach the input listener globally on root for delegation,
|
|
53
|
+
// but now that we hold the #searchInput ref, we could bind directly.
|
|
54
|
+
// Delegation is still safer if elements are recreated.
|
|
55
|
+
root.addEventListener('input', (e: Event) => {
|
|
56
|
+
const target = e.target as HTMLInputElement;
|
|
57
|
+
if (target.matches('.jux-search-input')) {
|
|
58
|
+
this.engine.filter(target.value);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
root.addEventListener('click', (e: MouseEvent) => {
|
|
63
|
+
const target = e.target as HTMLElement;
|
|
64
|
+
|
|
65
|
+
// Toolbar: Add
|
|
66
|
+
if (target.closest('.jux-action-add')) {
|
|
67
|
+
this.engine.addItem('New Item');
|
|
68
|
+
const newIndex = this.engine.state.items.length - 1;
|
|
69
|
+
if (newIndex >= 0) this.#openEditModal(newIndex);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Toolbar: Sort
|
|
74
|
+
if (target.closest('.jux-action-sort')) {
|
|
75
|
+
this.engine.toggleSort();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// List Item Actions
|
|
80
|
+
const li = target.closest('li');
|
|
81
|
+
if (!li) return;
|
|
82
|
+
|
|
83
|
+
// ✅ USE ID for robust targeting
|
|
84
|
+
const id = li.dataset.id;
|
|
85
|
+
const indexStr = li.dataset.index;
|
|
86
|
+
if (!indexStr || !id) return;
|
|
87
|
+
|
|
88
|
+
const index = parseInt(indexStr, 10);
|
|
89
|
+
|
|
90
|
+
// Action: Delete
|
|
91
|
+
if (target.closest('.jux-action-delete')) {
|
|
92
|
+
this.engine.removeItem(id); // ✅ Valid per new engine signature
|
|
93
|
+
e.stopImmediatePropagation();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Action: Edit
|
|
98
|
+
if (target.closest('.jux-action-edit')) {
|
|
99
|
+
this.#openEditModal(index);
|
|
100
|
+
e.stopImmediatePropagation();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// List Item: Selection
|
|
105
|
+
if (!target.closest('.jux-control')) {
|
|
106
|
+
this.engine.toggleSelection(index);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// 2. Drag & Drop Logic
|
|
111
|
+
root.addEventListener('dragstart', (e: DragEvent) => {
|
|
112
|
+
if (!this.#showMove) return;
|
|
113
|
+
const li = (e.target as HTMLElement).closest('li');
|
|
114
|
+
if (li) {
|
|
115
|
+
this.#dragSourceIndex = parseInt(li.dataset.index || '-1', 10);
|
|
116
|
+
e.dataTransfer!.effectAllowed = 'move';
|
|
117
|
+
e.dataTransfer!.setData('text/plain', li.dataset.index || '');
|
|
118
|
+
requestAnimationFrame(() => li.classList.add('jux-dragging'));
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
root.addEventListener('dragend', (e: DragEvent) => {
|
|
123
|
+
const li = (e.target as HTMLElement).closest('li');
|
|
124
|
+
if (li) li.classList.remove('jux-dragging');
|
|
125
|
+
root.querySelectorAll('.jux-item-drop-target').forEach(el => el.classList.remove('jux-item-drop-target'));
|
|
126
|
+
this.#dragSourceIndex = null;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
root.addEventListener('dragover', (e: DragEvent) => {
|
|
130
|
+
if (!this.#showMove) return;
|
|
131
|
+
e.preventDefault();
|
|
132
|
+
const li = (e.target as HTMLElement).closest('li');
|
|
133
|
+
if (li && !li.classList.contains('jux-dragging')) {
|
|
134
|
+
e.dataTransfer!.dropEffect = 'move';
|
|
135
|
+
root.querySelectorAll('.jux-item-drop-target').forEach(el => el.classList.remove('jux-item-drop-target'));
|
|
136
|
+
li.classList.add('jux-item-drop-target');
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
root.addEventListener('drop', (e: DragEvent) => {
|
|
141
|
+
if (!this.#showMove) return;
|
|
142
|
+
e.preventDefault();
|
|
143
|
+
const li = (e.target as HTMLElement).closest('li');
|
|
144
|
+
if (li && this.#dragSourceIndex !== null) {
|
|
145
|
+
const toIndex = parseInt(li.dataset.index || '-1', 10);
|
|
146
|
+
if (toIndex !== -1 && this.#dragSourceIndex !== toIndex) {
|
|
147
|
+
this.engine.moveItem(this.#dragSourceIndex, toIndex);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// --- Internals: Modal Editing ---
|
|
154
|
+
#openEditModal(index: number): void {
|
|
155
|
+
const item = this.engine.state.items[index];
|
|
156
|
+
if (!item) return;
|
|
157
|
+
|
|
158
|
+
const overlay = document.createElement('div');
|
|
159
|
+
overlay.className = 'jux-modal-overlay';
|
|
160
|
+
overlay.innerHTML = `
|
|
161
|
+
<div class="jux-modal">
|
|
162
|
+
<h3>Edit Item</h3>
|
|
163
|
+
<input type="text" id="jux-edit-input" value="${item.text.replace(/"/g, '"')}" />
|
|
164
|
+
<div class="jux-modal-actions">
|
|
165
|
+
<button class="jux-modal-btn jux-btn-cancel" id="jux-edit-cancel">Cancel</button>
|
|
166
|
+
<button class="jux-modal-btn jux-btn-save" id="jux-edit-save">Save</button>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
`;
|
|
170
|
+
document.body.appendChild(overlay);
|
|
171
|
+
const input = overlay.querySelector('#jux-edit-input') as HTMLInputElement;
|
|
172
|
+
const close = () => document.body.removeChild(overlay);
|
|
173
|
+
const save = () => {
|
|
174
|
+
if (input.value.trim()) this.engine.updateItem(index, input.value);
|
|
175
|
+
close();
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
overlay.querySelector('#jux-edit-cancel')?.addEventListener('click', close);
|
|
179
|
+
overlay.querySelector('#jux-edit-save')?.addEventListener('click', save);
|
|
180
|
+
overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
|
|
181
|
+
input.addEventListener('keydown', (e) => {
|
|
182
|
+
if (e.key === 'Enter') save();
|
|
183
|
+
if (e.key === 'Escape') close();
|
|
184
|
+
});
|
|
185
|
+
setTimeout(() => { input.focus(); input.select(); }, 0);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Skin Contract: Update/Changes
|
|
190
|
+
*/
|
|
191
|
+
protected updateSkin(state: ListState): void {
|
|
192
|
+
if (!this.root) return;
|
|
193
|
+
|
|
194
|
+
// Perform the DOM Update
|
|
195
|
+
const performUpdate = () => {
|
|
196
|
+
this.applySkinAttributes(this.root!, state);
|
|
197
|
+
|
|
198
|
+
// Lazy Creation
|
|
199
|
+
if (!this.#listContainer) {
|
|
200
|
+
this.root!.innerHTML = '';
|
|
201
|
+
this.#toolbar = document.createElement('div');
|
|
202
|
+
this.#toolbar.className = 'jux-list-toolbar';
|
|
203
|
+
this.root!.appendChild(this.#toolbar);
|
|
204
|
+
|
|
205
|
+
this.#listContainer = document.createElement('div');
|
|
206
|
+
this.root!.appendChild(this.#listContainer);
|
|
207
|
+
this.#renderToolbar(); // Initial render
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Always update toolbar state (e.g. icon direction) without destroying inputs
|
|
211
|
+
this.#renderToolbar();
|
|
212
|
+
|
|
213
|
+
// Container Classes
|
|
214
|
+
const classes = [...state.classes];
|
|
215
|
+
if (state.selectionMode !== 'none') classes.push('jux-list-selectable');
|
|
216
|
+
this.#listContainer!.className = classes.join(' ');
|
|
217
|
+
|
|
218
|
+
// Filtering
|
|
219
|
+
const visibleItems = state.items
|
|
220
|
+
.map((item, originalIndex) => ({ item, originalIndex }))
|
|
221
|
+
.filter(({ item }) =>
|
|
222
|
+
state.filterText
|
|
223
|
+
? item.text.toLowerCase().includes(state.filterText.toLowerCase())
|
|
224
|
+
: true
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
// Rendering Content
|
|
228
|
+
if (visibleItems.length === 0 && state.noItemsMessage) {
|
|
229
|
+
this.#listContainer!.innerHTML = `<div class="jux-list-empty-state">${state.noItemsMessage}</div>`;
|
|
230
|
+
} else {
|
|
231
|
+
const tag = state.listType === 'ordered' ? 'ol' : 'ul';
|
|
232
|
+
const html = visibleItems
|
|
233
|
+
.map(({ item, originalIndex }) => this.#renderListItem(item, originalIndex, state))
|
|
234
|
+
.join('');
|
|
235
|
+
|
|
236
|
+
this.#listContainer!.innerHTML = `<${tag} class="jux-list-element jux-list-type-${tag}">${html}</${tag}>`;
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// ✨ View Transitions Logic
|
|
241
|
+
// We SKIP transition if the search input is focused, otherwise typing janks the UI.
|
|
242
|
+
const isSearching = document.activeElement &&
|
|
243
|
+
(document.activeElement === this.#searchInput);
|
|
244
|
+
|
|
245
|
+
if (!isSearching && typeof document !== 'undefined' && 'startViewTransition' in document) {
|
|
246
|
+
// @ts-ignore
|
|
247
|
+
document.startViewTransition(performUpdate);
|
|
248
|
+
} else {
|
|
249
|
+
performUpdate();
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// --- DOM-Based Toolbar Rendering ---
|
|
254
|
+
#renderToolbar(): void {
|
|
255
|
+
if (!this.#toolbar) return;
|
|
256
|
+
|
|
257
|
+
// 1. Search Logic
|
|
258
|
+
if (this.#showSearch) {
|
|
259
|
+
if (!this.#searchInput) {
|
|
260
|
+
this.#searchInput = document.createElement('input');
|
|
261
|
+
this.#searchInput.type = 'text';
|
|
262
|
+
this.#searchInput.className = 'jux-search-input';
|
|
263
|
+
this.#searchInput.placeholder = 'Filter items...';
|
|
264
|
+
// Insert at start
|
|
265
|
+
this.#toolbar.prepend(this.#searchInput);
|
|
266
|
+
}
|
|
267
|
+
} else if (this.#searchInput) {
|
|
268
|
+
this.#searchInput.remove();
|
|
269
|
+
this.#searchInput = null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// 2. Sort Logic
|
|
273
|
+
if (this.#showSort) {
|
|
274
|
+
if (!this.#sortBtn) {
|
|
275
|
+
this.#sortBtn = document.createElement('button');
|
|
276
|
+
this.#sortBtn.className = 'jux-action-sort';
|
|
277
|
+
this.#sortBtn.type = 'button';
|
|
278
|
+
this.#sortBtn.ariaLabel = 'Sort';
|
|
279
|
+
this.#toolbar.appendChild(this.#sortBtn);
|
|
280
|
+
}
|
|
281
|
+
// Update Icon state
|
|
282
|
+
const s = this.engine.state.sorted;
|
|
283
|
+
const icon = s === 'asc' ? '↑' : (s === 'desc' ? '↓' : '↕');
|
|
284
|
+
if (this.#sortBtn.textContent !== icon) {
|
|
285
|
+
this.#sortBtn.textContent = icon;
|
|
286
|
+
}
|
|
287
|
+
} else if (this.#sortBtn) {
|
|
288
|
+
this.#sortBtn.remove();
|
|
289
|
+
this.#sortBtn = null;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// 3. Add Logic
|
|
293
|
+
if (this.#showAdd) {
|
|
294
|
+
if (!this.#addBtn) {
|
|
295
|
+
this.#addBtn = document.createElement('button');
|
|
296
|
+
this.#addBtn.className = 'jux-action-add';
|
|
297
|
+
this.#addBtn.type = 'button';
|
|
298
|
+
this.#addBtn.ariaLabel = 'Add Item';
|
|
299
|
+
this.#addBtn.textContent = '+';
|
|
300
|
+
this.#toolbar.appendChild(this.#addBtn);
|
|
301
|
+
}
|
|
302
|
+
} else if (this.#addBtn) {
|
|
303
|
+
this.#addBtn.remove();
|
|
304
|
+
this.#addBtn = null;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Parent Visibility
|
|
308
|
+
const hasChildren = this.#showSearch || this.#showSort || this.#showAdd;
|
|
309
|
+
this.#toolbar.style.display = hasChildren ? 'flex' : 'none';
|
|
310
|
+
|
|
311
|
+
// ensure Search input is first by re-prepending if it exists
|
|
312
|
+
if (this.#searchInput && this.#toolbar.firstChild !== this.#searchInput) {
|
|
313
|
+
this.#toolbar.prepend(this.#searchInput);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
#renderListItem(item: ListItem, index: number, state: ListState): string {
|
|
318
|
+
const css = [
|
|
319
|
+
'jux-list-item',
|
|
320
|
+
item.selected ? 'jux-selected' : '',
|
|
321
|
+
...(item.classes || [])
|
|
322
|
+
].filter(Boolean).join(' ');
|
|
323
|
+
|
|
324
|
+
// ✅ Determine Content based on Columns configuration
|
|
325
|
+
let content = item.text;
|
|
326
|
+
|
|
327
|
+
if (state.columns && state.columns.length > 0) {
|
|
328
|
+
content = state.columns.map(col => {
|
|
329
|
+
const key = typeof col === 'string' ? col : col.key;
|
|
330
|
+
const cssClass = (typeof col === 'object' && col.label) ? `jux-col-${key}` : ''; // Basic class hook
|
|
331
|
+
const value = item[key] ?? '';
|
|
332
|
+
// If complex object, stringify or fallback
|
|
333
|
+
const displayVal = (typeof value === 'object') ? JSON.stringify(value) : String(value);
|
|
334
|
+
|
|
335
|
+
return `<span class="jux-col ${cssClass}">${displayVal}</span>`;
|
|
336
|
+
}).join(' <span class="jux-col-sep"> </span> ');
|
|
337
|
+
} else {
|
|
338
|
+
// Default Fallback logic: If no columns but item.text is boring ("[object Object]"), try to find keys
|
|
339
|
+
if (content === '[object Object]') {
|
|
340
|
+
const keys = Object.keys(item).filter(k =>
|
|
341
|
+
k !== 'id' && k !== 'selected' && k !== 'classes' && k !== 'text' && k !== 'value'
|
|
342
|
+
);
|
|
343
|
+
if (keys.length > 0) {
|
|
344
|
+
content = `${item[keys[0]]}`;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const moveBtn = this.#showMove ? `<span class="jux-action-drag jux-control" title="Drag to reorder">≡</span>` : '';
|
|
350
|
+
const editBtn = this.#showEdit ? `<button class="jux-action-edit jux-control" aria-label="Edit">✎</button>` : '';
|
|
351
|
+
const deleteBtn = this.#showDelete ? `<button class="jux-action-delete jux-control" aria-label="Delete">×</button>` : '';
|
|
352
|
+
const draggableAttr = this.#showMove ? 'draggable="true"' : '';
|
|
353
|
+
const transitionName = `jux-item-${item.id.replace(/[^a-zA-Z0-9-_]/g, '-')}`;
|
|
354
|
+
|
|
355
|
+
return `
|
|
356
|
+
<li data-id="${item.id}" data-index="${index}" class="${css}" ${draggableAttr} style="view-transition-name: ${transitionName}">
|
|
357
|
+
<div class="jux-list-item-content">
|
|
358
|
+
${content}
|
|
359
|
+
</div>
|
|
360
|
+
<div class="jux-item-controls">
|
|
361
|
+
${moveBtn}
|
|
362
|
+
${editBtn}
|
|
363
|
+
${deleteBtn}
|
|
364
|
+
</div>
|
|
365
|
+
</li>`;
|
|
366
|
+
}
|
|
367
|
+
}
|