mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-10-16 17:53:18 +01:00
Compare commits
2 Commits
c45d6e2f5e
...
feature/mo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a8acd2780 | ||
|
|
0cfd5fd22b |
@@ -9,9 +9,9 @@
|
|||||||
"ext-zip": "*",
|
"ext-zip": "*",
|
||||||
"brick/money": "^0.10.0",
|
"brick/money": "^0.10.0",
|
||||||
"datomatic/laravel-enum-helper": "^2.0.0",
|
"datomatic/laravel-enum-helper": "^2.0.0",
|
||||||
"dedoc/scramble": "^0.13.43",
|
"dedoc/scramble": "^0.12.2",
|
||||||
"filament/filament": "^3.2",
|
"filament/filament": "^3.2",
|
||||||
"flowframe/laravel-trend": "^0.5.0",
|
"flowframe/laravel-trend": "^0.4.0",
|
||||||
"gotenberg/gotenberg-php": "^2.8",
|
"gotenberg/gotenberg-php": "^2.8",
|
||||||
"guzzlehttp/guzzle": "^7.2",
|
"guzzlehttp/guzzle": "^7.2",
|
||||||
"inertiajs/inertia-laravel": "^2.0.3",
|
"inertiajs/inertia-laravel": "^2.0.3",
|
||||||
|
|||||||
1457
composer.lock
generated
1457
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,6 @@
|
|||||||
},
|
},
|
||||||
"Invoicing": {
|
"Invoicing": {
|
||||||
"repository": "solidtime-io/extension-invoicing",
|
"repository": "solidtime-io/extension-invoicing",
|
||||||
"ref": "v0.0.6"
|
"ref": "v0.0.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ const max = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const backgroundColor = useCssVariable('--theme-color-card-background');
|
const backgroundColor = useCssVariable('--theme-color-card-background');
|
||||||
const borderColor = useCssVariable('--color-border-secondary');
|
const borderColor = useCssVariable('--color-border');
|
||||||
const labelColor = useCssVariable('--color-text-secondary');
|
const labelColor = useCssVariable('--color-text-secondary');
|
||||||
const chartColorRaw = useCssVariable('--theme-color-chart');
|
const chartColorRaw = useCssVariable('--theme-color-chart');
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import {
|
|||||||
CommandShortcut,
|
CommandShortcut,
|
||||||
} from '../command';
|
} from '../command';
|
||||||
import { cn } from '../utils/cn';
|
import { cn } from '../utils/cn';
|
||||||
import { useDialogFocusRestore } from '../utils/useDialogFocusRestore';
|
|
||||||
import type {
|
import type {
|
||||||
CommandPaletteCommand,
|
CommandPaletteCommand,
|
||||||
CommandPaletteGroup,
|
CommandPaletteGroup,
|
||||||
@@ -37,8 +36,6 @@ const emit = defineEmits<{
|
|||||||
select: [command: CommandPaletteCommand | EntitySearchResult];
|
select: [command: CommandPaletteCommand | EntitySearchResult];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { onOpenAutoFocus, onCloseAutoFocus } = useDialogFocusRestore();
|
|
||||||
|
|
||||||
// Non-empty groups for rendering
|
// Non-empty groups for rendering
|
||||||
const nonEmptyGroups = computed(() => props.groups.filter((g) => g.commands.length > 0));
|
const nonEmptyGroups = computed(() => props.groups.filter((g) => g.commands.length > 0));
|
||||||
|
|
||||||
@@ -74,9 +71,7 @@ watch(open, (isOpen) => {
|
|||||||
)
|
)
|
||||||
">
|
">
|
||||||
<DialogContent
|
<DialogContent
|
||||||
class="pointer-events-auto bg-default-background w-full max-w-lg border border-border-tertiary shadow-lg sm:rounded-lg outline-none overflow-hidden p-0 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95"
|
class="pointer-events-auto bg-default-background w-full max-w-lg border border-border-tertiary shadow-lg sm:rounded-lg outline-none overflow-hidden p-0 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95">
|
||||||
@open-auto-focus="onOpenAutoFocus"
|
|
||||||
@close-auto-focus="onCloseAutoFocus">
|
|
||||||
<CommandRoot
|
<CommandRoot
|
||||||
v-model:search-term="searchTerm"
|
v-model:search-term="searchTerm"
|
||||||
class="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
class="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { cn } from '../utils/cn';
|
import { cn } from '../utils/cn';
|
||||||
import { useDialogFocusRestore } from '../utils/useDialogFocusRestore';
|
|
||||||
import {
|
import {
|
||||||
DialogContent,
|
DialogContent,
|
||||||
type DialogContentEmits,
|
type DialogContentEmits,
|
||||||
@@ -21,10 +20,6 @@ const delegatedProps = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||||
|
|
||||||
// Forwarded consumer listeners run first, so a consumer can still take over
|
|
||||||
// by calling preventDefault() on close-auto-focus.
|
|
||||||
const { onOpenAutoFocus, onCloseAutoFocus } = useDialogFocusRestore();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -41,9 +36,7 @@ const { onOpenAutoFocus, onCloseAutoFocus } = useDialogFocusRestore();
|
|||||||
'my-3 md:my-14 xl:my-24 bg-default-background grid w-full max-w-lg border border-border-tertiary shadow-lg duration-200 rounded-lg outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
'my-3 md:my-14 xl:my-24 bg-default-background grid w-full max-w-lg border border-border-tertiary shadow-lg duration-200 rounded-lg outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
||||||
props.class
|
props.class
|
||||||
)
|
)
|
||||||
"
|
">
|
||||||
@open-auto-focus="onOpenAutoFocus"
|
|
||||||
@close-auto-focus="onCloseAutoFocus">
|
|
||||||
<slot />
|
<slot />
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { cn } from '../utils/cn';
|
import { cn } from '../utils/cn';
|
||||||
import { useDialogFocusRestore } from '../utils/useDialogFocusRestore';
|
|
||||||
import { X } from '@lucide/vue';
|
import { X } from '@lucide/vue';
|
||||||
import {
|
import {
|
||||||
DialogClose,
|
DialogClose,
|
||||||
@@ -23,10 +22,6 @@ const delegatedProps = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||||
|
|
||||||
// Forwarded consumer listeners run first, so a consumer can still take over
|
|
||||||
// by calling preventDefault() on close-auto-focus.
|
|
||||||
const { onOpenAutoFocus, onCloseAutoFocus } = useDialogFocusRestore();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -41,8 +36,6 @@ const { onOpenAutoFocus, onCloseAutoFocus } = useDialogFocusRestore();
|
|||||||
)
|
)
|
||||||
"
|
"
|
||||||
v-bind="forwarded"
|
v-bind="forwarded"
|
||||||
@open-auto-focus="onOpenAutoFocus"
|
|
||||||
@close-auto-focus="onCloseAutoFocus"
|
|
||||||
@pointer-down-outside="
|
@pointer-down-outside="
|
||||||
(event) => {
|
(event) => {
|
||||||
const originalEvent = event.detail.originalEvent;
|
const originalEvent = event.detail.originalEvent;
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
import { describe, expect, it, vi } from 'vitest';
|
|
||||||
import { useDialogFocusRestore } from './useDialogFocusRestore';
|
|
||||||
|
|
||||||
function closeEvent() {
|
|
||||||
return new CustomEvent('focusScope.autoFocusOnUnmount', { cancelable: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('useDialogFocusRestore', () => {
|
|
||||||
it('restores focus to the element focused when the dialog opened', () => {
|
|
||||||
vi.useFakeTimers();
|
|
||||||
const button = document.createElement('button');
|
|
||||||
document.body.appendChild(button);
|
|
||||||
button.focus();
|
|
||||||
|
|
||||||
const { onOpenAutoFocus, onCloseAutoFocus } = useDialogFocusRestore();
|
|
||||||
onOpenAutoFocus();
|
|
||||||
button.blur();
|
|
||||||
|
|
||||||
const event = closeEvent();
|
|
||||||
onCloseAutoFocus(event);
|
|
||||||
expect(event.defaultPrevented).toBe(true);
|
|
||||||
vi.runAllTimers();
|
|
||||||
expect(document.activeElement).toBe(button);
|
|
||||||
|
|
||||||
button.remove();
|
|
||||||
vi.useRealTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('focuses nothing when the dialog was opened with nothing focused', () => {
|
|
||||||
vi.useFakeTimers();
|
|
||||||
const stale = document.createElement('button');
|
|
||||||
document.body.appendChild(stale);
|
|
||||||
const { onOpenAutoFocus, onCloseAutoFocus } = useDialogFocusRestore();
|
|
||||||
|
|
||||||
// First open from the button, then close
|
|
||||||
stale.focus();
|
|
||||||
onOpenAutoFocus();
|
|
||||||
onCloseAutoFocus(closeEvent());
|
|
||||||
vi.runAllTimers();
|
|
||||||
stale.blur();
|
|
||||||
|
|
||||||
// Second open from the body must not refocus the stale button
|
|
||||||
onOpenAutoFocus();
|
|
||||||
const event = closeEvent();
|
|
||||||
onCloseAutoFocus(event);
|
|
||||||
expect(event.defaultPrevented).toBe(true);
|
|
||||||
vi.runAllTimers();
|
|
||||||
expect(document.activeElement).toBe(document.body);
|
|
||||||
|
|
||||||
stale.remove();
|
|
||||||
vi.useRealTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not restore focus to an element that was removed', () => {
|
|
||||||
vi.useFakeTimers();
|
|
||||||
const button = document.createElement('button');
|
|
||||||
document.body.appendChild(button);
|
|
||||||
button.focus();
|
|
||||||
const { onOpenAutoFocus, onCloseAutoFocus } = useDialogFocusRestore();
|
|
||||||
onOpenAutoFocus();
|
|
||||||
button.remove();
|
|
||||||
|
|
||||||
onCloseAutoFocus(closeEvent());
|
|
||||||
vi.runAllTimers();
|
|
||||||
expect(document.activeElement).toBe(document.body);
|
|
||||||
vi.useRealTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('leaves control to a consumer that already prevented the event', () => {
|
|
||||||
vi.useFakeTimers();
|
|
||||||
const button = document.createElement('button');
|
|
||||||
document.body.appendChild(button);
|
|
||||||
button.focus();
|
|
||||||
const { onOpenAutoFocus, onCloseAutoFocus } = useDialogFocusRestore();
|
|
||||||
onOpenAutoFocus();
|
|
||||||
button.blur();
|
|
||||||
|
|
||||||
const event = closeEvent();
|
|
||||||
event.preventDefault();
|
|
||||||
onCloseAutoFocus(event);
|
|
||||||
vi.runAllTimers();
|
|
||||||
expect(document.activeElement).toBe(document.body);
|
|
||||||
|
|
||||||
button.remove();
|
|
||||||
vi.useRealTimers();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
/**
|
|
||||||
* Restores focus to the element that was focused when a dialog opened.
|
|
||||||
*
|
|
||||||
* reka-ui remembers the active element at content mount as the dialog's
|
|
||||||
* "trigger" (only when it is not the body) and refocuses it on every close,
|
|
||||||
* but it never clears that value. A dialog opened while nothing is focused
|
|
||||||
* (e.g. the command palette via Cmd+K from the body) therefore refocuses
|
|
||||||
* whatever triggered a *previous* open. Bind these handlers to
|
|
||||||
* `DialogContent`'s `open-auto-focus` / `close-auto-focus` events to restore
|
|
||||||
* exactly the previously focused element, or nothing.
|
|
||||||
*
|
|
||||||
* A consumer handler that already called `preventDefault()` on
|
|
||||||
* `close-auto-focus` keeps control; this composable then does nothing.
|
|
||||||
*/
|
|
||||||
export function useDialogFocusRestore() {
|
|
||||||
let previouslyFocused: HTMLElement | null = null;
|
|
||||||
|
|
||||||
function onOpenAutoFocus() {
|
|
||||||
const active = document.activeElement;
|
|
||||||
previouslyFocused =
|
|
||||||
active instanceof HTMLElement && active !== document.body ? active : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onCloseAutoFocus(event: Event) {
|
|
||||||
const target = previouslyFocused;
|
|
||||||
previouslyFocused = null;
|
|
||||||
if (event.defaultPrevented) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Prevents both FocusScope's default restore and reka-ui's trigger refocus
|
|
||||||
event.preventDefault();
|
|
||||||
// Same tick reka-ui uses, so the dialog content is fully gone first
|
|
||||||
setTimeout(() => {
|
|
||||||
if (target?.isConnected) {
|
|
||||||
target.focus({ preventScroll: true });
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { onOpenAutoFocus, onCloseAutoFocus };
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user