@ixfx/components 0.13.0 → 0.13.2

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 (52) hide show
  1. package/bundle/index.d.ts +217 -73
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +375 -13
  4. package/bundle/index.js.map +1 -1
  5. package/dist/{button-BYuf72aR.d.ts → button-CTL45aBl.d.ts} +2 -2
  6. package/dist/{button-BYuf72aR.d.ts.map → button-CTL45aBl.d.ts.map} +1 -1
  7. package/dist/button.d.ts +1 -1
  8. package/dist/crumbs.d.ts +2 -2
  9. package/dist/{data-grid-BJ6XXvRc.d.ts → data-grid-Ck1Qx2H1.d.ts} +4 -4
  10. package/dist/{data-grid-BJ6XXvRc.d.ts.map → data-grid-Ck1Qx2H1.d.ts.map} +1 -1
  11. package/dist/data-grid.d.ts +2 -2
  12. package/dist/{detail-list-B0waVNeS.d.ts → detail-list-C1xzr1z7.d.ts} +4 -4
  13. package/dist/{detail-list-B0waVNeS.d.ts.map → detail-list-C1xzr1z7.d.ts.map} +1 -1
  14. package/dist/detail-list.d.ts +3 -3
  15. package/dist/editable-label-rzXkjf1H.d.ts.map +1 -1
  16. package/dist/editable-label.js +2 -2
  17. package/dist/editable-label.js.map +1 -1
  18. package/dist/{flat-list-eYItri8-.d.ts → flat-list-Ohxqsy0g.d.ts} +63 -5
  19. package/dist/flat-list-Ohxqsy0g.d.ts.map +1 -0
  20. package/dist/flat-list.d.ts +2 -2
  21. package/dist/flat-list.js +172 -9
  22. package/dist/flat-list.js.map +1 -1
  23. package/dist/{grid-list-_gI9ckcT.d.ts → grid-list-pefLORIr.d.ts} +4 -4
  24. package/dist/{grid-list-_gI9ckcT.d.ts.map → grid-list-pefLORIr.d.ts.map} +1 -1
  25. package/dist/grid-list.d.ts +2 -2
  26. package/dist/incr-search.d.ts +1 -1
  27. package/dist/{index-BvxHQUME.d.ts → index-CxZ61DO1.d.ts} +3 -3
  28. package/dist/{index-BvxHQUME.d.ts.map → index-CxZ61DO1.d.ts.map} +1 -1
  29. package/dist/{index-CW2QL33o.d.ts → index-Dq30pOnn.d.ts} +2 -2
  30. package/dist/{index-CW2QL33o.d.ts.map → index-Dq30pOnn.d.ts.map} +1 -1
  31. package/dist/index.d.ts +13 -13
  32. package/dist/{list-native-drag-controller-CllsHLxw.d.ts → list-native-drag-controller-ChIi3eK5.d.ts} +2 -2
  33. package/dist/{list-native-drag-controller-CllsHLxw.d.ts.map → list-native-drag-controller-ChIi3eK5.d.ts.map} +1 -1
  34. package/dist/menu.d.ts +1 -1
  35. package/dist/miller.d.ts +3 -3
  36. package/dist/{tree-component-DukLqesg.d.ts → tree-component-Cj8V3uar.d.ts} +2 -2
  37. package/dist/{tree-component-DukLqesg.d.ts.map → tree-component-Cj8V3uar.d.ts.map} +1 -1
  38. package/dist/tree.d.ts +4 -4
  39. package/dist/{types-D-kYF16b.d.ts → types-DQ7KOUIj.d.ts} +6 -1
  40. package/dist/types-DQ7KOUIj.d.ts.map +1 -0
  41. package/dist/{vertical-list-DheZSEmB.d.ts → vertical-list-CqBc-t8w.d.ts} +86 -4
  42. package/dist/vertical-list-CqBc-t8w.d.ts.map +1 -0
  43. package/dist/vertical-list.d.ts +3 -3
  44. package/dist/vertical-list.js +202 -2
  45. package/dist/vertical-list.js.map +1 -1
  46. package/docs-user/flat-list.md +45 -3
  47. package/docs-user/index.json +2 -2
  48. package/docs-user/vertical-list.md +59 -3
  49. package/package.json +1 -1
  50. package/dist/flat-list-eYItri8-.d.ts.map +0 -1
  51. package/dist/types-D-kYF16b.d.ts.map +0 -1
  52. package/dist/vertical-list-DheZSEmB.d.ts.map +0 -1
package/bundle/index.js CHANGED
@@ -23809,7 +23809,7 @@ let EditableLabel = class EditableLabel extends LitElement {
23809
23809
  @keydown=${this._handleKeydown}
23810
23810
  @beforeinput=${this._handleBeforeInput}
23811
23811
  @input=${this._handleInput}
23812
- >${this._editing ? nothing : this._getDisplayText()}</div>`;
23812
+ ></div>`;
23813
23813
  }
23814
23814
  updated(changed) {
23815
23815
  super.updated(changed);
@@ -23821,7 +23821,7 @@ let EditableLabel = class EditableLabel extends LitElement {
23821
23821
  _selectAll(el);
23822
23822
  }
23823
23823
  }
23824
- if (changed.has(`displayTextLogic`) || changed.has(`displayText`) || changed.has(`formatter`) || changed.has(`value`)) this._requestDisplayUpdate();
23824
+ if (changed.has(`_editing`) && !this._editing || changed.has(`displayTextLogic`) || changed.has(`displayText`) || changed.has(`formatter`) || changed.has(`value`)) this._requestDisplayUpdate();
23825
23825
  }
23826
23826
  static {
23827
23827
  this.styles = [themeFallbacks, css`
@@ -24695,6 +24695,7 @@ let VerticalListElement = class VerticalListElement extends LitElement {
24695
24695
  this.keyProperty = ``;
24696
24696
  this.virtualize = false;
24697
24697
  this.itemHeight = 0;
24698
+ this.itemsDroppable = false;
24698
24699
  this.#customWrappers = /* @__PURE__ */ new Map();
24699
24700
  this.#wrappedElements = /* @__PURE__ */ new Set();
24700
24701
  this.#itemData = /* @__PURE__ */ new WeakMap();
@@ -24814,7 +24815,16 @@ let VerticalListElement = class VerticalListElement extends LitElement {
24814
24815
  this.tickled.handlePointerLeave(e);
24815
24816
  };
24816
24817
  this._onKeyDown = (e) => {
24817
- if (this.search.isOpen) return;
24818
+ if (this.search.isOpen || this.#isEditEvent(e)) return;
24819
+ if (e.key === `ContextMenu` || e.shiftKey && e.key === `F10`) {
24820
+ const id = this.tickled.tickledId;
24821
+ if (id) {
24822
+ e.preventDefault();
24823
+ const rect = id.getBoundingClientRect();
24824
+ this.#requestContextMenu(id, rect.left, rect.bottom);
24825
+ }
24826
+ return;
24827
+ }
24818
24828
  if ((e.ctrlKey || e.metaKey) && e.key === `a`) {
24819
24829
  if (this.selection.canSelectAll()) {
24820
24830
  e.preventDefault();
@@ -24868,6 +24878,7 @@ let VerticalListElement = class VerticalListElement extends LitElement {
24868
24878
  }
24869
24879
  };
24870
24880
  this._onClick = (e) => {
24881
+ if (this.#isEditEvent(e)) return;
24871
24882
  if (this.#dragSelect.isDragging || this.#nativeDrag.isDragging || this.#postDragSuppressTimer !== 0) return;
24872
24883
  const item = this._findListItem(e.composedPath());
24873
24884
  if (!item) return;
@@ -24875,9 +24886,60 @@ let VerticalListElement = class VerticalListElement extends LitElement {
24875
24886
  this.selection.handleClick(item, e);
24876
24887
  };
24877
24888
  this._onDblClick = (e) => {
24889
+ if (this.#isEditEvent(e)) return;
24878
24890
  const item = this._findListItem(e.composedPath());
24879
24891
  if (item) this._activateItem(item);
24880
24892
  };
24893
+ this._onDragOver = (e) => {
24894
+ const dt = e.dataTransfer;
24895
+ if (!this.itemsDroppable || !dt || this.#isEditEvent(e)) return;
24896
+ const item = this._findListItem(e.composedPath());
24897
+ if (this.canDrop && !this.canDrop(item, dt)) {
24898
+ this.#clearDropTarget();
24899
+ return;
24900
+ }
24901
+ if (this.#dragOver && this.#dragOver.item !== item) this.#clearDropTarget();
24902
+ this.#dragOver = { item };
24903
+ if (!!this._dispatchCancelable(`list-drag-over`, {
24904
+ key: item ? keyForElement(item) : void 0,
24905
+ item,
24906
+ dataTransfer: dt
24907
+ })) {
24908
+ this.#highlightDropTarget(void 0, false);
24909
+ return;
24910
+ }
24911
+ e.preventDefault();
24912
+ if (dt.dropEffect === `none`) dt.dropEffect = `copy`;
24913
+ this.#highlightDropTarget(item, true);
24914
+ };
24915
+ this._onDragLeave = (e) => {
24916
+ const next = e.relatedTarget;
24917
+ if (next instanceof Node && this.contains(next)) return;
24918
+ this.#clearDropTarget();
24919
+ };
24920
+ this._onDrop = (e) => {
24921
+ const dt = e.dataTransfer;
24922
+ if (!this.itemsDroppable || !dt || this.#isEditEvent(e)) return;
24923
+ const item = this._findListItem(e.composedPath());
24924
+ if (this.canDrop && !this.canDrop(item, dt)) {
24925
+ this.#clearDropTarget();
24926
+ return;
24927
+ }
24928
+ e.preventDefault();
24929
+ this.#dragOver = void 0;
24930
+ this.#highlightDropTarget(void 0, false);
24931
+ this._dispatch(`list-drop`, {
24932
+ key: item ? keyForElement(item) : void 0,
24933
+ item,
24934
+ dataTransfer: dt
24935
+ });
24936
+ };
24937
+ this._onContextMenu = (e) => {
24938
+ if (this.#isEditEvent(e)) return;
24939
+ const item = this._findListItem(e.composedPath());
24940
+ if (!item) return;
24941
+ if (!this.#requestContextMenu(item, e.clientX, e.clientY)) e.preventDefault();
24942
+ };
24881
24943
  }
24882
24944
  #customWrappers;
24883
24945
  #wrappedElements;
@@ -25004,6 +25066,10 @@ let VerticalListElement = class VerticalListElement extends LitElement {
25004
25066
  this.addEventListener(`keydown`, this._onKeyDown);
25005
25067
  this.addEventListener(`click`, this._onClick);
25006
25068
  this.addEventListener(`dblclick`, this._onDblClick);
25069
+ this.addEventListener(`dragover`, this._onDragOver);
25070
+ this.addEventListener(`dragleave`, this._onDragLeave);
25071
+ this.addEventListener(`drop`, this._onDrop);
25072
+ this.addEventListener(`contextmenu`, this._onContextMenu);
25007
25073
  for (const el of this.querySelectorAll(ITEM_SELECTOR$2)) this.#animatedItems.add(el);
25008
25074
  this.#itemObserver ??= new MutationObserver((records) => this.#onItemsMutated(records));
25009
25075
  this.#itemObserver.observe(this, { childList: true });
@@ -25018,6 +25084,12 @@ let VerticalListElement = class VerticalListElement extends LitElement {
25018
25084
  this.removeEventListener(`keydown`, this._onKeyDown);
25019
25085
  this.removeEventListener(`click`, this._onClick);
25020
25086
  this.removeEventListener(`dblclick`, this._onDblClick);
25087
+ this.removeEventListener(`dragover`, this._onDragOver);
25088
+ this.removeEventListener(`dragleave`, this._onDragLeave);
25089
+ this.removeEventListener(`drop`, this._onDrop);
25090
+ this.removeEventListener(`contextmenu`, this._onContextMenu);
25091
+ this.#clearDropTarget();
25092
+ this.#edit = void 0;
25021
25093
  this.#itemObserver?.disconnect();
25022
25094
  this.#resizeObserver?.disconnect();
25023
25095
  this.#resizeObserver = void 0;
@@ -25704,7 +25776,7 @@ let VerticalListElement = class VerticalListElement extends LitElement {
25704
25776
  }
25705
25777
  /** Per-attach state the attached sweep can't cover (it only sees the window). */
25706
25778
  #applyRowState(el) {
25707
- if (el instanceof HTMLElement) el.draggable = this.itemsDraggable && this._isCheckable(el);
25779
+ if (el instanceof HTMLElement) el.draggable = this.itemsDraggable && this._isCheckable(el) && !el.hasAttribute(`data-editing`);
25708
25780
  el.removeAttribute(`data-marquee`);
25709
25781
  el.removeAttribute(`data-drag-origin`);
25710
25782
  }
@@ -25914,6 +25986,114 @@ let VerticalListElement = class VerticalListElement extends LitElement {
25914
25986
  if (this.interactionMode !== `manual`) this.selection.replace(item);
25915
25987
  this._dispatch(`list-activate`, { item });
25916
25988
  }
25989
+ /** The row (or `undefined` for empty space) the last `list-drag-over` was fired for. */
25990
+ #dragOver;
25991
+ /** Tell the consumer the drag left its last target and drop the highlight. */
25992
+ #clearDropTarget() {
25993
+ const last = this.#dragOver;
25994
+ this.#dragOver = void 0;
25995
+ this.#highlightDropTarget(void 0, false);
25996
+ if (last) this._dispatch(`list-drag-leave`, { key: last.item ? keyForElement(last.item) : void 0 });
25997
+ }
25998
+ /** Mark one row (or, for empty space, the host) as the drop target. */
25999
+ #highlightDropTarget(item, active) {
26000
+ for (const el of this.querySelectorAll(`[data-drop-target]`)) el.removeAttribute(`data-drop-target`);
26001
+ this.toggleAttribute(`data-drop-target`, active && item === void 0);
26002
+ if (active && item) item.setAttribute(`data-drop-target`, ``);
26003
+ }
26004
+ /**
26005
+ * Select the row if it is not already, then fire `list-context-menu`.
26006
+ * Returns false when a listener cancelled it (the native menu is then suppressed).
26007
+ */
26008
+ #requestContextMenu(item, x, y) {
26009
+ if (!this.selection.selected.has(item) && this.interactionMode !== `manual` && this._isCheckable(item)) this.selection.replace(item);
26010
+ return this._dispatchCancelable(`list-context-menu`, {
26011
+ key: keyForElement(item),
26012
+ item,
26013
+ x,
26014
+ y,
26015
+ selectedKeys: this.selection.selectedKeys
26016
+ });
26017
+ }
26018
+ #edit;
26019
+ /** The key of the row being edited, if any. */
26020
+ get editingKey() {
26021
+ return this.#edit?.key;
26022
+ }
26023
+ #isEditEvent(e) {
26024
+ return this.#edit !== void 0 && e.composedPath().includes(this.#edit.label);
26025
+ }
26026
+ /**
26027
+ * Edit a row's label in place with an `ixfx-editable-label`. The editor
26028
+ * replaces the row's `[data-edit-label]` element (else the whole row) and
26029
+ * the original content returns when editing ends. Fires `list-edit-commit`
26030
+ * `{ key, value }` when the text changed, otherwise `list-edit-cancel`
26031
+ * `{ key }` (Escape, an unchanged value, or one the label rejects). The list
26032
+ * never rewrites the label itself — update your data in response.
26033
+ *
26034
+ * Returns false when `editValidator` is not set, or the row is unknown or
26035
+ * not currently rendered.
26036
+ */
26037
+ beginEdit(key) {
26038
+ this.#finishEdit();
26039
+ const validate = this.editValidator;
26040
+ if (!validate) return false;
26041
+ const row = this.#itemForKey(key);
26042
+ if (!(row instanceof HTMLElement) || row.parentNode !== this) return false;
26043
+ const container = row.querySelector(`[data-edit-label]`) ?? row;
26044
+ const value = container.getAttribute(`data-edit-value`) ?? container.textContent?.trim() ?? ``;
26045
+ const label = document.createElement(`ixfx-editable-label`);
26046
+ label.mode = `label`;
26047
+ label.value = value;
26048
+ label.validator = (text) => validate(text, key);
26049
+ label.style.cssText = `flex:1;min-width:0;--label-align:left;--label-padding:0 var(--space-xs,3px)`;
26050
+ const edit = {
26051
+ key,
26052
+ row,
26053
+ container,
26054
+ saved: [...container.childNodes],
26055
+ label,
26056
+ committed: void 0,
26057
+ refocus: false
26058
+ };
26059
+ this.#edit = edit;
26060
+ label.addEventListener(`change`, (e) => {
26061
+ e.stopPropagation();
26062
+ edit.committed = e.detail.value;
26063
+ });
26064
+ label.addEventListener(`input`, (e) => e.stopPropagation());
26065
+ label.addEventListener(`keydown`, (e) => {
26066
+ edit.refocus = e.key === `Enter` || e.key === `Escape`;
26067
+ });
26068
+ for (const type of [`pointerdown`, `dragstart`]) label.addEventListener(type, (e) => e.stopPropagation());
26069
+ label.addEventListener(`focusout`, () => this.#finishEdit());
26070
+ container.replaceChildren(label);
26071
+ row.setAttribute(`data-editing`, ``);
26072
+ row.draggable = false;
26073
+ row.scrollIntoView({
26074
+ block: `nearest`,
26075
+ inline: `nearest`
26076
+ });
26077
+ label.updateComplete.then(() => {
26078
+ if (this.#edit === edit) label.shadowRoot?.querySelector(`.content`)?.click();
26079
+ });
26080
+ return true;
26081
+ }
26082
+ /** Restore the row's content and report how the edit ended. */
26083
+ #finishEdit() {
26084
+ const edit = this.#edit;
26085
+ if (!edit) return;
26086
+ this.#edit = void 0;
26087
+ if (edit.container.isConnected) edit.container.replaceChildren(...edit.saved);
26088
+ edit.row.removeAttribute(`data-editing`);
26089
+ this.#applyRowState(edit.row);
26090
+ if (edit.refocus) this.focus();
26091
+ if (edit.committed === void 0) this._dispatch(`list-edit-cancel`, { key: edit.key });
26092
+ else this._dispatch(`list-edit-commit`, {
26093
+ key: edit.key,
26094
+ value: edit.committed
26095
+ });
26096
+ }
25917
26097
  static {
25918
26098
  this.styles = [
25919
26099
  themeFallbacks,
@@ -26074,6 +26254,18 @@ let VerticalListElement = class VerticalListElement extends LitElement {
26074
26254
  opacity: 0.6;
26075
26255
  }
26076
26256
 
26257
+ /* Native drop target: the row under a drag, or the whole list over empty space. */
26258
+ ::slotted([data-drop-target]) {
26259
+ background: var(--list-drop-target-bg, color-mix(in srgb, var(--accent) 25%, transparent));
26260
+ outline: 2px solid var(--list-drop-target-outline, var(--accent));
26261
+ outline-offset: -2px;
26262
+ }
26263
+
26264
+ :host([data-drop-target]) {
26265
+ outline: 2px solid var(--list-drop-target-outline, var(--accent));
26266
+ outline-offset: -2px;
26267
+ }
26268
+
26077
26269
  /* checked mode: checkbox (injected into li) communicates selection — suppress bg highlight */
26078
26270
  :host([interaction-mode="checked"]) ::slotted([data-selected]) {
26079
26271
  background: transparent;
@@ -26146,12 +26338,23 @@ __decorate([property({
26146
26338
  type: Number,
26147
26339
  attribute: `item-height`
26148
26340
  })], VerticalListElement.prototype, "itemHeight", void 0);
26341
+ __decorate([property({
26342
+ type: Boolean,
26343
+ attribute: `items-droppable`,
26344
+ reflect: true
26345
+ })], VerticalListElement.prototype, "itemsDroppable", void 0);
26346
+ __decorate([property({ attribute: false })], VerticalListElement.prototype, "editValidator", void 0);
26347
+ __decorate([property({ attribute: false })], VerticalListElement.prototype, "canDrop", void 0);
26149
26348
  VerticalListElement = __decorate([safeCustomElement(`ixfx-vertical-list`)], VerticalListElement);
26150
26349
  //#endregion
26151
26350
  //#region src/flat-list/flat-list.ts
26152
- /** A node with children, or whose children are still to be loaded. */
26351
+ /**
26352
+ * A node with children, or whose children are still to be loaded.
26353
+ * `isLeaf: false` forces a branch even when `children` is `[]` (an empty
26354
+ * folder that can still be entered); `isLeaf: true` forces a leaf.
26355
+ */
26153
26356
  function isBranch(node) {
26154
- return node.item.isLeaf !== true && node.children?.length !== 0;
26357
+ return node.item.isLeaf === false || node.item.isLeaf !== true && node.children?.length !== 0;
26155
26358
  }
26156
26359
  function sameKeys$1(a, b) {
26157
26360
  return a.length === b.length && a.every((k, i) => k === b[i]);
@@ -26201,15 +26404,77 @@ let FlatListElement = class FlatListElement extends LitElement {
26201
26404
  this.interactionMode = `standard`;
26202
26405
  this.itemWrap = `stop`;
26203
26406
  this.itemsDraggable = false;
26407
+ this.itemsDroppable = false;
26408
+ this.virtualize = false;
26409
+ this.itemHeight = 0;
26204
26410
  this.withUp = true;
26205
26411
  this.hideRoot = false;
26206
26412
  this.emptyText = `Empty`;
26207
26413
  this._focusVisible = false;
26414
+ this._hasToolbar = false;
26208
26415
  this._rows = [];
26209
26416
  this._loading = false;
26210
26417
  this.#lastPath = [];
26211
26418
  this.#selected = /* @__PURE__ */ new Set();
26212
26419
  this.#direction = `jump`;
26420
+ this.#canDropOn = (el, dataTransfer) => {
26421
+ const node = this.#nodeForElement(el);
26422
+ return !!node && (this.canDrop?.(node, dataTransfer) ?? true);
26423
+ };
26424
+ this.#onListDragOver = (e) => {
26425
+ if (this.itemsDroppable) e.preventDefault();
26426
+ };
26427
+ this.#onListDrop = (e) => {
26428
+ const node = this.#nodeForKey(e.detail.key) ?? this.currentNode;
26429
+ if (!node) return;
26430
+ this.dispatchEvent(new CustomEvent(`flat-list-drop`, {
26431
+ detail: {
26432
+ node,
26433
+ dataTransfer: e.detail.dataTransfer
26434
+ },
26435
+ bubbles: true,
26436
+ composed: true
26437
+ }));
26438
+ };
26439
+ this.#onListContextMenu = (e) => {
26440
+ const node = this.#nodeForKey(e.detail.key);
26441
+ if (!node) return;
26442
+ const nodes = [...e.detail.selectedKeys].map((k) => this.#nodeForKey(k)).filter((n) => n !== void 0);
26443
+ if (!this.dispatchEvent(new CustomEvent(`flat-list-context-menu`, {
26444
+ detail: {
26445
+ node,
26446
+ nodes,
26447
+ x: e.detail.x,
26448
+ y: e.detail.y
26449
+ },
26450
+ bubbles: true,
26451
+ composed: true,
26452
+ cancelable: true
26453
+ }))) e.preventDefault();
26454
+ };
26455
+ this.#validateEditKey = (value, key) => {
26456
+ const node = this.#nodeForKey(key);
26457
+ return !!node && (this.validateEdit?.(value, node) ?? false);
26458
+ };
26459
+ this.#onListEditCommit = (e) => {
26460
+ const node = this.#nodeForKey(e.detail.key);
26461
+ if (node) this.dispatchEvent(new CustomEvent(`flat-list-edit-commit`, {
26462
+ detail: {
26463
+ node,
26464
+ value: e.detail.value
26465
+ },
26466
+ bubbles: true,
26467
+ composed: true
26468
+ }));
26469
+ };
26470
+ this.#onListEditCancel = (e) => {
26471
+ const node = this.#nodeForKey(e.detail.key);
26472
+ if (node) this.dispatchEvent(new CustomEvent(`flat-list-edit-cancel`, {
26473
+ detail: { node },
26474
+ bubbles: true,
26475
+ composed: true
26476
+ }));
26477
+ };
26213
26478
  this.#onListSelect = (e) => {
26214
26479
  const previous = this.#selected;
26215
26480
  const nodes = [...e.detail.selectedKeys].map((k) => this.#nodeForKey(k)).filter((n) => n !== void 0);
@@ -26238,6 +26503,9 @@ let FlatListElement = class FlatListElement extends LitElement {
26238
26503
  composed: true
26239
26504
  }));
26240
26505
  };
26506
+ this.#onToolbarSlotChange = (e) => {
26507
+ this._hasToolbar = e.target.assignedNodes({ flatten: true }).some((n) => n.nodeType === Node.ELEMENT_NODE || !!n.textContent?.trim());
26508
+ };
26241
26509
  this.#syncFocusVisible = () => {
26242
26510
  this._focusVisible = this.list?.matches(`:focus-visible`) ?? false;
26243
26511
  };
@@ -26274,11 +26542,8 @@ let FlatListElement = class FlatListElement extends LitElement {
26274
26542
  if (sameLevel) this.#settleList();
26275
26543
  }
26276
26544
  };
26277
- this.#renderRow = (row) => {
26278
- const { node } = row;
26279
- const textColor = appearanceColor(node.item.textColor);
26280
- return html`<span class="label" style=${textColor ? `color:${textColor}` : nothing}>${node.item.label}</span>`;
26281
- };
26545
+ this.#renderPlainRow = (row) => this.#renderRow(row, false);
26546
+ this.#renderEditableRow = (row) => this.#renderRow(row, true);
26282
26547
  this.#renderIcon = (row) => {
26283
26548
  const { node } = row;
26284
26549
  return node.item.icon ? html`<ixfx-icon name=${node.item.icon}></ixfx-icon>` : void 0;
@@ -26318,6 +26583,17 @@ let FlatListElement = class FlatListElement extends LitElement {
26318
26583
  dispatch(commandId, args) {
26319
26584
  this.list?.dispatch(commandId, args);
26320
26585
  }
26586
+ /**
26587
+ * Rename a row in place. Needs `validateEdit`, and only rows whose
26588
+ * `item.editable` is true can be edited. Ends with `flat-list-edit-commit` or `flat-list-edit-cancel`; the
26589
+ * label is not rewritten — update the tree in response. Returns false if
26590
+ * the row is unknown, not editable, or not on screen.
26591
+ */
26592
+ beginEdit(key) {
26593
+ const node = this.#nodeForKey(key);
26594
+ if (!this.validateEdit || !node?.item.editable) return false;
26595
+ return this.list?.beginEdit(key) ?? false;
26596
+ }
26321
26597
  /** The node whose children are listed. */
26322
26598
  get currentNode() {
26323
26599
  return resolvePath(this.#root, this.path).at(-1);
@@ -26407,6 +26683,7 @@ let FlatListElement = class FlatListElement extends LitElement {
26407
26683
  }
26408
26684
  }
26409
26685
  if (changed.has(`_rows`)) this.#settleList();
26686
+ if (changed.has(`_rows`) || changed.has(`markedKey`)) this.#applyMarked();
26410
26687
  }
26411
26688
  /** Recompute the level's rows, loading children first when they are not yet known. */
26412
26689
  #refreshRows(reload) {
@@ -26458,12 +26735,31 @@ let FlatListElement = class FlatListElement extends LitElement {
26458
26735
  e.stopPropagation();
26459
26736
  this.navigateInto(node.item.key);
26460
26737
  }
26738
+ #nodeForElement(el) {
26739
+ return el ? this.#nodeForKey(el.getAttribute(`data-key`) ?? void 0) : this.currentNode;
26740
+ }
26741
+ #canDropOn;
26742
+ /** Accept every drag that got past `canDrop`; the consumer only hears about the drop. */
26743
+ #onListDragOver;
26744
+ #onListDrop;
26745
+ #onListContextMenu;
26746
+ #validateEditKey;
26747
+ #onListEditCommit;
26748
+ #onListEditCancel;
26461
26749
  #onListSelect;
26462
26750
  #onListActivate;
26751
+ #onToolbarSlotChange;
26463
26752
  #syncFocusVisible;
26464
26753
  #onKeyDown;
26465
26754
  /** A crumb was chosen (or the Up button used): go to that node, or to its parent and select it if it is a leaf. */
26466
26755
  #onCrumbSelect;
26756
+ /** Flag the marked row. Rows are rebuilt with the level, so this runs after each rebuild. */
26757
+ async #applyMarked() {
26758
+ const list = this.list;
26759
+ if (!list) return;
26760
+ await list.updateComplete;
26761
+ for (const row of list.querySelectorAll(`li[data-key]`)) row.toggleAttribute(`data-marked`, row.getAttribute(`data-key`) === this.markedKey);
26762
+ }
26467
26763
  /** After the inner list has rebuilt, place the cursor / selection / focus. */
26468
26764
  async #settleList() {
26469
26765
  const list = this.list;
@@ -26478,7 +26774,15 @@ let FlatListElement = class FlatListElement extends LitElement {
26478
26774
  if (focusKey !== void 0) list.focusKey(focusKey);
26479
26775
  if (hadFocus) list.focus();
26480
26776
  }
26481
- #renderRow;
26777
+ /** Two stable formatters, so toggling `validateEdit` rebuilds the rows with or without edit hooks. */
26778
+ #renderPlainRow;
26779
+ #renderEditableRow;
26780
+ #renderRow(row, editingEnabled) {
26781
+ const { node } = row;
26782
+ const textColor = appearanceColor(node.item.textColor);
26783
+ const editable = editingEnabled && node.item.editable === true;
26784
+ return html`<span class="label" data-edit-label=${editable ? `` : nothing} data-edit-value=${editable ? node.item.label : nothing} style=${textColor ? `color:${textColor}` : nothing}>${node.item.label}</span>`;
26785
+ }
26482
26786
  #renderIcon;
26483
26787
  #renderBadge;
26484
26788
  render() {
@@ -26496,11 +26800,14 @@ let FlatListElement = class FlatListElement extends LitElement {
26496
26800
  ?hide-root=${this.hideRoot}
26497
26801
  @select=${this.#onCrumbSelect}
26498
26802
  ></ixfx-crumb-navigation>
26803
+ <div class="toolbar" part="toolbar" ?hidden=${!this._hasToolbar}>
26804
+ <slot @slotchange=${this.#onToolbarSlotChange}></slot>
26805
+ </div>
26499
26806
  </div>
26500
26807
  <div class="body">
26501
26808
  <ixfx-vertical-list
26502
26809
  .items=${this._rows}
26503
- .formatter=${this.#renderRow}
26810
+ .formatter=${this.validateEdit ? this.#renderEditableRow : this.#renderPlainRow}
26504
26811
  .icon=${this.#renderIcon}
26505
26812
  .iconColor=${(row) => appearanceColor(row.node.item.iconColor)}
26506
26813
  .badge=${this.#renderBadge}
@@ -26510,6 +26817,16 @@ let FlatListElement = class FlatListElement extends LitElement {
26510
26817
  .itemWrap=${this.itemWrap}
26511
26818
  .searchModes=${this.searchModes}
26512
26819
  ?items-draggable=${this.itemsDraggable}
26820
+ ?items-droppable=${this.itemsDroppable}
26821
+ .canDrop=${this.#canDropOn}
26822
+ .editValidator=${this.#validateEditKey}
26823
+ .virtualize=${this.virtualize}
26824
+ .itemHeight=${this.itemHeight}
26825
+ @list-drag-over=${this.#onListDragOver}
26826
+ @list-drop=${this.#onListDrop}
26827
+ @list-context-menu=${this.#onListContextMenu}
26828
+ @list-edit-commit=${this.#onListEditCommit}
26829
+ @list-edit-cancel=${this.#onListEditCancel}
26513
26830
  @list-select=${this.#onListSelect}
26514
26831
  @list-activate=${this.#onListActivate}
26515
26832
  @keydown=${this.#onKeyDown}
@@ -26536,12 +26853,31 @@ let FlatListElement = class FlatListElement extends LitElement {
26536
26853
  }
26537
26854
 
26538
26855
  .crumbs {
26856
+ display: flex;
26857
+ align-items: center;
26858
+ gap: var(--flat-list-toolbar-gap, var(--space-xs));
26539
26859
  flex: none;
26540
26860
  font-size: var(--flat-list-crumb-font-size, var(--text-s));
26541
26861
  padding: var(--flat-list-crumb-padding, var(--space-xs));
26542
26862
  border-bottom: 1px solid var(--flat-list-border, var(--list-border, var(--border)));
26543
26863
  }
26544
26864
 
26865
+ .crumbs ixfx-crumb-navigation {
26866
+ flex: 1 1 0;
26867
+ min-width: 0;
26868
+ }
26869
+
26870
+ .toolbar {
26871
+ display: flex;
26872
+ flex: none;
26873
+ align-items: center;
26874
+ gap: var(--flat-list-toolbar-gap, var(--space-xs));
26875
+ }
26876
+
26877
+ .toolbar[hidden] {
26878
+ display: none;
26879
+ }
26880
+
26545
26881
  .body {
26546
26882
  position: relative;
26547
26883
  flex: 1 1 auto;
@@ -26574,6 +26910,11 @@ let FlatListElement = class FlatListElement extends LitElement {
26574
26910
  color: var(--flat-list-empty-text, var(--surface-muted-text));
26575
26911
  }
26576
26912
 
26913
+ li[data-marked] {
26914
+ background: var(--flat-list-marked-bg, color-mix(in srgb, var(--accent) 18%, transparent));
26915
+ box-shadow: inset 3px 0 0 var(--flat-list-marked-bar, var(--accent));
26916
+ }
26917
+
26577
26918
  .label {
26578
26919
  flex: 1;
26579
26920
  overflow: hidden;
@@ -26631,6 +26972,26 @@ __decorate([property({
26631
26972
  attribute: `items-draggable`,
26632
26973
  reflect: true
26633
26974
  })], FlatListElement.prototype, "itemsDraggable", void 0);
26975
+ __decorate([property({
26976
+ type: Boolean,
26977
+ attribute: `items-droppable`,
26978
+ reflect: true
26979
+ })], FlatListElement.prototype, "itemsDroppable", void 0);
26980
+ __decorate([property({ attribute: false })], FlatListElement.prototype, "canDrop", void 0);
26981
+ __decorate([property({ attribute: false })], FlatListElement.prototype, "validateEdit", void 0);
26982
+ __decorate([property({
26983
+ type: String,
26984
+ attribute: `marked-key`,
26985
+ reflect: true
26986
+ })], FlatListElement.prototype, "markedKey", void 0);
26987
+ __decorate([property({
26988
+ type: Boolean,
26989
+ reflect: true
26990
+ })], FlatListElement.prototype, "virtualize", void 0);
26991
+ __decorate([property({
26992
+ type: Number,
26993
+ attribute: `item-height`
26994
+ })], FlatListElement.prototype, "itemHeight", void 0);
26634
26995
  __decorate([property({
26635
26996
  type: Boolean,
26636
26997
  attribute: `with-up`,
@@ -26650,6 +27011,7 @@ __decorate([property({
26650
27011
  reflect: true,
26651
27012
  attribute: `data-focus-visible`
26652
27013
  })], FlatListElement.prototype, "_focusVisible", void 0);
27014
+ __decorate([state()], FlatListElement.prototype, "_hasToolbar", void 0);
26653
27015
  __decorate([state()], FlatListElement.prototype, "_rows", void 0);
26654
27016
  __decorate([state()], FlatListElement.prototype, "_loading", void 0);
26655
27017
  FlatListElement = __decorate([safeCustomElement(`ixfx-flat-list`)], FlatListElement);