mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-04 13:30:48 +00:00
Fix Eyedropper tool and make Svelte's bind:this more robust
This commit is contained in:
parent
919779130f
commit
29af355f20
23 changed files with 177 additions and 142 deletions
|
@ -925,7 +925,7 @@ impl Fsm for SelectToolFsmState {
|
|||
HintInfo::keys([Key::Control], "Opp. Corner").prepend_plus(),
|
||||
]),
|
||||
HintGroup(vec![
|
||||
HintInfo::keys([Key::Alt], "Move Duplicate"),
|
||||
HintInfo::keys_and_mouse([Key::Alt], MouseMotion::LmbDrag, "Move Duplicate"),
|
||||
HintInfo::keys([Key::Control, Key::KeyD], "Duplicate").add_mac_keys([Key::Command, Key::KeyD]),
|
||||
]),
|
||||
]);
|
||||
|
|
|
@ -539,6 +539,17 @@ impl HintInfo {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn keys_and_mouse(keys: impl IntoIterator<Item = Key>, mouse_motion: MouseMotion, label: impl Into<String>) -> Self {
|
||||
let keys: Vec<_> = keys.into_iter().collect();
|
||||
Self {
|
||||
key_groups: vec![KeysGroup(keys).into()],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(mouse_motion),
|
||||
label: label.into(),
|
||||
plus: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn arrow_keys(label: impl Into<String>) -> Self {
|
||||
HintInfo {
|
||||
key_groups: vec![
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path class="color-vector" d="M21.98,15.79c-0.12-0.54-0.65-0.89-1.19-0.77c-0.08,0.02-2.09,0.46-4.39,2.18c-0.77,0.57-1.4,1.09-1.99,1.57c-1.21,0.99-2.17,1.78-3.69,2.47c-0.59,0.27-1.84,0.72-3.57,0.75l-3.61,1.55C4.68,23.84,5.81,24,6.94,24c0.02,0,0.05,0,0.07,0c2.2-0.01,3.8-0.59,4.55-0.94c1.75-0.8,2.85-1.7,4.13-2.75c0.56-0.46,1.17-0.96,1.91-1.51c1.93-1.44,3.6-1.82,3.61-1.82C21.75,16.86,22.09,16.33,21.98,15.79z" />
|
||||
<path class="color-solid" d="M21.98,15.79c-0.12-0.54-0.65-0.89-1.19-0.77c-0.08,0.02-2.09,0.46-4.39,2.18c-0.77,0.57-1.4,1.09-1.99,1.57c-1.21,0.99-2.17,1.78-3.69,2.47c-0.59,0.27-1.84,0.72-3.57,0.75l-3.61,1.55C4.68,23.84,5.81,24,6.94,24c0.02,0,0.05,0,0.07,0c2.2-0.01,3.8-0.59,4.55-0.94c1.75-0.8,2.85-1.7,4.13-2.75c0.56-0.46,1.17-0.96,1.91-1.51c1.93-1.44,3.6-1.82,3.61-1.82C21.75,16.86,22.09,16.33,21.98,15.79z" />
|
||||
<path class="color-vector" d="M18.5,5.5c-1-1-2.5-1.5-3-1L5,15c0.39,0.24,1.23,0.1,1.59,0.38c0.36,0.28,0.3,1.06,0.63,1.4c0.33,0.33,1.11,0.28,1.4,0.63C8.9,17.77,8.76,18.61,9,19L19.5,8.5C20,8,19.5,6.5,18.5,5.5z M16.5,7.5l-7,7c-0.14,0.14-0.32,0.21-0.5,0.21s-0.36-0.07-0.5-0.21c-0.28-0.28-0.28-0.72,0-1l7-7c0.28-0.28,0.72-0.28,1,0C16.78,6.78,16.78,7.22,16.5,7.5z" />
|
||||
<path class="color-solid" d="M7.35,18.47l-2.39,1.71c-0.1-0.27-0.22-0.51-0.42-0.71c-0.2-0.2-0.44-0.32-0.71-0.42l1.71-2.39C5.14,16.61,4.5,16.42,4,16l-3,7l7-3C7.58,19.5,7.39,18.86,7.35,18.47z" />
|
||||
<path class="color-solid" d="M21.99,2.01C21,1,19.5,0.5,19,1l-2,2c2,0,4,2,4,4l2-2C23.5,4.5,22.98,3.02,21.99,2.01z" />
|
||||
|
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
@ -11,7 +11,7 @@
|
|||
|
||||
const dialog = getContext<DialogState>("dialog");
|
||||
|
||||
let self: FloatingMenu;
|
||||
let self: FloatingMenu | undefined;
|
||||
|
||||
export function dismiss() {
|
||||
dialog.dismissDialog();
|
||||
|
@ -19,7 +19,7 @@
|
|||
|
||||
onMount(() => {
|
||||
// Focus the first button in the popup
|
||||
const emphasizedOrFirstButton = (self.div().querySelector("[data-emphasized]") || self.div().querySelector("[data-text-button]") || undefined) as HTMLButtonElement | undefined;
|
||||
const emphasizedOrFirstButton = (self?.div()?.querySelector("[data-emphasized]") || self?.div()?.querySelector("[data-text-button]") || undefined) as HTMLButtonElement | undefined;
|
||||
emphasizedOrFirstButton?.focus();
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts" context="module">
|
||||
// Should be equal to the width and height of the canvas in the CSS
|
||||
// Should be equal to the width and height of the zoom preview canvas in the CSS
|
||||
const ZOOM_WINDOW_DIMENSIONS_EXPANDED = 110;
|
||||
// Should be equal to the width and height of the `.pixel-outline` div in the CSS, and should be evenly divisible into the number above
|
||||
const UPSCALE_FACTOR = 10;
|
||||
|
@ -13,8 +13,10 @@
|
|||
import FloatingMenu from "@/components/layout/FloatingMenu.svelte";
|
||||
|
||||
const temporaryCanvas = document.createElement("canvas");
|
||||
temporaryCanvas.width = ZOOM_WINDOW_DIMENSIONS;
|
||||
temporaryCanvas.height = ZOOM_WINDOW_DIMENSIONS;
|
||||
|
||||
let zoomPreviewCanvas: HTMLCanvasElement;
|
||||
let zoomPreviewCanvas: HTMLCanvasElement | undefined;
|
||||
|
||||
export let imageData: ImageData | undefined = undefined;
|
||||
export let colorChoice: string;
|
||||
|
@ -23,19 +25,12 @@
|
|||
export let x: number;
|
||||
export let y: number;
|
||||
|
||||
$: watchImageData(imageData);
|
||||
|
||||
function watchImageData(imageData: ImageData | undefined) {
|
||||
displayImageDataPreview(imageData);
|
||||
}
|
||||
$: displayImageDataPreview(imageData);
|
||||
|
||||
function displayImageDataPreview(imageData: ImageData | undefined) {
|
||||
zoomPreviewCanvas.width = ZOOM_WINDOW_DIMENSIONS;
|
||||
zoomPreviewCanvas.height = ZOOM_WINDOW_DIMENSIONS;
|
||||
if (!zoomPreviewCanvas) return;
|
||||
const context = zoomPreviewCanvas.getContext("2d");
|
||||
|
||||
temporaryCanvas.width = ZOOM_WINDOW_DIMENSIONS;
|
||||
temporaryCanvas.height = ZOOM_WINDOW_DIMENSIONS;
|
||||
const temporaryContext = temporaryCanvas.getContext("2d");
|
||||
|
||||
if (!imageData || !context || !temporaryContext) return;
|
||||
|
@ -61,7 +56,7 @@
|
|||
>
|
||||
<div class="ring">
|
||||
<div class="canvas-container">
|
||||
<canvas bind:this={zoomPreviewCanvas} />
|
||||
<canvas width={ZOOM_WINDOW_DIMENSIONS} height={ZOOM_WINDOW_DIMENSIONS} bind:this={zoomPreviewCanvas} />
|
||||
<div class="pixel-outline" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
import TextLabel from "@/components/widgets/labels/TextLabel.svelte";
|
||||
import UserInputLabel from "@/components/widgets/labels/UserInputLabel.svelte";
|
||||
|
||||
let self: FloatingMenu;
|
||||
let scroller: LayoutCol;
|
||||
let self: FloatingMenu | undefined;
|
||||
let scroller: LayoutCol | undefined;
|
||||
|
||||
// emits: ["update:open", "update:activeEntry", "naturalWidth"],
|
||||
const dispatch = createEventDispatcher<{ open: boolean; activeEntry: MenuListEntry }>();
|
||||
|
@ -171,7 +171,7 @@
|
|||
}
|
||||
|
||||
export function scrollViewTo(distanceDown: number): void {
|
||||
scroller.div().scrollTo(0, distanceDown);
|
||||
scroller?.div()?.scrollTo(0, distanceDown);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -236,6 +236,7 @@
|
|||
{/if}
|
||||
|
||||
{#if entry.children}
|
||||
<!-- TODO: Solve the red underline error on the bind:this below -->
|
||||
<svelte:self on:naturalWidth open={entry.ref?.open || false} direction="TopRight" entries={entry.children} {minWidth} {drawIcon} {scrollableY} bind:this={entry.ref} />
|
||||
{/if}
|
||||
</LayoutRow>
|
||||
|
|
|
@ -28,10 +28,10 @@
|
|||
export let escapeCloses = true;
|
||||
export let strayCloses = true;
|
||||
|
||||
let tail: HTMLDivElement;
|
||||
let self: HTMLDivElement;
|
||||
let floatingMenuContainer: HTMLDivElement;
|
||||
let floatingMenuContent: LayoutCol;
|
||||
let tail: HTMLDivElement | undefined;
|
||||
let self: HTMLDivElement | undefined;
|
||||
let floatingMenuContainer: HTMLDivElement | undefined;
|
||||
let floatingMenuContent: LayoutCol | undefined;
|
||||
|
||||
// The resize observer is attached to the floating menu container, which is the zero-height div of the width of the parent element's floating menu spawner.
|
||||
// Since CSS doesn't let us make the floating menu (with `position: fixed`) have a 100% width of this container, we need to use JS to observe its size and
|
||||
|
@ -82,8 +82,10 @@
|
|||
await tick();
|
||||
|
||||
// Start a new observation of the now-open floating menu
|
||||
containerResizeObserver.disconnect();
|
||||
containerResizeObserver.observe(floatingMenuContainer);
|
||||
if (floatingMenuContainer) {
|
||||
containerResizeObserver.disconnect();
|
||||
containerResizeObserver.observe(floatingMenuContainer);
|
||||
}
|
||||
}
|
||||
|
||||
// Switching from open to closed
|
||||
|
@ -117,12 +119,13 @@
|
|||
|
||||
const workspace = document.querySelector("[data-workspace]");
|
||||
|
||||
if (!workspace || !self || !floatingMenuContainer || !floatingMenuContent) return;
|
||||
const floatingMenuContentDiv = floatingMenuContent?.div();
|
||||
if (!workspace || !self || !floatingMenuContainer || !floatingMenuContent || !floatingMenuContentDiv) return;
|
||||
|
||||
workspaceBounds = workspace.getBoundingClientRect();
|
||||
floatingMenuBounds = self.getBoundingClientRect();
|
||||
const floatingMenuContainerBounds = floatingMenuContainer.getBoundingClientRect();
|
||||
floatingMenuContentBounds = floatingMenuContent.div().getBoundingClientRect();
|
||||
floatingMenuContentBounds = floatingMenuContentDiv.getBoundingClientRect();
|
||||
|
||||
const inParentFloatingMenu = Boolean(floatingMenuContainer.closest("[data-floating-menu-content]"));
|
||||
|
||||
|
@ -130,10 +133,10 @@
|
|||
// Required to correctly position content when scrolled (it has a `position: fixed` to prevent clipping)
|
||||
// We use `.style` on a div (instead of a style DOM attribute binding) because the binding causes the `afterUpdate()` hook to call the function we're in recursively forever
|
||||
const tailOffset = type === "Popover" ? 10 : 0;
|
||||
if (direction === "Bottom") floatingMenuContent.div().style.top = `${tailOffset + floatingMenuBounds.top}px`;
|
||||
if (direction === "Top") floatingMenuContent.div().style.bottom = `${tailOffset + floatingMenuBounds.bottom}px`;
|
||||
if (direction === "Right") floatingMenuContent.div().style.left = `${tailOffset + floatingMenuBounds.left}px`;
|
||||
if (direction === "Left") floatingMenuContent.div().style.right = `${tailOffset + floatingMenuBounds.right}px`;
|
||||
if (direction === "Bottom") floatingMenuContentDiv.style.top = `${tailOffset + floatingMenuBounds.top}px`;
|
||||
if (direction === "Top") floatingMenuContentDiv.style.bottom = `${tailOffset + floatingMenuBounds.bottom}px`;
|
||||
if (direction === "Right") floatingMenuContentDiv.style.left = `${tailOffset + floatingMenuBounds.left}px`;
|
||||
if (direction === "Left") floatingMenuContentDiv.style.right = `${tailOffset + floatingMenuBounds.right}px`;
|
||||
|
||||
// Required to correctly position tail when scrolled (it has a `position: fixed` to prevent clipping)
|
||||
// We use `.style` on a div (instead of a style DOM attribute binding) because the binding causes the `afterUpdate()` hook to call the function we're in recursively forever
|
||||
|
@ -152,11 +155,11 @@
|
|||
|
||||
// We use `.style` on a div (instead of a style DOM attribute binding) because the binding causes the `afterUpdate()` hook to call the function we're in recursively forever
|
||||
if (floatingMenuContentBounds.left - windowEdgeMargin <= workspaceBounds.left) {
|
||||
floatingMenuContent.div().style.left = `${windowEdgeMargin}px`;
|
||||
floatingMenuContentDiv.style.left = `${windowEdgeMargin}px`;
|
||||
if (workspaceBounds.left + floatingMenuContainerBounds.left === 12) zeroedBorderHorizontal = "Left";
|
||||
}
|
||||
if (floatingMenuContentBounds.right + windowEdgeMargin >= workspaceBounds.right) {
|
||||
floatingMenuContent.div().style.right = `${windowEdgeMargin}px`;
|
||||
floatingMenuContentDiv.style.right = `${windowEdgeMargin}px`;
|
||||
if (workspaceBounds.right - floatingMenuContainerBounds.right === 12) zeroedBorderHorizontal = "Right";
|
||||
}
|
||||
}
|
||||
|
@ -165,11 +168,11 @@
|
|||
|
||||
// We use `.style` on a div (instead of a style DOM attribute binding) because the binding causes the `afterUpdate()` hook to call the function we're in recursively forever
|
||||
if (floatingMenuContentBounds.top - windowEdgeMargin <= workspaceBounds.top) {
|
||||
floatingMenuContent.div().style.top = `${windowEdgeMargin}px`;
|
||||
floatingMenuContentDiv.style.top = `${windowEdgeMargin}px`;
|
||||
if (workspaceBounds.top + floatingMenuContainerBounds.top === 12) zeroedBorderVertical = "Top";
|
||||
}
|
||||
if (floatingMenuContentBounds.bottom + windowEdgeMargin >= workspaceBounds.bottom) {
|
||||
floatingMenuContent.div().style.bottom = `${windowEdgeMargin}px`;
|
||||
floatingMenuContentDiv.style.bottom = `${windowEdgeMargin}px`;
|
||||
if (workspaceBounds.bottom - floatingMenuContainerBounds.bottom === 12) zeroedBorderVertical = "Bottom";
|
||||
}
|
||||
}
|
||||
|
@ -179,16 +182,16 @@
|
|||
// We use `.style` on a div (instead of a style DOM attribute binding) because the binding causes the `afterUpdate()` hook to call the function we're in recursively forever
|
||||
switch (`${zeroedBorderVertical}${zeroedBorderHorizontal}`) {
|
||||
case "TopLeft":
|
||||
floatingMenuContent.div().style.borderTopLeftRadius = "0";
|
||||
floatingMenuContentDiv.style.borderTopLeftRadius = "0";
|
||||
break;
|
||||
case "TopRight":
|
||||
floatingMenuContent.div().style.borderTopRightRadius = "0";
|
||||
floatingMenuContentDiv.style.borderTopRightRadius = "0";
|
||||
break;
|
||||
case "BottomLeft":
|
||||
floatingMenuContent.div().style.borderBottomLeftRadius = "0";
|
||||
floatingMenuContentDiv.style.borderBottomLeftRadius = "0";
|
||||
break;
|
||||
case "BottomRight":
|
||||
floatingMenuContent.div().style.borderBottomRightRadius = "0";
|
||||
floatingMenuContentDiv.style.borderBottomRightRadius = "0";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -196,7 +199,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
export function div(): HTMLDivElement {
|
||||
export function div(): HTMLDivElement | undefined {
|
||||
return self;
|
||||
}
|
||||
|
||||
|
@ -217,7 +220,7 @@
|
|||
|
||||
// Measure the width of the floating menu content element, if it's currently visible
|
||||
// The result will be `undefined` if the menu is invisible, perhaps because an ancestor component is hidden with a falsy Svelte template if condition
|
||||
const naturalWidth: number | undefined = floatingMenuContent?.div().clientWidth;
|
||||
const naturalWidth: number | undefined = floatingMenuContent?.div()?.clientWidth;
|
||||
|
||||
// Turn off measuring mode for the component, which triggers another call to the `afterUpdate()` Svelte event, so we can turn off the protection after that has happened
|
||||
measuringOngoing = false;
|
||||
|
@ -365,7 +368,7 @@
|
|||
|
||||
function isPointerEventOutsideFloatingMenu(e: PointerEvent, extraDistanceAllowed = 0): boolean {
|
||||
// Consider all child menus as well as the top-level one
|
||||
const allContainedFloatingMenus = [...self.querySelectorAll("[data-floating-menu-content]")];
|
||||
const allContainedFloatingMenus = [...(self?.querySelectorAll("[data-floating-menu-content]") || [])];
|
||||
|
||||
return !allContainedFloatingMenus.find((element) => !isPointerEventOutsideMenuElement(e, element, extraDistanceAllowed));
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
export let scrollableX = false;
|
||||
export let scrollableY = false;
|
||||
|
||||
let self: HTMLDivElement;
|
||||
let self: HTMLDivElement | undefined;
|
||||
|
||||
$: extraClasses = Object.entries(classes)
|
||||
.flatMap((classAndState) => (classAndState[1] ? [classAndState[0]] : []))
|
||||
|
@ -18,7 +18,7 @@
|
|||
.flatMap((styleAndValue) => (styleAndValue[1] !== undefined ? [`${styleAndValue[0]}: ${styleAndValue[1]};`] : []))
|
||||
.join(" ");
|
||||
|
||||
export function div(): HTMLDivElement {
|
||||
export function div(): HTMLDivElement | undefined {
|
||||
return self;
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
export let scrollableX = false;
|
||||
export let scrollableY = false;
|
||||
|
||||
let self: HTMLDivElement;
|
||||
let self: HTMLDivElement | undefined;
|
||||
|
||||
$: extraClasses = Object.entries(classes)
|
||||
.flatMap((classAndState) => (classAndState[1] ? [classAndState[0]] : []))
|
||||
|
@ -18,7 +18,7 @@
|
|||
.flatMap((styleAndValue) => (styleAndValue[1] !== undefined ? [`${styleAndValue[0]}: ${styleAndValue[1]};`] : []))
|
||||
.join(" ");
|
||||
|
||||
export function div(): HTMLDivElement {
|
||||
export function div(): HTMLDivElement | undefined {
|
||||
return self;
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -28,9 +28,9 @@
|
|||
import type { Editor } from "@/wasm-communication/editor";
|
||||
import type { DocumentState } from "@/state-providers/document";
|
||||
|
||||
let rulerHorizontal: CanvasRuler;
|
||||
let rulerVertical: CanvasRuler;
|
||||
let canvasContainer: HTMLDivElement;
|
||||
let rulerHorizontal: CanvasRuler | undefined;
|
||||
let rulerVertical: CanvasRuler | undefined;
|
||||
let canvasContainer: HTMLDivElement | undefined;
|
||||
|
||||
const editor = getContext<Editor>("editor");
|
||||
const document = getContext<DocumentState>("document");
|
||||
|
@ -125,8 +125,8 @@
|
|||
await tick();
|
||||
|
||||
if (textInput) {
|
||||
const foreignObject = canvasContainer.getElementsByTagName("foreignObject")[0] as SVGForeignObjectElement;
|
||||
if (foreignObject.children.length > 0) return;
|
||||
const foreignObject = canvasContainer?.getElementsByTagName("foreignObject")[0] as SVGForeignObjectElement | undefined;
|
||||
if (!foreignObject || foreignObject.children.length > 0) return;
|
||||
|
||||
const addedInput = foreignObject.appendChild(textInput);
|
||||
window.dispatchEvent(new CustomEvent("modifyinputfield", { detail: addedInput }));
|
||||
|
@ -282,6 +282,8 @@
|
|||
|
||||
// Resize elements to render the new viewport size
|
||||
export function viewportResize() {
|
||||
if (!canvasContainer) return;
|
||||
|
||||
// Resize the canvas
|
||||
canvasSvgWidth = Math.ceil(parseFloat(getComputedStyle(canvasContainer).width));
|
||||
canvasSvgHeight = Math.ceil(parseFloat(getComputedStyle(canvasContainer).height));
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
entry: LayerPanelEntry;
|
||||
};
|
||||
|
||||
let list: LayoutCol;
|
||||
let list: LayoutCol | undefined;
|
||||
|
||||
const RANGE_TO_INSERT_WITHIN_BOTTOM_FOLDER_NOT_ROOT = 20;
|
||||
const LAYER_INDENT = 16;
|
||||
|
@ -105,7 +105,7 @@
|
|||
|
||||
await tick();
|
||||
|
||||
const textInput = (list?.div().querySelector("[data-text-input]:not([disabled])") || undefined) as HTMLInputElement | undefined;
|
||||
const textInput = (list?.div()?.querySelector("[data-text-input]:not([disabled])") || undefined) as HTMLInputElement | undefined;
|
||||
textInput?.select();
|
||||
}
|
||||
|
||||
|
@ -153,8 +153,8 @@
|
|||
}
|
||||
|
||||
function calculateDragIndex(tree: LayoutCol, clientY: number, select?: () => void): DraggingData {
|
||||
const treeChildren = tree.div().children;
|
||||
const treeOffset = tree.div().getBoundingClientRect().top;
|
||||
const treeChildren = tree.div()?.children;
|
||||
const treeOffset = tree.div()?.getBoundingClientRect().top;
|
||||
|
||||
// Closest distance to the middle of the row along the Y axis
|
||||
let closest = Infinity;
|
||||
|
@ -171,45 +171,47 @@
|
|||
let markerHeight = 0;
|
||||
let previousHeight = undefined as undefined | number;
|
||||
|
||||
Array.from(treeChildren).forEach((treeChild, index) => {
|
||||
const layerComponents = treeChild.getElementsByClassName("layer");
|
||||
if (layerComponents.length !== 1) return;
|
||||
const child = layerComponents[0];
|
||||
if (treeChildren !== undefined && treeOffset !== undefined) {
|
||||
Array.from(treeChildren).forEach((treeChild, index) => {
|
||||
const layerComponents = treeChild.getElementsByClassName("layer");
|
||||
if (layerComponents.length !== 1) return;
|
||||
const child = layerComponents[0];
|
||||
|
||||
const indexAttribute = child.getAttribute("data-index");
|
||||
if (!indexAttribute) return;
|
||||
const { folderIndex, entry: layer } = layers[parseInt(indexAttribute, 10)];
|
||||
const indexAttribute = child.getAttribute("data-index");
|
||||
if (!indexAttribute) return;
|
||||
const { folderIndex, entry: layer } = layers[parseInt(indexAttribute, 10)];
|
||||
|
||||
const rect = child.getBoundingClientRect();
|
||||
const position = rect.top + rect.height / 2;
|
||||
const distance = position - clientY;
|
||||
const rect = child.getBoundingClientRect();
|
||||
const position = rect.top + rect.height / 2;
|
||||
const distance = position - clientY;
|
||||
|
||||
// Inserting above current row
|
||||
if (distance > 0 && distance < closest) {
|
||||
insertFolder = layer.path.slice(0, layer.path.length - 1);
|
||||
insertIndex = folderIndex;
|
||||
highlightFolder = false;
|
||||
closest = distance;
|
||||
markerHeight = previousHeight || treeOffset + INSERT_MARK_OFFSET;
|
||||
}
|
||||
// Inserting below current row
|
||||
else if (distance > -closest && distance > -RANGE_TO_INSERT_WITHIN_BOTTOM_FOLDER_NOT_ROOT && distance < 0) {
|
||||
insertFolder = layer.layerType === "Folder" ? layer.path : layer.path.slice(0, layer.path.length - 1);
|
||||
insertIndex = layer.layerType === "Folder" ? 0 : folderIndex + 1;
|
||||
highlightFolder = layer.layerType === "Folder";
|
||||
closest = -distance;
|
||||
markerHeight = index === treeChildren.length - 1 ? rect.bottom - INSERT_MARK_OFFSET : rect.bottom;
|
||||
}
|
||||
// Inserting with no nesting at the end of the panel
|
||||
else if (closest === Infinity) {
|
||||
if (layer.path.length === 1) insertIndex = folderIndex + 1;
|
||||
// Inserting above current row
|
||||
if (distance > 0 && distance < closest) {
|
||||
insertFolder = layer.path.slice(0, layer.path.length - 1);
|
||||
insertIndex = folderIndex;
|
||||
highlightFolder = false;
|
||||
closest = distance;
|
||||
markerHeight = previousHeight || treeOffset + INSERT_MARK_OFFSET;
|
||||
}
|
||||
// Inserting below current row
|
||||
else if (distance > -closest && distance > -RANGE_TO_INSERT_WITHIN_BOTTOM_FOLDER_NOT_ROOT && distance < 0) {
|
||||
insertFolder = layer.layerType === "Folder" ? layer.path : layer.path.slice(0, layer.path.length - 1);
|
||||
insertIndex = layer.layerType === "Folder" ? 0 : folderIndex + 1;
|
||||
highlightFolder = layer.layerType === "Folder";
|
||||
closest = -distance;
|
||||
markerHeight = index === treeChildren.length - 1 ? rect.bottom - INSERT_MARK_OFFSET : rect.bottom;
|
||||
}
|
||||
// Inserting with no nesting at the end of the panel
|
||||
else if (closest === Infinity) {
|
||||
if (layer.path.length === 1) insertIndex = folderIndex + 1;
|
||||
|
||||
markerHeight = rect.bottom - INSERT_MARK_OFFSET;
|
||||
}
|
||||
previousHeight = rect.bottom;
|
||||
});
|
||||
markerHeight = rect.bottom - INSERT_MARK_OFFSET;
|
||||
}
|
||||
previousHeight = rect.bottom;
|
||||
});
|
||||
}
|
||||
|
||||
markerHeight -= treeOffset;
|
||||
markerHeight -= (treeOffset || 0);
|
||||
|
||||
return {
|
||||
select,
|
||||
|
|
|
@ -22,9 +22,10 @@
|
|||
const editor = getContext<Editor>("editor");
|
||||
const nodeGraph = getContext<NodeGraphState>("nodeGraph");
|
||||
|
||||
let graph: LayoutRow;
|
||||
let nodesContainer: HTMLDivElement;
|
||||
let nodeSearchInput: TextInput;
|
||||
let graph: LayoutRow | undefined;
|
||||
let nodesContainer: HTMLDivElement | undefined;
|
||||
let nodeSearchInput: TextInput | undefined;
|
||||
|
||||
let transform = { scale: 1, x: 0, y: 0 };
|
||||
let panning = false;
|
||||
let selected: bigint[] = [];
|
||||
|
@ -75,7 +76,7 @@
|
|||
}
|
||||
|
||||
function createLinkPathInProgress(linkInProgressFromConnector?: HTMLDivElement, linkInProgressToConnector?: HTMLDivElement | DOMRect): [string, string] | undefined {
|
||||
if (linkInProgressFromConnector && linkInProgressToConnector) {
|
||||
if (linkInProgressFromConnector && linkInProgressToConnector && nodesContainer) {
|
||||
return createWirePath(linkInProgressFromConnector, linkInProgressToConnector, false, false);
|
||||
}
|
||||
return undefined;
|
||||
|
@ -107,9 +108,12 @@
|
|||
async function refreshLinks(): Promise<void> {
|
||||
await tick();
|
||||
|
||||
if (!nodesContainer) return;
|
||||
const theNodesContainer = nodesContainer;
|
||||
|
||||
const links = $nodeGraph.links;
|
||||
nodeLinkPaths = links.flatMap((link, index) => {
|
||||
const { nodePrimaryInput, nodePrimaryOutput } = resolveLink(link, nodesContainer);
|
||||
const { nodePrimaryInput, nodePrimaryOutput } = resolveLink(link, theNodesContainer);
|
||||
if (!nodePrimaryInput || !nodePrimaryOutput) return [];
|
||||
if (disconnecting?.linkIndex === index) return [];
|
||||
|
||||
|
@ -129,6 +133,8 @@
|
|||
}
|
||||
|
||||
function buildWirePathLocations(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): { x: number; y: number }[] {
|
||||
if (!nodesContainer) return [];
|
||||
|
||||
const containerBounds = nodesContainer.getBoundingClientRect();
|
||||
|
||||
const outX = verticalOut ? outputBounds.x + outputBounds.width / 2 : outputBounds.x + outputBounds.width - 1;
|
||||
|
@ -193,7 +199,9 @@
|
|||
let zoomFactor = 1 + Math.abs(scrollY) * WHEEL_RATE;
|
||||
if (scrollY > 0) zoomFactor = 1 / zoomFactor;
|
||||
|
||||
const { x, y, width, height } = graph.div().getBoundingClientRect();
|
||||
const bounds = graph?.div()?.getBoundingClientRect();
|
||||
if (!bounds) return;
|
||||
const { x, y, width, height } = bounds;
|
||||
|
||||
transform.scale *= zoomFactor;
|
||||
|
||||
|
@ -238,14 +246,15 @@
|
|||
|
||||
// Create the add node popup on right click, then exit
|
||||
if (rmb) {
|
||||
const graphBounds = graph.div().getBoundingClientRect();
|
||||
const graphBounds = graph?.div()?.getBoundingClientRect();
|
||||
if (!graphBounds) return;
|
||||
nodeListLocation = {
|
||||
x: Math.round(((e.clientX - graphBounds.x) / transform.scale - transform.x) / GRID_SIZE),
|
||||
y: Math.round(((e.clientY - graphBounds.y) / transform.scale - transform.y) / GRID_SIZE),
|
||||
};
|
||||
|
||||
// Find actual relevant child and focus it (setTimeout is required to actually focus the input element)
|
||||
setTimeout(() => nodeSearchInput.focus(), 0);
|
||||
setTimeout(() => nodeSearchInput?.focus(), 0);
|
||||
|
||||
document.addEventListener("keydown", keydown);
|
||||
return;
|
||||
|
@ -277,9 +286,9 @@
|
|||
const inputIndexInt = BigInt(inputIndex);
|
||||
const links = $nodeGraph.links;
|
||||
const linkIndex = links.findIndex((value) => value.linkEnd === nodeIdInt && value.linkEndInputIndex === inputIndexInt);
|
||||
const nodeOutputConnectors = nodesContainer.querySelectorAll(`[data-node="${String(links[linkIndex].linkStart)}"] [data-port="output"]`) || undefined;
|
||||
const nodeOutputConnectors = nodesContainer?.querySelectorAll(`[data-node="${String(links[linkIndex].linkStart)}"] [data-port="output"]`) || undefined;
|
||||
linkInProgressFromConnector = nodeOutputConnectors?.[Number(links[linkIndex].linkEndInputIndex)] as HTMLDivElement | undefined;
|
||||
const nodeInputConnectors = nodesContainer.querySelectorAll(`[data-node="${String(links[linkIndex].linkEnd)}"] [data-port="input"]`) || undefined;
|
||||
const nodeInputConnectors = nodesContainer?.querySelectorAll(`[data-node="${String(links[linkIndex].linkEnd)}"] [data-port="input"]`) || undefined;
|
||||
linkInProgressToConnector = nodeInputConnectors?.[Number(links[linkIndex].linkEndInputIndex)] as HTMLDivElement | undefined;
|
||||
disconnecting = { nodeId: nodeIdInt, inputIndex, linkIndex };
|
||||
refreshLinks();
|
||||
|
@ -400,24 +409,26 @@
|
|||
// Check if this node should be inserted between two other nodes
|
||||
if (selected.length === 1) {
|
||||
const selectedNodeId = selected[0];
|
||||
const selectedNode = nodesContainer.querySelector(`[data-node="${String(selectedNodeId)}"]`);
|
||||
const selectedNode = nodesContainer?.querySelector(`[data-node="${String(selectedNodeId)}"]`) || undefined;
|
||||
|
||||
// Check that neither the input or output of the selected node are already connected.
|
||||
const notConnected = $nodeGraph.links.findIndex((link) => link.linkStart === selectedNodeId || (link.linkEnd === selectedNodeId && link.linkEndInputIndex === BigInt(0))) === -1;
|
||||
const input = selectedNode?.querySelector(`[data-port="input"]`);
|
||||
const output = selectedNode?.querySelector(`[data-port="output"]`);
|
||||
const input = selectedNode?.querySelector(`[data-port="input"]`) || undefined;
|
||||
const output = selectedNode?.querySelector(`[data-port="output"]`) || undefined;
|
||||
|
||||
// TODO: Make sure inputs are correctly typed
|
||||
if (selectedNode && notConnected && input && output) {
|
||||
if (selectedNode && notConnected && input && output && nodesContainer) {
|
||||
const theNodesContainer = nodesContainer;
|
||||
|
||||
// Find the link that the node has been dragged on top of
|
||||
const link = $nodeGraph.links.find((link): boolean => {
|
||||
const { nodePrimaryInput, nodePrimaryOutput } = resolveLink(link, nodesContainer);
|
||||
const { nodePrimaryInput, nodePrimaryOutput } = resolveLink(link, theNodesContainer);
|
||||
if (!nodePrimaryInput || !nodePrimaryOutput) return false;
|
||||
|
||||
const wireCurveLocations = buildWirePathLocations(nodePrimaryOutput.getBoundingClientRect(), nodePrimaryInput.getBoundingClientRect(), false, false);
|
||||
|
||||
const selectedNodeBounds = selectedNode.getBoundingClientRect();
|
||||
const containerBoundsBounds = nodesContainer.getBoundingClientRect();
|
||||
const containerBoundsBounds = theNodesContainer.getBoundingClientRect();
|
||||
|
||||
return editor.instance.rectangleIntersects(
|
||||
new Float64Array(wireCurveLocations.map((loc) => loc.x)),
|
||||
|
@ -428,6 +439,7 @@
|
|||
selectedNodeBounds.right - containerBoundsBounds.x
|
||||
);
|
||||
});
|
||||
|
||||
// If the node has been dragged on top of the link then connect it into the middle.
|
||||
if (link) {
|
||||
editor.instance.connectNodesByLink(link.linkStart, 0, selectedNodeId, 0);
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
export let icon: IconName = "Checkmark";
|
||||
export let tooltip: string | undefined = undefined;
|
||||
|
||||
let inputElement: HTMLInputElement;
|
||||
let inputElement: HTMLInputElement | undefined;
|
||||
|
||||
let id = `${Math.random()}`.substring(2);
|
||||
|
||||
$: displayIcon = (!checked && icon === "Checkmark" ? "Empty12px" : icon) as IconName;
|
||||
|
@ -23,7 +24,7 @@
|
|||
return checked;
|
||||
}
|
||||
|
||||
export function input(): HTMLInputElement {
|
||||
export function input(): HTMLInputElement | undefined {
|
||||
return inputElement;
|
||||
}
|
||||
|
||||
|
@ -35,7 +36,7 @@
|
|||
</script>
|
||||
|
||||
<LayoutRow class="checkbox-input">
|
||||
<input type="checkbox" id={`checkbox-input-${id}`} {checked} on:change={(e) => dispatch("checked", inputElement.checked)} {disabled} tabindex={disabled ? -1 : 0} bind:this={inputElement} />
|
||||
<input type="checkbox" id={`checkbox-input-${id}`} {checked} on:change={(e) => dispatch("checked", inputElement?.checked)} {disabled} tabindex={disabled ? -1 : 0} bind:this={inputElement} />
|
||||
<label class:disabled class:checked for={`checkbox-input-${id}`} on:keydown={(e) => e.key === "Enter" && toggleCheckboxFromLabel(e)} title={tooltip}>
|
||||
<LayoutRow class="checkbox-box">
|
||||
<IconLabel icon={displayIcon} />
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
// emits: ["update:selectedIndex"],
|
||||
const dispatch = createEventDispatcher<{ selectedIndex: number }>();
|
||||
|
||||
let menuList: MenuList;
|
||||
let self: LayoutRow;
|
||||
let menuList: MenuList | undefined;
|
||||
let self: LayoutRow | undefined;
|
||||
|
||||
export let entries: MenuListEntry[][];
|
||||
export let selectedIndex: number | undefined = undefined; // When not provided, a dash is displayed
|
||||
|
@ -57,8 +57,8 @@
|
|||
}
|
||||
|
||||
function unFocusDropdownBox(e: FocusEvent) {
|
||||
const blurTarget = (e.target as HTMLDivElement | undefined)?.closest("[data-dropdown-input]");
|
||||
if (blurTarget !== self.div()) open = false;
|
||||
const blurTarget = (e.target as HTMLDivElement | undefined)?.closest("[data-dropdown-input]") || undefined;
|
||||
if (blurTarget !== self?.div()) open = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -70,7 +70,7 @@
|
|||
{tooltip}
|
||||
on:click={() => !disabled && (open = true)}
|
||||
on:blur={unFocusDropdownBox}
|
||||
on:keydown={(e) => menuList.keydown(e, false)}
|
||||
on:keydown={(e) => menuList?.keydown(e, false)}
|
||||
tabindex={disabled ? -1 : 0}
|
||||
data-floating-menu-spawner
|
||||
>
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
export let sharpRightCorners = false;
|
||||
export let placeholder: string | undefined = undefined;
|
||||
|
||||
let inputOrTextarea: HTMLInputElement | HTMLTextAreaElement;
|
||||
let inputOrTextarea: HTMLInputElement | HTMLTextAreaElement | undefined;
|
||||
let id = `${Math.random()}`.substring(2);
|
||||
let macKeyboardLayout = platformIsMac();
|
||||
|
||||
|
@ -37,28 +37,32 @@
|
|||
|
||||
// Select (highlight) all the text. For technical reasons, it is necessary to pass the current text.
|
||||
export function selectAllText(currentText: string) {
|
||||
if (!inputOrTextarea) return;
|
||||
|
||||
// Setting the value directly is required to make the following `select()` call work
|
||||
inputOrTextarea.value = currentText;
|
||||
inputOrTextarea.select();
|
||||
}
|
||||
|
||||
export function focus() {
|
||||
inputOrTextarea.focus();
|
||||
inputOrTextarea?.focus();
|
||||
}
|
||||
|
||||
export function unFocus() {
|
||||
inputOrTextarea.blur();
|
||||
inputOrTextarea?.blur();
|
||||
}
|
||||
|
||||
export function getValue(): string {
|
||||
return inputOrTextarea.value;
|
||||
return inputOrTextarea?.value || "";
|
||||
}
|
||||
|
||||
export function setInputElementValue(value: string) {
|
||||
if (!inputOrTextarea) return;
|
||||
|
||||
inputOrTextarea.value = value;
|
||||
}
|
||||
|
||||
export function element(): HTMLInputElement | HTMLTextAreaElement {
|
||||
export function element(): HTMLInputElement | HTMLTextAreaElement | undefined {
|
||||
return inputOrTextarea;
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
changeFont: { fontFamily: string; fontStyle: string; fontFileUrl: string | undefined };
|
||||
}>();
|
||||
|
||||
let menuList: MenuList;
|
||||
let menuList: MenuList | undefined;
|
||||
|
||||
export let fontFamily: string;
|
||||
export let fontStyle: string;
|
||||
|
@ -49,7 +49,7 @@
|
|||
|
||||
if (activeEntry) {
|
||||
const index = entries.indexOf(activeEntry);
|
||||
menuList.scrollViewTo(Math.max(0, index * 20 - 190));
|
||||
menuList?.scrollViewTo(Math.max(0, index * 20 - 190));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,7 +113,7 @@
|
|||
{tooltip}
|
||||
tabindex={disabled ? -1 : 0}
|
||||
on:click={toggleOpen}
|
||||
on:keydown={(e) => menuList.keydown(e, false)}
|
||||
on:keydown={(e) => menuList?.keydown(e, false)}
|
||||
data-floating-menu-spawner
|
||||
>
|
||||
<TextLabel class="dropdown-label">{activeEntry?.value || ""}</TextLabel>
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
export let incrementCallbackIncrease: (() => void) | undefined = undefined;
|
||||
export let incrementCallbackDecrease: (() => void) | undefined = undefined;
|
||||
|
||||
let self: FieldInput;
|
||||
let self: FieldInput | undefined;
|
||||
let text = displayText(value, displayDecimalPlaces, unit);
|
||||
let editing = false;
|
||||
// Stays in sync with a binding to the actual input range slider element.
|
||||
|
@ -130,7 +130,7 @@
|
|||
function onSliderPointerUp() {
|
||||
// User clicked but didn't drag, so we focus the text input element
|
||||
if (rangeSliderClickDragState === "mousedown") {
|
||||
const inputElement = self.element().querySelector("[data-input-element]") as HTMLInputElement | undefined;
|
||||
const inputElement = self?.element()?.querySelector("[data-input-element]") as HTMLInputElement | undefined;
|
||||
if (!inputElement) return;
|
||||
|
||||
// Set the slider position back to the original position to undo the user moving it
|
||||
|
@ -151,7 +151,7 @@
|
|||
|
||||
editing = true;
|
||||
|
||||
self.selectAllText(text);
|
||||
self?.selectAllText(text);
|
||||
}
|
||||
|
||||
// Called only when `value` is changed from the <input> element via user input and committed, either with the
|
||||
|
@ -167,7 +167,7 @@
|
|||
|
||||
editing = false;
|
||||
|
||||
self.unFocus();
|
||||
self?.unFocus();
|
||||
}
|
||||
|
||||
function onCancelTextChange() {
|
||||
|
@ -175,7 +175,7 @@
|
|||
|
||||
editing = false;
|
||||
|
||||
self.unFocus();
|
||||
self?.unFocus();
|
||||
}
|
||||
|
||||
function onIncrement(direction: "Decrease" | "Increase") {
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
export let tooltip: string | undefined = undefined;
|
||||
export let disabled = false;
|
||||
|
||||
let self: FieldInput;
|
||||
let self: FieldInput | undefined;
|
||||
let editing = false;
|
||||
|
||||
function onTextFocused() {
|
||||
|
@ -27,20 +27,20 @@
|
|||
onCancelTextChange();
|
||||
|
||||
// TODO: Find a less hacky way to do this
|
||||
dispatch("commitText", self.getValue());
|
||||
if (self) dispatch("commitText", self.getValue());
|
||||
|
||||
// Required if value is not changed by the parent component upon update:value event
|
||||
self.setInputElementValue(value);
|
||||
self?.setInputElementValue(value);
|
||||
}
|
||||
|
||||
function onCancelTextChange() {
|
||||
editing = false;
|
||||
|
||||
self.unFocus();
|
||||
self?.unFocus();
|
||||
}
|
||||
|
||||
export function focus() {
|
||||
self.focus();
|
||||
self?.focus();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -19,13 +19,13 @@
|
|||
export let minWidth = 0;
|
||||
export let sharpRightCorners = false;
|
||||
|
||||
let self: FieldInput;
|
||||
let self: FieldInput | undefined;
|
||||
let editing = false;
|
||||
|
||||
function onTextFocused() {
|
||||
editing = true;
|
||||
|
||||
self.selectAllText(value);
|
||||
self?.selectAllText(value);
|
||||
}
|
||||
|
||||
// Called only when `value` is changed from the <input> element via user input and committed, either with the
|
||||
|
@ -37,20 +37,20 @@
|
|||
onCancelTextChange();
|
||||
|
||||
// TODO: Find a less hacky way to do this
|
||||
dispatch("commitText", self.getValue());
|
||||
if (self) dispatch("commitText", self.getValue());
|
||||
|
||||
// Required if value is not changed by the parent component upon update:value event
|
||||
self.setInputElementValue(value);
|
||||
self?.setInputElementValue(value);
|
||||
}
|
||||
|
||||
function onCancelTextChange() {
|
||||
editing = false;
|
||||
|
||||
self.unFocus();
|
||||
self?.unFocus();
|
||||
}
|
||||
|
||||
export function focus() {
|
||||
self.focus();
|
||||
self?.focus();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
export let mediumDivisions = 5;
|
||||
export let minorDivisions = 2;
|
||||
|
||||
let canvasRuler: HTMLDivElement;
|
||||
let canvasRuler: HTMLDivElement | undefined;
|
||||
let rulerLength = 0;
|
||||
let svgBounds = { width: "0px", height: "0px" };
|
||||
|
||||
|
@ -76,6 +76,8 @@
|
|||
}
|
||||
|
||||
export function resize() {
|
||||
if (!canvasRuler) return;
|
||||
|
||||
const isVertical = direction === "Vertical";
|
||||
|
||||
const newLength = isVertical ? canvasRuler.clientHeight : canvasRuler.clientWidth;
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
export let handlePosition = 0.5;
|
||||
export let handleLength = 0.5;
|
||||
|
||||
let scrollTrack: HTMLDivElement;
|
||||
let scrollTrack: HTMLDivElement | undefined;
|
||||
let dragging = false;
|
||||
let pointerPos = 0;
|
||||
let thumbTop: string | undefined = undefined;
|
||||
|
@ -34,10 +34,12 @@
|
|||
$: [thumbTop, thumbBottom, thumbLeft, thumbRight] = direction === "Vertical" ? [`${start * 100}%`, `${end * 100}%`, "0%", "0%"] : ["0%", "0%", `${start * 100}%`, `${end * 100}%`];
|
||||
|
||||
function trackLength(): number | undefined {
|
||||
if (scrollTrack === undefined) return undefined;
|
||||
return direction === "Vertical" ? scrollTrack.clientHeight - handleLength : scrollTrack.clientWidth;
|
||||
}
|
||||
|
||||
function trackOffset(): number | undefined {
|
||||
if (scrollTrack === undefined) return undefined;
|
||||
return direction === "Vertical" ? scrollTrack.getBoundingClientRect().top : scrollTrack.getBoundingClientRect().left;
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
export let clickAction: ((index: number) => void) | undefined = undefined;
|
||||
export let closeAction: ((index: number) => void) | undefined = undefined;
|
||||
|
||||
let tabElements: LayoutRow[] = [];
|
||||
let tabElements: (LayoutRow | undefined)[] = [];
|
||||
|
||||
function newDocument() {
|
||||
editor.instance.newDocumentDialog();
|
||||
|
@ -63,7 +63,7 @@
|
|||
|
||||
export async function scrollTabIntoView(newIndex: number) {
|
||||
await tick();
|
||||
tabElements[newIndex].div().scrollIntoView();
|
||||
tabElements[newIndex]?.div()?.scrollIntoView();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
};
|
||||
|
||||
let panelSizes = PANEL_SIZES;
|
||||
let documentPanel: Panel;
|
||||
let documentPanel: Panel | undefined;
|
||||
|
||||
$: activeDocumentIndex = $portfolio.activeDocumentIndex;
|
||||
$: nodeGraphVisible = $workspace.nodeGraphVisible;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue