@ai-ecoverse/slicc-spectrum 1.5.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/slicc-ui.js CHANGED
@@ -5332,6 +5332,56 @@ var IconSaveFloppy = class extends IconBase {
5332
5332
  // node_modules/@spectrum-web-components/icons-workflow/icons/sp-icon-save-floppy.js
5333
5333
  defineElement("sp-icon-save-floppy", IconSaveFloppy);
5334
5334
 
5335
+ // node_modules/@spectrum-web-components/icons-workflow/src/icons-s2/OpenIn.js
5336
+ var OpenInIcon = ({ width: e14 = 24, height: t14 = 24, hidden: l10 = false, title: r13 = "Open In" } = {}) => tag2`<svg
5337
+ xmlns="http://www.w3.org/2000/svg"
5338
+ width="${e14}"
5339
+ height="${t14}"
5340
+ viewBox="0 0 20 20"
5341
+ aria-hidden=${l10 ? "true" : "false"}
5342
+ role="img"
5343
+ fill="currentColor"
5344
+ aria-label="${r13}"
5345
+ >
5346
+ <path
5347
+ d="m18,15.75V4.25c0-1.24072-1.00928-2.25-2.25-2.25H4.25c-1.24072,0-2.25,1.00928-2.25,2.25v3.71777c0,.41406.33594.75.75.75s.75-.33594.75-.75v-3.71777c0-.41357.33643-.75.75-.75h11.5c.41357,0,.75.33643.75.75v11.5c0,.41357-.33643.75-.75.75h-3.81104c-.41406,0-.75.33594-.75.75s.33594.75.75.75h3.81104c1.24072,0,2.25-1.00928,2.25-2.25Z"
5348
+ fill="currentColor"
5349
+ />
5350
+ <path
5351
+ d="m11,9.75v4.24268c0,.41406-.33594.75-.75.75s-.75-.33594-.75-.75v-2.43213l-6.46973,6.46973c-.14648.14648-.33838.21973-.53027.21973s-.38379-.07324-.53027-.21973c-.29297-.29297-.29297-.76758,0-1.06055l6.46973-6.46973h-2.43213c-.41406,0-.75-.33594-.75-.75s.33594-.75.75-.75h4.24268c.41406,0,.75.33594.75.75Z"
5352
+ fill="currentColor"
5353
+ />
5354
+ </svg>`;
5355
+
5356
+ // node_modules/@spectrum-web-components/icons-workflow/src/icons/OpenIn.js
5357
+ var OpenInIcon2 = ({ width: a11 = 24, height: e14 = 24, hidden: t14 = false, title: l10 = "Open In" } = {}) => tag2`<svg
5358
+ xmlns="http://www.w3.org/2000/svg"
5359
+ width="${a11}"
5360
+ height="${e14}"
5361
+ viewBox="0 0 36 36"
5362
+ aria-hidden=${t14 ? "true" : "false"}
5363
+ role="img"
5364
+ fill="currentColor"
5365
+ aria-label="${l10}"
5366
+ >
5367
+ <path
5368
+ d="M33 2H3a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1V6h24v24H19a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1Z"
5369
+ />
5370
+ <path
5371
+ d="M18.636 27.764a.784.784 0 0 0 .56.236.803.803 0 0 0 .804-.754v-10.75a.497.497 0 0 0-.496-.496H8.754a.803.803 0 0 0-.754.804.785.785 0 0 0 .235.56l3.786 3.786-9.042 9.042a1 1 0 0 0 0 1.415l1.414 1.414a1 1 0 0 0 1.414 0l9.043-9.042Z"
5372
+ />
5373
+ </svg>`;
5374
+
5375
+ // node_modules/@spectrum-web-components/icons-workflow/src/elements/IconOpenIn.js
5376
+ var IconOpenIn = class extends IconBase {
5377
+ render() {
5378
+ return setCustomTemplateLiteralTag2(b), this.spectrumVersion === 2 ? OpenInIcon({ hidden: !this.label, title: this.label }) : OpenInIcon2({ hidden: !this.label, title: this.label });
5379
+ }
5380
+ };
5381
+
5382
+ // node_modules/@spectrum-web-components/icons-workflow/icons/sp-icon-open-in.js
5383
+ defineElement("sp-icon-open-in", IconOpenIn);
5384
+
5335
5385
  // node_modules/@spectrum-web-components/icons-workflow/src/icons-s2/GlobeGrid.js
5336
5386
  var GlobeGridIcon = ({ width: e14 = 24, height: t14 = 24, hidden: r13 = false, title: l10 = "Globe Grid" } = {}) => tag2`<svg
5337
5387
  xmlns="http://www.w3.org/2000/svg"
@@ -6068,6 +6118,45 @@ var u8 = n7(b);
6068
6118
  var c9 = n7(w);
6069
6119
  var $2 = n7(T);
6070
6120
 
6121
+ // src/app/dips.ts
6122
+ var dipType = "application/x-slicc-sprinkle";
6123
+ var Dips = class extends Emitter {
6124
+ key = "slicc-ui.dips";
6125
+ #out = /* @__PURE__ */ new Set();
6126
+ #storage = null;
6127
+ load(storage) {
6128
+ this.#storage = storage;
6129
+ try {
6130
+ const saved = JSON.parse(storage?.getItem(this.key) ?? "[]");
6131
+ this.#out = new Set(Array.isArray(saved) ? saved.filter((id) => typeof id === "string") : []);
6132
+ } catch {
6133
+ this.#out = /* @__PURE__ */ new Set();
6134
+ }
6135
+ this.emit("change", this.list());
6136
+ }
6137
+ list() {
6138
+ return [...this.#out];
6139
+ }
6140
+ has(id) {
6141
+ return this.#out.has(id);
6142
+ }
6143
+ detach(id) {
6144
+ if (this.#out.has(id)) return;
6145
+ this.#out.add(id);
6146
+ this.#storage?.setItem(this.key, JSON.stringify(this.list()));
6147
+ this.emit("change", this.list());
6148
+ }
6149
+ };
6150
+ function dipsOf(node) {
6151
+ let current = node;
6152
+ while (current) {
6153
+ const found = current.dips;
6154
+ if (found instanceof Dips) return found;
6155
+ current = current.parentNode ?? current.host ?? null;
6156
+ }
6157
+ return null;
6158
+ }
6159
+
6071
6160
  // src/app/fonts.ts
6072
6161
  var adobeClean = [
6073
6162
  ["Regular", 400],
@@ -6571,11 +6660,39 @@ function openSurface(dock, item2, screen) {
6571
6660
  ...fresh && item2.width && screen !== "phone" ? { initialWidth: item2.width } : {}
6572
6661
  });
6573
6662
  }
6663
+ function chatId(agentId) {
6664
+ return `chat:${agentId}`;
6665
+ }
6666
+ function chatAgent(id) {
6667
+ return id?.startsWith("chat:") ? id.slice(5) : null;
6668
+ }
6669
+ function chats(dock) {
6670
+ return dock.api.panels.map((panel) => panel.id).filter((id) => chatAgent(id) !== null);
6671
+ }
6672
+ function openChat(dock, agent, screen, focus = true) {
6673
+ const id = chatId(agent.id);
6674
+ if (!dock.has(id)) {
6675
+ const sibling = chats(dock)[0];
6676
+ const place = sibling ? { position: { referencePanel: sibling, direction: "within" } } : placement(dock, "center", screen);
6677
+ dock.open({
6678
+ id,
6679
+ component: "chat",
6680
+ title: agent.name,
6681
+ params: { agent: agent.id },
6682
+ inactive: !focus,
6683
+ ...place
6684
+ });
6685
+ }
6686
+ if (focus) dock.focusPanel(id);
6687
+ else dock.reveal(id);
6688
+ return id;
6689
+ }
6574
6690
  function openDocument(dock, kind, path, screen) {
6575
6691
  const id = `${kind}:${path}`;
6576
6692
  if (!dock.has(id)) {
6577
6693
  const sibling = dock.api.panels.find((panel) => /^(file|diff):/.test(panel.id));
6578
- const place = sibling ? { position: { referencePanel: sibling.id, direction: "within" } } : dock.has("chat") && screen !== "phone" ? { position: { referencePanel: "chat", direction: "right" } } : placement(dock, "center", screen);
6694
+ const chat = chats(dock)[0];
6695
+ const place = sibling ? { position: { referencePanel: sibling.id, direction: "within" } } : chat && screen !== "phone" ? { position: { referencePanel: chat, direction: "right" } } : placement(dock, "center", screen);
6579
6696
  dock.open({
6580
6697
  id,
6581
6698
  component: kind,
@@ -6587,18 +6704,23 @@ function openDocument(dock, kind, path, screen) {
6587
6704
  dock.focusPanel(id);
6588
6705
  return id;
6589
6706
  }
6590
- function defaultLayout(dock, screen, items = surfaces) {
6707
+ function defaultLayout(dock, screen, agent, items = surfaces) {
6591
6708
  dock.clear();
6592
6709
  const order = ["center", "left", "right"];
6593
6710
  for (const side of order) {
6594
6711
  for (const item2 of items) {
6595
- if (item2.side === side && item2.open.includes(screen)) openSurface(dock, item2, screen);
6712
+ if (item2.side !== side || !item2.open.includes(screen)) continue;
6713
+ if (item2.id !== "chat") openSurface(dock, item2, screen);
6714
+ else if (agent) openChat(dock, agent, screen, false);
6596
6715
  }
6597
6716
  }
6598
- dock.api.getPanel("chat")?.api.setActive();
6717
+ const chat = chats(dock)[0];
6718
+ if (chat) dock.api.getPanel(chat)?.api.setActive();
6599
6719
  }
6600
6720
  function closed(dock, items = surfaces) {
6601
- return items.filter((item2) => !dock.has(item2.id));
6721
+ return items.filter(
6722
+ (item2) => item2.id === "chat" ? chats(dock).length === 0 : !dock.has(item2.id)
6723
+ );
6602
6724
  }
6603
6725
  function sprinkleSurface(sprinkle) {
6604
6726
  return {
@@ -6610,7 +6732,17 @@ function sprinkleSurface(sprinkle) {
6610
6732
  open: []
6611
6733
  };
6612
6734
  }
6613
- function openSprinkle(dock, sprinkle, screen) {
6735
+ var directions = {
6736
+ left: "left",
6737
+ right: "right",
6738
+ top: "above",
6739
+ bottom: "below",
6740
+ center: "within"
6741
+ };
6742
+ function dropPlacement(panel, position) {
6743
+ return panel ? { position: { referencePanel: panel, direction: directions[position] ?? "within" } } : null;
6744
+ }
6745
+ function openSprinkle(dock, sprinkle, screen, place = null) {
6614
6746
  const id = `sprinkle:${sprinkle.id}`;
6615
6747
  if (!dock.has(id)) {
6616
6748
  dock.open({
@@ -6618,7 +6750,7 @@ function openSprinkle(dock, sprinkle, screen) {
6618
6750
  component: "sprinkle",
6619
6751
  title: sprinkle.title,
6620
6752
  params: { sprinkle: sprinkle.id },
6621
- ...placement(dock, "right", screen)
6753
+ ...place ?? placement(dock, "right", screen)
6622
6754
  });
6623
6755
  }
6624
6756
  dock.focusPanel(id);
@@ -6639,7 +6771,8 @@ var SliccApp = class extends ThemedElement {
6639
6771
  surfaces: { attribute: false }
6640
6772
  };
6641
6773
  storage = globalThis.localStorage ?? null;
6642
- layoutKey = "slicc-ui.layout";
6774
+ layoutKey = "slicc-ui.layout.v2";
6775
+ dips = new Dips();
6643
6776
  #fontBase = defaultFontBase;
6644
6777
  #resize = null;
6645
6778
  #started = null;
@@ -6682,6 +6815,11 @@ var SliccApp = class extends ThemedElement {
6682
6815
  box-sizing: border-box;
6683
6816
  background: var(--spectrum-background-layer-1-color);
6684
6817
  }
6818
+ .rail.drop {
6819
+ background: var(--spectrum-accent-background-color-default);
6820
+ outline: 2px solid var(--spectrum-focus-indicator-color);
6821
+ outline-offset: -2px;
6822
+ }
6685
6823
  .rail.left {
6686
6824
  grid-column: 1;
6687
6825
  border-right: 1px solid var(--spectrum-gray-200);
@@ -6790,10 +6928,20 @@ var SliccApp = class extends ThemedElement {
6790
6928
  const update = () => this.requestUpdate();
6791
6929
  return [
6792
6930
  ...super.subscribe(model),
6793
- model.agent.on("agents", update),
6794
- model.agent.on("active", update),
6931
+ model.agent.on("agents", () => {
6932
+ this.#prune();
6933
+ update();
6934
+ }),
6935
+ model.agent.on("active", () => {
6936
+ const agent = this.#active();
6937
+ if (this.#started && agent && this.#offers("chat")) {
6938
+ openChat(this.dock, agent, this.screen, false);
6939
+ }
6940
+ update();
6941
+ }),
6795
6942
  model.files.on("changes", update),
6796
- model.sprinkles.on("sprinkles", update)
6943
+ model.sprinkles.on("sprinkles", update),
6944
+ this.dips.on("change", update)
6797
6945
  ];
6798
6946
  }
6799
6947
  connectedCallback() {
@@ -6842,12 +6990,16 @@ var SliccApp = class extends ThemedElement {
6842
6990
  (params) => create(document2.tag, model, params)
6843
6991
  ])
6844
6992
  ),
6993
+ ...this.#offers("chat") ? { chat: (params) => create("slicc-chat", model, params) } : {},
6845
6994
  sprinkle: (params) => create("slicc-sprinkle", model, params)
6846
6995
  };
6996
+ dock.accepts = [dipType];
6997
+ this.dips.load(this.storage);
6847
6998
  const saved = this.#saved();
6848
6999
  const restored = this.#offered(saved, dock.factories) && dock.restore(saved);
6849
- if (!restored) defaultLayout(dock, this.screen, this.surfaces);
7000
+ if (!restored) defaultLayout(dock, this.screen, this.#active(), this.surfaces);
6850
7001
  this.#started = this.screen;
7002
+ this.#prune();
6851
7003
  this.#saving = true;
6852
7004
  this.#save();
6853
7005
  }
@@ -6870,11 +7022,32 @@ var SliccApp = class extends ThemedElement {
6870
7022
  if (this.#saving) this.storage?.setItem(this.storageKey, JSON.stringify(this.dock.toJSON()));
6871
7023
  this.requestUpdate();
6872
7024
  }
7025
+ #active() {
7026
+ const model = this.model;
7027
+ return model?.agent.list().find((agent) => agent.id === model.agent.active()) ?? null;
7028
+ }
7029
+ #prune() {
7030
+ const dock = this.renderRoot.querySelector("slicc-dock");
7031
+ if (!dock || !this.#started) return;
7032
+ const known = new Set(this.model?.agent.list().map((agent) => agent.id));
7033
+ for (const id of chats(dock)) {
7034
+ if (!known.has(chatAgent(id))) dock.close(id);
7035
+ }
7036
+ }
7037
+ #activated(event) {
7038
+ const agent = chatAgent(event.detail.id);
7039
+ if (agent && agent !== this.model?.agent.active()) this.model?.agent.select(agent);
7040
+ }
6873
7041
  resetLayout() {
6874
- defaultLayout(this.dock, this.screen, this.surfaces);
7042
+ defaultLayout(this.dock, this.screen, this.#active(), this.surfaces);
6875
7043
  this.#save();
6876
7044
  }
6877
7045
  show(id) {
7046
+ const agent = this.#active();
7047
+ if (id === "chat" && agent && this.#offers("chat")) {
7048
+ openChat(this.dock, agent, this.screen);
7049
+ return;
7050
+ }
6878
7051
  const sprinkle = this.model?.sprinkles.list().find((candidate) => `sprinkle:${candidate.id}` === id);
6879
7052
  if (sprinkle) {
6880
7053
  openSprinkle(this.dock, sprinkle, this.screen);
@@ -6885,14 +7058,42 @@ var SliccApp = class extends ThemedElement {
6885
7058
  openSurface(this.dock, item2, this.screen);
6886
7059
  this.dock.focusPanel(id);
6887
7060
  }
7061
+ #sprinkle(id) {
7062
+ return this.model?.sprinkles.list().find((candidate) => candidate.id === id);
7063
+ }
7064
+ #detach(id, place, open) {
7065
+ const sprinkle = this.#sprinkle(id);
7066
+ if (!sprinkle) return;
7067
+ this.dips.detach(id);
7068
+ if (open) openSprinkle(this.dock, sprinkle, this.screen, place);
7069
+ }
7070
+ #dropped(event) {
7071
+ const { type, data, panel, position } = event.detail;
7072
+ if (type === dipType) this.#detach(data, dropPlacement(panel, position), true);
7073
+ }
7074
+ #railOver(event) {
7075
+ if (!event.dataTransfer?.types.includes(dipType)) return;
7076
+ event.preventDefault();
7077
+ event.currentTarget.classList.add("drop");
7078
+ }
7079
+ #railLeave(event) {
7080
+ event.currentTarget.classList.remove("drop");
7081
+ }
7082
+ #railDrop(event) {
7083
+ event.currentTarget.classList.remove("drop");
7084
+ const id = event.dataTransfer?.getData(dipType);
7085
+ if (!id) return;
7086
+ event.preventDefault();
7087
+ this.#detach(id, null, false);
7088
+ }
6888
7089
  #rail(items, side) {
6889
- return b`<nav class=${`rail ${side}`} aria-label=${`Closed panels, ${side}`}>${items.map(
7090
+ return b`<nav class=${`rail ${side}`} aria-label=${`Closed panels, ${side}`} @dragover=${this.#railOver} @dragleave=${this.#railLeave} @drop=${this.#railDrop}>${items.map(
6890
7091
  (item2) => u8`<sp-action-button quiet size="m" label=${`Open ${item2.title}`} title=${item2.title} data-surface=${item2.id} @click=${() => this.show(item2.id)}>${railIcon(item2.icon)}</sp-action-button>`
6891
7092
  )}</nav>`;
6892
7093
  }
6893
7094
  #rails() {
6894
7095
  const dock = this.renderRoot.querySelector("slicc-dock");
6895
- const sprinkles = (this.model?.sprinkles.list() ?? []).filter((sprinkle) => !sprinkle.inline).map(sprinkleSurface).filter((item2) => !dock?.has(item2.id));
7096
+ const sprinkles = (this.model?.sprinkles.list() ?? []).filter((sprinkle) => !sprinkle.inline || this.dips.has(sprinkle.id)).map(sprinkleSurface).filter((item2) => !dock?.has(item2.id));
6896
7097
  const shut = dock && this.#started ? [...sprinkles, ...closed(dock, this.surfaces)] : [];
6897
7098
  const phone = this.screen === "phone";
6898
7099
  return {
@@ -7002,6 +7203,9 @@ var SliccApp = class extends ThemedElement {
7002
7203
  <main>
7003
7204
  ${rails.left}
7004
7205
  <slicc-dock empty-text="All panels are closed. Open one from a rail or View." @layout-change=${this.#save}
7206
+ @active-panel-change=${this.#activated}
7207
+ @external-drop=${this.#dropped}
7208
+ @detach-sprinkle=${(event) => this.#detach(event.detail.id, null, true)}
7005
7209
  @open-file=${(event) => this.#request("file", event)}
7006
7210
  @open-diff=${(event) => this.#request("diff", event)}
7007
7211
  @show-surface=${(event) => this.show(event.detail.id)}
@@ -8410,6 +8614,11 @@ var messageCss = i`
8410
8614
 
8411
8615
  // src/app/chat.ts
8412
8616
  var SliccChat = class extends ThemedElement {
8617
+ static properties = { ...ThemedElement.properties, agent: {} };
8618
+ constructor() {
8619
+ super();
8620
+ this.agent = "";
8621
+ }
8413
8622
  static styles = [
8414
8623
  shared,
8415
8624
  messageCss,
@@ -8462,8 +8671,8 @@ var SliccChat = class extends ThemedElement {
8462
8671
  #handlers = {
8463
8672
  color: "light",
8464
8673
  model: null,
8465
- answer: (questionId, answer) => this.model?.agent.answer(this.model.agent.active(), questionId, answer),
8466
- resolve: (messageId, state) => this.model?.agent.resolveLick(this.model.agent.active(), messageId, state),
8674
+ answer: (questionId, answer) => this.model?.agent.answer(this.#id(), questionId, answer),
8675
+ resolve: (messageId, state) => this.model?.agent.resolveLick(this.#id(), messageId, state),
8467
8676
  action: (action) => this.#action(action),
8468
8677
  open: (path) => this.dispatchEvent(
8469
8678
  new CustomEvent("open-file", { detail: { path }, bubbles: true, composed: true })
@@ -8472,13 +8681,15 @@ var SliccChat = class extends ThemedElement {
8472
8681
  subscribe(model) {
8473
8682
  return [
8474
8683
  ...super.subscribe(model),
8475
- model.agent.on("active", () => this.#refresh(true)),
8684
+ model.agent.on("active", () => {
8685
+ if (!this.agent) this.#refresh(true);
8686
+ }),
8476
8687
  model.agent.on("agents", () => this.requestUpdate()),
8477
8688
  model.agent.on("messages", (agentId) => {
8478
- if (agentId === model.agent.active()) this.#refresh(true);
8689
+ if (agentId === this.#id()) this.#refresh(true);
8479
8690
  }),
8480
8691
  model.agent.on("message", ({ agentId }) => {
8481
- if (agentId === model.agent.active()) this.#refresh(false);
8692
+ if (agentId === this.#id()) this.#refresh(false);
8482
8693
  })
8483
8694
  ];
8484
8695
  }
@@ -8497,14 +8708,17 @@ var SliccChat = class extends ThemedElement {
8497
8708
  focus() {
8498
8709
  this.focusOn("slicc-composer");
8499
8710
  }
8711
+ #id() {
8712
+ return this.agent || (this.model?.agent.active() ?? "");
8713
+ }
8500
8714
  #agent() {
8501
- const model = this.model;
8502
- return model?.agent.list().find((agent) => agent.id === model.agent.active());
8715
+ const id = this.#id();
8716
+ return this.model?.agent.list().find((agent) => agent.id === id);
8503
8717
  }
8504
8718
  #action(action) {
8505
8719
  const model = this.model;
8506
8720
  if (!model) return;
8507
- const id = model.agent.active();
8721
+ const id = this.#id();
8508
8722
  if (action === "retry") {
8509
8723
  const last = model.agent.messages(id).filter((message) => message.role === "user").at(-1);
8510
8724
  if (last) void model.agent.send(id, { text: last.text, attachments: last.attachments });
@@ -8568,7 +8782,7 @@ var SliccChat = class extends ThemedElement {
8568
8782
  ${agent && messages.length > 0 ? this.#items(messages, agent) : b`<div class="empty">No messages yet. Ask ${agent?.name ?? "the agent"} something.</div>`}
8569
8783
  </div>
8570
8784
  </div>
8571
- ${this.model ? b`<slicc-composer .model=${this.model}></slicc-composer>` : A}
8785
+ ${this.model ? b`<slicc-composer .model=${this.model} .agent=${this.agent}></slicc-composer>` : A}
8572
8786
  `;
8573
8787
  }
8574
8788
  };
@@ -8660,7 +8874,8 @@ var SliccComposer = class extends ModelElement {
8660
8874
  value: { state: true },
8661
8875
  attachments: { state: true },
8662
8876
  popup: { state: true },
8663
- dictating: { state: true }
8877
+ dictating: { state: true },
8878
+ agent: {}
8664
8879
  };
8665
8880
  #drafts = /* @__PURE__ */ new Map();
8666
8881
  #history = -1;
@@ -8674,6 +8889,7 @@ var SliccComposer = class extends ModelElement {
8674
8889
  this.attachments = [];
8675
8890
  this.popup = null;
8676
8891
  this.dictating = false;
8892
+ this.agent = "";
8677
8893
  }
8678
8894
  static styles = [
8679
8895
  shared,
@@ -8872,14 +9088,22 @@ var SliccComposer = class extends ModelElement {
8872
9088
  }
8873
9089
  `
8874
9090
  ];
9091
+ willUpdate(changed) {
9092
+ super.willUpdate(changed);
9093
+ if (changed.has("agent") && this.agent && this.model && this.agent !== this.#agent) {
9094
+ this.#switch(this.agent);
9095
+ }
9096
+ }
8875
9097
  subscribe(model) {
8876
- this.#switch(model.agent.active());
9098
+ this.#switch(this.agent || model.agent.active());
8877
9099
  void model.files.list().then((entries) => {
8878
9100
  this.#paths = entries.filter((entry) => entry.kind === "file").map((entry) => entry.path);
8879
9101
  });
8880
9102
  const update = () => this.requestUpdate();
8881
9103
  return [
8882
- model.agent.on("active", (id) => this.#switch(id)),
9104
+ model.agent.on("active", (id) => {
9105
+ if (!this.agent) this.#switch(id);
9106
+ }),
8883
9107
  model.agent.on("agents", update),
8884
9108
  model.agent.on("messages", update),
8885
9109
  model.agent.on("message", update),
@@ -10131,6 +10355,7 @@ async function frameFonts(files, base) {
10131
10355
  (font) => Boolean(font.data)
10132
10356
  );
10133
10357
  }
10358
+ var grip = w`<svg class="grip" viewBox="0 0 8 14" width="8" height="14" aria-hidden="true">${[2, 7, 12].flatMap((y5) => [2, 6].map((x5) => w`<circle cx=${x5} cy=${y5} r="1.25"></circle>`))}</svg>`;
10134
10359
  var SliccSprinkle = class extends ThemedElement {
10135
10360
  static properties = {
10136
10361
  ...ThemedElement.properties,
@@ -10160,6 +10385,43 @@ var SliccSprinkle = class extends ThemedElement {
10160
10385
  height: 100%;
10161
10386
  border: 0;
10162
10387
  }
10388
+ .handle {
10389
+ display: flex;
10390
+ align-items: center;
10391
+ gap: 6px;
10392
+ width: max-content;
10393
+ max-width: 100%;
10394
+ box-sizing: border-box;
10395
+ height: 28px;
10396
+ padding: 0 4px 0 6px;
10397
+ border: 1px solid var(--spectrum-gray-200);
10398
+ border-bottom: 0;
10399
+ border-radius: 6px 6px 0 0;
10400
+ background: var(--spectrum-background-layer-1-color);
10401
+ color: var(--spectrum-neutral-subdued-content-color-default);
10402
+ font-size: var(--spectrum-font-size-75);
10403
+ cursor: grab;
10404
+ user-select: none;
10405
+ }
10406
+ .handle:active {
10407
+ cursor: grabbing;
10408
+ }
10409
+ .handle .grip {
10410
+ fill: currentColor;
10411
+ }
10412
+ .handle .name {
10413
+ color: var(--spectrum-neutral-content-color-default);
10414
+ }
10415
+ .moved {
10416
+ display: flex;
10417
+ align-items: center;
10418
+ gap: 8px;
10419
+ padding: 8px 12px;
10420
+ border: 1px dashed var(--spectrum-gray-300);
10421
+ border-radius: 8px;
10422
+ color: var(--spectrum-neutral-subdued-content-color-default);
10423
+ font-size: var(--spectrum-font-size-75);
10424
+ }
10163
10425
  .note {
10164
10426
  padding: 24px;
10165
10427
  color: var(--spectrum-neutral-subdued-content-color-default);
@@ -10185,22 +10447,50 @@ var SliccSprinkle = class extends ThemedElement {
10185
10447
  this.height = data.height;
10186
10448
  }
10187
10449
  };
10450
+ #dips = new Dips();
10188
10451
  subscribe(model) {
10452
+ this.#dips = dipsOf(this) ?? this.#dips;
10189
10453
  globalThis.addEventListener("message", this.#message);
10190
10454
  return [
10191
10455
  ...super.subscribe(model),
10192
10456
  () => globalThis.removeEventListener("message", this.#message),
10193
- model.sprinkles.on("sprinkles", () => this.requestUpdate())
10457
+ model.sprinkles.on("sprinkles", () => this.requestUpdate()),
10458
+ this.#dips.on("change", () => this.requestUpdate())
10194
10459
  ];
10195
10460
  }
10461
+ #drag = (event) => {
10462
+ const data = this.#data;
10463
+ event.dataTransfer?.setData(dipType, this.sprinkle);
10464
+ event.dataTransfer?.setData("text/plain", data?.title ?? this.sprinkle);
10465
+ if (event.dataTransfer) event.dataTransfer.effectAllowed = "move";
10466
+ };
10467
+ #send(type, id) {
10468
+ this.dispatchEvent(new CustomEvent(type, { detail: { id }, bubbles: true, composed: true }));
10469
+ }
10196
10470
  get #data() {
10197
10471
  return this.model?.sprinkles.list().find((candidate) => candidate.id === this.sprinkle);
10198
10472
  }
10199
10473
  render() {
10200
10474
  const data = this.#data;
10201
10475
  if (!data) return b`<div class="note">This sprinkle is gone.</div>`;
10476
+ if (this.inline && this.#dips.has(data.id)) {
10477
+ return b`<div class="moved">
10478
+ <sp-icon-open-in size="s"></sp-icon-open-in>
10479
+ <span>${data.title} is open as a panel.</span>
10480
+ <sp-action-button size="s" quiet @click=${() => this.#send("show-surface", `sprinkle:${data.id}`)}>Show</sp-action-button>
10481
+ </div>`;
10482
+ }
10202
10483
  const style = `color-scheme:${this.color};${this.inline ? `height:${this.height}px;` : ""}`;
10203
- return b`<iframe title=${data.title} sandbox="allow-scripts" style=${style} .srcdoc=${frameDocument(data.html, this.color, data.name)}></iframe>`;
10484
+ const frame = b`<iframe title=${data.title} sandbox="allow-scripts" style=${style} .srcdoc=${frameDocument(data.html, this.color, data.name)}></iframe>`;
10485
+ if (!this.inline) return frame;
10486
+ return b`<div class="handle" draggable="true" title="Drag into the dock or a rail to open as a panel" @dragstart=${this.#drag}>
10487
+ ${grip}
10488
+ <span class="name">${data.title}</span>
10489
+ <sp-action-button size="xs" quiet label=${`Open ${data.title} as a panel`} title="Open as a panel" @click=${() => this.#send("detach-sprinkle", data.id)}>
10490
+ <sp-icon-open-in slot="icon"></sp-icon-open-in>
10491
+ </sp-action-button>
10492
+ </div>
10493
+ ${frame}`;
10204
10494
  }
10205
10495
  };
10206
10496
 
@@ -52320,6 +52610,7 @@ var theme = {
52320
52610
  };
52321
52611
  var SliccDock = class extends HTMLElement {
52322
52612
  factories = {};
52613
+ accepts = [];
52323
52614
  #api = null;
52324
52615
  #contents = /* @__PURE__ */ new Map();
52325
52616
  get api() {
@@ -52367,12 +52658,30 @@ var SliccDock = class extends HTMLElement {
52367
52658
  api.onDidActivePanelChange(
52368
52659
  ({ panel }) => this.#dispatch("active-panel-change", { id: panel?.id ?? null })
52369
52660
  );
52661
+ api.onUnhandledDragOver((event) => {
52662
+ if (this.#accepted(event.nativeEvent)) event.accept();
52663
+ });
52664
+ api.onDidDrop((event) => {
52665
+ const type = this.#accepted(event.nativeEvent);
52666
+ if (!type) return;
52667
+ const transfer = event.nativeEvent.dataTransfer;
52668
+ this.#dispatch("external-drop", {
52669
+ type,
52670
+ data: transfer.getData(type),
52671
+ panel: event.group?.activePanel?.id ?? null,
52672
+ position: event.position
52673
+ });
52674
+ });
52370
52675
  api.onDidRemovePanel((panel) => {
52371
52676
  this.#contents.delete(panel.id);
52372
52677
  this.#dispatch("panel-close", { id: panel.id });
52373
52678
  });
52374
52679
  this.#api = api;
52375
52680
  }
52681
+ #accepted(native) {
52682
+ const types = native instanceof DragEvent ? [...native.dataTransfer?.types ?? []] : [];
52683
+ return this.accepts.find((type) => types.includes(type));
52684
+ }
52376
52685
  #create(id, name) {
52377
52686
  const element2 = document.createElement("div");
52378
52687
  element2.className = "panel";
@@ -52404,6 +52713,10 @@ var SliccDock = class extends HTMLElement {
52404
52713
  }
52405
52714
  return this.api.addPanel(options);
52406
52715
  }
52716
+ reveal(id) {
52717
+ const panel = this.api.getPanel(id);
52718
+ panel?.group.model.openPanel(panel, { skipSetGroupActive: true });
52719
+ }
52407
52720
  close(id) {
52408
52721
  this.api.getPanel(id)?.api.close();
52409
52722
  }