-
-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: CokaKoala <[email protected]>
- Loading branch information
1 parent
3f5a197
commit 6353651
Showing
24 changed files
with
547 additions
and
130 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"runed": minor | ||
--- | ||
|
||
feat: `IsFocusWithin` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import "@testing-library/svelte/vitest"; | ||
import "@testing-library/jest-dom/vitest"; | ||
import { vi } from "vitest"; | ||
import type { Navigation, Page } from "@sveltejs/kit"; | ||
import { readable } from "svelte/store"; | ||
import type * as environment from "$app/environment"; | ||
import type * as navigation from "$app/navigation"; | ||
import type * as stores from "$app/stores"; | ||
|
||
// Mock SvelteKit runtime module $app/environment | ||
vi.mock("$app/environment", (): typeof environment => ({ | ||
browser: false, | ||
dev: true, | ||
building: false, | ||
version: "any", | ||
})); | ||
|
||
// Mock SvelteKit runtime module $app/navigation | ||
vi.mock("$app/navigation", (): typeof navigation => ({ | ||
afterNavigate: () => {}, | ||
beforeNavigate: () => {}, | ||
disableScrollHandling: () => {}, | ||
goto: () => Promise.resolve(), | ||
invalidate: () => Promise.resolve(), | ||
invalidateAll: () => Promise.resolve(), | ||
preloadData: () => Promise.resolve({ type: "loaded" as const, status: 200, data: {} }), | ||
preloadCode: () => Promise.resolve(), | ||
onNavigate: () => {}, | ||
pushState: () => {}, | ||
replaceState: () => {}, | ||
})); | ||
|
||
// Mock SvelteKit runtime module $app/stores | ||
vi.mock("$app/stores", (): typeof stores => { | ||
const getStores: typeof stores.getStores = () => { | ||
const navigating = readable<Navigation | null>(null); | ||
const page = readable<Page>({ | ||
url: new URL("http://localhost"), | ||
params: {}, | ||
route: { | ||
id: null, | ||
}, | ||
status: 200, | ||
error: null, | ||
data: {}, | ||
form: undefined, | ||
state: {}, | ||
}); | ||
const updated = { subscribe: readable(false).subscribe, check: async () => false }; | ||
|
||
return { navigating, page, updated }; | ||
}; | ||
|
||
const page: typeof stores.page = { | ||
subscribe(fn) { | ||
return getStores().page.subscribe(fn); | ||
}, | ||
}; | ||
const navigating: typeof stores.navigating = { | ||
subscribe(fn) { | ||
return getStores().navigating.subscribe(fn); | ||
}, | ||
}; | ||
const updated: typeof stores.updated = { | ||
subscribe(fn) { | ||
return getStores().updated.subscribe(fn); | ||
}, | ||
check: async () => false, | ||
}; | ||
|
||
return { | ||
getStores, | ||
navigating, | ||
page, | ||
updated, | ||
}; | ||
}); | ||
|
||
// eslint-disable-next-line ts/no-require-imports | ||
globalThis.ResizeObserver = require("resize-observer-polyfill"); | ||
Element.prototype.scrollIntoView = () => {}; | ||
// eslint-disable-next-line ts/no-explicit-any | ||
Element.prototype.hasPointerCapture = (() => {}) as any; | ||
|
||
// @ts-expect-error - shut it | ||
globalThis.window.CSS.supports = (_property: string, _value: string) => true; | ||
|
||
globalThis.document.elementsFromPoint = () => []; | ||
globalThis.document.elementFromPoint = () => null; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
21 changes: 21 additions & 0 deletions
21
packages/runed/src/lib/utilities/IsFocusWithin/IsFocusWithin.svelte.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { extract } from "../extract/extract.js"; | ||
import { activeElement } from "../activeElement/activeElement.svelte.js"; | ||
import type { MaybeGetter } from "$lib/internal/types.js"; | ||
|
||
/** | ||
* Tracks whether the focus is within a target element. | ||
* @see {@link https://runed.dev/docs/utilities/is-focus-within} | ||
*/ | ||
export class IsFocusWithin { | ||
#node: MaybeGetter<HTMLElement | undefined>; | ||
#target = $derived.by(() => extract(this.#node)); | ||
|
||
constructor(node: MaybeGetter<HTMLElement | undefined>) { | ||
this.#node = node; | ||
} | ||
|
||
readonly current = $derived.by(() => { | ||
if (!this.#target || !activeElement.current) return false; | ||
return this.#target.contains(activeElement.current); | ||
}); | ||
} |
49 changes: 49 additions & 0 deletions
49
packages/runed/src/lib/utilities/IsFocusWithin/IsFocusWithin.test.svelte.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { render } from "@testing-library/svelte/svelte5"; | ||
import { describe, expect, it } from "vitest"; | ||
import { userEvent } from "@testing-library/user-event"; | ||
import TestIsFocusWithin from "./TestIsFocusWithin.svelte"; | ||
import { focus } from "$lib/test/util.svelte.js"; | ||
|
||
function setup() { | ||
const user = userEvent.setup(); | ||
const result = render(TestIsFocusWithin); | ||
const input = result.getByTestId("input"); | ||
const submit = result.getByTestId("submit"); | ||
const outside = result.getByTestId("outside"); | ||
const current = result.getByTestId("current"); | ||
|
||
return { | ||
...result, | ||
user, | ||
input, | ||
submit, | ||
outside, | ||
current, | ||
}; | ||
} | ||
|
||
describe("IsFocusWithin", () => { | ||
it("should be false on initial render", async () => { | ||
const { current } = setup(); | ||
expect(current).toHaveTextContent("false"); | ||
}); | ||
|
||
it("should be true when any child of the target is focused", async () => { | ||
const { user, input, current, submit } = setup(); | ||
expect(current).toHaveTextContent("false"); | ||
await user.click(input); | ||
expect(current).toHaveTextContent("true"); | ||
focus(submit); | ||
expect(submit).toHaveFocus(); | ||
expect(current).toHaveTextContent("true"); | ||
}); | ||
|
||
it("should be false when focus leaves the target after being true", async () => { | ||
const { user, input, current, outside } = setup(); | ||
await user.click(input); | ||
expect(current).toHaveTextContent("true"); | ||
focus(outside); | ||
expect(outside).toHaveFocus(); | ||
expect(current).toHaveTextContent("false"); | ||
}); | ||
}); |
12 changes: 12 additions & 0 deletions
12
packages/runed/src/lib/utilities/IsFocusWithin/TestIsFocusWithin.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<script lang="ts"> | ||
import { IsFocusWithin } from "./IsFocusWithin.svelte.js"; | ||
let formElement = $state<HTMLElement>(); | ||
const focusWithinForm = new IsFocusWithin(() => formElement); | ||
</script> | ||
|
||
<span data-testid="current">{focusWithinForm.current}</span> | ||
<form bind:this={formElement}> | ||
<input type="text" data-testid="input" /> | ||
<button data-testid="submit">btn</button> | ||
</form> | ||
<button data-testid="outside">outside</button> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./IsFocusWithin.svelte.js"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,28 @@ | ||
import process from "node:process"; | ||
import { sveltekit } from "@sveltejs/kit/vite"; | ||
import { defineConfig } from "vitest/config"; | ||
import { svelteTesting } from "@testing-library/svelte/vite"; | ||
import type { Plugin } from "vite"; | ||
|
||
const vitestBrowserConditionPlugin: Plugin = { | ||
name: "vite-plugin-vitest-browser-condition", | ||
configResolved({ resolve }: { resolve: { conditions: string[] } }) { | ||
if (process.env.VITEST) { | ||
resolve.conditions.unshift("browser"); | ||
} | ||
}, | ||
}; | ||
|
||
export default defineConfig({ | ||
plugins: [sveltekit()], | ||
plugins: [vitestBrowserConditionPlugin, sveltekit(), svelteTesting()], | ||
test: { | ||
include: ["src/**/*.{test,test.svelte,spec}.{js,ts}"], | ||
environment: "jsdom", | ||
}, | ||
resolve: { | ||
conditions: ["browser"], | ||
includeSource: ["src/**/*.{js,ts,svelte}"], | ||
setupFiles: ["./setupTest.ts"], | ||
globals: true, | ||
coverage: { | ||
exclude: ["./setupTest.ts"], | ||
}, | ||
}, | ||
}); |
Oops, something went wrong.