@aquera/nile-elements 1.7.3 → 1.7.5

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 (54) hide show
  1. package/README.md +7 -0
  2. package/dist/index-199b0eac.esm.js +1 -0
  3. package/dist/index-f5e587e2.cjs.js +2 -0
  4. package/dist/index-f5e587e2.cjs.js.map +1 -0
  5. package/dist/index.cjs.js +1 -1
  6. package/dist/index.esm.js +1 -1
  7. package/dist/index.js +994 -681
  8. package/dist/nile-combobox/index.cjs.js +1 -1
  9. package/dist/nile-combobox/index.esm.js +1 -1
  10. package/dist/nile-combobox/nile-combobox.cjs.js +1 -1
  11. package/dist/nile-combobox/nile-combobox.cjs.js.map +1 -1
  12. package/dist/nile-combobox/nile-combobox.esm.js +28 -28
  13. package/dist/nile-detail/index.cjs.js +1 -1
  14. package/dist/nile-detail/index.esm.js +1 -1
  15. package/dist/nile-detail/nile-detail.cjs.js +1 -1
  16. package/dist/nile-detail/nile-detail.cjs.js.map +1 -1
  17. package/dist/nile-detail/nile-detail.css.cjs.js +1 -1
  18. package/dist/nile-detail/nile-detail.css.cjs.js.map +1 -1
  19. package/dist/nile-detail/nile-detail.css.esm.js +230 -0
  20. package/dist/nile-detail/nile-detail.esm.js +89 -7
  21. package/dist/nile-floating-panel/index.cjs.js +1 -1
  22. package/dist/nile-floating-panel/nile-floating-panel.cjs.js +1 -1
  23. package/dist/nile-inline-sidebar-item/nile-inline-sidebar-item.cjs.js +1 -1
  24. package/dist/nile-inline-sidebar-item/nile-inline-sidebar-item.cjs.js.map +1 -1
  25. package/dist/nile-inline-sidebar-item/nile-inline-sidebar-item.esm.js +1 -1
  26. package/dist/nile-inline-sidebar-item-header/nile-inline-sidebar-item-header.css.cjs.js +1 -1
  27. package/dist/nile-inline-sidebar-item-header/nile-inline-sidebar-item-header.css.cjs.js.map +1 -1
  28. package/dist/nile-inline-sidebar-item-header/nile-inline-sidebar-item-header.css.esm.js +2 -1
  29. package/dist/nile-lite-tooltip/index.cjs.js +1 -1
  30. package/dist/nile-lite-tooltip/nile-lite-tooltip.cjs.js +1 -1
  31. package/dist/nile-otp-input/nile-otp-input.css.cjs.js +1 -1
  32. package/dist/nile-otp-input/nile-otp-input.css.cjs.js.map +1 -1
  33. package/dist/nile-otp-input/nile-otp-input.css.esm.js +1 -1
  34. package/dist/src/nile-detail/nile-detail.css.js +230 -0
  35. package/dist/src/nile-detail/nile-detail.css.js.map +1 -1
  36. package/dist/src/nile-detail/nile-detail.d.ts +151 -0
  37. package/dist/src/nile-detail/nile-detail.js +829 -4
  38. package/dist/src/nile-detail/nile-detail.js.map +1 -1
  39. package/dist/src/nile-inline-sidebar-item/nile-inline-sidebar-item.js +1 -1
  40. package/dist/src/nile-inline-sidebar-item/nile-inline-sidebar-item.js.map +1 -1
  41. package/dist/src/nile-inline-sidebar-item-header/nile-inline-sidebar-item-header.css.js +2 -1
  42. package/dist/src/nile-inline-sidebar-item-header/nile-inline-sidebar-item-header.css.js.map +1 -1
  43. package/dist/src/nile-otp-input/nile-otp-input.css.js +1 -1
  44. package/dist/src/nile-otp-input/nile-otp-input.css.js.map +1 -1
  45. package/dist/src/version.js +1 -1
  46. package/dist/src/version.js.map +1 -1
  47. package/dist/tsconfig.tsbuildinfo +1 -1
  48. package/package.json +1 -1
  49. package/src/nile-detail/nile-detail.css.ts +230 -0
  50. package/src/nile-detail/nile-detail.ts +876 -4
  51. package/src/nile-inline-sidebar-item/nile-inline-sidebar-item.ts +1 -1
  52. package/src/nile-inline-sidebar-item-header/nile-inline-sidebar-item-header.css.ts +2 -1
  53. package/src/nile-otp-input/nile-otp-input.css.ts +1 -1
  54. package/vscode-html-custom-data.json +120 -1
@@ -2,10 +2,18 @@ import { __decorate } from "tslib";
2
2
  import { html, nothing } from 'lit';
3
3
  import { customElement, property, query, state } from 'lit/decorators.js';
4
4
  import { classMap } from 'lit/directives/class-map.js';
5
+ import { unsafeHTML } from 'lit/directives/unsafe-html.js';
5
6
  import { styles } from './nile-detail.css';
6
7
  import NileElement from '../internal/nile-element';
7
8
  import { watch } from '../internal/watch';
8
9
  import { animateShow, animateHide, showDetail, hideDetail, handleSummaryClick, handleSummaryKeyDown, } from './nile-detail.utils';
10
+ import '../nile-checkbox/index';
11
+ import '../nile-input/index';
12
+ import '../nile-link/index';
13
+ import '../nile-icon/index';
14
+ import '../nile-lite-tooltip/index';
15
+ import { VirtualizerController } from '@tanstack/lit-virtual';
16
+ const IS_BROWSER = typeof window !== 'undefined';
9
17
  let NileDetail = class NileDetail extends NileElement {
10
18
  constructor() {
11
19
  super(...arguments);
@@ -14,12 +22,527 @@ let NileDetail = class NileDetail extends NileElement {
14
22
  this.description = '';
15
23
  this.expandIconPlacement = 'right';
16
24
  this.disabled = false;
25
+ this.variant = 'default';
26
+ this.items = [];
27
+ this.selected = [];
28
+ this.allowHtmlLabel = false;
29
+ this.disableLocalSearch = false;
30
+ this.pageSize = 200;
31
+ this.placeholderLabel = 'Loading…';
32
+ this.searchPlaceholder = 'Search...';
33
+ this.itemsLabel = 'attributes';
34
+ this.restoreLabel = 'Restore';
35
+ this.clearLabel = 'Clear';
36
+ this.gridRows = 6;
37
+ this.gridColumns = 3;
38
+ this.minColumnWidth = '220px';
39
+ this.laneHeight = 32;
40
+ this.orientation = 'horizontal';
41
+ this.maxHeight = '320px';
42
+ this.matrixColumns = 20;
43
+ this.virtualize = false;
44
+ this.virtualizeThreshold = 60;
45
+ this.overscan = 4;
17
46
  this._detailOpen = false;
47
+ this._searchTerm = '';
48
+ this._selectedSet = new Set();
49
+ this._restoreDefaults = null;
50
+ this._gridResizeObserver = null;
51
+ this._virtCtrl = null;
52
+ this._rowVirtCtrl = null;
53
+ this._colVirtCtrl = null;
54
+ this._pageCache = new Map();
55
+ this._pendingPages = new Set();
56
+ }
57
+ get _isInfiniteMode() {
58
+ return (this.variant === 'selection' &&
59
+ typeof this.fetchPage === 'function' &&
60
+ typeof this.totalCount === 'number' &&
61
+ this.orientation !== 'both');
62
+ }
63
+ _effectiveTotalCount() {
64
+ if (this._isInfiniteMode)
65
+ return this.totalCount ?? 0;
66
+ return this._filteredItems.length;
67
+ }
68
+ _getItemAt(index) {
69
+ if (!this._isInfiniteMode)
70
+ return this._filteredItems[index];
71
+ const pageSize = Math.max(1, this.pageSize);
72
+ const pageIndex = Math.floor(index / pageSize);
73
+ const page = this._pageCache.get(pageIndex);
74
+ if (!page)
75
+ return undefined;
76
+ return page[index - pageIndex * pageSize];
77
+ }
78
+ _scheduleFetchForRange(start, end) {
79
+ if (!this._isInfiniteMode || !this.fetchPage)
80
+ return;
81
+ const pageSize = Math.max(1, this.pageSize);
82
+ const total = this.totalCount ?? 0;
83
+ if (total <= 0)
84
+ return;
85
+ const firstPage = Math.floor(start / pageSize);
86
+ const lastPage = Math.floor(Math.min(end, total - 1) / pageSize);
87
+ for (let p = firstPage; p <= lastPage; p++) {
88
+ if (this._pageCache.has(p) || this._pendingPages.has(p))
89
+ continue;
90
+ this._loadPage(p);
91
+ }
92
+ }
93
+ async _loadPage(pageIndex) {
94
+ if (!this.fetchPage)
95
+ return;
96
+ const pageSize = Math.max(1, this.pageSize);
97
+ const offset = pageIndex * pageSize;
98
+ const total = this.totalCount ?? 0;
99
+ const limit = Math.min(pageSize, Math.max(0, total - offset));
100
+ if (limit <= 0)
101
+ return;
102
+ this._pendingPages.add(pageIndex);
103
+ try {
104
+ const rows = await this.fetchPage(offset, limit);
105
+ this._pageCache.set(pageIndex, Array.isArray(rows) ? rows : []);
106
+ this.dispatchEvent(new CustomEvent('nile-page-load', {
107
+ detail: { pageIndex, offset, limit, rows: this._pageCache.get(pageIndex) },
108
+ bubbles: true,
109
+ composed: true,
110
+ }));
111
+ }
112
+ catch (err) {
113
+ this.dispatchEvent(new CustomEvent('nile-page-error', {
114
+ detail: { pageIndex, offset, limit, error: err },
115
+ bubbles: true,
116
+ composed: true,
117
+ }));
118
+ }
119
+ finally {
120
+ this._pendingPages.delete(pageIndex);
121
+ this.requestUpdate();
122
+ }
123
+ }
124
+ /** Public: clear the page cache (use after a query change to refetch). */
125
+ resetPages() {
126
+ this._pageCache = new Map();
127
+ this._pendingPages = new Set();
128
+ this.requestUpdate();
129
+ }
130
+ get _isVirtualized() {
131
+ if (!IS_BROWSER)
132
+ return false;
133
+ if (this._isInfiniteMode)
134
+ return true;
135
+ return this.variant === 'selection'
136
+ && (this.virtualize || this.items.length > this.virtualizeThreshold);
137
+ }
138
+ get _columnWidthPx() {
139
+ const parsed = parseInt(this.minColumnWidth, 10);
140
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 220;
18
141
  }
19
142
  firstUpdated() {
20
143
  this._detailOpen = this.open;
21
144
  this.body.hidden = !this.open;
22
145
  this.body.style.height = this.open ? 'auto' : '0';
146
+ this._syncSelectedFromProperty();
147
+ if (this._restoreDefaults === null) {
148
+ this._restoreDefaults = [...this._selectedSet];
149
+ }
150
+ this._setupGridResizeObserver();
151
+ }
152
+ disconnectedCallback() {
153
+ super.disconnectedCallback();
154
+ this._gridResizeObserver?.disconnect();
155
+ this._gridResizeObserver = null;
156
+ }
157
+ updated(_changed) {
158
+ if (this.variant === 'selection') {
159
+ this._setupGridResizeObserver();
160
+ this._syncVirtualizer();
161
+ this._scheduleTooltipOverflowCheck();
162
+ }
163
+ }
164
+ _syncVirtualizer() {
165
+ if (!IS_BROWSER)
166
+ return;
167
+ if (!this._isVirtualized) {
168
+ this._virtCtrl = null;
169
+ this._rowVirtCtrl = null;
170
+ this._colVirtCtrl = null;
171
+ return;
172
+ }
173
+ const grid = this.shadowRoot?.querySelector('.detail__selection-grid');
174
+ if (!grid)
175
+ return;
176
+ if (this.orientation === 'both') {
177
+ this._virtCtrl = null;
178
+ this._syncBothVirtualizers();
179
+ return;
180
+ }
181
+ // Reset 2-axis controllers if we switched away from 'both'.
182
+ this._rowVirtCtrl = null;
183
+ this._colVirtCtrl = null;
184
+ const count = this._effectiveTotalCount();
185
+ const isVertical = this.orientation === 'vertical';
186
+ const lanes = Math.max(1, isVertical ? this.gridColumns : this.gridRows);
187
+ const itemSize = isVertical ? this.laneHeight : this._columnWidthPx;
188
+ if (!this._virtCtrl) {
189
+ this._virtCtrl = new VirtualizerController(this, {
190
+ count,
191
+ getScrollElement: () => this.shadowRoot?.querySelector('.detail__selection-grid'),
192
+ estimateSize: () => itemSize,
193
+ horizontal: !isVertical,
194
+ lanes,
195
+ overscan: this.overscan,
196
+ });
197
+ return;
198
+ }
199
+ const v = this._virtCtrl.getVirtualizer();
200
+ const prev = v.options;
201
+ const orientationChanged = !!prev.horizontal !== !isVertical;
202
+ if (prev.count !== count ||
203
+ prev.lanes !== lanes ||
204
+ prev.overscan !== this.overscan ||
205
+ orientationChanged) {
206
+ v.setOptions({
207
+ ...prev,
208
+ count,
209
+ lanes,
210
+ overscan: this.overscan,
211
+ horizontal: !isVertical,
212
+ estimateSize: () => itemSize,
213
+ });
214
+ v.measure();
215
+ }
216
+ }
217
+ _syncBothVirtualizers() {
218
+ const totalItems = this._filteredItems.length;
219
+ const cols = Math.max(1, this.matrixColumns);
220
+ const rows = Math.max(1, Math.ceil(totalItems / cols));
221
+ const columnWidth = this._columnWidthPx;
222
+ const rowHeight = this.laneHeight;
223
+ const getScrollEl = () => this.shadowRoot?.querySelector('.detail__selection-grid');
224
+ if (!this._rowVirtCtrl) {
225
+ this._rowVirtCtrl = new VirtualizerController(this, {
226
+ count: rows,
227
+ getScrollElement: getScrollEl,
228
+ estimateSize: () => rowHeight,
229
+ horizontal: false,
230
+ overscan: this.overscan,
231
+ });
232
+ }
233
+ else {
234
+ const rv = this._rowVirtCtrl.getVirtualizer();
235
+ const prev = rv.options;
236
+ if (prev.count !== rows || prev.overscan !== this.overscan) {
237
+ rv.setOptions({
238
+ ...prev,
239
+ count: rows,
240
+ overscan: this.overscan,
241
+ estimateSize: () => rowHeight,
242
+ });
243
+ rv.measure();
244
+ }
245
+ }
246
+ if (!this._colVirtCtrl) {
247
+ this._colVirtCtrl = new VirtualizerController(this, {
248
+ count: cols,
249
+ getScrollElement: getScrollEl,
250
+ estimateSize: () => columnWidth,
251
+ horizontal: true,
252
+ overscan: this.overscan,
253
+ });
254
+ }
255
+ else {
256
+ const cv = this._colVirtCtrl.getVirtualizer();
257
+ const prev = cv.options;
258
+ if (prev.count !== cols || prev.overscan !== this.overscan) {
259
+ cv.setOptions({
260
+ ...prev,
261
+ count: cols,
262
+ overscan: this.overscan,
263
+ estimateSize: () => columnWidth,
264
+ });
265
+ cv.measure();
266
+ }
267
+ }
268
+ }
269
+ _setupGridResizeObserver() {
270
+ if (this.variant !== 'selection')
271
+ return;
272
+ if (typeof ResizeObserver === 'undefined')
273
+ return;
274
+ const grid = this.shadowRoot?.querySelector('.detail__selection-grid');
275
+ if (!grid)
276
+ return;
277
+ if (this._gridResizeObserver)
278
+ return;
279
+ this._gridResizeObserver = new ResizeObserver(() => this._scheduleTooltipOverflowCheck());
280
+ this._gridResizeObserver.observe(grid);
281
+ }
282
+ _scheduleTooltipOverflowCheck() {
283
+ if (typeof requestAnimationFrame === 'undefined')
284
+ return;
285
+ requestAnimationFrame(() => this._updateTooltipOverflowStates());
286
+ }
287
+ async _updateTooltipOverflowStates() {
288
+ const tooltips = this.shadowRoot?.querySelectorAll('.detail__selection-tooltip');
289
+ if (!tooltips)
290
+ return;
291
+ for (const tip of tooltips) {
292
+ const cb = tip.querySelector('nile-checkbox');
293
+ if (!cb)
294
+ continue;
295
+ if (cb.updateComplete)
296
+ await cb.updateComplete;
297
+ const labelEl = cb.shadowRoot?.querySelector('[part="label"]');
298
+ const overflowing = !!labelEl && labelEl.scrollWidth > labelEl.clientWidth + 1;
299
+ if (overflowing)
300
+ tip.removeAttribute('disabled');
301
+ else
302
+ tip.setAttribute('disabled', '');
303
+ }
304
+ }
305
+ _onSelectedPropertyChange() {
306
+ this._syncSelectedFromProperty();
307
+ }
308
+ _onConfigChange() {
309
+ this._applyConfig();
310
+ }
311
+ _applyConfig() {
312
+ const c = this.config;
313
+ if (!c)
314
+ return;
315
+ if (c.variant !== undefined)
316
+ this.variant = c.variant;
317
+ if (c.heading !== undefined)
318
+ this.heading = c.heading;
319
+ if (c.description !== undefined)
320
+ this.description = c.description;
321
+ if (c.itemsLabel !== undefined)
322
+ this.itemsLabel = c.itemsLabel;
323
+ if (c.open !== undefined)
324
+ this.open = c.open;
325
+ if (c.disabled !== undefined)
326
+ this.disabled = c.disabled;
327
+ if (c.items !== undefined)
328
+ this.items = c.items;
329
+ if (c.selected !== undefined)
330
+ this.selected = c.selected;
331
+ if (c.renderItemConfig !== undefined)
332
+ this.renderItemConfig = c.renderItemConfig;
333
+ if (c.allowHtmlLabel !== undefined)
334
+ this.allowHtmlLabel = c.allowHtmlLabel;
335
+ if (c.layout) {
336
+ const l = c.layout;
337
+ if (l.orientation !== undefined)
338
+ this.orientation = l.orientation;
339
+ if (l.gridRows !== undefined)
340
+ this.gridRows = l.gridRows;
341
+ if (l.gridColumns !== undefined)
342
+ this.gridColumns = l.gridColumns;
343
+ if (l.matrixColumns !== undefined)
344
+ this.matrixColumns = l.matrixColumns;
345
+ if (l.minColumnWidth !== undefined)
346
+ this.minColumnWidth = l.minColumnWidth;
347
+ if (l.laneHeight !== undefined)
348
+ this.laneHeight = l.laneHeight;
349
+ if (l.maxHeight !== undefined)
350
+ this.maxHeight = l.maxHeight;
351
+ if (l.expandIconPlacement !== undefined)
352
+ this.expandIconPlacement = l.expandIconPlacement;
353
+ }
354
+ if (c.search) {
355
+ if (c.search.placeholder !== undefined)
356
+ this.searchPlaceholder = c.search.placeholder;
357
+ if (c.search.disableLocal !== undefined)
358
+ this.disableLocalSearch = c.search.disableLocal;
359
+ }
360
+ if (c.virtualization) {
361
+ if (c.virtualization.enabled !== undefined)
362
+ this.virtualize = c.virtualization.enabled;
363
+ if (c.virtualization.threshold !== undefined)
364
+ this.virtualizeThreshold = c.virtualization.threshold;
365
+ if (c.virtualization.overscan !== undefined)
366
+ this.overscan = c.virtualization.overscan;
367
+ }
368
+ if (c.pagination) {
369
+ if (c.pagination.totalCount !== undefined)
370
+ this.totalCount = c.pagination.totalCount;
371
+ if (c.pagination.pageSize !== undefined)
372
+ this.pageSize = c.pagination.pageSize;
373
+ if (c.pagination.fetchPage !== undefined)
374
+ this.fetchPage = c.pagination.fetchPage;
375
+ if (c.pagination.placeholderLabel !== undefined)
376
+ this.placeholderLabel = c.pagination.placeholderLabel;
377
+ }
378
+ if (c.labels) {
379
+ if (c.labels.restore !== undefined)
380
+ this.restoreLabel = c.labels.restore;
381
+ if (c.labels.clear !== undefined)
382
+ this.clearLabel = c.labels.clear;
383
+ }
384
+ }
385
+ _syncSelectedFromProperty() {
386
+ this._selectedSet = new Set(this.selected || []);
387
+ }
388
+ _getItemValue(item) {
389
+ const rc = this.renderItemConfig;
390
+ if (rc?.getValue)
391
+ return String(rc.getValue(item));
392
+ if (typeof item === 'string')
393
+ return item;
394
+ return String(item.value ?? '');
395
+ }
396
+ _getItemLabel(item) {
397
+ const rc = this.renderItemConfig;
398
+ if (rc?.getDisplayText)
399
+ return rc.getDisplayText(item);
400
+ if (typeof item === 'string')
401
+ return item;
402
+ return item.label ?? this._getItemValue(item);
403
+ }
404
+ _getItemSearchText(item) {
405
+ const rc = this.renderItemConfig;
406
+ if (rc?.getSearchText)
407
+ return rc.getSearchText(item);
408
+ return this._getItemLabel(item);
409
+ }
410
+ _getItemDescription(item) {
411
+ const rc = this.renderItemConfig;
412
+ if (rc?.getDescription)
413
+ return rc.getDescription(item) ?? '';
414
+ if (typeof item === 'string')
415
+ return '';
416
+ return item.description ?? '';
417
+ }
418
+ _getItemPrefix(item) {
419
+ const rc = this.renderItemConfig;
420
+ if (rc?.getPrefix)
421
+ return rc.getPrefix(item) ?? '';
422
+ if (typeof item === 'string')
423
+ return '';
424
+ return item.prefix ?? '';
425
+ }
426
+ _getItemSuffix(item) {
427
+ const rc = this.renderItemConfig;
428
+ if (rc?.getSuffix)
429
+ return rc.getSuffix(item) ?? '';
430
+ if (typeof item === 'string')
431
+ return '';
432
+ return item.suffix ?? '';
433
+ }
434
+ _getItemDisabled(item) {
435
+ const rc = this.renderItemConfig;
436
+ if (rc?.getDisabled)
437
+ return !!rc.getDisabled(item);
438
+ if (typeof item === 'string')
439
+ return false;
440
+ return !!item.disabled;
441
+ }
442
+ _getItemClassName(item) {
443
+ const rc = this.renderItemConfig;
444
+ if (rc?.getClassName)
445
+ return rc.getClassName(item) ?? '';
446
+ if (typeof item === 'string')
447
+ return '';
448
+ return item.className ?? '';
449
+ }
450
+ get _filteredItems() {
451
+ if (this.disableLocalSearch)
452
+ return this.items;
453
+ const q = this._searchTerm.trim().toLowerCase();
454
+ if (!q)
455
+ return this.items;
456
+ return this.items.filter(item => this._getItemSearchText(item).toLowerCase().includes(q));
457
+ }
458
+ _selectableValues() {
459
+ const source = this._isInfiniteMode
460
+ ? Array.from(this._pageCache.values()).flat()
461
+ : this.items;
462
+ return source
463
+ .filter(i => !this._getItemDisabled(i))
464
+ .map(i => this._getItemValue(i));
465
+ }
466
+ get _allChecked() {
467
+ const selectable = this._selectableValues();
468
+ if (selectable.length === 0)
469
+ return false;
470
+ return selectable.every(v => this._selectedSet.has(v));
471
+ }
472
+ get _indeterminate() {
473
+ const selectable = this._selectableValues();
474
+ const sel = selectable.filter(v => this._selectedSet.has(v)).length;
475
+ return sel > 0 && sel < selectable.length;
476
+ }
477
+ get _countLabel() {
478
+ const total = this._isInfiniteMode ? (this.totalCount ?? 0) : this.items.length;
479
+ return `${this._selectedSet.size} of ${total} ${this.itemsLabel} selected`;
480
+ }
481
+ _emitSelectionChange() {
482
+ this.selected = [...this._selectedSet];
483
+ this.dispatchEvent(new CustomEvent('nile-change', {
484
+ detail: { selected: [...this._selectedSet] },
485
+ bubbles: true,
486
+ composed: true,
487
+ }));
488
+ }
489
+ _onItemChange(value, disabled, event) {
490
+ if (disabled) {
491
+ event.preventDefault();
492
+ return;
493
+ }
494
+ const target = event.target;
495
+ const next = new Set(this._selectedSet);
496
+ if (target.checked)
497
+ next.add(value);
498
+ else
499
+ next.delete(value);
500
+ this._selectedSet = next;
501
+ this._emitSelectionChange();
502
+ }
503
+ _onSelectAllChange(event) {
504
+ const target = event.target;
505
+ const selectable = this._selectableValues();
506
+ const next = new Set(this._selectedSet);
507
+ if (target.checked)
508
+ selectable.forEach(v => next.add(v));
509
+ else
510
+ selectable.forEach(v => next.delete(v));
511
+ this._selectedSet = next;
512
+ this._emitSelectionChange();
513
+ }
514
+ _emitSearch(value) {
515
+ this.dispatchEvent(new CustomEvent('nile-search', {
516
+ detail: { value },
517
+ bubbles: true,
518
+ composed: true,
519
+ }));
520
+ }
521
+ _onSearchInput(event) {
522
+ const target = event.target;
523
+ const value = target.value ?? '';
524
+ this._searchTerm = value;
525
+ if (this.disableLocalSearch)
526
+ this._emitSearch(value);
527
+ }
528
+ _onSearchClear() {
529
+ this._searchTerm = '';
530
+ if (this.disableLocalSearch)
531
+ this._emitSearch('');
532
+ }
533
+ _onRestore(event) {
534
+ event.preventDefault();
535
+ const source = this._restoreDefaults ?? this._selectableValues();
536
+ this._selectedSet = new Set(source);
537
+ this._emitSelectionChange();
538
+ }
539
+ _onClear(event) {
540
+ event.preventDefault();
541
+ this._selectedSet = new Set();
542
+ this._emitSelectionChange();
543
+ }
544
+ _stopHeaderToggle(event) {
545
+ event.stopPropagation();
23
546
  }
24
547
  _handleSummaryClick(event) {
25
548
  event.preventDefault();
@@ -47,7 +570,214 @@ let NileDetail = class NileDetail extends NileElement {
47
570
  get _summaryLabel() {
48
571
  return [this.heading, this.description].filter(Boolean).join(', ');
49
572
  }
573
+ _renderSelectionLabel() {
574
+ return html `
575
+ <div part="selection-label" class="detail__selection-label">
576
+ <nile-checkbox
577
+ part="select-all"
578
+ class="detail__select-all"
579
+ ?checked=${this._allChecked}
580
+ ?indeterminate=${this._indeterminate}
581
+ aria-label=${`Select all ${this.heading || this.itemsLabel}`}
582
+ @click=${this._stopHeaderToggle}
583
+ @nile-change=${this._onSelectAllChange}
584
+ ></nile-checkbox>
585
+ <div class="detail__selection-title">
586
+ <span part="label-text" class="detail__heading-text">${this.heading}</span>
587
+ <span part="selection-count" class="detail__selection-count">${this._countLabel}</span>
588
+ </div>
589
+ </div>
590
+ `;
591
+ }
592
+ _renderSelectionBody() {
593
+ const virtualized = this._isVirtualized;
594
+ const isVertical = this.orientation === 'vertical';
595
+ const isBoth = this.orientation === 'both';
596
+ const laneHeightPx = this.laneHeight;
597
+ const columnWidth = this._columnWidthPx;
598
+ let gridStyle = '';
599
+ if (virtualized) {
600
+ if (isBoth) {
601
+ gridStyle = `max-height: ${this.maxHeight}; height: ${this.maxHeight};`;
602
+ }
603
+ else if (isVertical) {
604
+ gridStyle = `max-height: ${this.maxHeight}; height: ${this.maxHeight};`;
605
+ }
606
+ else {
607
+ const lanes = Math.max(1, this.gridRows);
608
+ gridStyle = `height: ${lanes * laneHeightPx}px;`;
609
+ }
610
+ }
611
+ else if (isVertical) {
612
+ const cols = Math.max(1, this.gridColumns);
613
+ gridStyle = `grid-template-columns: repeat(${cols}, minmax(0, 1fr)); grid-auto-flow: row; max-height: ${this.maxHeight}; overflow-y: auto; overflow-x: hidden;`;
614
+ }
615
+ else {
616
+ const lanes = Math.max(1, this.gridRows);
617
+ gridStyle = `grid-template-rows: repeat(${lanes}, auto); grid-auto-columns: minmax(${this.minColumnWidth}, 1fr);`;
618
+ }
619
+ return html `
620
+ <div part="selection-toolbar" class="detail__selection-toolbar">
621
+ <nile-input
622
+ part="search"
623
+ class="detail__selection-search"
624
+ placeholder=${this.searchPlaceholder}
625
+ clearable
626
+ .value=${this._searchTerm}
627
+ @nile-input=${this._onSearchInput}
628
+ @nile-clear=${this._onSearchClear}
629
+ >
630
+ <nile-icon slot="prefix" name="search" library="system"></nile-icon>
631
+ </nile-input>
632
+ <div part="selection-actions" class="detail__selection-actions">
633
+ <nile-link href="#" variant="subtle" @click=${this._onRestore}>${this.restoreLabel}</nile-link>
634
+ <nile-link href="#" variant="subtle" @click=${this._onClear}>${this.clearLabel}</nile-link>
635
+ </div>
636
+ </div>
637
+ <div
638
+ part="selection-grid"
639
+ class=${classMap({
640
+ 'detail__selection-grid': true,
641
+ 'detail__selection-grid--virtual': virtualized,
642
+ 'detail__selection-grid--vertical': isVertical,
643
+ 'detail__selection-grid--both': isBoth,
644
+ })}
645
+ style=${gridStyle}
646
+ >
647
+ ${virtualized
648
+ ? (isBoth
649
+ ? this._renderBothVirtualGrid(laneHeightPx, columnWidth)
650
+ : this._renderVirtualGrid(laneHeightPx, columnWidth, isVertical))
651
+ : this._renderStaticGrid()}
652
+ </div>
653
+ `;
654
+ }
655
+ _renderItemContent(item) {
656
+ const label = this._getItemLabel(item);
657
+ const description = this._getItemDescription(item);
658
+ const prefix = this._getItemPrefix(item);
659
+ const suffix = this._getItemSuffix(item);
660
+ const allowHtml = this.allowHtmlLabel;
661
+ return html `
662
+ ${prefix
663
+ ? html `<span class="detail__selection-prefix" part="item-prefix">${allowHtml ? unsafeHTML(prefix) : prefix}</span>`
664
+ : ''}
665
+ <span class="detail__selection-text" part="item-text">
666
+ <span class="detail__selection-item-label" part="item-label">${allowHtml ? unsafeHTML(label) : label}</span>
667
+ ${description
668
+ ? html `<span class="detail__selection-desc" part="item-description">${allowHtml ? unsafeHTML(description) : description}</span>`
669
+ : ''}
670
+ </span>
671
+ ${suffix
672
+ ? html `<span class="detail__selection-suffix" part="item-suffix">${allowHtml ? unsafeHTML(suffix) : suffix}</span>`
673
+ : ''}
674
+ `;
675
+ }
676
+ _renderItemTooltip(item, opts = {}) {
677
+ const value = this._getItemValue(item);
678
+ const label = this._getItemLabel(item);
679
+ const description = this._getItemDescription(item);
680
+ const isDisabled = this._getItemDisabled(item);
681
+ const className = this._getItemClassName(item);
682
+ const tooltipContent = description ? `${label} — ${description}` : label;
683
+ const tooltipClass = `detail__selection-tooltip${opts.extraClasses ? ' ' + opts.extraClasses : ''}`;
684
+ const checkboxClass = classMap({
685
+ 'detail__selection-checkbox': true,
686
+ 'detail__selection-checkbox--disabled': isDisabled,
687
+ [className]: !!className,
688
+ });
689
+ return html `
690
+ <nile-lite-tooltip
691
+ class=${tooltipClass}
692
+ content=${tooltipContent}
693
+ size="small"
694
+ disabled
695
+ style=${opts.positionStyle ?? nothing}
696
+ >
697
+ <nile-checkbox
698
+ class=${checkboxClass}
699
+ ?checked=${this._selectedSet.has(value)}
700
+ ?disabled=${isDisabled}
701
+ @nile-change=${(e) => this._onItemChange(value, isDisabled, e)}
702
+ >${this._renderItemContent(item)}</nile-checkbox>
703
+ </nile-lite-tooltip>
704
+ `;
705
+ }
706
+ _renderStaticGrid() {
707
+ return html `${this._filteredItems.map(item => this._renderItemTooltip(item))}`;
708
+ }
709
+ _renderBothVirtualGrid(_laneHeightPx, _columnWidthPx) {
710
+ const rowV = this._rowVirtCtrl?.getVirtualizer();
711
+ const colV = this._colVirtCtrl?.getVirtualizer();
712
+ const rowTotal = rowV?.getTotalSize() ?? 0;
713
+ const colTotal = colV?.getTotalSize() ?? 0;
714
+ const rowItems = rowV?.getVirtualItems() ?? [];
715
+ const colItems = colV?.getVirtualItems() ?? [];
716
+ const cols = Math.max(1, this.matrixColumns);
717
+ const total = this._filteredItems.length;
718
+ return html `
719
+ <div
720
+ class="detail__selection-track"
721
+ style="width: ${colTotal}px; height: ${rowTotal}px; position: relative;"
722
+ >
723
+ ${rowItems.flatMap(rvi => colItems.map(cvi => {
724
+ const idx = rvi.index * cols + cvi.index;
725
+ if (idx >= total)
726
+ return null;
727
+ const item = this._filteredItems[idx];
728
+ if (item === undefined)
729
+ return null;
730
+ const positionStyle = `position: absolute; top: ${rvi.start}px; left: ${cvi.start}px; width: ${cvi.size}px; height: ${rvi.size}px;`;
731
+ return this._renderItemTooltip(item, {
732
+ positionStyle,
733
+ extraClasses: 'detail__selection-tooltip--virtual',
734
+ });
735
+ }))}
736
+ </div>
737
+ `;
738
+ }
739
+ _renderVirtualGrid(laneHeightPx, _columnWidthPx, isVertical) {
740
+ const v = this._virtCtrl?.getVirtualizer();
741
+ const totalSize = v?.getTotalSize() ?? 0;
742
+ const virtualItems = v?.getVirtualItems() ?? [];
743
+ const lanes = Math.max(1, isVertical ? this.gridColumns : this.gridRows);
744
+ if (this._isInfiniteMode && virtualItems.length > 0) {
745
+ const first = virtualItems[0].index;
746
+ const last = virtualItems[virtualItems.length - 1].index;
747
+ this._scheduleFetchForRange(first, last);
748
+ }
749
+ const trackStyle = isVertical
750
+ ? `width: 100%; height: ${totalSize}px; position: relative;`
751
+ : `width: ${totalSize}px; height: 100%; position: relative;`;
752
+ return html `
753
+ <div class="detail__selection-track" style=${trackStyle}>
754
+ ${virtualItems.map(vi => {
755
+ const laneIdx = vi.lane ?? 0;
756
+ const positionStyle = isVertical
757
+ ? `position: absolute; top: ${vi.start}px; left: calc(${laneIdx} * (100% / ${lanes})); width: calc(100% / ${lanes}); height: ${vi.size}px;`
758
+ : `position: absolute; top: ${laneIdx * laneHeightPx}px; left: ${vi.start}px; width: ${vi.size}px; height: ${laneHeightPx}px;`;
759
+ const item = this._getItemAt(vi.index);
760
+ if (item === undefined) {
761
+ return this._renderPlaceholder(positionStyle);
762
+ }
763
+ return this._renderItemTooltip(item, {
764
+ positionStyle,
765
+ extraClasses: 'detail__selection-tooltip--virtual',
766
+ });
767
+ })}
768
+ </div>
769
+ `;
770
+ }
771
+ _renderPlaceholder(positionStyle) {
772
+ return html `
773
+ <div class="detail__selection-placeholder" style=${positionStyle} part="item-placeholder">
774
+ <div class="detail__selection-placeholder-bar"></div>
775
+ <span class="detail__selection-placeholder-label">${this.placeholderLabel}</span>
776
+ </div>
777
+ `;
778
+ }
50
779
  render() {
780
+ const isSelection = this.variant === 'selection';
51
781
  return html `
52
782
  <details
53
783
  part="base"
@@ -56,6 +786,7 @@ let NileDetail = class NileDetail extends NileElement {
56
786
  'detail': true,
57
787
  'detail--open': this.open,
58
788
  'detail--disabled': this.disabled,
789
+ 'detail--selection': isSelection,
59
790
  })}
60
791
  >
61
792
  <summary
@@ -73,10 +804,14 @@ let NileDetail = class NileDetail extends NileElement {
73
804
  @keydown=${this._handleSummaryKeyDown}
74
805
  >
75
806
  <slot name="label" part="label" class="detail__label">
76
- <span part="label-text" class="detail__heading-text">${this.heading}</span>
77
- ${this.description
78
- ? html `<span part="description" class="detail__description">${this.description}</span>`
79
- : ''}
807
+ ${isSelection
808
+ ? this._renderSelectionLabel()
809
+ : html `
810
+ <span part="label-text" class="detail__heading-text">${this.heading}</span>
811
+ ${this.description
812
+ ? html `<span part="description" class="detail__description">${this.description}</span>`
813
+ : ''}
814
+ `}
80
815
  </slot>
81
816
 
82
817
  <slot name="header-actions" part="header-actions" class="detail__header-actions"></slot>
@@ -98,6 +833,9 @@ let NileDetail = class NileDetail extends NileElement {
98
833
  </summary>
99
834
 
100
835
  <div part="content" class="detail__body">
836
+ ${isSelection
837
+ ? html `<div part="selection-content" class="detail__selection-content">${this._renderSelectionBody()}</div>`
838
+ : ''}
101
839
  <slot id="content" class="detail__content"></slot>
102
840
  </div>
103
841
  </details>
@@ -129,9 +867,96 @@ __decorate([
129
867
  __decorate([
130
868
  property({ attribute: true, type: Boolean, reflect: true })
131
869
  ], NileDetail.prototype, "disabled", void 0);
870
+ __decorate([
871
+ property({ attribute: true, type: String, reflect: true })
872
+ ], NileDetail.prototype, "variant", void 0);
873
+ __decorate([
874
+ property({ attribute: false, type: Array })
875
+ ], NileDetail.prototype, "items", void 0);
876
+ __decorate([
877
+ property({ attribute: false, type: Array })
878
+ ], NileDetail.prototype, "selected", void 0);
879
+ __decorate([
880
+ property({ attribute: false })
881
+ ], NileDetail.prototype, "renderItemConfig", void 0);
882
+ __decorate([
883
+ property({ attribute: 'allow-html-label', type: Boolean })
884
+ ], NileDetail.prototype, "allowHtmlLabel", void 0);
885
+ __decorate([
886
+ property({ attribute: 'disable-local-search', type: Boolean })
887
+ ], NileDetail.prototype, "disableLocalSearch", void 0);
888
+ __decorate([
889
+ property({ attribute: 'total-count', type: Number })
890
+ ], NileDetail.prototype, "totalCount", void 0);
891
+ __decorate([
892
+ property({ attribute: 'page-size', type: Number })
893
+ ], NileDetail.prototype, "pageSize", void 0);
894
+ __decorate([
895
+ property({ attribute: false })
896
+ ], NileDetail.prototype, "fetchPage", void 0);
897
+ __decorate([
898
+ property({ attribute: 'placeholder-label', type: String })
899
+ ], NileDetail.prototype, "placeholderLabel", void 0);
900
+ __decorate([
901
+ property({ attribute: false })
902
+ ], NileDetail.prototype, "config", void 0);
903
+ __decorate([
904
+ property({ attribute: 'search-placeholder', type: String })
905
+ ], NileDetail.prototype, "searchPlaceholder", void 0);
906
+ __decorate([
907
+ property({ attribute: 'items-label', type: String })
908
+ ], NileDetail.prototype, "itemsLabel", void 0);
909
+ __decorate([
910
+ property({ attribute: 'restore-label', type: String })
911
+ ], NileDetail.prototype, "restoreLabel", void 0);
912
+ __decorate([
913
+ property({ attribute: 'clear-label', type: String })
914
+ ], NileDetail.prototype, "clearLabel", void 0);
915
+ __decorate([
916
+ property({ attribute: 'grid-rows', type: Number })
917
+ ], NileDetail.prototype, "gridRows", void 0);
918
+ __decorate([
919
+ property({ attribute: 'grid-columns', type: Number })
920
+ ], NileDetail.prototype, "gridColumns", void 0);
921
+ __decorate([
922
+ property({ attribute: 'min-column-width', type: String })
923
+ ], NileDetail.prototype, "minColumnWidth", void 0);
924
+ __decorate([
925
+ property({ attribute: 'lane-height', type: Number })
926
+ ], NileDetail.prototype, "laneHeight", void 0);
927
+ __decorate([
928
+ property({ attribute: true, type: String, reflect: true })
929
+ ], NileDetail.prototype, "orientation", void 0);
930
+ __decorate([
931
+ property({ attribute: 'max-height', type: String })
932
+ ], NileDetail.prototype, "maxHeight", void 0);
933
+ __decorate([
934
+ property({ attribute: 'matrix-columns', type: Number })
935
+ ], NileDetail.prototype, "matrixColumns", void 0);
936
+ __decorate([
937
+ property({ attribute: true, type: Boolean })
938
+ ], NileDetail.prototype, "virtualize", void 0);
939
+ __decorate([
940
+ property({ attribute: 'virtualize-threshold', type: Number })
941
+ ], NileDetail.prototype, "virtualizeThreshold", void 0);
942
+ __decorate([
943
+ property({ attribute: 'overscan', type: Number })
944
+ ], NileDetail.prototype, "overscan", void 0);
132
945
  __decorate([
133
946
  state()
134
947
  ], NileDetail.prototype, "_detailOpen", void 0);
948
+ __decorate([
949
+ state()
950
+ ], NileDetail.prototype, "_searchTerm", void 0);
951
+ __decorate([
952
+ state()
953
+ ], NileDetail.prototype, "_selectedSet", void 0);
954
+ __decorate([
955
+ watch('selected', { waitUntilFirstUpdate: false })
956
+ ], NileDetail.prototype, "_onSelectedPropertyChange", null);
957
+ __decorate([
958
+ watch('config', { waitUntilFirstUpdate: false })
959
+ ], NileDetail.prototype, "_onConfigChange", null);
135
960
  __decorate([
136
961
  watch('open', { waitUntilFirstUpdate: true })
137
962
  ], NileDetail.prototype, "handleOpenChange", null);