@ai-ecoverse/slicc-spectrum 1.86.5 → 1.86.6

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/README.md CHANGED
@@ -228,6 +228,7 @@ The sans stack (`--swc-sans-font-family-stack`, also used by the 1.12 components
228
228
  The shell: header with the agent picker, status items, the update status, the network indicator, the tray, the View menu and the theme switch, rails, and a `<slicc-dock>`.
229
229
  Saves the dock layout per screen class to `storage` (default `localStorage`) under `layoutKey`.
230
230
  The dock builds new panel content whenever it lays panels out again (a switch between phone, tablet and desktop, or a layout reset), so what the user opens in a panel (sections, cones, rows, folders, disclosures) lives outside the element, per model and panel (`view-state.ts`, in memory only): a rebuilt panel shows it again, and a click on the outgoing panel while the new one mounts reaches the new one too.
231
+ Files keeps its selected row, its keyboard focus target (without taking focus) and its scroll the same way, and Agents, Memory, Changes, Monitor, the Browser tab cards, Freezer and Settings keep their scroll; the chat log keeps its own. The scroll comes back once the list has laid out, anchored on the selected row when it was in view and otherwise on the first row in view, at the same distance from the top, so a narrower phone layout keeps those rows in view. A list scrolled to its end, Files included, comes back at its end. Files puts its scroll back even when the tree scrolls its focused row into view on its own. After that, a resize or new content keeps the anchored row where it was. These lists set `overflow-anchor: none` on their scroller, since they anchor it themselves. When the app switches models in place, each panel shows the state it keeps for the new model.
231
232
  While connected it sets `overscroll-behavior` to `none !important` on the page's `html` and `body`, so only scroll areas inside panels rubber-band.
232
233
  It sets the page's `color-scheme` to its resolved theme, and when the page has a `theme-color` meta per `prefers-color-scheme`, gives every one of them the content of the meta for that theme, so the page and the installed app's title bar follow the app's setting instead of the OS.
233
234
  When the last app on the page is removed, the page's own inline values and `theme-color` contents come back.
package/dist/slicc-ui.js CHANGED
@@ -10134,6 +10134,103 @@ var ViewSet = class extends ViewState {
10134
10134
  return (event2) => this.toggle(id, event2.currentTarget.open);
10135
10135
  }
10136
10136
  };
10137
+ function keyOf(row) {
10138
+ return row.getAttribute("data-id") ?? row.getAttribute("data-path");
10139
+ }
10140
+ function within(row, box) {
10141
+ const rect = row.getBoundingClientRect();
10142
+ return rect.height > 0 && rect.top >= box.top && rect.bottom <= box.bottom;
10143
+ }
10144
+ var ViewScroll = class {
10145
+ #state;
10146
+ #options;
10147
+ #el = null;
10148
+ #pending = false;
10149
+ #set = -1;
10150
+ #holder = null;
10151
+ #resize = new ResizeObserver(() => {
10152
+ this.#pending = true;
10153
+ this.#restore();
10154
+ });
10155
+ #scrolled = () => this.#save();
10156
+ constructor(host4, key2, options) {
10157
+ this.#state = new ViewState(host4, key2, () => ({ spot: null }));
10158
+ this.#options = options;
10159
+ host4.addController(this);
10160
+ }
10161
+ #rows(el) {
10162
+ const rows2 = this.#options.rows;
10163
+ const found = rows2 ? [...(el.shadowRoot ?? el).querySelectorAll(rows2)] : [];
10164
+ return found.filter((row) => keyOf(row) !== null);
10165
+ }
10166
+ #bind(el) {
10167
+ if (el === this.#el) return;
10168
+ this.#el?.removeEventListener("scroll", this.#scrolled);
10169
+ this.#resize.disconnect();
10170
+ this.#el = el;
10171
+ this.#pending = true;
10172
+ this.#set = -1;
10173
+ if (!el) return;
10174
+ el.style.overflowAnchor = "none";
10175
+ el.addEventListener("scroll", this.#scrolled, { passive: true });
10176
+ this.#resize.observe(el);
10177
+ }
10178
+ #save() {
10179
+ const el = this.#el;
10180
+ if (!el?.clientHeight || Math.abs(el.scrollTop - this.#set) < 1) return;
10181
+ this.#set = -1;
10182
+ this.#pending = false;
10183
+ const box = el.getBoundingClientRect();
10184
+ const current = this.#options.current;
10185
+ const chosen = current ? (el.shadowRoot ?? el).querySelector(current)?.closest("[data-id], [data-path]") ?? null : null;
10186
+ const rows2 = this.#rows(el);
10187
+ const row = (chosen && within(chosen, box) ? chosen : null) ?? rows2.find((item2) => within(item2, box)) ?? rows2.find((item2) => item2.getBoundingClientRect().bottom > box.top);
10188
+ this.#state.value.spot = {
10189
+ top: el.scrollTop,
10190
+ key: row ? keyOf(row) : null,
10191
+ offset: row ? row.getBoundingClientRect().top - box.top : 0,
10192
+ end: el.scrollTop > 0 && el.scrollTop >= el.scrollHeight - el.clientHeight - 1
10193
+ };
10194
+ }
10195
+ #restore() {
10196
+ const el = this.#el;
10197
+ if (!el?.clientHeight || !this.#pending) return;
10198
+ const spot = this.#state.value.spot;
10199
+ if (!spot) {
10200
+ this.#pending = false;
10201
+ return;
10202
+ }
10203
+ const max2 = el.scrollHeight - el.clientHeight;
10204
+ const rows2 = this.#rows(el);
10205
+ const row = spot.key ? rows2.find((item2) => keyOf(item2) === spot.key) : void 0;
10206
+ let target2 = spot.end ? max2 : spot.top;
10207
+ if (row && !spot.end) {
10208
+ const rect = row.getBoundingClientRect();
10209
+ const offset3 = Math.min(spot.offset, Math.max(0, el.clientHeight - rect.height));
10210
+ target2 = el.scrollTop + rect.top - el.getBoundingClientRect().top - offset3;
10211
+ }
10212
+ el.scrollTop = Math.min(target2, max2);
10213
+ this.#set = el.scrollTop;
10214
+ const gone3 = !spot.end && !!spot.key && !row;
10215
+ if (gone3 ? rows2.length > 0 : target2 <= max2 + 1) this.#pending = false;
10216
+ }
10217
+ hostUpdated() {
10218
+ const el = this.#options.scroller();
10219
+ this.#bind(el);
10220
+ if (!el) return;
10221
+ const holder = this.#state.value;
10222
+ if (holder !== this.#holder) {
10223
+ if (this.#holder && !holder.spot) el.scrollTop = 0;
10224
+ this.#holder = holder;
10225
+ this.#pending = true;
10226
+ }
10227
+ for (const child of (el.shadowRoot ?? el).children) this.#resize.observe(child);
10228
+ this.#restore();
10229
+ }
10230
+ hostDisconnected() {
10231
+ this.#bind(null);
10232
+ }
10233
+ };
10137
10234
 
10138
10235
  // src/app/files.ts
10139
10236
  function relative(path) {
@@ -10269,6 +10366,7 @@ var errorCss = i`
10269
10366
  color: var(--swc-negative-content-color-default);
10270
10367
  }
10271
10368
  `;
10369
+ var blank = { scroll: 0, selected: [], focused: null, shown: false, end: false };
10272
10370
  var SliccFiles = class extends ModelElement {
10273
10371
  static properties = {
10274
10372
  ...ModelElement.properties,
@@ -10288,6 +10386,8 @@ var SliccFiles = class extends ModelElement {
10288
10386
  #reveal = null;
10289
10387
  #folders = new ViewState(this, "files", () => null);
10290
10388
  #applied = null;
10389
+ #view = new ViewState(this, "files:view", () => ({ tree: null }));
10390
+ #holder = null;
10291
10391
  constructor() {
10292
10392
  super();
10293
10393
  this.count = 0;
@@ -10411,6 +10511,7 @@ var SliccFiles = class extends ModelElement {
10411
10511
  `
10412
10512
  ];
10413
10513
  subscribe(model) {
10514
+ if (this.#holder && this.#view.value !== this.#holder) this.#entries = [];
10414
10515
  void model.files.list().then((entries) => this.#load(entries));
10415
10516
  this.#mounts(model.files);
10416
10517
  return [
@@ -10443,8 +10544,14 @@ var SliccFiles = class extends ModelElement {
10443
10544
  const tree = this.#tree();
10444
10545
  const changes2 = this.model ? changesOf(this.model).changes() : [];
10445
10546
  if (!tree) return;
10446
- if (tree.paths.length === 0)
10547
+ const holder = this.#view.value;
10548
+ const moved2 = !!this.#holder && holder !== this.#holder;
10549
+ if (tree.paths.length === 0 || moved2) {
10447
10550
  tree.expanded = this.#folders.value ?? ancestors(changes2.map((change) => change.path));
10551
+ const view = holder.tree ?? (moved2 ? blank : null);
10552
+ if (view) tree.view = view;
10553
+ this.#holder = holder;
10554
+ }
10448
10555
  tree.paths = treePaths(this.#entries);
10449
10556
  tree.gitStatus = treeStatus(changes2);
10450
10557
  this.#show();
@@ -10615,8 +10722,14 @@ var SliccFiles = class extends ModelElement {
10615
10722
  @tree-matches=${this.#matches}
10616
10723
  @tree-search-close=${this.#closed}
10617
10724
  @tree-expand=${this.#expand}
10725
+ @tree-view=${this.#viewed}
10618
10726
  ></slicc-file-tree>`;
10619
10727
  }
10728
+ #viewed(event2) {
10729
+ event2.stopPropagation();
10730
+ const holder = this.#view.value;
10731
+ if (holder === this.#holder) holder.tree = event2.detail;
10732
+ }
10620
10733
  #expand(event2) {
10621
10734
  this.#applied = event2.detail.expanded;
10622
10735
  this.#folders.value = event2.detail.expanded;
@@ -10838,6 +10951,10 @@ function freezerOrder(rows2) {
10838
10951
  return [...rows2].sort((a23, b13) => Number(!!b13.live) - Number(!!a23.live) || b13.frozenAt - a23.frozenAt);
10839
10952
  }
10840
10953
  var SliccFreezer = class extends ModelElement {
10954
+ #scroll = new ViewScroll(this, "freezer:scroll", {
10955
+ scroller: () => this.renderRoot.querySelector(".list"),
10956
+ rows: ".card[data-id]"
10957
+ });
10841
10958
  static styles = [
10842
10959
  shared,
10843
10960
  panelCss,
@@ -11096,6 +11213,10 @@ var SliccMonitor = class extends ModelElement {
11096
11213
  copied: { state: true }
11097
11214
  };
11098
11215
  #sections = new ViewState(this, "monitor", () => ({}));
11216
+ #scroll = new ViewScroll(this, "monitor:scroll", {
11217
+ scroller: () => this.renderRoot.querySelector(".body"),
11218
+ rows: ".section[data-id], .row[data-id]"
11219
+ });
11099
11220
  constructor() {
11100
11221
  super();
11101
11222
  this.copied = null;
@@ -11565,6 +11686,11 @@ var SliccAgents = class extends ModelElement {
11565
11686
  static properties = { ...ModelElement.properties, errors: { state: true } };
11566
11687
  #expanded = new ViewSet(this, "agents");
11567
11688
  #revealed = new ViewState(this, "agents:revealed", () => "");
11689
+ #scroll = new ViewScroll(this, "agents:scroll", {
11690
+ scroller: () => this.renderRoot.querySelector(".list"),
11691
+ rows: "li[data-id]",
11692
+ current: "li[aria-selected='true']"
11693
+ });
11568
11694
  #pointer = false;
11569
11695
  #modality = (event2) => {
11570
11696
  this.#pointer = event2.type === "pointerdown";
@@ -35402,6 +35528,11 @@ var SliccBrowser = class extends ModelElement {
35402
35528
  #expanded = new ViewSet(this, "browser");
35403
35529
  #recent = new ViewState(this, "browser:recent", () => false);
35404
35530
  #chosen = new ViewState(this, "browser:tab", () => void 0);
35531
+ #scroll = new ViewScroll(this, "browser:scroll", {
35532
+ scroller: () => this.renderRoot.querySelector(".cards")?.parentElement ?? null,
35533
+ rows: ".cards > li[data-id]",
35534
+ current: ".cards .window[aria-current='true']"
35535
+ });
35405
35536
  constructor() {
35406
35537
  super();
35407
35538
  this.address = "";
@@ -36456,6 +36587,10 @@ var SliccChanges = class extends ModelElement {
36456
36587
  copied: { state: true },
36457
36588
  uncopied: { state: true }
36458
36589
  };
36590
+ #scroll = new ViewScroll(this, "changes:scroll", {
36591
+ scroller: () => this.renderRoot.querySelector(".bar ~ ul"),
36592
+ rows: "li[data-path]"
36593
+ });
36459
36594
  constructor() {
36460
36595
  super();
36461
36596
  this.error = "";
@@ -40198,6 +40333,11 @@ var SliccMemory = class extends ModelElement {
40198
40333
  editing: { state: true }
40199
40334
  };
40200
40335
  #open = new ViewState(this, "memory", () => null);
40336
+ #scroll = new ViewScroll(this, "memory:scroll", {
40337
+ scroller: () => this.renderRoot.querySelector(".list"),
40338
+ rows: ".row[data-id]",
40339
+ current: ".row[data-open]"
40340
+ });
40201
40341
  constructor() {
40202
40342
  super();
40203
40343
  this.query = "";
@@ -41844,6 +41984,7 @@ var SliccSettings = class extends ThemedElement {
41844
41984
  problem: { state: true },
41845
41985
  blocked: { state: true }
41846
41986
  };
41987
+ #scroll = new ViewScroll(this, "settings:scroll", { scroller: () => this, rows: "[data-id]" });
41847
41988
  constructor() {
41848
41989
  super();
41849
41990
  this.pending = /* @__PURE__ */ new Set();
@@ -85474,6 +85615,7 @@ file-tree-container {
85474
85615
  color-scheme: inherit;
85475
85616
  }
85476
85617
  `;
85618
+ var gestures = ["wheel", "touchmove", "pointerdown", "keydown"];
85477
85619
  var folderName = "[data-item-type='folder'] > [data-item-section='content']";
85478
85620
  var rows = `
85479
85621
  ${folderName} {
@@ -85512,6 +85654,26 @@ var SliccFileTree = class extends HTMLElement {
85512
85654
  #query = "";
85513
85655
  #chosen = [];
85514
85656
  #applying = false;
85657
+ #want = null;
85658
+ #back = null;
85659
+ #hold = null;
85660
+ #quiet = false;
85661
+ #told = "";
85662
+ #scroller = null;
85663
+ #top = 0;
85664
+ #height = 0;
85665
+ #set = -1;
85666
+ #user = false;
85667
+ #fought = -1;
85668
+ #gesture = () => {
85669
+ this.#user = true;
85670
+ };
85671
+ #resize = new ResizeObserver(() => {
85672
+ this.#fought = -1;
85673
+ this.#measure();
85674
+ this.#scrollBack();
85675
+ });
85676
+ #scrolled = () => this.#onScroll();
85515
85677
  #titles = null;
85516
85678
  get paths() {
85517
85679
  return this.#paths;
@@ -85522,6 +85684,7 @@ var SliccFileTree = class extends HTMLElement {
85522
85684
  this.#tree?.resetPaths(value, { initialExpandedPaths: expanded });
85523
85685
  this.#research();
85524
85686
  this.#matched();
85687
+ this.#restore();
85525
85688
  }
85526
85689
  #research() {
85527
85690
  const query = this.#query;
@@ -85604,7 +85767,95 @@ var SliccFileTree = class extends HTMLElement {
85604
85767
  this.#applying = false;
85605
85768
  this.#chosen = this.#expandedNow();
85606
85769
  }
85770
+ get view() {
85771
+ const tree = this.#tree;
85772
+ const back = this.#back;
85773
+ const index = tree?.getFocusedIndex() ?? -1;
85774
+ const height = tree?.getItemHeight() ?? 0;
85775
+ const top = this.#top;
85776
+ const bottom = top + this.#height;
85777
+ const rows2 = (tree?.getVisibleCount() ?? 0) * height;
85778
+ return {
85779
+ scroll: back ? back.scroll : top,
85780
+ selected: tree?.getSelectedPaths() ?? [],
85781
+ focused: tree?.getFocusedPath() ?? null,
85782
+ shown: back ? back.shown : index >= 0 && index * height >= top && (index + 1) * height <= bottom,
85783
+ end: back ? back.end : top > 0 && bottom >= rows2 - 1
85784
+ };
85785
+ }
85786
+ set view(value) {
85787
+ this.#want = value;
85788
+ this.#back = value;
85789
+ this.#restore();
85790
+ }
85791
+ #restore() {
85792
+ const tree = this.#tree;
85793
+ const want = this.#want;
85794
+ if (want && tree && this.#paths.length > 0) {
85795
+ this.#want = null;
85796
+ this.#quiet = true;
85797
+ for (const path of tree.getSelectedPaths())
85798
+ if (!want.selected.includes(path)) tree.getItem(path)?.deselect();
85799
+ for (const path of want.selected) tree.getItem(path)?.select();
85800
+ if (want.focused) tree.focusPath(want.focused);
85801
+ this.#quiet = false;
85802
+ }
85803
+ this.#scrollBack();
85804
+ }
85805
+ #scrollBack() {
85806
+ const back = this.#back ?? this.#hold;
85807
+ const el = this.#scroller;
85808
+ const tree = this.#tree;
85809
+ if (!back || !el || !tree || this.#want || !el.clientHeight) return;
85810
+ if (el.scrollHeight + 1 < tree.getVisibleCount() * tree.getItemHeight()) return;
85811
+ this.#back = null;
85812
+ this.#hold = back;
85813
+ const index = tree.getFocusedIndex();
85814
+ const height = tree.getItemHeight();
85815
+ const max2 = el.scrollHeight - el.clientHeight;
85816
+ let top = back.end ? max2 : Math.min(back.scroll, max2);
85817
+ if (back.shown && index >= 0)
85818
+ top = Math.min(Math.max(top, (index + 1) * height - el.clientHeight), index * height);
85819
+ el.scrollTop = top;
85820
+ this.#set = el.scrollTop;
85821
+ this.#user = false;
85822
+ this.#measure();
85823
+ }
85824
+ #measure() {
85825
+ const el = this.#scroller;
85826
+ if (!el?.clientHeight) return;
85827
+ this.#top = el.scrollTop;
85828
+ this.#height = el.clientHeight;
85829
+ }
85830
+ #onScroll() {
85831
+ this.#measure();
85832
+ const el = this.#scroller;
85833
+ if (!el?.clientHeight || this.#want || Math.abs(el.scrollTop - this.#set) < 1) return;
85834
+ if ((this.#back || this.#hold) && !this.#user) {
85835
+ if (!this.#hold || el.scrollTop === this.#fought) return;
85836
+ this.#fought = el.scrollTop;
85837
+ this.#scrollBack();
85838
+ return;
85839
+ }
85840
+ this.#set = -1;
85841
+ this.#back = null;
85842
+ this.#hold = null;
85843
+ this.#tell(true);
85844
+ }
85845
+ #tell(always = false) {
85846
+ if (this.#want || this.#quiet || this.#paths.length === 0 || !this.isConnected) return;
85847
+ const view = this.view;
85848
+ const told = `${view.selected.join("\n")}
85849
+
85850
+ ${view.focused}`;
85851
+ if (!always && told === this.#told) return;
85852
+ this.#told = told;
85853
+ this.dispatchEvent(
85854
+ new CustomEvent("tree-view", { detail: view, bubbles: true, composed: true })
85855
+ );
85856
+ }
85607
85857
  #changed() {
85858
+ this.#tell();
85608
85859
  if (this.#applying || !this.#tree || this.#query) return;
85609
85860
  const expanded = this.#expandedNow();
85610
85861
  if (expanded.join("\n") === this.#chosen.join("\n")) return;
@@ -85642,9 +85893,34 @@ var SliccFileTree = class extends HTMLElement {
85642
85893
  this.#title();
85643
85894
  this.#chosen = this.#query ? this.#expanded : this.#expandedNow();
85644
85895
  this.#tree.subscribe(() => this.#changed());
85896
+ this.#watch();
85645
85897
  this.#matched();
85898
+ this.#restore();
85899
+ }
85900
+ #watch() {
85901
+ const root2 = this.#tree?.getFileTreeContainer()?.shadowRoot;
85902
+ const el = root2?.querySelector("[data-file-tree-virtualized-scroll]") ?? null;
85903
+ this.#scroller = el;
85904
+ if (!el) return;
85905
+ el.addEventListener("scroll", this.#scrolled, { passive: true });
85906
+ for (const type of gestures) el.addEventListener(type, this.#gesture, { passive: true });
85907
+ this.#set = -1;
85908
+ this.#user = false;
85909
+ this.#resize.observe(el);
85910
+ const list5 = el.querySelector("[data-file-tree-virtualized-list]");
85911
+ if (list5) this.#resize.observe(list5);
85646
85912
  }
85647
85913
  disconnectedCallback() {
85914
+ if (this.#paths.length > 0 && !this.#want) {
85915
+ const view = this.view;
85916
+ this.#want = view;
85917
+ this.#back = view;
85918
+ }
85919
+ this.#scroller?.removeEventListener("scroll", this.#scrolled);
85920
+ for (const type of gestures) this.#scroller?.removeEventListener(type, this.#gesture);
85921
+ this.#scroller = null;
85922
+ this.#hold = null;
85923
+ this.#resize.disconnect();
85648
85924
  if (this.#paths.length > 0) this.#expanded = this.#held();
85649
85925
  this.#titles?.disconnect();
85650
85926
  this.#titles = null;
@@ -85672,7 +85948,7 @@ var SliccFileTree = class extends HTMLElement {
85672
85948
  );
85673
85949
  }
85674
85950
  #selected(paths) {
85675
- if (paths.length === 1) this.#open(paths[0]);
85951
+ if (paths.length === 1 && !this.#quiet) this.#open(paths[0]);
85676
85952
  }
85677
85953
  #keydown(event2) {
85678
85954
  if (event2.defaultPrevented) return;
@@ -85700,6 +85976,7 @@ var SliccFileTree = class extends HTMLElement {
85700
85976
  return container?.shadowRoot?.querySelector(`[data-item-path="${CSS.escape(path)}"]`) ?? null;
85701
85977
  }
85702
85978
  reveal(path, focus = true) {
85979
+ this.#hold = null;
85703
85980
  this.#tree?.scrollToPath(path, { focus });
85704
85981
  if (!focus) return;
85705
85982
  const row = this.#row(path);