Bump frontend dependencies to Svelte 5 (#3648)

* Add more recommended VS Code default configs

* Upgrade frontend dependencies including Svelte 5

* Fix derived_references_self runtime error

* Fix lint warnings
This commit is contained in:
Keavon Chambers 2026-01-17 01:50:14 -08:00 committed by Timon Schelling
parent 3b55064f44
commit 915a344a05
24 changed files with 971 additions and 792 deletions

View File

@ -48,7 +48,7 @@ let
npmDeps = pkgs.fetchNpmDeps { npmDeps = pkgs.fetchNpmDeps {
inherit (info) pname version; inherit (info) pname version;
src = "${info.src}/frontend"; src = "${info.src}/frontend";
hash = "sha256-D8VCNK+Ca3gxO+5wriBn8FszG8/x8n/zM6/MPo9E2j4="; hash = "sha256-WlwzWGoFi3hjRuM5ucrgavko/gg4iFAwMc6uMLjT/FI=";
}; };
npmRoot = "frontend"; npmRoot = "frontend";

View File

@ -11,9 +11,10 @@
// Code quality // Code quality
"wayou.vscode-todo-highlight", "wayou.vscode-todo-highlight",
"streetsidesoftware.code-spell-checker", "streetsidesoftware.code-spell-checker",
// Helpful // Git
"mhutchie.git-graph", "mhutchie.git-graph",
"qezhu.gitlink", "qezhu.gitlink",
// Helpful
"wmaurer.change-case" "wmaurer.change-case"
] ]
} }

25
.vscode/settings.json vendored
View File

@ -32,6 +32,7 @@
"editor.formatOnSave": false "editor.formatOnSave": false
}, },
// Rust Analyzer config // Rust Analyzer config
"rust-analyzer.check.command": "clippy",
"rust-analyzer.cargo.allTargets": false, "rust-analyzer.cargo.allTargets": false,
"rust-analyzer.procMacro.ignored": { "rust-analyzer.procMacro.ignored": {
"serde_derive": ["Serialize", "Deserialize"], "serde_derive": ["Serialize", "Deserialize"],
@ -47,13 +48,33 @@
"vite-plugin-svelte-css-no-scopable-elements": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts` "vite-plugin-svelte-css-no-scopable-elements": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
"a11y-no-static-element-interactions": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts` "a11y-no-static-element-interactions": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
"a11y-no-noninteractive-element-interactions": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts` "a11y-no-noninteractive-element-interactions": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
"a11y-click-events-have-key-events": "ignore" // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts` "a11y-click-events-have-key-events": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
"a11y_consider_explicit_label": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
"a11y_click_events_have_key_events": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
"a11y_no_noninteractive_element_interactions": "ignore" // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
}, },
// Git Graph config
"git-graph.repository.fetchAndPrune": true,
"git-graph.repository.showRemoteHeads": false,
"git-graph.repository.commits.fetchAvatars": true,
// VS Code Git config
"git.autofetch": true,
"git.enableStatusBarSync": false,
"git.showActionButton": {
"sync": false
},
// CSpell config
"cSpell.language": "en-US",
"cSpell.logLevel": "Information",
"cSpell.allowCompoundWords": true,
// VS Code config // VS Code config
"html.format.wrapLineLength": 200, "html.format.wrapLineLength": 200,
"files.eol": "\n", "files.eol": "\n",
"files.insertFinalNewline": true, "files.insertFinalNewline": true,
"files.associations": { "files.associations": {
"*.graphite": "json" "*.graphite": "json"
} },
"editor.renderWhitespace": "boundary",
"editor.minimap.markSectionHeaderRegex": "// ===+\\n\\s*//\\s*(?<label>[^\\n]{1,18})[^\\n]*(\\n\\s*//[^\\n]*)*\\n\\s*// ===+",
"evenBetterToml.formatter.alignComments": false
} }

View File

@ -32,9 +32,6 @@ export default defineConfig([
"import/resolver": { typescript: true, node: true }, "import/resolver": { typescript: true, node: true },
}, },
languageOptions: { languageOptions: {
parserOptions: {
project: "./tsconfig.json",
},
globals: { globals: {
...globals.browser, ...globals.browser,
...globals.node, ...globals.node,

View File

@ -32,7 +32,11 @@ if (isInstallNeeded()) {
console.log("Finished installing npm packages."); console.log("Finished installing npm packages.");
} catch (_) { } catch (_) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error("Failed to install npm packages. Please delete the `node_modules` folder and run `npm install` from the `/frontend` directory."); console.error(
"\n\n" +
"------------------------------------------------------------> " +
"Failed to install npm packages. Please delete the `node_modules` folder and run `npm install` from the `/frontend` directory.",
);
process.exit(1); process.exit(1);
} }
} else { } else {

File diff suppressed because it is too large Load Diff

View File

@ -38,32 +38,32 @@
"source-sans": "github:adobe-fonts/source-sans#2.045R-ro%2F1.095R-it" "source-sans": "github:adobe-fonts/source-sans#2.045R-ro%2F1.095R-it"
}, },
"devDependencies": { "devDependencies": {
"@eslint/compat": "^1.3.2", "@eslint/compat": "^2.0.1",
"@eslint/eslintrc": "^3.3.1", "@eslint/eslintrc": "^3.3.3",
"@eslint/js": "^9.34.0", "@eslint/js": "^9.39.2",
"@sveltejs/vite-plugin-svelte": "^3.1.2", "@sveltejs/vite-plugin-svelte": "^6.2.4",
"@types/node": "^24.3.0", "@types/node": "^25.0.9",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"concurrently": "^9.2.1", "concurrently": "^9.2.1",
"eslint-import-resolver-typescript": "^4.4.4", "eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.32.0", "eslint-plugin-import": "^2.32.0",
"eslint-plugin-prettier": "^5.5.4", "eslint-plugin-prettier": "^5.5.5",
"eslint-plugin-svelte": "^3.11.0", "eslint-plugin-svelte": "^3.14.0",
"globals": "^16.3.0", "globals": "^17.0.0",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"prettier": "^3.6.2", "prettier": "^3.8.0",
"prettier-plugin-svelte": "^3.4.0", "prettier-plugin-svelte": "^3.4.1",
"process": "^0.11.10", "process": "^0.11.10",
"rollup-plugin-license": "^3.6.0", "rollup-plugin-license": "^3.6.0",
"sass": "^1.91.0", "sass": "^1.97.2",
"svelte": "4.2.20", "svelte": "5.46.4",
"svelte-preprocess": "^6.0.3", "svelte-preprocess": "^6.0.3",
"tar": "^7.5.2", "tar": "^7.5.3",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.9.2", "typescript": "^5.9.3",
"typescript-eslint": "^8.41.0", "typescript-eslint": "^8.53.0",
"vite": "^5.4.19", "vite": "^7.3.1",
"vite-multiple-assets": "2.2.5" "vite-multiple-assets": "2.2.6"
}, },
"homepage": "https://graphite.art", "homepage": "https://graphite.art",
"license": "Apache-2.0", "license": "Apache-2.0",

View File

@ -439,7 +439,7 @@
data-saturation-value-picker data-saturation-value-picker
> >
{#if !isNone} {#if !isNone}
<div class="selection-circle" style:top={`${(1 - value) * 100}%`} style:left={`${saturation * 100}%`} /> <div class="selection-circle" style:top={`${(1 - value) * 100}%`} style:left={`${saturation * 100}%`}></div>
{/if} {/if}
{#if alignedAxis} {#if alignedAxis}
<div <div
@ -448,7 +448,7 @@
class:value={alignedAxis === "value"} class:value={alignedAxis === "value"}
style:top={`${(1 - value) * 100}%`} style:top={`${(1 - value) * 100}%`}
style:left={`${saturation * 100}%`} style:left={`${saturation * 100}%`}
/> ></div>
{/if} {/if}
</LayoutCol> </LayoutCol>
<LayoutCol <LayoutCol
@ -459,7 +459,7 @@
data-hue-picker data-hue-picker
> >
{#if !isNone} {#if !isNone}
<div class="selection-needle" style:top={`${(1 - hue) * 100}%`} /> <div class="selection-needle" style:top={`${(1 - hue) * 100}%`}></div>
{/if} {/if}
</LayoutCol> </LayoutCol>
<LayoutCol <LayoutCol
@ -470,7 +470,7 @@
data-alpha-picker data-alpha-picker
> >
{#if !isNone} {#if !isNone}
<div class="selection-needle" style:top={`${(1 - alpha) * 100}%`} /> <div class="selection-needle" style:top={`${(1 - alpha) * 100}%`}></div>
{/if} {/if}
</LayoutCol> </LayoutCol>
</LayoutRow> </LayoutRow>
@ -677,7 +677,7 @@
style:--pure-color-gray={gray} style:--pure-color-gray={gray}
data-tooltip-label={`Set to ${name}`} data-tooltip-label={`Set to ${name}`}
data-tooltip-description={disabled ? "Disabled (read-only)." : ""} data-tooltip-description={disabled ? "Disabled (read-only)." : ""}
/> ></div>
{/each} {/each}
</button> </button>
{#if eyedropperSupported()} {#if eyedropperSupported()}

View File

@ -56,8 +56,8 @@
> >
<div class="ring"> <div class="ring">
<div class="canvas-container"> <div class="canvas-container">
<canvas width={ZOOM_WINDOW_DIMENSIONS} height={ZOOM_WINDOW_DIMENSIONS} bind:this={zoomPreviewCanvas} /> <canvas width={ZOOM_WINDOW_DIMENSIONS} height={ZOOM_WINDOW_DIMENSIONS} bind:this={zoomPreviewCanvas}></canvas>
<div class="pixel-outline" /> <div class="pixel-outline"></div>
</div> </div>
</div> </div>
</FloatingMenu> </FloatingMenu>

View File

@ -42,6 +42,7 @@
// Keep the child references outside of the entries array so as to avoid infinite recursion. // Keep the child references outside of the entries array so as to avoid infinite recursion.
let childReferences: MenuList[][] = []; let childReferences: MenuList[][] = [];
let openChildValue: string | undefined = undefined;
let search = ""; let search = "";
let reactiveEntries = entries; let reactiveEntries = entries;
let highlighted = activeEntry as MenuListEntry | undefined; let highlighted = activeEntry as MenuListEntry | undefined;
@ -172,7 +173,7 @@
// Close the containing menu // Close the containing menu
let childReference = getChildReference(menuListEntry); let childReference = getChildReference(menuListEntry);
if (childReference) { if (childReference) {
childReference.open = false; openChildValue = undefined;
reactiveEntries = reactiveEntries; reactiveEntries = reactiveEntries;
} }
dispatch("open", false); dispatch("open", false);
@ -192,7 +193,7 @@
let childReference = getChildReference(menuListEntry); let childReference = getChildReference(menuListEntry);
if (childReference) { if (childReference) {
childReference.open = true; openChildValue = menuListEntry.value;
reactiveEntries = reactiveEntries; reactiveEntries = reactiveEntries;
} else { } else {
dispatch("open", true); dispatch("open", true);
@ -207,19 +208,13 @@
let childReference = getChildReference(menuListEntry); let childReference = getChildReference(menuListEntry);
if (childReference) { if (childReference) {
childReference.open = false; openChildValue = undefined;
reactiveEntries = reactiveEntries; reactiveEntries = reactiveEntries;
} else { } else {
dispatch("open", false); dispatch("open", false);
} }
} }
function isEntryOpen(menuListEntry: MenuListEntry): boolean {
if (!menuListEntry.children?.length) return false;
return getChildReference(menuListEntry)?.open || false;
}
function includeSeparator(entries: MenuListEntry[][], section: MenuListEntry[], sectionIndex: number, search: string): boolean { function includeSeparator(entries: MenuListEntry[][], section: MenuListEntry[], sectionIndex: number, search: string): boolean {
const elementsBeforeCurrentSection = entries const elementsBeforeCurrentSection = entries
.slice(0, sectionIndex) .slice(0, sectionIndex)
@ -242,7 +237,7 @@
// No submenu to open // No submenu to open
if (!childReference || !highlightedEntry.children?.length) return false; if (!childReference || !highlightedEntry.children?.length) return false;
childReference.open = true; openChildValue = highlightedEntry.value;
// The reason we bother taking `highlightdEntry` as an argument is because, when this function is called, it can ensure `highlightedEntry` is not undefined. // The reason we bother taking `highlightdEntry` as an argument is because, when this function is called, it can ensure `highlightedEntry` is not undefined.
// But here we still have to set `highlighted` to itself so Svelte knows to reactively update it after we set its `childReference.open` property. // But here we still have to set `highlighted` to itself so Svelte knows to reactively update it after we set its `childReference.open` property.
highlighted = highlighted; highlighted = highlighted;
@ -262,7 +257,7 @@
const menuOpen = open; const menuOpen = open;
const flatEntries = filteredEntries.flat().filter((entry) => !entry.disabled); const flatEntries = filteredEntries.flat().filter((entry) => !entry.disabled);
const openChild = flatEntries.findIndex((entry) => (entry.children?.length ?? 0) > 0 && getChildReference(entry)?.open); const openChild = (openChildValue !== undefined && flatEntries.findIndex((entry) => entry.value === openChildValue)) || -1;
// Allow opening menu with space or enter // Allow opening menu with space or enter
if (!menuOpen && (e.key === " " || e.key === "Enter")) { if (!menuOpen && (e.key === " " || e.key === "Enter")) {
@ -442,7 +437,7 @@
{#each currentEntries(section, virtualScrollingEntryHeight, virtualScrollingStartIndex, virtualScrollingEndIndex, search) as entry, entryIndex (entryIndex + startIndex)} {#each currentEntries(section, virtualScrollingEntryHeight, virtualScrollingStartIndex, virtualScrollingEndIndex, search) as entry, entryIndex (entryIndex + startIndex)}
<LayoutRow <LayoutRow
class="row" class="row"
classes={{ open: isEntryOpen(entry), active: entry.label === highlighted?.label, disabled: Boolean(entry.disabled) }} classes={{ open: openChildValue === entry.value, active: entry.label === highlighted?.label, disabled: Boolean(entry.disabled) }}
styles={{ height: virtualScrollingEntryHeight || "20px" }} styles={{ height: virtualScrollingEntryHeight || "20px" }}
tooltipLabel={entry.tooltipLabel} tooltipLabel={entry.tooltipLabel}
tooltipDescription={entry.tooltipDescription} tooltipDescription={entry.tooltipDescription}
@ -454,7 +449,7 @@
{#if entry.icon && drawIcon} {#if entry.icon && drawIcon}
<IconLabel icon={entry.icon} iconSizeOverride={16} class="entry-icon" /> <IconLabel icon={entry.icon} iconSizeOverride={16} class="entry-icon" />
{:else if drawIcon} {:else if drawIcon}
<div class="no-icon" /> <div class="no-icon"></div>
{/if} {/if}
{#if entry.font} {#if entry.font}
@ -470,7 +465,7 @@
{#if entry.children?.length} {#if entry.children?.length}
<IconLabel class="submenu-arrow" icon="DropdownArrow" /> <IconLabel class="submenu-arrow" icon="DropdownArrow" />
{:else} {:else}
<div class="no-submenu-arrow" /> <div class="no-submenu-arrow"></div>
{/if} {/if}
{#if entry.children} {#if entry.children}
@ -483,7 +478,7 @@
}} }}
on:selectedEntryValuePath={({ detail }) => dispatch("selectedEntryValuePath", detail)} on:selectedEntryValuePath={({ detail }) => dispatch("selectedEntryValuePath", detail)}
parentsValuePath={[...parentsValuePath, entry.value]} parentsValuePath={[...parentsValuePath, entry.value]}
open={getChildReference(entry)?.open || false} open={openChildValue === entry.value}
direction="TopRight" direction="TopRight"
entries={entry.children} entries={entry.children}
entriesHash={entry.childrenHash || 0n} entriesHash={entry.childrenHash || 0n}

View File

@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher, getContext, onMount } from "svelte"; import { createEventDispatcher, getContext, onMount } from "svelte";
import { SvelteMap } from "svelte/reactivity";
import type { FrontendNodeType } from "@graphite/messages"; import type { FrontendNodeType } from "@graphite/messages";
import type { NodeGraphState } from "@graphite/state-providers/node-graph"; import type { NodeGraphState } from "@graphite/state-providers/node-graph";
@ -28,7 +29,7 @@
}; };
function buildNodeCategories(nodeTypes: FrontendNodeType[], searchTerm: string): [string, NodeCategoryDetails][] { function buildNodeCategories(nodeTypes: FrontendNodeType[], searchTerm: string): [string, NodeCategoryDetails][] {
const categories = new Map<string, NodeCategoryDetails>(); const categories = new SvelteMap<string, NodeCategoryDetails>();
const isTypeSearch = searchTerm.toLowerCase().startsWith("type:"); const isTypeSearch = searchTerm.toLowerCase().startsWith("type:");
let typeSearchTerm = ""; let typeSearchTerm = "";
let remainingSearchTerms = [searchTerm.toLowerCase()]; let remainingSearchTerms = [searchTerm.toLowerCase()];

View File

@ -476,7 +476,7 @@
{...$$restProps} {...$$restProps}
> >
{#if displayTail} {#if displayTail}
<div class="tail" bind:this={tail} /> <div class="tail" bind:this={tail}></div>
{/if} {/if}
{#if displayContainer} {#if displayContainer}
<div class="floating-menu-container" bind:this={floatingMenuContainer}> <div class="floating-menu-container" bind:this={floatingMenuContainer}>

View File

@ -580,7 +580,7 @@
{/if} {/if}
<div class="text-input" style:width={canvasWidthCSS} style:height={canvasHeightCSS} style:pointer-events={showTextInput ? "auto" : ""}> <div class="text-input" style:width={canvasWidthCSS} style:height={canvasHeightCSS} style:pointer-events={showTextInput ? "auto" : ""}>
{#if showTextInput} {#if showTextInput}
<div bind:this={textInput} style:transform="matrix({textInputMatrix})" on:scroll={preventTextEditingScroll} /> <div bind:this={textInput} style:transform="matrix({textInputMatrix})" on:scroll={preventTextEditingScroll}></div>
{/if} {/if}
</div> </div>
{#if !$appWindow.viewportHolePunch} {#if !$appWindow.viewportHolePunch}

View File

@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { getContext, onMount, onDestroy, tick } from "svelte"; import { getContext, onMount, onDestroy, tick } from "svelte";
import { SvelteMap } from "svelte/reactivity";
import type { Editor } from "@graphite/editor"; import type { Editor } from "@graphite/editor";
import { import {
@ -54,7 +55,7 @@
let list: LayoutCol | undefined; let list: LayoutCol | undefined;
// Layer data // Layer data
let layerCache = new Map<string, LayerPanelEntry>(); // TODO: replace with BigUint64Array as index let layerCache = new SvelteMap<string, LayerPanelEntry>(); // TODO: replace with BigUint64Array as index
let layers: LayerListingInfo[] = []; let layers: LayerListingInfo[] = [];
// Interactive dragging // Interactive dragging
@ -686,7 +687,7 @@
{/each} {/each}
</LayoutCol> </LayoutCol>
{#if draggingData && !draggingData.highlightFolder && dragInPanel} {#if draggingData && !draggingData.highlightFolder && dragInPanel}
<div class="insert-mark" style:left={`${4 + draggingData.insertDepth * 16}px`} style:top={`${draggingData.markerHeight}px`} /> <div class="insert-mark" style:left={`${4 + draggingData.insertDepth * 16}px`} style:top={`${draggingData.markerHeight}px`}></div>
{/if} {/if}
</LayoutRow> </LayoutRow>
<LayoutRow class="bottom-bar" scrollableX={true}> <LayoutRow class="bottom-bar" scrollableX={true}>

View File

@ -349,7 +349,7 @@
}} }}
/> />
{#if index > 0} {#if index > 0}
<div class="reorder-drag-grip" data-tooltip-description="Reorder this export" /> <div class="reorder-drag-grip" data-tooltip-description="Reorder this export"></div>
{/if} {/if}
{/if} {/if}
</div> </div>
@ -396,7 +396,7 @@
> >
{#if (hoveringExportIndex === index || editingNameExportIndex === index) && $nodeGraph.updateImportsExports.addImportExport} {#if (hoveringExportIndex === index || editingNameExportIndex === index) && $nodeGraph.updateImportsExports.addImportExport}
{#if index > 0} {#if index > 0}
<div class="reorder-drag-grip" data-tooltip-description="Reorder this export" /> <div class="reorder-drag-grip" data-tooltip-description="Reorder this export"></div>
{/if} {/if}
<IconButton <IconButton
size={16} size={16}
@ -457,7 +457,7 @@
x: Number($nodeGraph.updateImportsExports.importPosition.x), x: Number($nodeGraph.updateImportsExports.importPosition.x),
y: Number($nodeGraph.updateImportsExports.importPosition.y) + Number($nodeGraph.reorderImportIndex) * 24, y: Number($nodeGraph.updateImportsExports.importPosition.y) + Number($nodeGraph.reorderImportIndex) * 24,
}} }}
<div class="reorder-bar" style:--offset-left={(position.x - 48) / 24} style:--offset-top={(position.y - 12) / 24} /> <div class="reorder-bar" style:--offset-left={(position.x - 48) / 24} style:--offset-top={(position.y - 12) / 24}></div>
{/if} {/if}
{#if $nodeGraph.reorderExportIndex !== undefined} {#if $nodeGraph.reorderExportIndex !== undefined}
@ -465,7 +465,7 @@
x: Number($nodeGraph.updateImportsExports.exportPosition.x), x: Number($nodeGraph.updateImportsExports.exportPosition.x),
y: Number($nodeGraph.updateImportsExports.exportPosition.y) + Number($nodeGraph.reorderExportIndex) * 24, y: Number($nodeGraph.updateImportsExports.exportPosition.y) + Number($nodeGraph.reorderExportIndex) * 24,
}} }}
<div class="reorder-bar" style:--offset-left={position.x / 24} style:--offset-top={(position.y - 12) / 24} /> <div class="reorder-bar" style:--offset-left={position.x / 24} style:--offset-top={(position.y - 12) / 24}></div>
{/if} {/if}
{/if} {/if}
</div> </div>

View File

@ -24,7 +24,7 @@
<!-- TODO: Implement collapsable sections with properties system --> <!-- TODO: Implement collapsable sections with properties system -->
<LayoutCol class={`widget-section ${className}`.trim()} {classes}> <LayoutCol class={`widget-section ${className}`.trim()} {classes}>
<button class="header" class:expanded on:click|stopPropagation={() => (expanded = !expanded)} tabindex="0"> <button class="header" class:expanded on:click|stopPropagation={() => (expanded = !expanded)} tabindex="0">
<div class="expand-arrow" /> <div class="expand-arrow"></div>
<TextLabel tooltipLabel={widgetData.name} tooltipDescription={widgetData.description} bold={true}>{widgetData.name}</TextLabel> <TextLabel tooltipLabel={widgetData.name} tooltipDescription={widgetData.description} bold={true}>{widgetData.name}</TextLabel>
<IconButton <IconButton
icon={widgetData.pinned ? "PinActive" : "PinInactive"} icon={widgetData.pinned ? "PinActive" : "PinInactive"}

View File

@ -12,6 +12,7 @@
const dispatch = createEventDispatcher<{ selectedEntryValuePath: string[] }>(); const dispatch = createEventDispatcher<{ selectedEntryValuePath: string[] }>();
let self: MenuList; let self: MenuList;
let open = false;
// Note: IconButton should instead be used if only an icon, but no label, is desired. // Note: IconButton should instead be used if only an icon, but no label, is desired.
// However, if multiple TextButton widgets are used in a group with only some having no label, this component is able to accommodate that. // However, if multiple TextButton widgets are used in a group with only some having no label, this component is able to accommodate that.
@ -93,9 +94,9 @@
</button> </button>
{#if menuListChildrenExists} {#if menuListChildrenExists}
<MenuList <MenuList
on:open={({ detail }) => self && (self.open = detail)}
on:selectedEntryValuePath={({ detail }) => dispatch("selectedEntryValuePath", detail)} on:selectedEntryValuePath={({ detail }) => dispatch("selectedEntryValuePath", detail)}
open={self?.open || false} on:open={({ detail }) => (open = detail)}
{open}
entries={menuListChildren || []} entries={menuListChildren || []}
entriesHash={menuListChildrenHash || 0n} entriesHash={menuListChildrenHash || 0n}
direction="Bottom" direction="Bottom"

View File

@ -114,7 +114,7 @@
on:keydown={(e) => e.key === "Escape" && cancel()} on:keydown={(e) => e.key === "Escape" && cancel()}
on:pointerdown on:pointerdown
on:contextmenu={(e) => hideContextMenu && e.preventDefault()} on:contextmenu={(e) => hideContextMenu && e.preventDefault()}
/> ></textarea>
{/if} {/if}
{#if label} {#if label}
<label for={`field-input-${id}`} on:pointerdown>{label}</label> <label for={`field-input-${id}`} on:pointerdown>{label}</label>

View File

@ -754,9 +754,9 @@
bind:this={inputRangeElement} bind:this={inputRangeElement}
/> />
{#if rangeSliderClickDragState === "Deciding"} {#if rangeSliderClickDragState === "Deciding"}
<div class="fake-slider-thumb" /> <div class="fake-slider-thumb"></div>
{/if} {/if}
<div class="slider-progress" /> <div class="slider-progress"></div>
{/if} {/if}
{/if} {/if}
</FieldInput> </FieldInput>

View File

@ -25,15 +25,15 @@
data-tooltip-description={tooltipDescription} data-tooltip-description={tooltipDescription}
data-tooltip-shortcut={tooltipShortcut?.shortcut ? JSON.stringify(tooltipShortcut.shortcut) : undefined} data-tooltip-shortcut={tooltipShortcut?.shortcut ? JSON.stringify(tooltipShortcut.shortcut) : undefined}
> >
<button on:click={() => setValue("TopLeft")} class="row-1 col-1" class:active={value === "TopLeft"} tabindex="-1" {disabled}><div /></button> <button on:click={() => setValue("TopLeft")} class="row-1 col-1" class:active={value === "TopLeft"} tabindex="-1" {disabled}><div></div></button>
<button on:click={() => setValue("TopCenter")} class="row-1 col-2" class:active={value === "TopCenter"} tabindex="-1" {disabled}><div /></button> <button on:click={() => setValue("TopCenter")} class="row-1 col-2" class:active={value === "TopCenter"} tabindex="-1" {disabled}><div></div></button>
<button on:click={() => setValue("TopRight")} class="row-1 col-3" class:active={value === "TopRight"} tabindex="-1" {disabled}><div /></button> <button on:click={() => setValue("TopRight")} class="row-1 col-3" class:active={value === "TopRight"} tabindex="-1" {disabled}><div></div></button>
<button on:click={() => setValue("CenterLeft")} class="row-2 col-1" class:active={value === "CenterLeft"} tabindex="-1" {disabled}><div /></button> <button on:click={() => setValue("CenterLeft")} class="row-2 col-1" class:active={value === "CenterLeft"} tabindex="-1" {disabled}><div></div></button>
<button on:click={() => setValue("Center")} class="row-2 col-2" class:active={value === "Center"} tabindex="-1" {disabled}><div /></button> <button on:click={() => setValue("Center")} class="row-2 col-2" class:active={value === "Center"} tabindex="-1" {disabled}><div></div></button>
<button on:click={() => setValue("CenterRight")} class="row-2 col-3" class:active={value === "CenterRight"} tabindex="-1" {disabled}><div /></button> <button on:click={() => setValue("CenterRight")} class="row-2 col-3" class:active={value === "CenterRight"} tabindex="-1" {disabled}><div></div></button>
<button on:click={() => setValue("BottomLeft")} class="row-3 col-1" class:active={value === "BottomLeft"} tabindex="-1" {disabled}><div /></button> <button on:click={() => setValue("BottomLeft")} class="row-3 col-1" class:active={value === "BottomLeft"} tabindex="-1" {disabled}><div></div></button>
<button on:click={() => setValue("BottomCenter")} class="row-3 col-2" class:active={value === "BottomCenter"} tabindex="-1" {disabled}><div /></button> <button on:click={() => setValue("BottomCenter")} class="row-3 col-2" class:active={value === "BottomCenter"} tabindex="-1" {disabled}><div></div></button>
<button on:click={() => setValue("BottomRight")} class="row-3 col-3" class:active={value === "BottomRight"} tabindex="-1" {disabled}><div /></button> <button on:click={() => setValue("BottomRight")} class="row-3 col-3" class:active={value === "BottomRight"} tabindex="-1" {disabled}><div></div></button>
</div> </div>
<style lang="scss" global> <style lang="scss" global>

View File

@ -210,7 +210,7 @@
<div class={`scrollbar-input ${direction.toLowerCase()}`}> <div class={`scrollbar-input ${direction.toLowerCase()}`}>
<button class="arrow decrease" on:pointerdown={() => pressArrow(-1)} tabindex="-1" data-scrollbar-arrow></button> <button class="arrow decrease" on:pointerdown={() => pressArrow(-1)} tabindex="-1" data-scrollbar-arrow></button>
<div class="scroll-track" on:pointerdown={pressTrack} bind:this={scrollTrack}> <div class="scroll-track" on:pointerdown={pressTrack} bind:this={scrollTrack}>
<div class="scroll-thumb" on:pointerdown={dragThumb} class:dragging style:top={thumbTop} style:bottom={thumbBottom} style:left={thumbLeft} style:right={thumbRight} /> <div class="scroll-thumb" on:pointerdown={dragThumb} class:dragging style:top={thumbTop} style:bottom={thumbBottom} style:left={thumbLeft} style:right={thumbRight}></div>
</div> </div>
<button class="arrow increase" on:pointerdown={() => pressArrow(1)} tabindex="-1" data-scrollbar-arrow></button> <button class="arrow increase" on:pointerdown={() => pressArrow(1)} tabindex="-1" data-scrollbar-arrow></button>
</div> </div>

View File

@ -8,7 +8,7 @@
<div class={`separator ${direction.toLowerCase()} ${style.toLowerCase()}`}> <div class={`separator ${direction.toLowerCase()} ${style.toLowerCase()}`}>
{#if style === "Section"} {#if style === "Section"}
<div /> <div></div>
{/if} {/if}
</div> </div>

View File

@ -4,9 +4,10 @@
// It is needed for class-transformer to work and is imported as a side effect. // It is needed for class-transformer to work and is imported as a side effect.
// The library replaces the Reflect API on the window to support more features. // The library replaces the Reflect API on the window to support more features.
import "reflect-metadata"; import "reflect-metadata";
import { mount } from "svelte";
import App from "@graphite/App.svelte"; import App from "@graphite/App.svelte";
document.body.setAttribute("data-app-container", ""); document.body.setAttribute("data-app-container", "");
export default new App({ target: document.body }); export default mount(App, { target: document.body });

View File

@ -25,6 +25,9 @@ export default defineConfig(({ mode }) => {
"a11y-no-static-element-interactions", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` "a11y-no-static-element-interactions", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json`
"a11y-no-noninteractive-element-interactions", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` "a11y-no-noninteractive-element-interactions", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json`
"a11y-click-events-have-key-events", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json` "a11y-click-events-have-key-events", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json`
"a11y_consider_explicit_label", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json`
"a11y_click_events_have_key_events", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json`
"a11y_no_noninteractive_element_interactions", // NOTICE: Keep this list in sync with the list in `.vscode/settings.json`
]; ];
if (suppressed.includes(warning.code)) return; if (suppressed.includes(warning.code)) return;