From 22ab55251d8092e3f73b2daa921fe74b982c309b Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sat, 27 Jun 2026 22:48:11 -0700 Subject: [PATCH 1/2] Add a Vite plugin to auto-prefix -webkit-user-select so it's not so often forgotten --- frontend/src/components/Editor.svelte | 1 - .../components/floating-menus/Dialog.svelte | 1 - frontend/src/components/panels/Layers.svelte | 1 - frontend/src/components/views/Graph.svelte | 1 - frontend/vite.config.ts | 27 ++++++++++++++++++- 5 files changed, 26 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/Editor.svelte b/frontend/src/components/Editor.svelte index 0122277a83..ed8339ef81 100644 --- a/frontend/src/components/Editor.svelte +++ b/frontend/src/components/Editor.svelte @@ -233,7 +233,6 @@ height: 100%; background: var(--color-2-mildblack); overscroll-behavior: none; - -webkit-user-select: none; // Still required by Safari as of 2025 user-select: none; } diff --git a/frontend/src/components/floating-menus/Dialog.svelte b/frontend/src/components/floating-menus/Dialog.svelte index 4e5e0da385..3b2ca07d4d 100644 --- a/frontend/src/components/floating-menus/Dialog.svelte +++ b/frontend/src/components/floating-menus/Dialog.svelte @@ -136,7 +136,6 @@ } .text-label.multiline { - -webkit-user-select: text; // Still required by Safari as of 2026 user-select: text; } diff --git a/frontend/src/components/panels/Layers.svelte b/frontend/src/components/panels/Layers.svelte index 5b5a393dcc..a9995954f3 100644 --- a/frontend/src/components/panels/Layers.svelte +++ b/frontend/src/components/panels/Layers.svelte @@ -884,7 +884,6 @@ width: 100%; &:disabled { - -webkit-user-select: none; // Still required by Safari as of 2025 user-select: none; // Workaround for `user-select: none` not working on elements pointer-events: none; diff --git a/frontend/src/components/views/Graph.svelte b/frontend/src/components/views/Graph.svelte index 1bce166f6e..773c59f5df 100644 --- a/frontend/src/components/views/Graph.svelte +++ b/frontend/src/components/views/Graph.svelte @@ -952,7 +952,6 @@ &.faded:hover { z-index: 2; opacity: 1; - -webkit-user-select: text; user-select: text; transition: opacity 0.2s, diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 241df61326..1cb69c025c 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -11,7 +11,7 @@ const projectRootDir = path.resolve(__dirname); // https://vitejs.dev/config/ export default defineConfig(({ mode }) => { return { - plugins: [svelteGlobalStyles(), svelte(), staticAssets(), mode !== "native" && thirdPartyLicenses(), mode !== "native" && serviceWorker()], + plugins: [svelteGlobalStyles(), webkitUserSelectPrefix(), svelte(), staticAssets(), mode !== "native" && thirdPartyLicenses(), mode !== "native" && serviceWorker()], resolve: { alias: [{ find: /\/..\/branding\/(.*\.svg)/, replacement: path.resolve(projectRootDir, "../branding", "$1?raw") }], }, @@ -35,6 +35,31 @@ function svelteGlobalStyles(): PluginOption { }; } +// Adds the `-webkit-user-select` prefix alongside every `user-select` declaration in Svelte component styles (still required by Safari). Remove when Safari ships the unprefixed version. +// WebKit tracking issue: +// https://bugs.webkit.org/show_bug.cgi?id=208677 +// Included in Interop 2026: +// https://webkit.org/blog/17818/announcing-interop-2026/#web-compat +// https://github.com/web-platform-tests/interop/issues/1000#issuecomment-3892214470 +// Web platform test for WebKit implementation status: +// https://wpt.fyi/results/css/css-ui/parsing/user-select-computed.html?label=master&label=experimental&aligned&view=interop&q=label%3Ainterop-2026-webcompat +function webkitUserSelectPrefix(): PluginOption { + return { + name: "webkit-user-select-prefix", + enforce: "pre", + transform(code, id) { + if (!id.endsWith(".svelte")) return; + + return code.replace(/)([^>]*)>(.*?)<\/style>/gs, (_, attrs, content) => { + // The negative lookbehind avoids re-matching the `user-select` inside `-webkit-user-select`/`-moz-user-select`. + // Excluding newlines/braces from the value stops a `user-select` mentioned in a comment from swallowing the following declarations. + const prefixed = content.replace(/(?${prefixed}`; + }); + }, + }; +} + function staticAssets(): PluginOption { const STATIC_ASSET_DIRS: { source: string; urlPrefix: string }[] = [ { source: "../demo-artwork", urlPrefix: "/demo-artwork" }, From 5c8d3d4537d72cc2b1cfb368f611fa4247eafa82 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sat, 27 Jun 2026 22:53:33 -0700 Subject: [PATCH 2/2] Enforce a property boundary on the left --- frontend/vite.config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 1cb69c025c..8a0adc56c3 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -51,9 +51,9 @@ function webkitUserSelectPrefix(): PluginOption { if (!id.endsWith(".svelte")) return; return code.replace(/)([^>]*)>(.*?)<\/style>/gs, (_, attrs, content) => { - // The negative lookbehind avoids re-matching the `user-select` inside `-webkit-user-select`/`-moz-user-select`. + // The lookbehind requires a property boundary on the left, so it skips `-webkit-`/`-moz-` prefixes, `--custom` properties, and `$scss-variables`. // Excluding newlines/braces from the value stops a `user-select` mentioned in a comment from swallowing the following declarations. - const prefixed = content.replace(/(?${prefixed}`; }); },