@bquery/bquery 1.8.1 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -25
- package/dist/{a11y-DVBCy09c.js → a11y-_9X-kt-_.js} +2 -2
- package/dist/{a11y-DVBCy09c.js.map → a11y-_9X-kt-_.js.map} +1 -1
- package/dist/a11y.es.mjs +1 -1
- package/dist/{forms-UcRHsYxC.js → forms-UhAeJEoO.js} +13 -13
- package/dist/{forms-UcRHsYxC.js.map → forms-UhAeJEoO.js.map} +1 -1
- package/dist/forms.es.mjs +1 -1
- package/dist/full.d.ts +4 -4
- package/dist/full.d.ts.map +1 -1
- package/dist/full.es.mjs +201 -196
- package/dist/full.iife.js +20 -20
- package/dist/full.iife.js.map +1 -1
- package/dist/full.umd.js +20 -20
- package/dist/full.umd.js.map +1 -1
- package/dist/index.es.mjs +214 -209
- package/dist/media/index.d.ts +10 -3
- package/dist/media/index.d.ts.map +1 -1
- package/dist/media/observers.d.ts +99 -0
- package/dist/media/observers.d.ts.map +1 -0
- package/dist/media/types.d.ts +125 -0
- package/dist/media/types.d.ts.map +1 -1
- package/dist/media-D4zLj9t-.js +514 -0
- package/dist/media-D4zLj9t-.js.map +1 -0
- package/dist/media.es.mjs +12 -9
- package/dist/mount-B-JvH6Y0.js +449 -0
- package/dist/mount-B-JvH6Y0.js.map +1 -0
- package/dist/reactive/index.d.ts +2 -2
- package/dist/reactive/index.d.ts.map +1 -1
- package/dist/reactive/signal.d.ts +2 -1
- package/dist/reactive/signal.d.ts.map +1 -1
- package/dist/reactive/watch.d.ts +49 -0
- package/dist/reactive/watch.d.ts.map +1 -1
- package/dist/reactive-BjpLkclt.js +1184 -0
- package/dist/{reactive-DwkhUJfP.js.map → reactive-BjpLkclt.js.map} +1 -1
- package/dist/reactive.es.mjs +35 -33
- package/dist/{router-CQikC9Ed.js → router-BieVwgci.js} +2 -2
- package/dist/{router-CQikC9Ed.js.map → router-BieVwgci.js.map} +1 -1
- package/dist/router.es.mjs +1 -1
- package/dist/{ssr-_dAcGdzu.js → ssr-CrGSJySz.js} +3 -3
- package/dist/{ssr-_dAcGdzu.js.map → ssr-CrGSJySz.js.map} +1 -1
- package/dist/ssr.es.mjs +1 -1
- package/dist/{store-Cb3gPRve.js → store-CY6sjTW3.js} +2 -2
- package/dist/{store-Cb3gPRve.js.map → store-CY6sjTW3.js.map} +1 -1
- package/dist/store.es.mjs +1 -1
- package/dist/{testing-C5Sjfsna.js → testing-UjAtu9aQ.js} +9 -9
- package/dist/{testing-C5Sjfsna.js.map → testing-UjAtu9aQ.js.map} +1 -1
- package/dist/testing.es.mjs +1 -1
- package/dist/view/directives/aria.d.ts +7 -0
- package/dist/view/directives/aria.d.ts.map +1 -0
- package/dist/view/directives/error.d.ts +7 -0
- package/dist/view/directives/error.d.ts.map +1 -0
- package/dist/view/directives/index.d.ts +2 -0
- package/dist/view/directives/index.d.ts.map +1 -1
- package/dist/view/mount.d.ts.map +1 -1
- package/dist/view/process.d.ts +2 -0
- package/dist/view/process.d.ts.map +1 -1
- package/dist/view.es.mjs +2 -2
- package/package.json +7 -6
- package/src/full.ts +12 -0
- package/src/media/index.ts +20 -2
- package/src/media/observers.ts +421 -0
- package/src/media/types.ts +136 -0
- package/src/reactive/index.ts +3 -0
- package/src/reactive/signal.ts +2 -1
- package/src/reactive/watch.ts +137 -0
- package/src/view/directives/aria.ts +72 -0
- package/src/view/directives/error.ts +56 -0
- package/src/view/directives/index.ts +2 -0
- package/src/view/mount.ts +4 -0
- package/src/view/process.ts +6 -0
- package/dist/media-i-fB5WxI.js +0 -340
- package/dist/media-i-fB5WxI.js.map +0 -1
- package/dist/mount-B4Y8bk8Z.js +0 -403
- package/dist/mount-B4Y8bk8Z.js.map +0 -1
- package/dist/reactive-DwkhUJfP.js +0 -1148
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing-C5Sjfsna.js","names":[],"sources":["../src/testing/testing.ts"],"sourcesContent":["/**\n * Testing utilities for bQuery.js.\n *\n * Provides helpers for mounting components, controlling signals, mocking\n * the router, dispatching events, and asserting async conditions — all\n * designed for use with `bun:test` and happy-dom.\n *\n * @module bquery/testing\n */\n\nimport { batch, Signal, signal } from '../reactive/index';\nimport { getNormalizedRouteConstraint } from '../router/constraints';\nimport type {\n FireEventOptions,\n MockRouteDefinition,\n MockRouter,\n MockRouterOptions,\n MockSignal,\n RenderComponentOptions,\n RenderResult,\n TestRoute,\n WaitForOptions,\n} from './types';\n\n// ============================================================================\n// renderComponent\n// ============================================================================\n\nconst isWordChar = (char: string | undefined): boolean =>\n char !== undefined &&\n ((char >= 'a' && char <= 'z') ||\n (char >= 'A' && char <= 'Z') ||\n (char >= '0' && char <= '9') ||\n char === '_');\n\nconst readRouteConstraint = (\n pattern: string,\n startIndex: number\n): { constraint: string; endIndex: number } | null => {\n let depth = 1;\n let constraint = '';\n let i = startIndex + 1;\n\n while (i < pattern.length) {\n const char = pattern[i];\n\n if (char === '\\\\' && i + 1 < pattern.length) {\n constraint += char + pattern[i + 1];\n i += 2;\n continue;\n }\n\n if (char === '(') {\n depth++;\n } else if (char === ')') {\n depth--;\n if (depth === 0) {\n return { constraint, endIndex: i + 1 };\n }\n }\n\n constraint += char;\n i++;\n }\n\n return null;\n};\n\nconst routeConstraintRegexCache = new Map<string, RegExp>();\n\nconst getRouteConstraintRegex = (constraint: string): RegExp => {\n const normalized = getNormalizedRouteConstraint(constraint);\n const cached = routeConstraintRegexCache.get(normalized);\n if (cached) {\n return cached;\n }\n\n const compiled = new RegExp(`^(?:${normalized})$`);\n routeConstraintRegexCache.set(normalized, compiled);\n return compiled;\n};\n\n/**\n * Mounts a custom element by tag name for testing and returns a handle\n * to interact with it.\n *\n * The element is created, configured with the given props and slots,\n * and appended to the container (defaults to `document.body`). Call\n * `unmount()` to remove the element and trigger its `disconnectedCallback`.\n *\n * @param tagName - The custom element tag name (must already be registered)\n * @param options - Props, slots, and container configuration\n * @returns A {@link RenderResult} with the element and an unmount function\n * @throws {Error} If the tag name is not a valid custom element name\n *\n * @example\n * ```ts\n * import { renderComponent } from '@bquery/bquery/testing';\n *\n * const { el, unmount } = renderComponent('my-counter', {\n * props: { start: '5' },\n * });\n * expect(el.shadowRoot?.textContent).toContain('5');\n * unmount();\n * ```\n */\nexport function renderComponent(\n tagName: string,\n options: RenderComponentOptions = {}\n): RenderResult {\n if (!tagName || !tagName.includes('-')) {\n throw new Error(\n `bQuery testing: \"${tagName}\" is not a valid custom element tag name (must contain a hyphen)`\n );\n }\n\n const { props, slots, container = document.body } = options;\n\n const el = document.createElement(tagName);\n\n // Set attributes (props) before connecting\n if (props) {\n for (const [key, value] of Object.entries(props)) {\n if (value === null || value === undefined) continue;\n el.setAttribute(key, String(value));\n }\n }\n\n // Inject slot content before connecting so the component can discover it\n if (slots) {\n if (typeof slots === 'string') {\n el.innerHTML = slots;\n } else {\n const parts: string[] = [];\n for (const [slotName, html] of Object.entries(slots)) {\n if (slotName === 'default') {\n parts.push(html);\n } else {\n const safeSlotName = slotName\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n parts.push(`<div slot=\"${safeSlotName}\">${html}</div>`);\n }\n }\n el.innerHTML = parts.join('');\n }\n }\n\n // Connect — triggers connectedCallback\n container.appendChild(el);\n\n const unmount = (): void => {\n if (el.parentNode) {\n el.parentNode.removeChild(el);\n }\n };\n\n return { el, unmount };\n}\n\n// ============================================================================\n// flushEffects\n// ============================================================================\n\n/**\n * Synchronously flushes any pending reactive effects.\n *\n * In bQuery's reactive system, effects outside of a batch are executed\n * synchronously. This helper exists primarily for clarity and for\n * flushing effects that may have been deferred inside a batch.\n *\n * Internally it performs a no-op batch to trigger the flush of any\n * pending observers that were queued during a prior `batch()` call.\n *\n * @example\n * ```ts\n * import { signal, batch } from '@bquery/bquery/reactive';\n * import { flushEffects } from '@bquery/bquery/testing';\n *\n * const count = signal(0);\n * let observed = 0;\n * effect(() => { observed = count.value; });\n *\n * batch(() => { count.value = 42; });\n * flushEffects();\n * expect(observed).toBe(42);\n * ```\n */\nexport function flushEffects(): void {\n // A no-op batch triggers endBatch which flushes any pending observers.\n // Since bQuery's effects are synchronous outside of batches, this is\n // mainly useful after manual batch calls or micro-task boundaries.\n batch(() => {\n /* intentionally empty — triggers pending observer flush */\n });\n}\n\n// ============================================================================\n// mockSignal\n// ============================================================================\n\n/**\n * Creates a controllable signal for tests with `set()` and `reset()` helpers.\n *\n * This is a thin wrapper around `signal()` that records the initial value\n * and adds explicit `set()` / `reset()` methods for clearer test intent.\n *\n * @template T - The type of the signal value\n * @param initialValue - The initial value\n * @returns A {@link MockSignal} instance\n *\n * @example\n * ```ts\n * import { mockSignal } from '@bquery/bquery/testing';\n *\n * const count = mockSignal(0);\n * count.set(5);\n * expect(count.value).toBe(5);\n * count.reset();\n * expect(count.value).toBe(0);\n * ```\n */\nexport function mockSignal<T>(initialValue: T): MockSignal<T> {\n const s = signal(initialValue) as Signal<T> & {\n set: (value: T) => void;\n reset: () => void;\n initialValue: T;\n };\n\n Object.defineProperty(s, 'initialValue', {\n value: initialValue,\n writable: false,\n enumerable: true,\n });\n\n s.set = function (value: T): void {\n s.value = value;\n };\n\n s.reset = function (): void {\n s.value = initialValue;\n };\n\n return s as MockSignal<T>;\n}\n\n// ============================================================================\n// mockRouter\n// ============================================================================\n\n/**\n * Parses a path string into the route's `path`, `query`, and `hash` parts.\n * @internal\n */\nfunction parsePath(\n fullPath: string,\n base: string\n): { path: string; query: Record<string, string | string[]>; hash: string } {\n let working = fullPath;\n\n // Strip base prefix\n if (base && working.startsWith(base)) {\n working = working.slice(base.length) || '/';\n }\n\n // Extract hash\n let hash = '';\n const hashIdx = working.indexOf('#');\n if (hashIdx >= 0) {\n hash = working.slice(hashIdx + 1);\n working = working.slice(0, hashIdx);\n }\n\n // Extract query string\n const query: Record<string, string | string[]> = {};\n const qIdx = working.indexOf('?');\n if (qIdx >= 0) {\n const qs = working.slice(qIdx + 1);\n working = working.slice(0, qIdx);\n for (const pair of qs.split('&')) {\n const eqIdx = pair.indexOf('=');\n const key = eqIdx >= 0 ? decodeURIComponent(pair.slice(0, eqIdx)) : decodeURIComponent(pair);\n const val = eqIdx >= 0 ? decodeURIComponent(pair.slice(eqIdx + 1)) : '';\n const existing = query[key];\n if (existing !== undefined) {\n if (Array.isArray(existing)) {\n existing.push(val);\n } else {\n query[key] = [existing, val];\n }\n } else {\n query[key] = val;\n }\n }\n }\n\n return { path: working || '/', query, hash };\n}\n\n/**\n * Matches a path against a route definition, extracting params.\n * @internal\n */\nfunction matchRoute(\n path: string,\n routes: MockRouteDefinition[]\n): { matched: MockRouteDefinition | null; params: Record<string, string> } {\n for (const route of routes) {\n const params = matchRoutePattern(route.path, path);\n if (params) {\n return { matched: route, params };\n }\n }\n return { matched: null, params: {} };\n}\n\n/**\n * Builds param matches from a route path pattern without compiling the full path into a regex.\n * @internal\n */\nfunction matchRoutePattern(pattern: string, path: string): Record<string, string> | null {\n if (pattern === '*') {\n return {};\n }\n\n // Memoization keeps wildcard/param backtracking linear for repeated subproblems\n // within a single pattern/path match attempt.\n const memo = new Map<string, Record<string, string> | null>();\n\n const findSegmentBoundary = (value: string, startIndex: number): number => {\n const slashIndex = value.indexOf('/', startIndex);\n return slashIndex === -1 ? value.length : slashIndex;\n };\n\n const matchFrom = (patternIndex: number, pathIndex: number): Record<string, string> | null => {\n const memoKey = `${patternIndex}:${pathIndex}`;\n if (memo.has(memoKey)) {\n return memo.get(memoKey) ?? null;\n }\n\n if (patternIndex === pattern.length) {\n const result = pathIndex === path.length ? {} : null;\n memo.set(memoKey, result);\n return result;\n }\n\n const patternChar = pattern[patternIndex];\n\n if (patternChar === '*') {\n for (let candidateEnd = path.length; candidateEnd >= pathIndex; candidateEnd--) {\n const suffixMatch = matchFrom(patternIndex + 1, candidateEnd);\n if (suffixMatch) {\n memo.set(memoKey, suffixMatch);\n return suffixMatch;\n }\n }\n\n memo.set(memoKey, null);\n return null;\n }\n\n if (patternChar === ':' && isWordChar(pattern[patternIndex + 1])) {\n let nameEnd = patternIndex + 2;\n while (nameEnd < pattern.length && isWordChar(pattern[nameEnd])) {\n nameEnd++;\n }\n\n const name = pattern.slice(patternIndex + 1, nameEnd);\n let nextPatternIndex = nameEnd;\n let constraint: string | undefined;\n let catchAll = false;\n\n if (pattern[nameEnd] === '(') {\n const parsedConstraint = readRouteConstraint(pattern, nameEnd);\n if (parsedConstraint) {\n constraint = parsedConstraint.constraint;\n nextPatternIndex = parsedConstraint.endIndex;\n }\n }\n\n if (pattern[nextPatternIndex] === '*') {\n catchAll = true;\n nextPatternIndex++;\n }\n\n const candidateLimit = catchAll\n ? path.length\n : constraint\n ? path.length\n : findSegmentBoundary(path, pathIndex);\n\n for (let candidateEnd = candidateLimit; candidateEnd > pathIndex; candidateEnd--) {\n const candidateValue = path.slice(pathIndex, candidateEnd);\n if (constraint) {\n const constraintRegex = getRouteConstraintRegex(constraint);\n if (!constraintRegex.test(candidateValue)) {\n continue;\n }\n }\n\n const suffixMatch = matchFrom(nextPatternIndex, candidateEnd);\n if (suffixMatch) {\n const result = {\n [name]: candidateValue,\n ...suffixMatch,\n };\n memo.set(memoKey, result);\n return result;\n }\n }\n\n memo.set(memoKey, null);\n return null;\n }\n\n if (pathIndex >= path.length || patternChar !== path[pathIndex]) {\n memo.set(memoKey, null);\n return null;\n }\n\n const result = matchFrom(patternIndex + 1, pathIndex + 1);\n memo.set(memoKey, result);\n return result;\n };\n\n return matchFrom(0, 0);\n}\n\n/**\n * Creates a lightweight mock router for testing that does not interact\n * with the browser History API.\n *\n * The mock router provides a reactive `currentRoute` signal that updates\n * when `push()` or `replace()` is called, making it ideal for testing\n * components or logic that depend on route state.\n *\n * @param options - Mock router configuration\n * @returns A {@link MockRouter} instance\n *\n * @example\n * ```ts\n * import { mockRouter } from '@bquery/bquery/testing';\n *\n * const router = mockRouter({\n * routes: [\n * { path: '/', component: () => null },\n * { path: '/user/:id', component: () => null },\n * ],\n * initialPath: '/',\n * });\n *\n * router.push('/user/42');\n * expect(router.currentRoute.value.params.id).toBe('42');\n * router.destroy();\n * ```\n */\nexport function mockRouter(options: MockRouterOptions = {}): MockRouter {\n const routes = options.routes ?? [{ path: '*', component: () => null }];\n const base = options.base ?? '';\n const initialPath = options.initialPath ?? '/';\n\n const resolveRoute = (fullPath: string): TestRoute => {\n const { path, query, hash } = parsePath(fullPath, base);\n const { matched, params } = matchRoute(path, routes);\n return { path, params, query, matched, hash };\n };\n\n const routeSignal = signal<TestRoute>(resolveRoute(initialPath));\n\n return {\n push(path: string): void {\n routeSignal.value = resolveRoute(path);\n },\n replace(path: string): void {\n routeSignal.value = resolveRoute(path);\n },\n get currentRoute(): Signal<TestRoute> {\n return routeSignal;\n },\n get routes(): MockRouteDefinition[] {\n return routes;\n },\n destroy(): void {\n routeSignal.dispose();\n },\n };\n}\n\n// ============================================================================\n// fireEvent\n// ============================================================================\n\n/**\n * Dispatches a synthetic event on an element and flushes pending effects.\n *\n * By default the event bubbles, is cancelable, and is composed (crosses\n * shadow DOM boundaries). Pass a `detail` option to create a `CustomEvent`.\n *\n * @param el - The target element\n * @param eventName - The event type (e.g. 'click', 'input', 'my-event')\n * @param options - Event configuration\n * @returns `true` if the event was not cancelled\n *\n * @example\n * ```ts\n * import { fireEvent } from '@bquery/bquery/testing';\n *\n * const button = document.createElement('button');\n * let clicked = false;\n * button.addEventListener('click', () => { clicked = true; });\n * fireEvent(button, 'click');\n * expect(clicked).toBe(true);\n * ```\n */\nexport function fireEvent(el: Element, eventName: string, options: FireEventOptions = {}): boolean {\n if (!el) {\n throw new Error('bQuery testing: fireEvent requires a valid element');\n }\n if (!eventName) {\n throw new Error('bQuery testing: fireEvent requires an event name');\n }\n\n const { bubbles = true, cancelable = true, composed = true, detail } = options;\n\n let event: Event;\n if (detail !== undefined) {\n event = new CustomEvent(eventName, { bubbles, cancelable, composed, detail });\n } else {\n event = new Event(eventName, { bubbles, cancelable, composed });\n }\n\n const result = el.dispatchEvent(event);\n\n // Flush any effects triggered by event handlers\n flushEffects();\n\n return result;\n}\n\n// ============================================================================\n// waitFor\n// ============================================================================\n\n/**\n * Waits for a predicate to return `true`, polling at a configurable interval.\n *\n * Useful for asserting conditions that depend on asynchronous operations,\n * timers, or deferred reactive updates.\n *\n * @param predicate - A function that returns `true` when the condition is met\n * @param options - Timeout and interval configuration\n * @returns A promise that resolves when the predicate returns `true`\n * @throws {Error} If the predicate does not return `true` within the timeout\n *\n * @example\n * ```ts\n * import { waitFor } from '@bquery/bquery/testing';\n *\n * await waitFor(() => document.querySelector('.loaded') !== null, {\n * timeout: 2000,\n * });\n * ```\n */\nexport async function waitFor(\n predicate: () => boolean | Promise<boolean>,\n options: WaitForOptions = {}\n): Promise<void> {\n if (typeof predicate !== 'function') {\n throw new Error('bQuery testing: waitFor requires a predicate function');\n }\n\n const { timeout = 1000, interval = 10 } = options;\n const start = Date.now();\n\n while (true) {\n try {\n const result = await predicate();\n if (result) return;\n } catch {\n // Predicate threw — treat as not-yet-met and keep polling\n }\n\n if (Date.now() - start >= timeout) {\n throw new Error(\n `bQuery testing: waitFor timed out after ${timeout}ms — predicate never returned true`\n );\n }\n\n await new Promise((resolve) => setTimeout(resolve, interval));\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"testing-UjAtu9aQ.js","names":[],"sources":["../src/testing/testing.ts"],"sourcesContent":["/**\n * Testing utilities for bQuery.js.\n *\n * Provides helpers for mounting components, controlling signals, mocking\n * the router, dispatching events, and asserting async conditions — all\n * designed for use with `bun:test` and happy-dom.\n *\n * @module bquery/testing\n */\n\nimport { batch, Signal, signal } from '../reactive/index';\nimport { getNormalizedRouteConstraint } from '../router/constraints';\nimport type {\n FireEventOptions,\n MockRouteDefinition,\n MockRouter,\n MockRouterOptions,\n MockSignal,\n RenderComponentOptions,\n RenderResult,\n TestRoute,\n WaitForOptions,\n} from './types';\n\n// ============================================================================\n// renderComponent\n// ============================================================================\n\nconst isWordChar = (char: string | undefined): boolean =>\n char !== undefined &&\n ((char >= 'a' && char <= 'z') ||\n (char >= 'A' && char <= 'Z') ||\n (char >= '0' && char <= '9') ||\n char === '_');\n\nconst readRouteConstraint = (\n pattern: string,\n startIndex: number\n): { constraint: string; endIndex: number } | null => {\n let depth = 1;\n let constraint = '';\n let i = startIndex + 1;\n\n while (i < pattern.length) {\n const char = pattern[i];\n\n if (char === '\\\\' && i + 1 < pattern.length) {\n constraint += char + pattern[i + 1];\n i += 2;\n continue;\n }\n\n if (char === '(') {\n depth++;\n } else if (char === ')') {\n depth--;\n if (depth === 0) {\n return { constraint, endIndex: i + 1 };\n }\n }\n\n constraint += char;\n i++;\n }\n\n return null;\n};\n\nconst routeConstraintRegexCache = new Map<string, RegExp>();\n\nconst getRouteConstraintRegex = (constraint: string): RegExp => {\n const normalized = getNormalizedRouteConstraint(constraint);\n const cached = routeConstraintRegexCache.get(normalized);\n if (cached) {\n return cached;\n }\n\n const compiled = new RegExp(`^(?:${normalized})$`);\n routeConstraintRegexCache.set(normalized, compiled);\n return compiled;\n};\n\n/**\n * Mounts a custom element by tag name for testing and returns a handle\n * to interact with it.\n *\n * The element is created, configured with the given props and slots,\n * and appended to the container (defaults to `document.body`). Call\n * `unmount()` to remove the element and trigger its `disconnectedCallback`.\n *\n * @param tagName - The custom element tag name (must already be registered)\n * @param options - Props, slots, and container configuration\n * @returns A {@link RenderResult} with the element and an unmount function\n * @throws {Error} If the tag name is not a valid custom element name\n *\n * @example\n * ```ts\n * import { renderComponent } from '@bquery/bquery/testing';\n *\n * const { el, unmount } = renderComponent('my-counter', {\n * props: { start: '5' },\n * });\n * expect(el.shadowRoot?.textContent).toContain('5');\n * unmount();\n * ```\n */\nexport function renderComponent(\n tagName: string,\n options: RenderComponentOptions = {}\n): RenderResult {\n if (!tagName || !tagName.includes('-')) {\n throw new Error(\n `bQuery testing: \"${tagName}\" is not a valid custom element tag name (must contain a hyphen)`\n );\n }\n\n const { props, slots, container = document.body } = options;\n\n const el = document.createElement(tagName);\n\n // Set attributes (props) before connecting\n if (props) {\n for (const [key, value] of Object.entries(props)) {\n if (value === null || value === undefined) continue;\n el.setAttribute(key, String(value));\n }\n }\n\n // Inject slot content before connecting so the component can discover it\n if (slots) {\n if (typeof slots === 'string') {\n el.innerHTML = slots;\n } else {\n const parts: string[] = [];\n for (const [slotName, html] of Object.entries(slots)) {\n if (slotName === 'default') {\n parts.push(html);\n } else {\n const safeSlotName = slotName\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n parts.push(`<div slot=\"${safeSlotName}\">${html}</div>`);\n }\n }\n el.innerHTML = parts.join('');\n }\n }\n\n // Connect — triggers connectedCallback\n container.appendChild(el);\n\n const unmount = (): void => {\n if (el.parentNode) {\n el.parentNode.removeChild(el);\n }\n };\n\n return { el, unmount };\n}\n\n// ============================================================================\n// flushEffects\n// ============================================================================\n\n/**\n * Synchronously flushes any pending reactive effects.\n *\n * In bQuery's reactive system, effects outside of a batch are executed\n * synchronously. This helper exists primarily for clarity and for\n * flushing effects that may have been deferred inside a batch.\n *\n * Internally it performs a no-op batch to trigger the flush of any\n * pending observers that were queued during a prior `batch()` call.\n *\n * @example\n * ```ts\n * import { signal, batch } from '@bquery/bquery/reactive';\n * import { flushEffects } from '@bquery/bquery/testing';\n *\n * const count = signal(0);\n * let observed = 0;\n * effect(() => { observed = count.value; });\n *\n * batch(() => { count.value = 42; });\n * flushEffects();\n * expect(observed).toBe(42);\n * ```\n */\nexport function flushEffects(): void {\n // A no-op batch triggers endBatch which flushes any pending observers.\n // Since bQuery's effects are synchronous outside of batches, this is\n // mainly useful after manual batch calls or micro-task boundaries.\n batch(() => {\n /* intentionally empty — triggers pending observer flush */\n });\n}\n\n// ============================================================================\n// mockSignal\n// ============================================================================\n\n/**\n * Creates a controllable signal for tests with `set()` and `reset()` helpers.\n *\n * This is a thin wrapper around `signal()` that records the initial value\n * and adds explicit `set()` / `reset()` methods for clearer test intent.\n *\n * @template T - The type of the signal value\n * @param initialValue - The initial value\n * @returns A {@link MockSignal} instance\n *\n * @example\n * ```ts\n * import { mockSignal } from '@bquery/bquery/testing';\n *\n * const count = mockSignal(0);\n * count.set(5);\n * expect(count.value).toBe(5);\n * count.reset();\n * expect(count.value).toBe(0);\n * ```\n */\nexport function mockSignal<T>(initialValue: T): MockSignal<T> {\n const s = signal(initialValue) as Signal<T> & {\n set: (value: T) => void;\n reset: () => void;\n initialValue: T;\n };\n\n Object.defineProperty(s, 'initialValue', {\n value: initialValue,\n writable: false,\n enumerable: true,\n });\n\n s.set = function (value: T): void {\n s.value = value;\n };\n\n s.reset = function (): void {\n s.value = initialValue;\n };\n\n return s as MockSignal<T>;\n}\n\n// ============================================================================\n// mockRouter\n// ============================================================================\n\n/**\n * Parses a path string into the route's `path`, `query`, and `hash` parts.\n * @internal\n */\nfunction parsePath(\n fullPath: string,\n base: string\n): { path: string; query: Record<string, string | string[]>; hash: string } {\n let working = fullPath;\n\n // Strip base prefix\n if (base && working.startsWith(base)) {\n working = working.slice(base.length) || '/';\n }\n\n // Extract hash\n let hash = '';\n const hashIdx = working.indexOf('#');\n if (hashIdx >= 0) {\n hash = working.slice(hashIdx + 1);\n working = working.slice(0, hashIdx);\n }\n\n // Extract query string\n const query: Record<string, string | string[]> = {};\n const qIdx = working.indexOf('?');\n if (qIdx >= 0) {\n const qs = working.slice(qIdx + 1);\n working = working.slice(0, qIdx);\n for (const pair of qs.split('&')) {\n const eqIdx = pair.indexOf('=');\n const key = eqIdx >= 0 ? decodeURIComponent(pair.slice(0, eqIdx)) : decodeURIComponent(pair);\n const val = eqIdx >= 0 ? decodeURIComponent(pair.slice(eqIdx + 1)) : '';\n const existing = query[key];\n if (existing !== undefined) {\n if (Array.isArray(existing)) {\n existing.push(val);\n } else {\n query[key] = [existing, val];\n }\n } else {\n query[key] = val;\n }\n }\n }\n\n return { path: working || '/', query, hash };\n}\n\n/**\n * Matches a path against a route definition, extracting params.\n * @internal\n */\nfunction matchRoute(\n path: string,\n routes: MockRouteDefinition[]\n): { matched: MockRouteDefinition | null; params: Record<string, string> } {\n for (const route of routes) {\n const params = matchRoutePattern(route.path, path);\n if (params) {\n return { matched: route, params };\n }\n }\n return { matched: null, params: {} };\n}\n\n/**\n * Builds param matches from a route path pattern without compiling the full path into a regex.\n * @internal\n */\nfunction matchRoutePattern(pattern: string, path: string): Record<string, string> | null {\n if (pattern === '*') {\n return {};\n }\n\n // Memoization keeps wildcard/param backtracking linear for repeated subproblems\n // within a single pattern/path match attempt.\n const memo = new Map<string, Record<string, string> | null>();\n\n const findSegmentBoundary = (value: string, startIndex: number): number => {\n const slashIndex = value.indexOf('/', startIndex);\n return slashIndex === -1 ? value.length : slashIndex;\n };\n\n const matchFrom = (patternIndex: number, pathIndex: number): Record<string, string> | null => {\n const memoKey = `${patternIndex}:${pathIndex}`;\n if (memo.has(memoKey)) {\n return memo.get(memoKey) ?? null;\n }\n\n if (patternIndex === pattern.length) {\n const result = pathIndex === path.length ? {} : null;\n memo.set(memoKey, result);\n return result;\n }\n\n const patternChar = pattern[patternIndex];\n\n if (patternChar === '*') {\n for (let candidateEnd = path.length; candidateEnd >= pathIndex; candidateEnd--) {\n const suffixMatch = matchFrom(patternIndex + 1, candidateEnd);\n if (suffixMatch) {\n memo.set(memoKey, suffixMatch);\n return suffixMatch;\n }\n }\n\n memo.set(memoKey, null);\n return null;\n }\n\n if (patternChar === ':' && isWordChar(pattern[patternIndex + 1])) {\n let nameEnd = patternIndex + 2;\n while (nameEnd < pattern.length && isWordChar(pattern[nameEnd])) {\n nameEnd++;\n }\n\n const name = pattern.slice(patternIndex + 1, nameEnd);\n let nextPatternIndex = nameEnd;\n let constraint: string | undefined;\n let catchAll = false;\n\n if (pattern[nameEnd] === '(') {\n const parsedConstraint = readRouteConstraint(pattern, nameEnd);\n if (parsedConstraint) {\n constraint = parsedConstraint.constraint;\n nextPatternIndex = parsedConstraint.endIndex;\n }\n }\n\n if (pattern[nextPatternIndex] === '*') {\n catchAll = true;\n nextPatternIndex++;\n }\n\n const candidateLimit = catchAll\n ? path.length\n : constraint\n ? path.length\n : findSegmentBoundary(path, pathIndex);\n\n for (let candidateEnd = candidateLimit; candidateEnd > pathIndex; candidateEnd--) {\n const candidateValue = path.slice(pathIndex, candidateEnd);\n if (constraint) {\n const constraintRegex = getRouteConstraintRegex(constraint);\n if (!constraintRegex.test(candidateValue)) {\n continue;\n }\n }\n\n const suffixMatch = matchFrom(nextPatternIndex, candidateEnd);\n if (suffixMatch) {\n const result = {\n [name]: candidateValue,\n ...suffixMatch,\n };\n memo.set(memoKey, result);\n return result;\n }\n }\n\n memo.set(memoKey, null);\n return null;\n }\n\n if (pathIndex >= path.length || patternChar !== path[pathIndex]) {\n memo.set(memoKey, null);\n return null;\n }\n\n const result = matchFrom(patternIndex + 1, pathIndex + 1);\n memo.set(memoKey, result);\n return result;\n };\n\n return matchFrom(0, 0);\n}\n\n/**\n * Creates a lightweight mock router for testing that does not interact\n * with the browser History API.\n *\n * The mock router provides a reactive `currentRoute` signal that updates\n * when `push()` or `replace()` is called, making it ideal for testing\n * components or logic that depend on route state.\n *\n * @param options - Mock router configuration\n * @returns A {@link MockRouter} instance\n *\n * @example\n * ```ts\n * import { mockRouter } from '@bquery/bquery/testing';\n *\n * const router = mockRouter({\n * routes: [\n * { path: '/', component: () => null },\n * { path: '/user/:id', component: () => null },\n * ],\n * initialPath: '/',\n * });\n *\n * router.push('/user/42');\n * expect(router.currentRoute.value.params.id).toBe('42');\n * router.destroy();\n * ```\n */\nexport function mockRouter(options: MockRouterOptions = {}): MockRouter {\n const routes = options.routes ?? [{ path: '*', component: () => null }];\n const base = options.base ?? '';\n const initialPath = options.initialPath ?? '/';\n\n const resolveRoute = (fullPath: string): TestRoute => {\n const { path, query, hash } = parsePath(fullPath, base);\n const { matched, params } = matchRoute(path, routes);\n return { path, params, query, matched, hash };\n };\n\n const routeSignal = signal<TestRoute>(resolveRoute(initialPath));\n\n return {\n push(path: string): void {\n routeSignal.value = resolveRoute(path);\n },\n replace(path: string): void {\n routeSignal.value = resolveRoute(path);\n },\n get currentRoute(): Signal<TestRoute> {\n return routeSignal;\n },\n get routes(): MockRouteDefinition[] {\n return routes;\n },\n destroy(): void {\n routeSignal.dispose();\n },\n };\n}\n\n// ============================================================================\n// fireEvent\n// ============================================================================\n\n/**\n * Dispatches a synthetic event on an element and flushes pending effects.\n *\n * By default the event bubbles, is cancelable, and is composed (crosses\n * shadow DOM boundaries). Pass a `detail` option to create a `CustomEvent`.\n *\n * @param el - The target element\n * @param eventName - The event type (e.g. 'click', 'input', 'my-event')\n * @param options - Event configuration\n * @returns `true` if the event was not cancelled\n *\n * @example\n * ```ts\n * import { fireEvent } from '@bquery/bquery/testing';\n *\n * const button = document.createElement('button');\n * let clicked = false;\n * button.addEventListener('click', () => { clicked = true; });\n * fireEvent(button, 'click');\n * expect(clicked).toBe(true);\n * ```\n */\nexport function fireEvent(el: Element, eventName: string, options: FireEventOptions = {}): boolean {\n if (!el) {\n throw new Error('bQuery testing: fireEvent requires a valid element');\n }\n if (!eventName) {\n throw new Error('bQuery testing: fireEvent requires an event name');\n }\n\n const { bubbles = true, cancelable = true, composed = true, detail } = options;\n\n let event: Event;\n if (detail !== undefined) {\n event = new CustomEvent(eventName, { bubbles, cancelable, composed, detail });\n } else {\n event = new Event(eventName, { bubbles, cancelable, composed });\n }\n\n const result = el.dispatchEvent(event);\n\n // Flush any effects triggered by event handlers\n flushEffects();\n\n return result;\n}\n\n// ============================================================================\n// waitFor\n// ============================================================================\n\n/**\n * Waits for a predicate to return `true`, polling at a configurable interval.\n *\n * Useful for asserting conditions that depend on asynchronous operations,\n * timers, or deferred reactive updates.\n *\n * @param predicate - A function that returns `true` when the condition is met\n * @param options - Timeout and interval configuration\n * @returns A promise that resolves when the predicate returns `true`\n * @throws {Error} If the predicate does not return `true` within the timeout\n *\n * @example\n * ```ts\n * import { waitFor } from '@bquery/bquery/testing';\n *\n * await waitFor(() => document.querySelector('.loaded') !== null, {\n * timeout: 2000,\n * });\n * ```\n */\nexport async function waitFor(\n predicate: () => boolean | Promise<boolean>,\n options: WaitForOptions = {}\n): Promise<void> {\n if (typeof predicate !== 'function') {\n throw new Error('bQuery testing: waitFor requires a predicate function');\n }\n\n const { timeout = 1000, interval = 10 } = options;\n const start = Date.now();\n\n while (true) {\n try {\n const result = await predicate();\n if (result) return;\n } catch {\n // Predicate threw — treat as not-yet-met and keep polling\n }\n\n if (Date.now() - start >= timeout) {\n throw new Error(\n `bQuery testing: waitFor timed out after ${timeout}ms — predicate never returned true`\n );\n }\n\n await new Promise((resolve) => setTimeout(resolve, interval));\n }\n}\n"],"mappings":"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"}
|
package/dist/testing.es.mjs
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aria.d.ts","sourceRoot":"","sources":["../../../src/view/directives/aria.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAkBjD;;;GAGG;AACH,eAAO,MAAM,UAAU,EAAE,gBA8CxB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../../src/view/directives/error.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAoBjD;;;GAGG;AACH,eAAO,MAAM,WAAW,EAAE,gBA6BzB,CAAC"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
export { handleAria } from './aria';
|
|
1
2
|
export { handleBind } from './bind';
|
|
2
3
|
export { handleClass } from './class';
|
|
4
|
+
export { handleError } from './error';
|
|
3
5
|
export { createForHandler } from './for';
|
|
4
6
|
export { handleHtml } from './html';
|
|
5
7
|
export { handleIf } from './if';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/view/directives/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AAChC,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/view/directives/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AAChC,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC"}
|
package/dist/view/mount.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../../src/view/mount.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../../src/view/mount.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAElE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,eAAO,MAAM,KAAK,GAChB,UAAU,MAAM,GAAG,OAAO,EAC1B,SAAS,cAAc,EACvB,UAAS,YAAiB,KACzB,IA6EF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,cAAc,GACzB,UAAU,MAAM,EAChB,UAAS,YAAiB,KACzB,CAAC,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAiCpC,CAAC"}
|
package/dist/view/process.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ import type { CleanupFn } from '../reactive/index';
|
|
|
2
2
|
import type { BindingContext, DirectiveHandler } from './types';
|
|
3
3
|
export type DirectiveHandlers = {
|
|
4
4
|
text: DirectiveHandler;
|
|
5
|
+
error: DirectiveHandler;
|
|
6
|
+
aria: DirectiveHandler;
|
|
5
7
|
html: DirectiveHandler;
|
|
6
8
|
if: DirectiveHandler;
|
|
7
9
|
show: DirectiveHandler;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../src/view/process.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAGnD,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,gBAAgB,CAAC;IACvB,IAAI,EAAE,gBAAgB,CAAC;IACvB,EAAE,EAAE,gBAAgB,CAAC;IACrB,IAAI,EAAE,gBAAgB,CAAC;IACvB,KAAK,EAAE,gBAAgB,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;IACxB,GAAG,EAAE,gBAAgB,CAAC;IACtB,GAAG,EAAE,gBAAgB,CAAC;IACtB,IAAI,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,gBAAgB,CAAC;IAC7C,EAAE,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,gBAAgB,CAAC;CAC7C,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc,GACzB,IAAI,OAAO,EACX,SAAS,cAAc,EACvB,QAAQ,MAAM,EACd,UAAU,SAAS,EAAE,EACrB,UAAU,iBAAiB,KAC1B,
|
|
1
|
+
{"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../src/view/process.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAGnD,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,gBAAgB,CAAC;IACvB,KAAK,EAAE,gBAAgB,CAAC;IACxB,IAAI,EAAE,gBAAgB,CAAC;IACvB,IAAI,EAAE,gBAAgB,CAAC;IACvB,EAAE,EAAE,gBAAgB,CAAC;IACrB,IAAI,EAAE,gBAAgB,CAAC;IACvB,KAAK,EAAE,gBAAgB,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;IACxB,GAAG,EAAE,gBAAgB,CAAC;IACtB,GAAG,EAAE,gBAAgB,CAAC;IACtB,IAAI,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,gBAAgB,CAAC;IAC7C,EAAE,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,gBAAgB,CAAC;CAC7C,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc,GACzB,IAAI,OAAO,EACX,SAAS,cAAc,EACvB,QAAQ,MAAM,EACd,UAAU,SAAS,EAAE,EACrB,UAAU,iBAAiB,KAC1B,IA2DF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,GAC1B,IAAI,OAAO,EACX,SAAS,cAAc,EACvB,QAAQ,MAAM,EACd,UAAU,SAAS,EAAE,EACrB,UAAU,iBAAiB,KAC1B,IAWF,CAAC"}
|
package/dist/view.es.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as a, r as o } from "./core-DdtZHzsS.js";
|
|
2
|
-
import {
|
|
2
|
+
import { k as e } from "./reactive-BjpLkclt.js";
|
|
3
3
|
import { r as s } from "./untrack-D0fnO5k2.js";
|
|
4
|
-
import { n as c, r as f, t as i } from "./mount-
|
|
4
|
+
import { n as c, r as f, t as i } from "./mount-B-JvH6Y0.js";
|
|
5
5
|
export {
|
|
6
6
|
e as batch,
|
|
7
7
|
f as clearExpressionCache,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bquery/bquery",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "The jQuery for the Modern Web Platform - Zero build, TypeScript-first library with reactivity, components, and motion",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/full.umd.js",
|
|
@@ -154,21 +154,22 @@
|
|
|
154
154
|
"registry": "https://registry.npmjs.org/"
|
|
155
155
|
},
|
|
156
156
|
"engines": {
|
|
157
|
-
"node": ">=
|
|
157
|
+
"node": ">=24.0.0",
|
|
158
|
+
"bun": ">=1.3.11"
|
|
158
159
|
},
|
|
159
160
|
"devDependencies": {
|
|
160
|
-
"@storybook/addon-docs": "^10.3.
|
|
161
|
-
"@storybook/web-components-vite": "^10.3.
|
|
161
|
+
"@storybook/addon-docs": "^10.3.4",
|
|
162
|
+
"@storybook/web-components-vite": "^10.3.4",
|
|
162
163
|
"@typescript-eslint/eslint-plugin": "^8.58.0",
|
|
163
164
|
"@typescript-eslint/parser": "^8.58.0",
|
|
164
165
|
"bun-types": "^1.3.11",
|
|
165
|
-
"eslint": "^10.
|
|
166
|
+
"eslint": "^10.2.0",
|
|
166
167
|
"eslint-config-prettier": "^10.1.8",
|
|
167
168
|
"globals": "^17.4.0",
|
|
168
169
|
"happy-dom": "^20.8.9",
|
|
169
170
|
"prettier": "^3.8.1",
|
|
170
171
|
"rimraf": "^6.1.3",
|
|
171
|
-
"storybook": "^10.3.
|
|
172
|
+
"storybook": "^10.3.4",
|
|
172
173
|
"typedoc": "^0.28.18",
|
|
173
174
|
"typescript": "^5.9.3",
|
|
174
175
|
"vite": "^8.0.3",
|
package/src/full.ts
CHANGED
|
@@ -80,6 +80,8 @@ export {
|
|
|
80
80
|
useWebSocket,
|
|
81
81
|
useWebSocketChannel,
|
|
82
82
|
watch,
|
|
83
|
+
watchDebounce,
|
|
84
|
+
watchThrottle,
|
|
83
85
|
} from './reactive/index';
|
|
84
86
|
export type {
|
|
85
87
|
AsyncDataState,
|
|
@@ -129,6 +131,7 @@ export type {
|
|
|
129
131
|
UseWebSocketChannelReturn,
|
|
130
132
|
UseWebSocketOptions,
|
|
131
133
|
UseWebSocketReturn,
|
|
134
|
+
WatchOptions,
|
|
132
135
|
WebSocketHeartbeatConfig,
|
|
133
136
|
WebSocketReconnectConfig,
|
|
134
137
|
WebSocketSerializer,
|
|
@@ -446,7 +449,10 @@ export {
|
|
|
446
449
|
useDeviceMotion,
|
|
447
450
|
useDeviceOrientation,
|
|
448
451
|
useGeolocation,
|
|
452
|
+
useIntersectionObserver,
|
|
453
|
+
useMutationObserver,
|
|
449
454
|
useNetworkStatus,
|
|
455
|
+
useResizeObserver,
|
|
450
456
|
useViewport,
|
|
451
457
|
} from './media/index';
|
|
452
458
|
export type {
|
|
@@ -457,7 +463,13 @@ export type {
|
|
|
457
463
|
DeviceOrientationState,
|
|
458
464
|
GeolocationOptions,
|
|
459
465
|
GeolocationState,
|
|
466
|
+
IntersectionObserverOptions,
|
|
467
|
+
IntersectionObserverState,
|
|
468
|
+
MutationObserverOptions,
|
|
469
|
+
MutationObserverState,
|
|
460
470
|
NetworkState,
|
|
471
|
+
ResizeObserverOptions,
|
|
472
|
+
ResizeObserverState,
|
|
461
473
|
ViewportState,
|
|
462
474
|
} from './media/index';
|
|
463
475
|
|
package/src/media/index.ts
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
* bQuery Media module — reactive browser and device API signals.
|
|
3
3
|
*
|
|
4
4
|
* Provides reactive wrappers around browser media queries, viewport,
|
|
5
|
-
* network status, battery, geolocation, device sensors,
|
|
5
|
+
* network status, battery, geolocation, device sensors, clipboard,
|
|
6
|
+
* and browser Observer APIs (IntersectionObserver, ResizeObserver,
|
|
7
|
+
* MutationObserver).
|
|
6
8
|
*
|
|
7
9
|
* @module bquery/media
|
|
8
10
|
*
|
|
9
11
|
* @example
|
|
10
12
|
* ```ts
|
|
11
|
-
* import { mediaQuery, breakpoints, useViewport, useNetworkStatus, clipboard } from '@bquery/bquery/media';
|
|
13
|
+
* import { mediaQuery, breakpoints, useViewport, useNetworkStatus, clipboard, useIntersectionObserver } from '@bquery/bquery/media';
|
|
12
14
|
* import { effect } from '@bquery/bquery/reactive';
|
|
13
15
|
*
|
|
14
16
|
* // Reactive media query
|
|
@@ -28,6 +30,10 @@
|
|
|
28
30
|
* // Clipboard
|
|
29
31
|
* await clipboard.write('Hello!');
|
|
30
32
|
* const text = await clipboard.read();
|
|
33
|
+
*
|
|
34
|
+
* // Intersection observer
|
|
35
|
+
* const io = useIntersectionObserver(document.querySelector('#el'));
|
|
36
|
+
* effect(() => console.log('Visible:', io.value.isIntersecting));
|
|
31
37
|
* ```
|
|
32
38
|
*/
|
|
33
39
|
|
|
@@ -55,6 +61,9 @@ export { useDeviceMotion, useDeviceOrientation } from './device-sensors';
|
|
|
55
61
|
// Clipboard
|
|
56
62
|
export { clipboard } from './clipboard';
|
|
57
63
|
|
|
64
|
+
// Observers
|
|
65
|
+
export { useIntersectionObserver, useMutationObserver, useResizeObserver } from './observers';
|
|
66
|
+
|
|
58
67
|
// Types
|
|
59
68
|
export type {
|
|
60
69
|
BatterySignal,
|
|
@@ -68,9 +77,18 @@ export type {
|
|
|
68
77
|
GeolocationOptions,
|
|
69
78
|
GeolocationSignal,
|
|
70
79
|
GeolocationState,
|
|
80
|
+
IntersectionObserverOptions,
|
|
81
|
+
IntersectionObserverSignal,
|
|
82
|
+
IntersectionObserverState,
|
|
71
83
|
MediaSignalHandle,
|
|
84
|
+
MutationObserverOptions,
|
|
85
|
+
MutationObserverSignal,
|
|
86
|
+
MutationObserverState,
|
|
72
87
|
NetworkSignal,
|
|
73
88
|
NetworkState,
|
|
89
|
+
ResizeObserverOptions,
|
|
90
|
+
ResizeObserverSignal,
|
|
91
|
+
ResizeObserverState,
|
|
74
92
|
ViewportSignal,
|
|
75
93
|
ViewportState,
|
|
76
94
|
} from './types';
|