@jxsuite/studio 5.1.0 → 5.3.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/chunks/{cf-account-picker-qwkm9zrf.js → cf-account-picker-nwnwdm6p.js} +10 -10
- package/dist/chunks/{cf-settings-71bv5zn9.js → cf-settings-adg74qsk.js} +4 -4
- package/dist/chunks/{components-vj72gqtq.js → components-5y78e05d.js} +4 -4
- package/dist/chunks/content-source-4rzvmp0m.js +46 -0
- package/dist/chunks/{convert-to-repeater-n64h8znc.js → convert-to-repeater-jxsv62v7.js} +22 -22
- package/dist/chunks/entry-commands-1wnsqn5d.js +42 -0
- package/dist/chunks/{extension-sections-3hpcambp.js → extension-sections-2mrptyek.js} +27 -27
- package/dist/chunks/{file-ops-8yhd83xr.js → file-ops-knfqshrm.js} +22 -22
- package/dist/chunks/{files-hnxqxtsg.js → files-y6fr4he9.js} +32 -30
- package/dist/chunks/{format-host-779sfp2n.js → format-host-1k4qrcqd.js} +2 -2
- package/dist/chunks/{fs-events-b88z7g4h.js → fs-events-3y6y0nrm.js} +4 -4
- package/dist/chunks/{git-panel-xwj8fpab.js → git-panel-2rkrsjva.js} +23 -22
- package/dist/chunks/grid-view-1p6hsyjx.css +8406 -0
- package/dist/chunks/grid-view-a7b87n2c.js +15322 -0
- package/dist/chunks/{iframe-host-syr0f9sb.js → iframe-host-scpw01h8.js} +13 -13
- package/dist/chunks/{jx-validate-j0wxk72w.js → jx-validate-epp2z3s3.js} +2 -2
- package/dist/chunks/{layers-ff81wthn.js → layers-jnvdbqva.js} +5 -5
- package/dist/chunks/layers-panel-73gfkcga.js +41 -0
- package/dist/chunks/{listbox-6v2ac08j.js → listbox-b7jxnhq1.js} +19 -3
- package/dist/chunks/{media-picker-cqhhxhhe.js → media-picker-f66nav16.js} +13 -13
- package/dist/chunks/menu-1dfhry4s.js +15 -0
- package/dist/chunks/{monaco-buffer-bhy3hb6m.js → monaco-buffer-pykwbm92.js} +3 -3
- package/dist/chunks/{monaco-setup-8r62fj6y.js → monaco-setup-km6abc6q.js} +2 -2
- package/dist/chunks/{notify-628t0anh.js → notify-42gk8ha1.js} +2 -2
- package/dist/chunks/{parse-0zx14y15.js → parse-reg9eevt.js} +2 -2
- package/dist/chunks/publish-panel-z1zd2da1.js +27 -0
- package/dist/chunks/quick-search-zdv7h8ht.js +47 -0
- package/dist/chunks/{references-qvvxej7h.js → references-h69xp9zz.js} +2 -2
- package/dist/chunks/right-panel-889vbnm5.js +45 -0
- package/dist/chunks/{split-g7nm1rgb.js → split-4cz4b6ef.js} +14 -3
- package/dist/chunks/{studio-vfq8qpjt.js → studio-0j7r7s9g.js} +3 -3
- package/dist/chunks/{studio-b4s9eykd.js → studio-151w0xnh.js} +15 -15
- package/dist/chunks/studio-1tbjfqv4.js +146 -0
- package/dist/chunks/{studio-8scdew0e.js → studio-27q131j3.js} +4 -4
- package/dist/chunks/{studio-7r42c6ca.js → studio-3ntvq6cg.js} +5 -5
- package/dist/chunks/{studio-g3jevyjt.js → studio-4c041r8y.js} +2 -2
- package/dist/chunks/{studio-js8nv53n.js → studio-4f3psrk1.js} +14 -14
- package/dist/chunks/{studio-pxbgypmg.js → studio-50wfwy44.js} +17 -14
- package/dist/chunks/{studio-vw7mzp6z.js → studio-6gryj0sg.js} +5 -5
- package/dist/chunks/{studio-e1pgga3k.js → studio-6kvgrkrp.js} +7 -7
- package/dist/chunks/{studio-wqz6dggm.js → studio-7144epdr.js} +9 -9
- package/dist/chunks/{studio-b0k5tqgp.js → studio-7rrb1esn.js} +32 -28
- package/dist/chunks/{studio-38emantv.js → studio-a5h5j33a.js} +19 -5
- package/dist/chunks/{studio-3773086z.js → studio-a65w20ex.js} +4 -4
- package/dist/chunks/{studio-nzzpvzx9.js → studio-ab87phj1.js} +4 -4
- package/dist/chunks/{studio-r6nvacqs.js → studio-b66rsh78.js} +20 -20
- package/dist/chunks/{studio-jxp12rnj.js → studio-b7xacrwe.js} +6 -6
- package/dist/chunks/{studio-6xa21ej9.js → studio-c04nzbby.js} +2 -2
- package/dist/chunks/{studio-try13h8c.js → studio-c0qgc3tn.js} +18 -18
- package/dist/chunks/{studio-tqgryxwg.js → studio-cgprcdxn.js} +7 -7
- package/dist/chunks/{studio-kkqkcp3d.js → studio-d1n2bykn.js} +40 -13
- package/dist/chunks/{studio-p1dw8wfw.js → studio-d8z5bajd.js} +14 -10
- package/dist/chunks/{studio-3rz5md06.js → studio-dd1qqkry.js} +6 -6
- package/dist/chunks/{studio-1z1kcjv4.js → studio-drg8qj9k.js} +11 -11
- package/dist/chunks/{studio-kcgff4r0.js → studio-fgmfksz8.js} +2 -2
- package/dist/chunks/{studio-qk90fgqm.js → studio-fzjx96t2.js} +7 -7
- package/dist/chunks/{studio-a79k90n9.js → studio-g33v4fe9.js} +17 -16
- package/dist/chunks/{studio-k75n7vhp.js → studio-gk2twyh4.js} +74 -36
- package/dist/chunks/{studio-syg2tadj.js → studio-gtfq1dkz.js} +3 -3
- package/dist/chunks/{studio-1knfqgy7.js → studio-j0w60gft.js} +8 -8
- package/dist/chunks/{studio-aj37eer3.js → studio-jt27ta6x.js} +4 -4
- package/dist/chunks/{studio-gb13095a.js → studio-ke09tn9k.js} +59 -20
- package/dist/chunks/{studio-d0300636.js → studio-ks2zzth2.js} +17 -2
- package/dist/chunks/{studio-6ewqv390.js → studio-m1p9b6c3.js} +5 -5
- package/dist/chunks/{studio-qk4bfsq2.js → studio-msc1ee1m.js} +4 -4
- package/dist/chunks/{studio-93zafjcm.js → studio-nfx354t1.js} +10 -10
- package/dist/chunks/{studio-mhhyamxf.js → studio-nsgw7dbk.js} +6 -6
- package/dist/chunks/{studio-qhhm6hbt.js → studio-rtskkm43.js} +4 -4
- package/dist/chunks/studio-t171hxhe.js +23 -0
- package/dist/chunks/{studio-hhvs4sdg.js → studio-vn5hske5.js} +10 -4
- package/dist/chunks/{studio-q3x06vmj.js → studio-wnsyffg1.js} +2 -2
- package/dist/chunks/{studio-5wt9nvhd.js → studio-xajk70w0.js} +29 -42
- package/dist/chunks/{studio-7xnmgv66.js → studio-ye9drbk8.js} +45 -44
- package/dist/chunks/{studio-y9zedyjn.js → studio-z2smx5ce.js} +2 -2
- package/dist/chunks/{studio-sbjyc8bt.js → studio-ze46gzjh.js} +9 -9
- package/dist/chunks/{studio-zdn2g6dt.js → studio-zjjde71a.js} +2 -2
- package/dist/chunks/{workspace-t55jdjbx.js → workspace-np10mayv.js} +18 -10
- package/dist/iframe-entry.js +9 -5
- package/dist/studio.css +1 -2
- package/dist/studio.js +910 -15705
- package/package.json +10 -10
- package/src/files/files.ts +38 -15
- package/src/grid/grid-lazy.ts +59 -0
- package/src/grid/grid-open.ts +20 -3
- package/src/grid/grid-panel.ts +43 -9
- package/src/media/media-open.ts +14 -2
- package/src/panels/chat-panel.ts +40 -5
- package/src/panels/pane-context.ts +5 -4
- package/src/panels/pane-grid.ts +100 -10
- package/src/panels/tab-drop.ts +220 -0
- package/src/panels/tab-strip.ts +235 -30
- package/src/services/ai-models.ts +13 -0
- package/src/services/module-preloads.ts +67 -0
- package/src/studio.ts +16 -6
- package/src/surfaces/commandbar.json +26 -16
- package/src/surfaces/formula-palette.json +8 -7
- package/src/surfaces/formula-palette.ts +10 -6
- package/src/surfaces/palette.json +8 -7
- package/src/surfaces/pane-grid.json +35 -3
- package/src/surfaces/pane-grid.ts +26 -4
- package/src/surfaces/tab-strip.json +16 -39
- package/src/surfaces/tab-strip.ts +76 -12
- package/src/ui/ai-credentials-form.ts +12 -1
- package/src/ui/panel-resize.ts +3 -23
- package/src/ui/virtual-window.ts +57 -4
- package/src/workspace/pane-derive.ts +7 -5
- package/src/workspace/session.ts +3 -3
- package/src/workspace/workspace.ts +160 -43
- package/styles/shell-frame.css +8 -0
- package/styles/shell-frame.json +4 -0
- package/dist/chunks/content-source-5mex4x1h.js +0 -46
- package/dist/chunks/entry-commands-234hjnqw.js +0 -42
- package/dist/chunks/layers-panel-pnnghw12.js +0 -40
- package/dist/chunks/menu-9p2w46gj.js +0 -15
- package/dist/chunks/publish-panel-5zt840sy.js +0 -27
- package/dist/chunks/quick-search-3748564r.js +0 -45
- package/dist/chunks/right-panel-eccz92qt.js +0 -45
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* @license MIT
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
import { fetchAvailableModels } from "../services/ai-models";
|
|
26
|
+
import { fetchAvailableModels, proxyModelsErrorMessage } from "../services/ai-models";
|
|
27
27
|
import {
|
|
28
28
|
getBaseUrl,
|
|
29
29
|
getOpenAiKey,
|
|
@@ -165,6 +165,17 @@ export function createAiCredentialsForm(opts: AiCredentialsFormOptions): AiCrede
|
|
|
165
165
|
},
|
|
166
166
|
force: true,
|
|
167
167
|
});
|
|
168
|
+
/*
|
|
169
|
+
* A 200 from the proxy does not mean the UPSTREAM listing worked — handleModels answers with
|
|
170
|
+
* defaults and `configured: true` even when the provider's /models route failed, so the list
|
|
171
|
+
* fetch never throws for that case. Cloudflare Workers AI's OpenAI-compatible surface has no
|
|
172
|
+
* /models route at all, so this is the only way that failure ever reaches the form; without
|
|
173
|
+
* it, the field silently filled with OpenAI's default ids instead of the user's own models.
|
|
174
|
+
*/
|
|
175
|
+
const upstreamMessage = proxyModelsErrorMessage();
|
|
176
|
+
if (upstreamMessage) {
|
|
177
|
+
modelsError = `${upstreamMessage} — type the model ID directly instead.`;
|
|
178
|
+
}
|
|
168
179
|
} catch (error: unknown) {
|
|
169
180
|
modelsError = (error as Error).message || "Failed to fetch models";
|
|
170
181
|
} finally {
|
package/src/ui/panel-resize.ts
CHANGED
|
@@ -43,8 +43,7 @@ import {
|
|
|
43
43
|
} from "../shell";
|
|
44
44
|
import type { DockId } from "../shell";
|
|
45
45
|
import { effect } from "../reactivity";
|
|
46
|
-
import {
|
|
47
|
-
import { splitModifiersOf } from "@jxsuite/ui/behaviors/split";
|
|
46
|
+
import { splitModifiersOf, trackOf } from "@jxsuite/ui/behaviors/split";
|
|
48
47
|
import type { SplitModifiers } from "@jxsuite/ui/behaviors/split";
|
|
49
48
|
|
|
50
49
|
/**
|
|
@@ -160,25 +159,6 @@ export interface SplitElement extends HTMLElement {
|
|
|
160
159
|
collapse: number;
|
|
161
160
|
}
|
|
162
161
|
|
|
163
|
-
/**
|
|
164
|
-
* The length of the box a handle divides, along its axis — the same box `jx-split` measures at
|
|
165
|
-
* pointerdown, read here so the share this adapter writes and the one the element drags against are
|
|
166
|
-
* two readings of one number.
|
|
167
|
-
*/
|
|
168
|
-
function trackOf(handle: HTMLElement, axis: "x" | "y"): number {
|
|
169
|
-
for (let node = handle.parentElement; node; node = node.parentElement) {
|
|
170
|
-
if (globalThis.getComputedStyle(node).display === "contents") {
|
|
171
|
-
continue;
|
|
172
|
-
}
|
|
173
|
-
const box = rectOf(node);
|
|
174
|
-
const length = axis === "x" ? box.width : box.height;
|
|
175
|
-
if (length > 0) {
|
|
176
|
-
return length;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
return 0;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
162
|
/**
|
|
183
163
|
* A target's pixel size as the leading side's share of the track, and back.
|
|
184
164
|
*
|
|
@@ -231,7 +211,7 @@ function sizeOf(target: ResizeTarget, share: number, track: number): number {
|
|
|
231
211
|
*/
|
|
232
212
|
export function bindSplit(handle: SplitElement, target: ResizeTarget): () => void {
|
|
233
213
|
const sync = (): void => {
|
|
234
|
-
const track = trackOf(handle, target.axis);
|
|
214
|
+
const track = trackOf(handle, target.axis === "x");
|
|
235
215
|
if (track <= 0) {
|
|
236
216
|
return;
|
|
237
217
|
}
|
|
@@ -243,7 +223,7 @@ export function bindSplit(handle: SplitElement, target: ResizeTarget): () => voi
|
|
|
243
223
|
handle.value = shareOf(target, target.read(), track);
|
|
244
224
|
};
|
|
245
225
|
const onInput = (event: Event): void => {
|
|
246
|
-
const track = trackOf(handle, target.axis);
|
|
226
|
+
const track = trackOf(handle, target.axis === "x");
|
|
247
227
|
if (track <= 0) {
|
|
248
228
|
return;
|
|
249
229
|
}
|
package/src/ui/virtual-window.ts
CHANGED
|
@@ -300,6 +300,44 @@ export function nearestScroller(el: HTMLElement | null): HTMLElement | null {
|
|
|
300
300
|
return null;
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
+
/**
|
|
304
|
+
* The scroller that resolved for `list` last time, memoized while the tree still holds it.
|
|
305
|
+
*
|
|
306
|
+
* Every render pass asks {@link nearestScroller} the same question about the same list, and each
|
|
307
|
+
* ask is a walk whose reads (scrollHeight, clientHeight, computed overflow) land on a tree that is
|
|
308
|
+
* mid-paint — they force the flush the DevTools timeline charges to whatever function happened to
|
|
309
|
+
* read first. Profiling the program boot put 400 ms of style recalculation behind a handful of
|
|
310
|
+
* these walks (`trackOf` and this one together), and the answer did not change between any two
|
|
311
|
+
* repaints.
|
|
312
|
+
*
|
|
313
|
+
* Only a POSITIVE resolution is cached, and negative answers are never: a list that has not grown
|
|
314
|
+
* its rows yet has nothing to scroll (the first paint of every session), and a null answer memoized
|
|
315
|
+
* at that instant would never be revisited. Once a scroller is found it is kept while
|
|
316
|
+
* `cached.contains(list)` and both are connected — containment and connectivity are plain node
|
|
317
|
+
* walks that never read layout, so the cache invalidates itself on reparenting or teardown without
|
|
318
|
+
* paying for it.
|
|
319
|
+
*
|
|
320
|
+
* **The one semantic residue is deliberate**: the original walk additionally required the scroller
|
|
321
|
+
* to HAVE something to scroll right then (the "declares overflow but never scrolls" guard above). A
|
|
322
|
+
* memoized scroller that has since stopped scrolling stays answered — which is harmless for the
|
|
323
|
+
* window: `computeWindow` then yields the whole list (a list that fits has no window to cut), and
|
|
324
|
+
* the watch's own ResizeObserver drives a measure when the box moves so the empty answer of the
|
|
325
|
+
* next moment arrives without a walk.
|
|
326
|
+
*/
|
|
327
|
+
export function scrollerFor(list: HTMLElement): HTMLElement | null {
|
|
328
|
+
const cached = scrollerCache.get(list);
|
|
329
|
+
if (cached && cached.isConnected && list.isConnected && cached.contains(list)) {
|
|
330
|
+
return cached;
|
|
331
|
+
}
|
|
332
|
+
const found = nearestScroller(list);
|
|
333
|
+
if (found) {
|
|
334
|
+
scrollerCache.set(list, found);
|
|
335
|
+
} else {
|
|
336
|
+
scrollerCache.delete(list);
|
|
337
|
+
}
|
|
338
|
+
return found;
|
|
339
|
+
}
|
|
340
|
+
|
|
303
341
|
/**
|
|
304
342
|
* How far `list`'s first row has scrolled above the top of `scroller`'s viewport.
|
|
305
343
|
*
|
|
@@ -327,7 +365,7 @@ export function listWindow(
|
|
|
327
365
|
list: HTMLElement | null,
|
|
328
366
|
spec: { count: number; rowHeight: number; columns?: number; overscanRows?: number },
|
|
329
367
|
): WindowRange {
|
|
330
|
-
const scroller = list?.isConnected === true ?
|
|
368
|
+
const scroller = list?.isConnected === true ? scrollerFor(list) : null;
|
|
331
369
|
if (!scroller || !list) {
|
|
332
370
|
const count = Math.max(0, Math.trunc(spec.count));
|
|
333
371
|
return { end: count, padBottom: 0, padTop: 0, start: 0, totalRows: count };
|
|
@@ -377,7 +415,7 @@ export function scrollTopToReveal(spec: {
|
|
|
377
415
|
* would paint twice for one keystroke.
|
|
378
416
|
*/
|
|
379
417
|
export function revealListRow(list: HTMLElement | null, index: number, rowHeight: number): boolean {
|
|
380
|
-
const scroller = list?.isConnected === true ?
|
|
418
|
+
const scroller = list?.isConnected === true ? scrollerFor(list) : null;
|
|
381
419
|
if (!scroller || !list || index < 0 || !(rowHeight > 0)) {
|
|
382
420
|
return false;
|
|
383
421
|
}
|
|
@@ -437,9 +475,24 @@ export interface ListWindowWatch {
|
|
|
437
475
|
window: VirtualWindow;
|
|
438
476
|
}
|
|
439
477
|
|
|
478
|
+
const scrollerCache = new WeakMap<HTMLElement, HTMLElement>();
|
|
479
|
+
|
|
440
480
|
/**
|
|
441
481
|
* Bind — or keep — the scroll watch that repaints a windowed list.
|
|
442
482
|
*
|
|
483
|
+
* Runs synchronously, on purpose. Deferring the resolve-and-bind to the next animation frame was
|
|
484
|
+
* tried and measured (`scripts/perf/REPORT.md`, "deferring the first measure a frame"): it moved
|
|
485
|
+
* the boot trace's forced-flush cost rather than removing it, since the layout work still runs on
|
|
486
|
+
* the frame's own pass either way, and it cost eight test-semantics changes in this file's test
|
|
487
|
+
* suite for the same totals — so it was reverted. What actually removed the repeated flush is
|
|
488
|
+
* `scrollerFor`'s identity memoization (alongside `trackOf`'s, in `split.ts`): once a scroller is
|
|
489
|
+
* resolved for a list, later calls skip the ancestor walk entirely instead of re-reading
|
|
490
|
+
* `scrollHeight`/`clientHeight` on a dirty tree every render.
|
|
491
|
+
*
|
|
492
|
+
* A steady state where the SAME list and scroller are bound hands back `previous` unchanged — no
|
|
493
|
+
* work happens when nothing changed. Only a rebind (new list, changed scroller, first bind) creates
|
|
494
|
+
* a new watch.
|
|
495
|
+
*
|
|
443
496
|
* Called from the surface's `afterRender`, where the rows exist and the scroller can be resolved,
|
|
444
497
|
* and handed back its own previous handle so re-binding is the exception rather than the rule: the
|
|
445
498
|
* tree element survives every re-render (lit reuses it), so the steady state is "same list, same
|
|
@@ -450,14 +503,14 @@ export interface ListWindowWatch {
|
|
|
450
503
|
* the element whose box moves when the ROWS change height, so it is what the window observes to
|
|
451
504
|
* learn that `rowHeight()` now answers differently ({@link createVirtualWindow}).
|
|
452
505
|
*
|
|
453
|
-
*
|
|
506
|
+
* @returns The handle to keep, or null when nothing scrolls this list.
|
|
454
507
|
*/
|
|
455
508
|
export function watchListWindow(
|
|
456
509
|
previous: ListWindowWatch | null,
|
|
457
510
|
list: HTMLElement,
|
|
458
511
|
spec: { count: () => number; rowHeight: () => number; onChange: () => void },
|
|
459
512
|
): ListWindowWatch | null {
|
|
460
|
-
const scroller =
|
|
513
|
+
const scroller = scrollerFor(list);
|
|
461
514
|
if (previous && previous.list === list && previous.scroller === scroller) {
|
|
462
515
|
return previous;
|
|
463
516
|
}
|
|
@@ -43,9 +43,9 @@ import {
|
|
|
43
43
|
activePane,
|
|
44
44
|
detachTab,
|
|
45
45
|
insertIntoPane,
|
|
46
|
+
paneBeside,
|
|
46
47
|
paneById,
|
|
47
48
|
promoteTab,
|
|
48
|
-
sidePane,
|
|
49
49
|
workspace,
|
|
50
50
|
} from "./workspace";
|
|
51
51
|
import {
|
|
@@ -1143,9 +1143,11 @@ function pinnablePane(): Pane | null {
|
|
|
1143
1143
|
* **Unlike `pane.derive`, the COMMAND's subject is the grid rather than the focused pane**, and
|
|
1144
1144
|
* that is deliberate: the author reaches "Keep This Document" from the palette while the keyboard
|
|
1145
1145
|
* is in the page they are editing, and a grid holds at most one derived pane — `pane.derive` puts
|
|
1146
|
-
* the projection in `
|
|
1147
|
-
*
|
|
1148
|
-
*
|
|
1146
|
+
* the projection in `paneBeside(source.id)`, and `workspace.ts`'s `MAX_PANES` is 2.
|
|
1147
|
+
* {@link
|
|
1148
|
+
* pinnablePane} finds it by scanning, which reads no focus and is therefore the same answer
|
|
1149
|
+
* from anywhere — so the preset menu can ask it about its own pane and get an answer about its own
|
|
1150
|
+
* pane.
|
|
1149
1151
|
*
|
|
1150
1152
|
* @param {string} paneId
|
|
1151
1153
|
* @returns {string | null}
|
|
@@ -1227,7 +1229,7 @@ export function derivationCommands(deps: DerivationDeps): AnyCommand[] {
|
|
|
1227
1229
|
`command "pane.derive" argument "preset": "${preset}" requires ${refusal}`,
|
|
1228
1230
|
);
|
|
1229
1231
|
}
|
|
1230
|
-
const target =
|
|
1232
|
+
const target = paneBeside(source.id);
|
|
1231
1233
|
/* The DERIVATION IS PUBLISHED FIRST, and the order is not cosmetic.
|
|
1232
1234
|
§18.1 rule 3 removes a pane with no subject, and `detachTab` applies it as each tab
|
|
1233
1235
|
leaves — so handing the side pane's tabs back before it had a derivation collapsed the
|
package/src/workspace/session.ts
CHANGED
|
@@ -217,9 +217,9 @@ export interface RestoreDeps {
|
|
|
217
217
|
/**
|
|
218
218
|
* Make sure the grid HAS a second pane, without moving anything into it.
|
|
219
219
|
*
|
|
220
|
-
* `receivingPane()`, not `splitRight()`: a split moves the focused pane's active tab,
|
|
221
|
-
* point nothing is open — so `splitRight()` returns `null`, the pane is never
|
|
222
|
-
* second pane's documents all land in the first.
|
|
220
|
+
* `receivingPane(PRIMARY_PANE)`, not `splitRight()`: a split moves the focused pane's active tab,
|
|
221
|
+
* and at this point nothing is open — so `splitRight()` returns `null`, the pane is never
|
|
222
|
+
* created, and the second pane's documents all land in the first.
|
|
223
223
|
*/
|
|
224
224
|
ensureSecondPane: () => void;
|
|
225
225
|
}
|
|
@@ -368,68 +368,82 @@ export function focusOtherPane() {
|
|
|
368
368
|
*/
|
|
369
369
|
|
|
370
370
|
/**
|
|
371
|
-
* Split the grid and move
|
|
371
|
+
* Split the grid and move a tab into the new pane, which becomes focused.
|
|
372
372
|
*
|
|
373
373
|
* Moves rather than duplicates: one tab is one document with one undo history and one collab
|
|
374
374
|
* session, and two strips claiming the same id is the duplicate-`repeat`-key bug §4.3 describes.
|
|
375
375
|
*
|
|
376
|
+
* The subject is `tabId` when given, else the focused pane's active tab. The drag resolver names
|
|
377
|
+
* the tab it is carrying — the gesture points at a chip, not at whatever the keyboard is in — and
|
|
378
|
+
* `⌘\` and `pane.splitRight` call it bare, which is the focused tab as it has always been.
|
|
379
|
+
*
|
|
380
|
+
* @param {string} [tabId] The tab to move; defaults to the focused pane's active tab.
|
|
376
381
|
* @returns {Pane | null} The pane the tab landed in, or null when the split was refused
|
|
377
382
|
*/
|
|
378
|
-
export function splitRight(): Pane | null {
|
|
379
|
-
const
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
+
export function splitRight(tabId?: string): Pane | null {
|
|
384
|
+
const id = tabId ?? activePane().activeTabId;
|
|
385
|
+
const tab = id ? workspace.tabs.get(id) : null;
|
|
386
|
+
if (!id || !tab) {
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
const source = paneOfTab(id);
|
|
390
|
+
if (!source) {
|
|
383
391
|
return null;
|
|
384
392
|
}
|
|
385
393
|
/* The tab moves AS IT IS. `capToPaneKind` used to run here and rewrite `session.ui.canvasMode`
|
|
386
394
|
when the target pane could not host the tab's kind — which, for a Design tab, meant `⌘\`
|
|
387
395
|
silently reopened your page as Code in the pane you had just made. Both panes host every kind
|
|
388
396
|
now, so a split is a move and nothing else. */
|
|
389
|
-
const target = receivingPane();
|
|
390
|
-
detachTab(
|
|
391
|
-
insertIntoPane(target,
|
|
397
|
+
const target = receivingPane(source.id);
|
|
398
|
+
detachTab(id);
|
|
399
|
+
insertIntoPane(target, id);
|
|
392
400
|
/* Focus moves LAST, and every write above went through {@link addPane}'s reactive record.
|
|
393
401
|
Publishing the focus first made the pane observable while it still showed nothing: the
|
|
394
402
|
`activeTab` computed re-ran, cached null, and the jump bar, the Inspector and the toolbar all
|
|
395
403
|
printed "no document" over a stage that was drawing one. The write that should have corrected
|
|
396
404
|
them notified nobody, because it went through the raw literal this used to push. */
|
|
397
|
-
target.activeTabId =
|
|
405
|
+
target.activeTabId = id;
|
|
398
406
|
workspace.activePaneId = target.id;
|
|
399
407
|
resetTabCycle();
|
|
400
|
-
promoteMru(
|
|
408
|
+
promoteMru(id);
|
|
401
409
|
return target;
|
|
402
410
|
}
|
|
403
411
|
|
|
404
412
|
/**
|
|
405
|
-
* The pane
|
|
413
|
+
* The pane beside `paneId`, created if the grid has only one.
|
|
406
414
|
*
|
|
407
415
|
* The `existing ?? addPane(SECONDARY_PANE)` shape {@link splitRight} has always used, named because
|
|
408
|
-
*
|
|
409
|
-
* it moves NOTHING — "give me the other pane" and "send my document to it" are
|
|
410
|
-
* only the split makes both.
|
|
416
|
+
* four callers now want it: the split, `pane.compareWith`, `pane.derive` and the drag resolver.
|
|
417
|
+
* Unlike `splitRight` it moves NOTHING — "give me the other pane" and "send my document to it" are
|
|
418
|
+
* two requests, and only the split makes both.
|
|
419
|
+
*
|
|
420
|
+
* Takes the pane it is asked about rather than reading the focus, because a drag names the pane its
|
|
421
|
+
* chip came from and `pane.derive`'s preset menu asks about its own pane — and
|
|
422
|
+
* `scripts/check-pane-singletons.ts` rule 4 refuses a function handed a `paneId` that reads the
|
|
423
|
+
* focus. The one caller that means "beside ME" passes `activePane().id`.
|
|
411
424
|
*
|
|
412
425
|
* `SECONDARY_PANE` unconditionally, and it is free unconditionally: {@link closePane} refuses to
|
|
413
426
|
* remove the primary, so "there is no other pane" can only mean the grid is exactly `[primary]` and
|
|
414
|
-
* the
|
|
427
|
+
* the asked-about pane is that primary. This used to be `existing?.id ?? SECONDARY_PANE` handed
|
|
415
428
|
* straight to `addPane` with no check that the id was free — which, on a grid of `["secondary"]`,
|
|
416
429
|
* pushed a SECOND record under that id and gave lit's keyed `repeat` a duplicate key. Both ends are
|
|
417
430
|
* closed: the state is unreachable, and {@link addPane} would not mint the duplicate even if it
|
|
418
431
|
* were. {@link MAX_PANES} is therefore satisfied by construction — there are two ids in play.
|
|
419
432
|
*
|
|
433
|
+
* @param {string} paneId The pane to answer "beside" for.
|
|
420
434
|
* @returns {Pane}
|
|
435
|
+
* @docs studio/interface/tabs
|
|
421
436
|
*/
|
|
422
|
-
export function
|
|
423
|
-
|
|
424
|
-
return workspace.panes.find((pane) => pane.id !== source.id) ?? addPane(SECONDARY_PANE);
|
|
437
|
+
export function paneBeside(paneId: string): Pane {
|
|
438
|
+
return workspace.panes.find((pane) => pane.id !== paneId) ?? addPane(SECONDARY_PANE);
|
|
425
439
|
}
|
|
426
440
|
|
|
427
441
|
/**
|
|
428
|
-
* The pane beside
|
|
442
|
+
* The pane beside `paneId`, ready to OWN a tab.
|
|
429
443
|
*
|
|
430
|
-
* {@link
|
|
431
|
-
* IN", and they differ by exactly one case — a LENS, whose invariant D2 is that it owns no
|
|
432
|
-
* all.
|
|
444
|
+
* {@link paneBeside} answers "which pane is beside that one". This answers "which pane may I put a
|
|
445
|
+
* document IN", and they differ by exactly one case — a LENS, whose invariant D2 is that it owns no
|
|
446
|
+
* tab at all. Four callers mean the second question and all four were asking the first:
|
|
433
447
|
*
|
|
434
448
|
* - `splitRight` moved the focused tab into a lens. Between that write and the follow's next frame
|
|
435
449
|
* the pane held both a derivation and a tab — D2 violated and observable — and `applyDerivation`
|
|
@@ -441,6 +455,8 @@ export function sidePane(): Pane {
|
|
|
441
455
|
* touches — never ran to repair it.
|
|
442
456
|
* - `navigateToComponent` read `tabOfPane(target.id)` back and got the SOURCE tab, so the §14.2
|
|
443
457
|
* `openedFrom` relationship the function exists to record was silently skipped.
|
|
458
|
+
* - The drag resolver dropped a tab or a file onto a lens's strip, where the same three failures
|
|
459
|
+
* apply with a gesture instead of a command.
|
|
444
460
|
*
|
|
445
461
|
* **The derivation is released rather than the request refused.** "Put this document over there" is
|
|
446
462
|
* an explicit instruction about the pane a projection is currently borrowing, and a projection is a
|
|
@@ -456,10 +472,11 @@ export function sidePane(): Pane {
|
|
|
456
472
|
* `activeTabId` — promotes whichever document is on top, which after `⌘\` is the page, not the
|
|
457
473
|
* layout the author was keeping. A gesture that empties a derivation of its meaning ends it.
|
|
458
474
|
*
|
|
475
|
+
* @param {string} paneId The pane the receiving pane sits beside.
|
|
459
476
|
* @returns {Pane}
|
|
460
477
|
*/
|
|
461
|
-
export function receivingPane(): Pane {
|
|
462
|
-
const target =
|
|
478
|
+
export function receivingPane(paneId: string): Pane {
|
|
479
|
+
const target = paneBeside(paneId);
|
|
463
480
|
if (target.derived) {
|
|
464
481
|
/* The same write {@link closePane} makes for D4, for the same reason and in the same module:
|
|
465
482
|
clearing a derivation is a fact about the PANE GRID. `pane-derive.ts`'s
|
|
@@ -582,8 +599,11 @@ export function closePane(paneId: string) {
|
|
|
582
599
|
* same question while every pane's subject was its tabs. A lens pane's subject is its derivation —
|
|
583
600
|
* its `tabOrder` is empty BY DESIGN — so the literal test would collapse it the instant it was
|
|
584
601
|
* created.
|
|
602
|
+
*
|
|
603
|
+
* Exported for `panels/tab-drop.ts`, whose edge-drop resolution refuses to leave a pane it minted
|
|
604
|
+
* standing empty over a refused open — the same rule `document.openToSide` applies in-module.
|
|
585
605
|
*/
|
|
586
|
-
function paneIsEmpty(pane: Pane): boolean {
|
|
606
|
+
export function paneIsEmpty(pane: Pane): boolean {
|
|
587
607
|
return pane.tabOrder.length === 0 && pane.derived === null;
|
|
588
608
|
}
|
|
589
609
|
|
|
@@ -759,12 +779,12 @@ function syncTreeSelection(tab: Tab) {
|
|
|
759
779
|
/**
|
|
760
780
|
* Make `tabId` the active tab OF ITS OWN PANE. The one place activation happens.
|
|
761
781
|
*
|
|
762
|
-
* `focus: false` is "put it on screen there, and leave the keyboard where it is" — the shape a
|
|
763
|
-
*
|
|
764
|
-
* `resetTabCycle`, no `promoteMru`, no `syncTreeSelection`,
|
|
765
|
-
* where the AUTHOR is and the author has not moved.
|
|
766
|
-
*
|
|
767
|
-
*
|
|
782
|
+
* `focus: false` is "put it on screen there, and leave the keyboard where it is" — the shape a READ
|
|
783
|
+
* needs: the derivation follow, `pane.compareWith`, session restore. It writes `pane.activeTabId`
|
|
784
|
+
* and stops: no `activePaneId` write, no `resetTabCycle`, no `promoteMru`, no `syncTreeSelection`,
|
|
785
|
+
* because every one of those describes where the AUTHOR is and the author has not moved. The
|
|
786
|
+
* gestures that OPEN something — drilling into a component, Open to the Side — follow instead: the
|
|
787
|
+
* pane they open into takes the keyboard, because the author asked to go there.
|
|
768
788
|
*
|
|
769
789
|
* Legal here and nowhere else — this module is `check-pane-singletons.ts`'s `FOCUS_OWNER`, and the
|
|
770
790
|
* default is still to move the focus, so no existing caller changes behaviour.
|
|
@@ -809,10 +829,12 @@ function setActiveTab(tabId: string, opts: { cycling?: boolean; focus?: boolean
|
|
|
809
829
|
*
|
|
810
830
|
* `paneId` says WHERE, and `focus` says whether the keyboard goes with it. Both default to today's
|
|
811
831
|
* answer — the focused pane, and yes — because "open this document" has always meant "in front of
|
|
812
|
-
* me". They exist because
|
|
813
|
-
*
|
|
814
|
-
*
|
|
815
|
-
* the
|
|
832
|
+
* me". They exist because the gestures that mean the opposite are the READS: comparing two
|
|
833
|
+
* documents and following a derivation put it in the pane beside this one and leave the author
|
|
834
|
+
* where they are. The gestures that OPEN something — drill-in, Open to the Side — pass neither: the
|
|
835
|
+
* pane they open into takes the keyboard, because the author asked to go there. A `paneId` no pane
|
|
836
|
+
* carries falls back to the focused pane rather than dropping the open, which is the same answer a
|
|
837
|
+
* stale persisted layout gets everywhere else in this module.
|
|
816
838
|
*
|
|
817
839
|
* @param {{
|
|
818
840
|
* id: string;
|
|
@@ -960,12 +982,33 @@ export function moveTab(tabId: string, toIndex: number) {
|
|
|
960
982
|
if (!tab || !pane) {
|
|
961
983
|
return;
|
|
962
984
|
}
|
|
985
|
+
pane.tabOrder = reorderWithinPane(pane, tabId, toIndex);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/**
|
|
989
|
+
* The order `pane`'s strip takes when `tabId` moves to `toIndex`, clamped into the region the tab's
|
|
990
|
+
* pinned state allows — the pinned prefix and the unpinned tail never interleave.
|
|
991
|
+
*
|
|
992
|
+
* Extracted from `moveTab` because the cross-pane move needs the SAME clamp: a tab dragged into
|
|
993
|
+
* another pane's strip at index 0 must land after that pane's pinned prefix, not inside it, and
|
|
994
|
+
* duplicating the boundary arithmetic is how the two moves drift apart.
|
|
995
|
+
*
|
|
996
|
+
* @param {Pane} pane
|
|
997
|
+
* @param {string} tabId
|
|
998
|
+
* @param {number} toIndex
|
|
999
|
+
* @returns {string[]} The pane's new `tabOrder`.
|
|
1000
|
+
*/
|
|
1001
|
+
function reorderWithinPane(pane: Pane, tabId: string, toIndex: number): string[] {
|
|
1002
|
+
const tab = workspace.tabs.get(tabId);
|
|
1003
|
+
if (!tab) {
|
|
1004
|
+
return pane.tabOrder;
|
|
1005
|
+
}
|
|
963
1006
|
const without = pane.tabOrder.filter((id) => id !== tabId);
|
|
964
1007
|
const boundary = without.filter((id) => workspace.tabs.get(id)?.pinned === true).length;
|
|
965
1008
|
const lower = tab.pinned ? 0 : boundary;
|
|
966
1009
|
const upper = tab.pinned ? boundary : without.length;
|
|
967
1010
|
const at = Math.min(Math.max(toIndex, lower), upper);
|
|
968
|
-
|
|
1011
|
+
return [...without.slice(0, at), tabId, ...without.slice(at)];
|
|
969
1012
|
}
|
|
970
1013
|
|
|
971
1014
|
/**
|
|
@@ -1177,20 +1220,30 @@ export function activateTab(tabId: string, opts: { focus?: boolean } = {}) {
|
|
|
1177
1220
|
*
|
|
1178
1221
|
* Idempotent for a tab already in `paneId`, which is what makes it safe on the follow path.
|
|
1179
1222
|
*
|
|
1223
|
+
* `index` is the slot in `paneId`'s order the tab takes, clamped into the region the tab's pinned
|
|
1224
|
+
* state allows — the same clamp `moveTab` applies, so a cross-pane move can never interleave a
|
|
1225
|
+
* pinned tab with an unpinned one. Omitted, the tab takes the default slot: inside the pinned
|
|
1226
|
+
* prefix if pinned, else at the end. A same-pane move with an index delegates to `moveTab`, so the
|
|
1227
|
+
* drag-reorder and the cross-pane move share one clamp rather than two.
|
|
1228
|
+
*
|
|
1180
1229
|
* @param {string} tabId
|
|
1181
1230
|
* @param {string} paneId
|
|
1231
|
+
* @param {number} [index]
|
|
1182
1232
|
* @returns {Pane | null} The pane it landed in, or null when either id names nothing.
|
|
1183
1233
|
*/
|
|
1184
|
-
export function moveTabToPane(tabId: string, paneId: string): Pane | null {
|
|
1234
|
+
export function moveTabToPane(tabId: string, paneId: string, index?: number): Pane | null {
|
|
1185
1235
|
const pane = paneById(paneId);
|
|
1186
1236
|
if (!pane || !workspace.tabs.has(tabId)) {
|
|
1187
1237
|
return null;
|
|
1188
1238
|
}
|
|
1189
1239
|
if (pane.tabOrder.includes(tabId)) {
|
|
1240
|
+
if (index !== undefined) {
|
|
1241
|
+
pane.tabOrder = reorderWithinPane(pane, tabId, index);
|
|
1242
|
+
}
|
|
1190
1243
|
return pane;
|
|
1191
1244
|
}
|
|
1192
1245
|
detachTab(tabId);
|
|
1193
|
-
insertIntoPane(pane, tabId);
|
|
1246
|
+
insertIntoPane(pane, tabId, index);
|
|
1194
1247
|
return pane;
|
|
1195
1248
|
}
|
|
1196
1249
|
|
|
@@ -1226,8 +1279,16 @@ export function renameTab(oldId: string, newId: string, newDocumentPath: string)
|
|
|
1226
1279
|
|
|
1227
1280
|
/** What the tab commands need from the rest of Studio. */
|
|
1228
1281
|
export interface TabCommandDeps {
|
|
1229
|
-
/**
|
|
1230
|
-
|
|
1282
|
+
/**
|
|
1283
|
+
* Read a file from disk and show it — `files/files.ts`'s `openFileInTab`. The options are the
|
|
1284
|
+
* opener's own: `paneId` names the pane the tab lands in (defaulting to the focused one), and
|
|
1285
|
+
* `focus: false` browses without moving the keyboard — the shape session restore and the
|
|
1286
|
+
* derivation follow need.
|
|
1287
|
+
*/
|
|
1288
|
+
openFile: (
|
|
1289
|
+
path: string,
|
|
1290
|
+
opts?: { paneId?: string; focus?: boolean; preview?: boolean },
|
|
1291
|
+
) => void | Promise<void>;
|
|
1231
1292
|
/**
|
|
1232
1293
|
* Read a file from disk and show it in a NAMED pane, browsing rather than committing and leaving
|
|
1233
1294
|
* the keyboard where it is — `files/files.ts`'s `openFileInPane`.
|
|
@@ -1357,6 +1418,58 @@ export function tabCommands(deps: TabCommandDeps): AnyCommand[] {
|
|
|
1357
1418
|
}
|
|
1358
1419
|
},
|
|
1359
1420
|
},
|
|
1421
|
+
{
|
|
1422
|
+
args: argsSchema({
|
|
1423
|
+
file: stringProperty(
|
|
1424
|
+
'Project-relative path of the file to open in the other pane, e.g. "pages/about.json". ' +
|
|
1425
|
+
"It must exist.",
|
|
1426
|
+
),
|
|
1427
|
+
}),
|
|
1428
|
+
id: "document.openToSide",
|
|
1429
|
+
title: "Open to the Side",
|
|
1430
|
+
category: "Document",
|
|
1431
|
+
/* PROJECT level for the same reason `document.open` is: it adds a tab to the workspace, and
|
|
1432
|
+
a second pane may not exist yet — the record mints one. */
|
|
1433
|
+
level: "project",
|
|
1434
|
+
menus: ["context/file", "palette"],
|
|
1435
|
+
group: "1_file",
|
|
1436
|
+
when: (ctx) => ctx.project.open,
|
|
1437
|
+
requires: "an open project",
|
|
1438
|
+
/* No `aiTool`, by §12.4's first deletion rule: chrome — it arranges what the person is
|
|
1439
|
+
looking at. No `undo`: opening a tab is not a state the undo stack owns. */
|
|
1440
|
+
undo: "none",
|
|
1441
|
+
/* The non-drag equivalent of dragging a file row onto the other pane's strip (SC 2.5.7): the
|
|
1442
|
+
same `receivingPane` the drag resolver uses, so a file tree row and this command can never
|
|
1443
|
+
disagree about where "the side" is. The open is a FOLLOW — the pane it lands in takes the
|
|
1444
|
+
keyboard, which is what "open to the side" means when the side pane did not exist a moment
|
|
1445
|
+
ago. */
|
|
1446
|
+
run: async (_ctx, args) => {
|
|
1447
|
+
const file = stringArg("document.openToSide", args, "file");
|
|
1448
|
+
const target = receivingPane(activePane().id);
|
|
1449
|
+
await deps.openFile(file, { paneId: target.id });
|
|
1450
|
+
if (![...workspace.tabs.values()].some((tab) => tab.documentPath === file)) {
|
|
1451
|
+
/* The open failed (a missing file, or no editor claims the format) and `openFileInTab`
|
|
1452
|
+
reported it as a Problem rather than throwing. If the target pane was minted for this
|
|
1453
|
+
open and holds nothing, close it — a command that fails must not leave a new empty
|
|
1454
|
+
pane behind.
|
|
1455
|
+
|
|
1456
|
+
`target.id !== PRIMARY_PANE` GUARDS it, and the guard is load-bearing rather than
|
|
1457
|
+
defensive: `receivingPane` answers PRIMARY when the author is focused on the secondary,
|
|
1458
|
+
and an empty primary is a legitimate state on its own (`detachTab`'s "welcome screen
|
|
1459
|
+
beside the document" exemption) — not a hole this command minted. `closePane`'s own
|
|
1460
|
+
contract reads `PRIMARY_PANE` as "collapse the OTHER pane", so calling it unguarded here
|
|
1461
|
+
would close the secondary the author was just looking at, over a refusal about a
|
|
1462
|
+
completely different pane. */
|
|
1463
|
+
if (paneIsEmpty(target) && target.id !== PRIMARY_PANE) {
|
|
1464
|
+
closePane(target.id);
|
|
1465
|
+
}
|
|
1466
|
+
throw new RangeError(
|
|
1467
|
+
`command "document.openToSide" argument "file": "${file}" could not be opened — it ` +
|
|
1468
|
+
`does not exist, or no editor claims its format. Problems has the reason.`,
|
|
1469
|
+
);
|
|
1470
|
+
}
|
|
1471
|
+
},
|
|
1472
|
+
},
|
|
1360
1473
|
{
|
|
1361
1474
|
id: "document.reopenClosed",
|
|
1362
1475
|
title: "Reopen Closed Document",
|
|
@@ -1470,6 +1583,10 @@ export function paneCommands(deps: TabCommandDeps): AnyCommand[] {
|
|
|
1470
1583
|
requires: "an open document",
|
|
1471
1584
|
undo: "none",
|
|
1472
1585
|
run: () => {
|
|
1586
|
+
/* The non-drag equivalent of every drag move (SC 2.5.7): `splitRight` moves the active tab
|
|
1587
|
+
to a new pane at the right of the grid, which is what dragging a tab onto the grid's
|
|
1588
|
+
right edge does, and `document.openToSide` is its file-tree counterpart. A keyboard-only
|
|
1589
|
+
author can reach every arrangement the drag offers. */
|
|
1473
1590
|
splitRight();
|
|
1474
1591
|
},
|
|
1475
1592
|
},
|
|
@@ -1508,11 +1625,11 @@ export function paneCommands(deps: TabCommandDeps): AnyCommand[] {
|
|
|
1508
1625
|
);
|
|
1509
1626
|
}
|
|
1510
1627
|
/* Nothing closes, nothing unsplits, and the FOCUS DOES NOT MOVE: comparing is a read.
|
|
1511
|
-
{@link receivingPane} rather than `
|
|
1628
|
+
{@link receivingPane} rather than `paneBeside`, because the pane beside this one may be a
|
|
1512
1629
|
LENS — and a lens owns no tab, so the document landed in a `tabOrder` that `tabOfPane`
|
|
1513
1630
|
hops straight past. Nothing was on screen, nothing was in a strip, and
|
|
1514
1631
|
`workspace.activeTabId` went on reporting the source. */
|
|
1515
|
-
await deps.openFileInPane(receivingPane().id, path);
|
|
1632
|
+
await deps.openFileInPane(receivingPane(here.id).id, path);
|
|
1516
1633
|
},
|
|
1517
1634
|
},
|
|
1518
1635
|
{
|
package/styles/shell-frame.css
CHANGED
|
@@ -54,6 +54,14 @@
|
|
|
54
54
|
#toolbar.electrobun-webkit-app-region-drag {
|
|
55
55
|
-webkit-app-region: drag;
|
|
56
56
|
}
|
|
57
|
+
/* The mirror of #toolbar's own drag class above: every interactive control inside the Command Bar
|
|
58
|
+
carries this (commandbar.json) so Electrobun's ancestry walk finds a working no-drag signal on
|
|
59
|
+
the button itself instead of falling through to the toolbar's drag region. Unscoped, like
|
|
60
|
+
.resize-edge below, because the class name is self-describing and used nowhere else. A no-op
|
|
61
|
+
outside Electrobun. */
|
|
62
|
+
.electrobun-webkit-app-region-no-drag {
|
|
63
|
+
-webkit-app-region: no-drag;
|
|
64
|
+
}
|
|
57
65
|
/* Resize edge zones */
|
|
58
66
|
.resize-edge {
|
|
59
67
|
position: fixed;
|
package/styles/shell-frame.json
CHANGED
|
@@ -35,6 +35,10 @@
|
|
|
35
35
|
"#toolbar.electrobun-webkit-app-region-drag": {
|
|
36
36
|
"-webkit-app-region": "drag"
|
|
37
37
|
},
|
|
38
|
+
".electrobun-webkit-app-region-no-drag": {
|
|
39
|
+
"$description": "The mirror of #toolbar's own drag class above: every interactive control inside the Command Bar carries this (commandbar.json) so Electrobun's ancestry walk finds a working no-drag signal on the button itself instead of falling through to the toolbar's drag region. Unscoped, like .resize-edge below, because the class name is self-describing and used nowhere else. A no-op outside Electrobun.",
|
|
40
|
+
"-webkit-app-region": "no-drag"
|
|
41
|
+
},
|
|
38
42
|
".resize-edge": {
|
|
39
43
|
"$description": "Resize edge zones",
|
|
40
44
|
"position": "fixed",
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
collectionInfo2,
|
|
3
|
-
collectionDirs2,
|
|
4
|
-
listCollectionEntryIds2,
|
|
5
|
-
reloadDraftAwareGrids2,
|
|
6
|
-
createCollectionSource2,
|
|
7
|
-
createPagesSource2
|
|
8
|
-
} from "./studio-1knfqgy7.js";
|
|
9
|
-
import"./studio-gb13095a.js";
|
|
10
|
-
import"./studio-g3jevyjt.js";
|
|
11
|
-
import"./studio-q0a3yk4n.js";
|
|
12
|
-
import"./studio-3773086z.js";
|
|
13
|
-
import"./studio-tqgryxwg.js";
|
|
14
|
-
import"./studio-mhhyamxf.js";
|
|
15
|
-
import"./studio-8scdew0e.js";
|
|
16
|
-
import"./studio-nq5yf5mp.js";
|
|
17
|
-
import"./studio-1z1kcjv4.js";
|
|
18
|
-
import"./studio-qk90fgqm.js";
|
|
19
|
-
import"./studio-zdn2g6dt.js";
|
|
20
|
-
import"./studio-y9zedyjn.js";
|
|
21
|
-
import"./studio-jxp12rnj.js";
|
|
22
|
-
import"./studio-nzzpvzx9.js";
|
|
23
|
-
import"./studio-k75n7vhp.js";
|
|
24
|
-
import"./studio-q3x06vmj.js";
|
|
25
|
-
import"./studio-6ewqv390.js";
|
|
26
|
-
import"./studio-esrxgwh6.js";
|
|
27
|
-
import"./studio-vw7mzp6z.js";
|
|
28
|
-
import"./studio-6nzaktvy.js";
|
|
29
|
-
import"./studio-aj37eer3.js";
|
|
30
|
-
import"./studio-vfq8qpjt.js";
|
|
31
|
-
import"./studio-1k325pwf.js";
|
|
32
|
-
import"./studio-kkqkcp3d.js";
|
|
33
|
-
import"./studio-p1dw8wfw.js";
|
|
34
|
-
import"./studio-yxa81f45.js";
|
|
35
|
-
import"./studio-7r42c6ca.js";
|
|
36
|
-
export {
|
|
37
|
-
collectionDirs2 as collectionDirs,
|
|
38
|
-
collectionInfo2 as collectionInfo,
|
|
39
|
-
createCollectionSource2 as createCollectionSource,
|
|
40
|
-
createPagesSource2 as createPagesSource,
|
|
41
|
-
listCollectionEntryIds2 as listCollectionEntryIds,
|
|
42
|
-
reloadDraftAwareGrids2 as reloadDraftAwareGrids
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
//# debugId=597BB9A23802D85864756E2164756E21
|
|
46
|
-
//# sourceMappingURL=content-source-5mex4x1h.js.map
|