fix: can't select empty chord outputs

update dependencies
use new title popover
This commit is contained in:
2025-07-29 20:18:13 +02:00
parent 977bdf3043
commit 048dee0a6d
7 changed files with 832 additions and 660 deletions

View File

@@ -36,61 +36,61 @@
"devDependencies": { "devDependencies": {
"@codemirror/autocomplete": "^6.18.6", "@codemirror/autocomplete": "^6.18.6",
"@codemirror/commands": "^6.8.1", "@codemirror/commands": "^6.8.1",
"@codemirror/lang-javascript": "^6.2.3", "@codemirror/lang-javascript": "^6.2.4",
"@codemirror/language": "^6.11.0", "@codemirror/language": "^6.11.2",
"@codemirror/state": "^6.5.2", "@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.36.5", "@codemirror/view": "^6.38.1",
"@fontsource-variable/material-symbols-rounded": "^5.2.8", "@fontsource-variable/material-symbols-rounded": "^5.2.17",
"@fontsource-variable/noto-sans-mono": "^5.2.6", "@fontsource-variable/noto-sans-mono": "^5.2.7",
"@lezer/highlight": "^1.2.1", "@lezer/highlight": "^1.2.1",
"@material/material-color-utilities": "^0.3.0", "@material/material-color-utilities": "^0.3.0",
"@melt-ui/pp": "^0.3.2", "@melt-ui/pp": "^0.3.2",
"@melt-ui/svelte": "^0.86.6", "@melt-ui/svelte": "^0.86.6",
"@modyfi/vite-plugin-yaml": "^1.1.1", "@modyfi/vite-plugin-yaml": "^1.1.1",
"@sveltejs/adapter-static": "^3.0.8", "@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/kit": "^2.20.2", "@sveltejs/kit": "^2.26.1",
"@sveltejs/vite-plugin-svelte": "^5.0.3", "@sveltejs/vite-plugin-svelte": "^6.1.0",
"@tauri-apps/api": "^1.6.0", "@tauri-apps/api": "^1.6.0",
"@tauri-apps/cli": "^1.6.0", "@tauri-apps/cli": "^1.6.0",
"@types/dom-view-transitions": "^1.0.6", "@types/dom-view-transitions": "^1.0.6",
"@types/semver": "^7.7.0", "@types/semver": "^7.7.0",
"@types/w3c-web-serial": "^1.0.8", "@types/w3c-web-serial": "^1.0.8",
"@types/w3c-web-usb": "^1.0.10", "@types/w3c-web-usb": "^1.0.10",
"@types/wicg-file-system-access": "^2023.10.5", "@types/wicg-file-system-access": "^2023.10.6",
"@vite-pwa/sveltekit": "^1.0.0", "@vite-pwa/sveltekit": "^1.0.0",
"autoprefixer": "^10.4.21", "autoprefixer": "^10.4.21",
"codemirror": "^6.0.1", "codemirror": "^6.0.2",
"cypress": "^14.2.1", "cypress": "^14.5.3",
"d3": "^7.9.0", "d3": "^7.9.0",
"esptool-js": "^0.5.4", "esptool-js": "^0.5.6",
"flexsearch": "^0.8.147", "flexsearch": "^0.8.205",
"fontkit": "^2.0.4", "fontkit": "^2.0.4",
"glob": "^11.0.1", "glob": "^11.0.3",
"jsdom": "^26.0.0", "jsdom": "^26.1.0",
"matrix-js-sdk": "^37.2.0", "matrix-js-sdk": "^37.12.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"prettier": "^3.5.3", "prettier": "^3.6.2",
"prettier-plugin-svelte": "^3.3.3", "prettier-plugin-svelte": "^3.4.0",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"sass": "^1.86.0", "sass": "^1.89.2",
"semver": "^7.7.2", "semver": "^7.7.2",
"socket.io-client": "^4.8.1", "socket.io-client": "^4.8.1",
"stylelint": "^16.17.0", "stylelint": "^16.23.0",
"stylelint-config-clean-order": "^7.0.0", "stylelint-config-clean-order": "^7.0.0",
"stylelint-config-html": "^1.1.0", "stylelint-config-html": "^1.1.0",
"stylelint-config-prettier-scss": "^1.0.0", "stylelint-config-prettier-scss": "^1.0.0",
"stylelint-config-recommended-scss": "^14.1.0", "stylelint-config-recommended-scss": "^15.0.1",
"stylelint-config-standard-scss": "^14.0.0", "stylelint-config-standard-scss": "^15.0.1",
"svelte": "5.25.3", "svelte": "5.37.1",
"svelte-check": "^4.1.5", "svelte-check": "^4.3.0",
"svelte-preprocess": "^6.0.3", "svelte-preprocess": "^6.0.3",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"typesafe-i18n": "^5.26.2", "typesafe-i18n": "^5.26.2",
"typescript": "^5.8.2", "typescript": "^5.8.3",
"vite": "^6.2.4", "vite": "^7.0.6",
"vite-plugin-mkcert": "^1.17.8", "vite-plugin-mkcert": "^1.17.8",
"vite-plugin-pwa": "^1.0.0", "vite-plugin-pwa": "^1.0.2",
"vitest": "^3.1.1", "vitest": "^3.2.4",
"web-serial-polyfill": "^1.0.15", "web-serial-polyfill": "^1.0.15",
"workbox-window": "^7.3.0" "workbox-window": "^7.3.0"
}, },

1323
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import { KEYMAP_CODES } from "$lib/serial/keymap-codes"; import { KEYMAP_CODES } from "$lib/serial/keymap-codes";
import type { KeyInfo } from "$lib/serial/keymap-codes"; import type { KeyInfo } from "$lib/serial/keymap-codes";
import { action as title } from "$lib/title";
import { osLayout } from "$lib/os-layout"; import { osLayout } from "$lib/os-layout";
import { tooltip } from "$lib/hover-popover";
let { let {
action, action,
@@ -16,42 +16,49 @@
); );
let dynamicMapping = $derived(info.keyCode && $osLayout.get(info.keyCode)); let dynamicMapping = $derived(info.keyCode && $osLayout.get(info.keyCode));
let tooltip = $derived( let popover: HTMLElement | undefined = $state(undefined);
`&lt;${info.id ?? `0x${info.code.toString(16)}`}&gt; ` +
(info.title ?? "") +
(info.variant === "left"
? " (left)"
: info.variant === "right"
? " (right)"
: ""),
);
</script> </script>
{#snippet popoverSnippet()}
<div bind:this={popover} popover="hint">
&lt;{info.id ?? `0x${info.code.toString(16)}`}&gt;
{#if info.title}
{info.title}
{/if}
{#if info.variant === "left"}
(Left)
{:else if info.variant === "right"}
(Right)
{/if}
</div>
{/snippet}
{#if display === "keys"} {#if display === "keys"}
<kbd <kbd
class:icon={!!info.icon} class:icon={!!info.icon}
class:left={info.variant === "left"} class:left={info.variant === "left"}
class:right={info.variant === "right"} class:right={info.variant === "right"}
use:title={{ title: tooltip }} {@attach tooltip(popover)}
> >
{dynamicMapping ?? {dynamicMapping ??
info.icon ?? info.icon ??
info.display ?? info.display ??
info.id ?? info.id ??
`0x${info.code.toString(16)}`} `0x${info.code.toString(16)}`}
{@render popoverSnippet()}
</kbd> </kbd>
{:else if display === "inline-keys"} {:else if display === "inline-keys"}
{#if !info.icon && dynamicMapping?.length === 1} {#if !info.icon && dynamicMapping?.length === 1}
<span <span
use:title={{ title: tooltip }} {@attach tooltip(popover)}
class:left={info.variant === "left"} class:left={info.variant === "left"}
class:right={info.variant === "right"}>{dynamicMapping}</span class:right={info.variant === "right"}>{dynamicMapping}{@render popoverSnippet()}</span
> >
{:else if !info.icon && info.id?.length === 1} {:else if !info.icon && info.id?.length === 1}
<span <span
use:title={{ title: tooltip }} {@attach tooltip(popover)}
class:left={info.variant === "left"} class:left={info.variant === "left"}
class:right={info.variant === "right"}>{info.id}</span class:right={info.variant === "right"}>{info.id}{@render popoverSnippet()}</span
> >
{:else} {:else}
<kbd <kbd
@@ -59,13 +66,13 @@
class:left={info.variant === "left"} class:left={info.variant === "left"}
class:right={info.variant === "right"} class:right={info.variant === "right"}
class:icon={!!info.icon} class:icon={!!info.icon}
use:title={{ title: tooltip }} {@attach tooltip(popover)}
> >
{dynamicMapping ?? {dynamicMapping ??
info.icon ?? info.icon ??
info.display ?? info.display ??
info.id ?? info.id ??
`0x${info.code.toString(16)}`}</kbd `0x${info.code.toString(16)}`}{@render popoverSnippet()}</kbd
> >
{/if} {/if}
{/if} {/if}

39
src/lib/hover-popover.ts Normal file
View File

@@ -0,0 +1,39 @@
import type { Attachment } from "svelte/attachments";
export const hotkeys = new Map<string, HTMLElement>();
export function tooltip(
target: HTMLElement | undefined,
shortcut?: string,
): Attachment<HTMLElement> {
return (node: HTMLElement) => {
function show() {
if (!target) return;
target.showPopover({ source: node });
}
function hide() {
if (!target) return;
target.hidePopover();
}
node.addEventListener("mouseenter", show);
node.addEventListener("focus", show);
node.addEventListener("mouseout", hide);
node.addEventListener("blur", hide);
if (shortcut && node instanceof HTMLElement) {
hotkeys.set(shortcut, node);
}
return () => {
node.removeEventListener("mouseenter", show);
node.removeEventListener("focus", show);
node.removeEventListener("mouseout", hide);
node.removeEventListener("blur", hide);
if (shortcut && node instanceof HTMLElement) {
hotkeys.delete(shortcut);
}
};
};
}

View File

@@ -4,13 +4,15 @@ $translate: translateY(8px);
[popover] { [popover] {
position: absolute; position: absolute;
inset: unset; inset: unset;
transform: $translate;
margin: 0; margin: 0;
padding: 8px; padding: 8px;
border: 1px solid var(--md-sys-color-outline); border: 1px solid var(--md-sys-color-outline);
border-radius: 8px; border-radius: 8px;
font-family: "Noto Sans Mono", monospace;
font-size: initial;
font-weight: initial;
color: var(--md-sys-color-on-surface); color: var(--md-sys-color-on-surface);
opacity: 0; opacity: 0;
@@ -18,12 +20,14 @@ $translate: translateY(8px);
transition: transition:
transform $animation-duration ease, transform $animation-duration ease,
opacity $animation-duration ease, opacity $animation-duration linear,
overlay $animation-duration allow-discrete, overlay $animation-duration allow-discrete,
display $animation-duration allow-discrete; display $animation-duration allow-discrete;
position-area: bottom span-right; position-area: bottom span-all;
position-try-fallbacks: position-try-fallbacks:
top span-all,
bottom span-right,
top span-right, top span-right,
bottom span-left, bottom span-left,
top span-left; top span-left;
@@ -43,9 +47,21 @@ $translate: translateY(8px);
} }
} }
[popover="auto"] {
transform: $translate;
}
[popover="hint"] {
margin-top: 0.5rem;
font-size: 0.9rem;
}
@starting-style { @starting-style {
[popover]:popover-open { [popover]:popover-open {
transform: $translate;
opacity: 0; opacity: 0;
} }
[popover="auto"] {
transform: $translate;
}
} }

View File

@@ -5,6 +5,9 @@ import Tooltip from "$lib/components/Tooltip.svelte";
export const hotkeys = new Map<string, HTMLElement>(); export const hotkeys = new Map<string, HTMLElement>();
/**
* @deprecated Use `tooltip` instead.
*/
export const action: Action<Element, { title?: string; shortcut?: string }> = ( export const action: Action<Element, { title?: string; shortcut?: string }> = (
node: Element, node: Element,
{ title, shortcut }, { title, shortcut },

View File

@@ -183,7 +183,13 @@
); );
</script> </script>
<div class="wrapper" class:edited={!chord.deleted && chord.phraseChanged}> <div
class="wrapper"
class:edited={!chord.deleted && chord.phraseChanged}
onclick={() => {
box.focus();
}}
>
{#if supportsAutospace} {#if supportsAutospace}
<label <label
class="auto-space-edit" class="auto-space-edit"