@pyreon/zero 0.4.1 → 0.5.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/lib/image.js +5 -5
- package/lib/image.js.map +1 -1
- package/lib/index.js +14 -14
- package/lib/index.js.map +1 -1
- package/lib/link.js +9 -9
- package/lib/link.js.map +1 -1
- package/lib/script.js +1 -1
- package/lib/script.js.map +1 -1
- package/lib/theme.js +2 -2
- package/lib/theme.js.map +1 -1
- package/lib/types/image.d.ts +1 -1
- package/lib/types/link.d.ts +5 -5
- package/package.json +10 -10
- package/src/image.tsx +5 -5
- package/src/link.tsx +8 -8
- package/src/theme.tsx +1 -1
package/lib/link.js
CHANGED
|
@@ -26,7 +26,7 @@ function useIntersectionObserver(getElement, onIntersect, rootMargin = "200px")
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
//#endregion
|
|
29
|
-
//#region ../../node_modules/.bun/@pyreon+core@0.7.
|
|
29
|
+
//#region ../../node_modules/.bun/@pyreon+core@0.7.12/node_modules/@pyreon/core/lib/jsx-runtime.js
|
|
30
30
|
/**
|
|
31
31
|
* Hyperscript function — the compiled output of JSX.
|
|
32
32
|
* `<div class="x">hello</div>` → `h("div", { class: "x" }, "hello")`
|
|
@@ -102,7 +102,7 @@ function doPrefetch(href) {
|
|
|
102
102
|
* function MyLink(props: LinkProps) {
|
|
103
103
|
* const link = useLink(props)
|
|
104
104
|
* return (
|
|
105
|
-
* <button ref={link.ref} class={link.classes()}
|
|
105
|
+
* <button ref={link.ref} class={link.classes()} onClick={link.handleClick}>
|
|
106
106
|
* {props.children}
|
|
107
107
|
* </button>
|
|
108
108
|
* )
|
|
@@ -164,8 +164,8 @@ function useLink(props) {
|
|
|
164
164
|
* <button
|
|
165
165
|
* ref={props.ref}
|
|
166
166
|
* class={props.class}
|
|
167
|
-
*
|
|
168
|
-
*
|
|
167
|
+
* onClick={props.onClick}
|
|
168
|
+
* onMouseEnter={props.onMouseEnter}
|
|
169
169
|
* >
|
|
170
170
|
* {props.children}
|
|
171
171
|
* </button>
|
|
@@ -176,8 +176,8 @@ function useLink(props) {
|
|
|
176
176
|
* <div
|
|
177
177
|
* ref={props.ref}
|
|
178
178
|
* class={`card ${props.isActive() ? "card--active" : ""}`}
|
|
179
|
-
*
|
|
180
|
-
*
|
|
179
|
+
* onClick={props.onClick}
|
|
180
|
+
* onMouseEnter={props.onMouseEnter}
|
|
181
181
|
* >
|
|
182
182
|
* {props.children}
|
|
183
183
|
* </div>
|
|
@@ -225,9 +225,9 @@ const Link = createLink((props) => /* @__PURE__ */ jsx("a", {
|
|
|
225
225
|
...props.rel ? { rel: props.rel } : {},
|
|
226
226
|
...props["aria-label"] ? { "aria-label": props["aria-label"] } : {},
|
|
227
227
|
...props.isExactActive() ? { "aria-current": "page" } : {},
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
228
|
+
onClick: props.onClick,
|
|
229
|
+
onMouseEnter: props.onMouseEnter,
|
|
230
|
+
onTouchStart: props.onTouchStart,
|
|
231
231
|
children: props.children
|
|
232
232
|
}));
|
|
233
233
|
|
package/lib/link.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"link.js","names":[],"sources":["../src/utils/use-intersection-observer.ts","../../../node_modules/.bun/@pyreon+core@0.7.5/node_modules/@pyreon/core/lib/jsx-runtime.js","../src/link.tsx"],"sourcesContent":["import { onMount, onUnmount } from '@pyreon/core'\n\n/**\n * Observes an element and calls `onIntersect` once it enters the viewport.\n * Automatically disconnects after the first intersection.\n *\n * @param getElement - Getter for the target element (may be undefined before mount).\n * @param onIntersect - Callback fired when the element becomes visible.\n * @param rootMargin - IntersectionObserver rootMargin. Default: \"200px\".\n */\nexport function useIntersectionObserver(\n getElement: () => HTMLElement | undefined,\n onIntersect: () => void,\n rootMargin = '200px',\n) {\n onMount(() => {\n const el = getElement()\n if (!el) return undefined\n\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n onIntersect()\n observer.disconnect()\n }\n }\n },\n { rootMargin },\n )\n\n observer.observe(el)\n onUnmount(() => observer.disconnect())\n return undefined\n })\n}\n","//#region src/h.ts\n/** Marker for fragment nodes — renders children without a wrapper element */\nconst Fragment = Symbol(\"Pyreon.Fragment\");\n/**\n* Hyperscript function — the compiled output of JSX.\n* `<div class=\"x\">hello</div>` → `h(\"div\", { class: \"x\" }, \"hello\")`\n*\n* Generic on P so TypeScript validates props match the component's signature\n* at the call site, then stores the result in the loosely-typed VNode.\n*/\n/** Shared empty props sentinel — identity-checked in mountElement to skip applyProps. */\nconst EMPTY_PROPS = {};\nfunction h(type, props, ...children) {\n\treturn {\n\t\ttype,\n\t\tprops: props ?? EMPTY_PROPS,\n\t\tchildren: normalizeChildren(children),\n\t\tkey: props?.key ?? null\n\t};\n}\nfunction normalizeChildren(children) {\n\tfor (let i = 0; i < children.length; i++) if (Array.isArray(children[i])) return flattenChildren(children);\n\treturn children;\n}\nfunction flattenChildren(children) {\n\tconst result = [];\n\tfor (const child of children) if (Array.isArray(child)) result.push(...flattenChildren(child));\n\telse result.push(child);\n\treturn result;\n}\n\n//#endregion\n//#region src/jsx-runtime.ts\n/**\n* JSX automatic runtime.\n*\n* When tsconfig has `\"jsxImportSource\": \"@pyreon/core\"`, the TS/bundler compiler\n* rewrites JSX to imports from this file automatically:\n* <div class=\"x\" /> → jsx(\"div\", { class: \"x\" })\n*/\nfunction jsx(type, props, key) {\n\tconst { children, ...rest } = props;\n\tconst propsWithKey = key != null ? {\n\t\t...rest,\n\t\tkey\n\t} : rest;\n\tif (typeof type === \"function\") return h(type, children !== void 0 ? {\n\t\t...propsWithKey,\n\t\tchildren\n\t} : propsWithKey);\n\treturn h(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);\n}\nconst jsxs = jsx;\n\n//#endregion\nexport { Fragment, jsx, jsxs };\n//# sourceMappingURL=jsx-runtime.js.map","import { createRef } from '@pyreon/core'\nimport { useRouter } from '@pyreon/router'\nimport { useIntersectionObserver } from './utils/use-intersection-observer'\n\n// ─── Link component with prefetching ────────────────────────────────────────\n//\n// Provides client-side navigation, prefetching, and active state tracking.\n// Three levels of API:\n//\n// 1. useLink(props) — composable returning handlers, state, and ref callback\n// 2. createLink(Comp) — HOC wrapping any component with link behavior\n// 3. Link — default <a>-based link (built on createLink)\n\nexport interface LinkProps {\n /** Target URL path. */\n href: string\n /** Link content. */\n children?: any\n /** CSS class name. */\n class?: string\n /** Class applied when this link matches the current route. */\n activeClass?: string\n /** Class applied when this link exactly matches the current route. */\n exactActiveClass?: string\n /** Prefetch strategy. Default: \"hover\" */\n prefetch?: 'hover' | 'viewport' | 'none'\n /** Open in new tab. */\n external?: boolean\n /** Inline styles. */\n style?: string\n /** ARIA label. */\n 'aria-label'?: string\n}\n\n/** Props passed to a custom component via createLink. */\nexport interface LinkRenderProps {\n href: string\n ref: import('@pyreon/core').Ref<HTMLElement>\n onClick: (e: MouseEvent) => void\n onMouseEnter: () => void\n onTouchStart: () => void\n isActive: () => boolean\n isExactActive: () => boolean\n /** Reactive class string — pass directly to element for auto-updates on route change. */\n class: (() => string) | string | undefined\n style?: string\n target?: string\n rel?: string\n 'aria-label'?: string\n children?: any\n}\n\n/** Return type of useLink. */\nexport interface UseLinkReturn {\n /** Ref object — attach to the root element for viewport-based prefetch. */\n ref: import('@pyreon/core').Ref<HTMLElement>\n /** Click handler — performs client-side navigation. */\n handleClick: (e: MouseEvent) => void\n /** Mouse enter handler — triggers hover prefetch. */\n handleMouseEnter: () => void\n /** Touch start handler — triggers prefetch on mobile. */\n handleTouchStart: () => void\n /** Whether the link partially matches the current route. */\n isActive: () => boolean\n /** Whether the link exactly matches the current route. */\n isExactActive: () => boolean\n /** Resolved class string including active classes. */\n classes: () => string\n}\n\nconst prefetched = new Set<string>()\n\nfunction doPrefetch(href: string) {\n if (prefetched.has(href)) return\n prefetched.add(href)\n\n const docLink = document.createElement('link')\n docLink.rel = 'prefetch'\n docLink.href = href\n docLink.as = 'document'\n document.head.appendChild(docLink)\n\n try {\n const chunkHint = document.createElement('link')\n chunkHint.rel = 'modulepreload'\n chunkHint.href = href\n document.head.appendChild(chunkHint)\n } catch {\n // modulepreload is a hint, not critical\n }\n}\n\n/**\n * Composable that provides all link behavior — navigation, prefetching,\n * active state, and viewport observation.\n *\n * Use this for full control when `createLink` is too opinionated.\n *\n * @example\n * function MyLink(props: LinkProps) {\n * const link = useLink(props)\n * return (\n * <button ref={link.ref} class={link.classes()} onclick={link.handleClick}>\n * {props.children}\n * </button>\n * )\n * }\n */\nexport function useLink(props: LinkProps): UseLinkReturn {\n const router = useRouter()\n const elementRef = createRef<HTMLElement>()\n const strategy = props.prefetch ?? 'hover'\n\n function handleClick(e: MouseEvent) {\n if (\n e.defaultPrevented ||\n e.button !== 0 ||\n e.metaKey ||\n e.ctrlKey ||\n e.shiftKey ||\n e.altKey ||\n props.external\n ) {\n return\n }\n e.preventDefault()\n router.push(props.href)\n }\n\n function handleMouseEnter() {\n if (strategy === 'hover') {\n doPrefetch(props.href)\n }\n }\n\n function handleTouchStart() {\n if (strategy === 'hover' || strategy === 'viewport') {\n doPrefetch(props.href)\n }\n }\n\n if (strategy === 'viewport') {\n useIntersectionObserver(\n () => elementRef.current ?? undefined,\n () => doPrefetch(props.href),\n )\n }\n\n const isActive = () => {\n const currentPath = router.currentRoute()?.path\n if (!currentPath || !props.href) return false\n if (props.href === '/') return currentPath === '/'\n return currentPath.startsWith(props.href)\n }\n\n const isExactActive = () => {\n const currentPath = router.currentRoute()?.path\n if (!currentPath) return false\n return currentPath === props.href\n }\n\n const classes = () => {\n const cls: string[] = []\n if (props.class) cls.push(props.class)\n if (props.activeClass && isActive()) cls.push(props.activeClass)\n if (props.exactActiveClass && isExactActive())\n cls.push(props.exactActiveClass)\n return cls.join(' ')\n }\n\n return {\n ref: elementRef,\n handleClick,\n handleMouseEnter,\n handleTouchStart,\n isActive,\n isExactActive,\n classes,\n }\n}\n\n/**\n * Higher-order component that wraps any component with link behavior.\n *\n * The wrapped component receives {@link LinkRenderProps} with all handlers,\n * active state, and accessibility attributes pre-wired.\n *\n * @example\n * // Custom button link\n * const ButtonLink = createLink((props) => (\n * <button\n * ref={props.ref}\n * class={props.class}\n * onclick={props.onClick}\n * onmouseenter={props.onMouseEnter}\n * >\n * {props.children}\n * </button>\n * ))\n *\n * // Custom styled component\n * const CardLink = createLink((props) => (\n * <div\n * ref={props.ref}\n * class={`card ${props.isActive() ? \"card--active\" : \"\"}`}\n * onclick={props.onClick}\n * onmouseenter={props.onMouseEnter}\n * >\n * {props.children}\n * </div>\n * ))\n *\n * // Usage\n * <ButtonLink href=\"/about\">About</ButtonLink>\n * <CardLink href=\"/posts\" prefetch=\"viewport\">Posts</CardLink>\n */\nexport function createLink(\n Component: (props: LinkRenderProps) => any,\n): (props: LinkProps) => any {\n return function WrappedLink(props: LinkProps) {\n const link = useLink(props)\n\n return (\n <Component\n href={props.href}\n ref={link.ref}\n onClick={link.handleClick}\n onMouseEnter={link.handleMouseEnter}\n onTouchStart={link.handleTouchStart}\n isActive={link.isActive}\n isExactActive={link.isExactActive}\n class={link.classes}\n {...(props.style ? { style: props.style } : {})}\n {...(props.external ? { target: '_blank', rel: 'noopener noreferrer' } : {})}\n {...(props['aria-label'] ? { 'aria-label': props['aria-label'] } : {})}\n children={props.children}\n />\n )\n }\n}\n\n/**\n * Default navigation link built on an `<a>` tag.\n *\n * @example\n * <Link href=\"/about\" prefetch=\"viewport\">About</Link>\n * <Link href=\"/posts\" activeClass=\"nav-active\">Posts</Link>\n */\nexport const Link = createLink((props: LinkRenderProps) => (\n <a\n ref={props.ref as any}\n href={props.href}\n {...(props.class ? { class: props.class } : {})}\n {...(props.style ? { style: props.style } : {})}\n {...(props.target ? { target: props.target } : {})}\n {...(props.rel ? { rel: props.rel } : {})}\n {...(props['aria-label'] ? { 'aria-label': props['aria-label'] } : {})}\n {...(props.isExactActive() ? { 'aria-current': 'page' as const } : {})}\n onclick={props.onClick}\n onmouseenter={props.onMouseEnter}\n ontouchstart={props.onTouchStart}\n >\n {props.children}\n </a>\n))\n"],"x_google_ignoreList":[1],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"link.js","names":[],"sources":["../src/utils/use-intersection-observer.ts","../../../node_modules/.bun/@pyreon+core@0.7.12/node_modules/@pyreon/core/lib/jsx-runtime.js","../src/link.tsx"],"sourcesContent":["import { onMount, onUnmount } from '@pyreon/core'\n\n/**\n * Observes an element and calls `onIntersect` once it enters the viewport.\n * Automatically disconnects after the first intersection.\n *\n * @param getElement - Getter for the target element (may be undefined before mount).\n * @param onIntersect - Callback fired when the element becomes visible.\n * @param rootMargin - IntersectionObserver rootMargin. Default: \"200px\".\n */\nexport function useIntersectionObserver(\n getElement: () => HTMLElement | undefined,\n onIntersect: () => void,\n rootMargin = '200px',\n) {\n onMount(() => {\n const el = getElement()\n if (!el) return undefined\n\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n onIntersect()\n observer.disconnect()\n }\n }\n },\n { rootMargin },\n )\n\n observer.observe(el)\n onUnmount(() => observer.disconnect())\n return undefined\n })\n}\n","//#region src/h.ts\n/** Marker for fragment nodes — renders children without a wrapper element */\nconst Fragment = Symbol(\"Pyreon.Fragment\");\n/**\n* Hyperscript function — the compiled output of JSX.\n* `<div class=\"x\">hello</div>` → `h(\"div\", { class: \"x\" }, \"hello\")`\n*\n* Generic on P so TypeScript validates props match the component's signature\n* at the call site, then stores the result in the loosely-typed VNode.\n*/\n/** Shared empty props sentinel — identity-checked in mountElement to skip applyProps. */\nconst EMPTY_PROPS = {};\nfunction h(type, props, ...children) {\n\treturn {\n\t\ttype,\n\t\tprops: props ?? EMPTY_PROPS,\n\t\tchildren: normalizeChildren(children),\n\t\tkey: props?.key ?? null\n\t};\n}\nfunction normalizeChildren(children) {\n\tfor (let i = 0; i < children.length; i++) if (Array.isArray(children[i])) return flattenChildren(children);\n\treturn children;\n}\nfunction flattenChildren(children) {\n\tconst result = [];\n\tfor (const child of children) if (Array.isArray(child)) result.push(...flattenChildren(child));\n\telse result.push(child);\n\treturn result;\n}\n\n//#endregion\n//#region src/jsx-runtime.ts\n/**\n* JSX automatic runtime.\n*\n* When tsconfig has `\"jsxImportSource\": \"@pyreon/core\"`, the TS/bundler compiler\n* rewrites JSX to imports from this file automatically:\n* <div class=\"x\" /> → jsx(\"div\", { class: \"x\" })\n*/\nfunction jsx(type, props, key) {\n\tconst { children, ...rest } = props;\n\tconst propsWithKey = key != null ? {\n\t\t...rest,\n\t\tkey\n\t} : rest;\n\tif (typeof type === \"function\") return h(type, children !== void 0 ? {\n\t\t...propsWithKey,\n\t\tchildren\n\t} : propsWithKey);\n\treturn h(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);\n}\nconst jsxs = jsx;\n\n//#endregion\nexport { Fragment, jsx, jsxs };\n//# sourceMappingURL=jsx-runtime.js.map","import { createRef } from '@pyreon/core'\nimport { useRouter } from '@pyreon/router'\nimport { useIntersectionObserver } from './utils/use-intersection-observer'\n\n// ─── Link component with prefetching ────────────────────────────────────────\n//\n// Provides client-side navigation, prefetching, and active state tracking.\n// Three levels of API:\n//\n// 1. useLink(props) — composable returning handlers, state, and ref callback\n// 2. createLink(Comp) — HOC wrapping any component with link behavior\n// 3. Link — default <a>-based link (built on createLink)\n\nexport interface LinkProps {\n /** Target URL path. */\n href: string\n /** Link content. */\n children?: any\n /** CSS class name. */\n class?: string\n /** Class applied when this link matches the current route. */\n activeClass?: string\n /** Class applied when this link exactly matches the current route. */\n exactActiveClass?: string\n /** Prefetch strategy. Default: \"hover\" */\n prefetch?: 'hover' | 'viewport' | 'none'\n /** Open in new tab. */\n external?: boolean\n /** Inline styles. */\n style?: string\n /** ARIA label. */\n 'aria-label'?: string\n}\n\n/** Props passed to a custom component via createLink. */\nexport interface LinkRenderProps {\n href: string\n ref: import('@pyreon/core').Ref<HTMLElement>\n onClick: (e: MouseEvent) => void\n onMouseEnter: () => void\n onTouchStart: () => void\n isActive: () => boolean\n isExactActive: () => boolean\n /** Reactive class string — pass directly to element for auto-updates on route change. */\n class: (() => string) | string | undefined\n style?: string\n target?: string\n rel?: string\n 'aria-label'?: string\n children?: any\n}\n\n/** Return type of useLink. */\nexport interface UseLinkReturn {\n /** Ref object — attach to the root element for viewport-based prefetch. */\n ref: import('@pyreon/core').Ref<HTMLElement>\n /** Click handler — performs client-side navigation. */\n handleClick: (e: MouseEvent) => void\n /** Mouse enter handler — triggers hover prefetch. */\n handleMouseEnter: () => void\n /** Touch start handler — triggers prefetch on mobile. */\n handleTouchStart: () => void\n /** Whether the link partially matches the current route. */\n isActive: () => boolean\n /** Whether the link exactly matches the current route. */\n isExactActive: () => boolean\n /** Resolved class string including active classes. */\n classes: () => string\n}\n\nconst prefetched = new Set<string>()\n\nfunction doPrefetch(href: string) {\n if (prefetched.has(href)) return\n prefetched.add(href)\n\n const docLink = document.createElement('link')\n docLink.rel = 'prefetch'\n docLink.href = href\n docLink.as = 'document'\n document.head.appendChild(docLink)\n\n try {\n const chunkHint = document.createElement('link')\n chunkHint.rel = 'modulepreload'\n chunkHint.href = href\n document.head.appendChild(chunkHint)\n } catch {\n // modulepreload is a hint, not critical\n }\n}\n\n/**\n * Composable that provides all link behavior — navigation, prefetching,\n * active state, and viewport observation.\n *\n * Use this for full control when `createLink` is too opinionated.\n *\n * @example\n * function MyLink(props: LinkProps) {\n * const link = useLink(props)\n * return (\n * <button ref={link.ref} class={link.classes()} onClick={link.handleClick}>\n * {props.children}\n * </button>\n * )\n * }\n */\nexport function useLink(props: LinkProps): UseLinkReturn {\n const router = useRouter()\n const elementRef = createRef<HTMLElement>()\n const strategy = props.prefetch ?? 'hover'\n\n function handleClick(e: MouseEvent) {\n if (\n e.defaultPrevented ||\n e.button !== 0 ||\n e.metaKey ||\n e.ctrlKey ||\n e.shiftKey ||\n e.altKey ||\n props.external\n ) {\n return\n }\n e.preventDefault()\n router.push(props.href)\n }\n\n function handleMouseEnter() {\n if (strategy === 'hover') {\n doPrefetch(props.href)\n }\n }\n\n function handleTouchStart() {\n if (strategy === 'hover' || strategy === 'viewport') {\n doPrefetch(props.href)\n }\n }\n\n if (strategy === 'viewport') {\n useIntersectionObserver(\n () => elementRef.current ?? undefined,\n () => doPrefetch(props.href),\n )\n }\n\n const isActive = () => {\n const currentPath = router.currentRoute()?.path\n if (!currentPath || !props.href) return false\n if (props.href === '/') return currentPath === '/'\n return currentPath.startsWith(props.href)\n }\n\n const isExactActive = () => {\n const currentPath = router.currentRoute()?.path\n if (!currentPath) return false\n return currentPath === props.href\n }\n\n const classes = () => {\n const cls: string[] = []\n if (props.class) cls.push(props.class)\n if (props.activeClass && isActive()) cls.push(props.activeClass)\n if (props.exactActiveClass && isExactActive())\n cls.push(props.exactActiveClass)\n return cls.join(' ')\n }\n\n return {\n ref: elementRef,\n handleClick,\n handleMouseEnter,\n handleTouchStart,\n isActive,\n isExactActive,\n classes,\n }\n}\n\n/**\n * Higher-order component that wraps any component with link behavior.\n *\n * The wrapped component receives {@link LinkRenderProps} with all handlers,\n * active state, and accessibility attributes pre-wired.\n *\n * @example\n * // Custom button link\n * const ButtonLink = createLink((props) => (\n * <button\n * ref={props.ref}\n * class={props.class}\n * onClick={props.onClick}\n * onMouseEnter={props.onMouseEnter}\n * >\n * {props.children}\n * </button>\n * ))\n *\n * // Custom styled component\n * const CardLink = createLink((props) => (\n * <div\n * ref={props.ref}\n * class={`card ${props.isActive() ? \"card--active\" : \"\"}`}\n * onClick={props.onClick}\n * onMouseEnter={props.onMouseEnter}\n * >\n * {props.children}\n * </div>\n * ))\n *\n * // Usage\n * <ButtonLink href=\"/about\">About</ButtonLink>\n * <CardLink href=\"/posts\" prefetch=\"viewport\">Posts</CardLink>\n */\nexport function createLink(\n Component: (props: LinkRenderProps) => any,\n): (props: LinkProps) => any {\n return function WrappedLink(props: LinkProps) {\n const link = useLink(props)\n\n return (\n <Component\n href={props.href}\n ref={link.ref}\n onClick={link.handleClick}\n onMouseEnter={link.handleMouseEnter}\n onTouchStart={link.handleTouchStart}\n isActive={link.isActive}\n isExactActive={link.isExactActive}\n class={link.classes}\n {...(props.style ? { style: props.style } : {})}\n {...(props.external ? { target: '_blank', rel: 'noopener noreferrer' } : {})}\n {...(props['aria-label'] ? { 'aria-label': props['aria-label'] } : {})}\n children={props.children}\n />\n )\n }\n}\n\n/**\n * Default navigation link built on an `<a>` tag.\n *\n * @example\n * <Link href=\"/about\" prefetch=\"viewport\">About</Link>\n * <Link href=\"/posts\" activeClass=\"nav-active\">Posts</Link>\n */\nexport const Link = createLink((props: LinkRenderProps) => (\n <a\n ref={props.ref as any}\n href={props.href}\n {...(props.class ? { class: props.class } : {})}\n {...(props.style ? { style: props.style } : {})}\n {...(props.target ? { target: props.target } : {})}\n {...(props.rel ? { rel: props.rel } : {})}\n {...(props['aria-label'] ? { 'aria-label': props['aria-label'] } : {})}\n {...(props.isExactActive() ? { 'aria-current': 'page' as const } : {})}\n onClick={props.onClick}\n onMouseEnter={props.onMouseEnter}\n onTouchStart={props.onTouchStart}\n >\n {props.children}\n </a>\n))\n"],"x_google_ignoreList":[1],"mappings":"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"}
|
package/lib/script.js
CHANGED
|
@@ -25,7 +25,7 @@ function useIntersectionObserver(getElement, onIntersect, rootMargin = "200px")
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
//#endregion
|
|
28
|
-
//#region ../../node_modules/.bun/@pyreon+core@0.7.
|
|
28
|
+
//#region ../../node_modules/.bun/@pyreon+core@0.7.12/node_modules/@pyreon/core/lib/jsx-runtime.js
|
|
29
29
|
/**
|
|
30
30
|
* Hyperscript function — the compiled output of JSX.
|
|
31
31
|
* `<div class="x">hello</div>` → `h("div", { class: "x" }, "hello")`
|
package/lib/script.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"script.js","names":[],"sources":["../src/utils/use-intersection-observer.ts","../../../node_modules/.bun/@pyreon+core@0.7.
|
|
1
|
+
{"version":3,"file":"script.js","names":[],"sources":["../src/utils/use-intersection-observer.ts","../../../node_modules/.bun/@pyreon+core@0.7.12/node_modules/@pyreon/core/lib/jsx-runtime.js","../src/script.tsx"],"sourcesContent":["import { onMount, onUnmount } from '@pyreon/core'\n\n/**\n * Observes an element and calls `onIntersect` once it enters the viewport.\n * Automatically disconnects after the first intersection.\n *\n * @param getElement - Getter for the target element (may be undefined before mount).\n * @param onIntersect - Callback fired when the element becomes visible.\n * @param rootMargin - IntersectionObserver rootMargin. Default: \"200px\".\n */\nexport function useIntersectionObserver(\n getElement: () => HTMLElement | undefined,\n onIntersect: () => void,\n rootMargin = '200px',\n) {\n onMount(() => {\n const el = getElement()\n if (!el) return undefined\n\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n onIntersect()\n observer.disconnect()\n }\n }\n },\n { rootMargin },\n )\n\n observer.observe(el)\n onUnmount(() => observer.disconnect())\n return undefined\n })\n}\n","//#region src/h.ts\n/** Marker for fragment nodes — renders children without a wrapper element */\nconst Fragment = Symbol(\"Pyreon.Fragment\");\n/**\n* Hyperscript function — the compiled output of JSX.\n* `<div class=\"x\">hello</div>` → `h(\"div\", { class: \"x\" }, \"hello\")`\n*\n* Generic on P so TypeScript validates props match the component's signature\n* at the call site, then stores the result in the loosely-typed VNode.\n*/\n/** Shared empty props sentinel — identity-checked in mountElement to skip applyProps. */\nconst EMPTY_PROPS = {};\nfunction h(type, props, ...children) {\n\treturn {\n\t\ttype,\n\t\tprops: props ?? EMPTY_PROPS,\n\t\tchildren: normalizeChildren(children),\n\t\tkey: props?.key ?? null\n\t};\n}\nfunction normalizeChildren(children) {\n\tfor (let i = 0; i < children.length; i++) if (Array.isArray(children[i])) return flattenChildren(children);\n\treturn children;\n}\nfunction flattenChildren(children) {\n\tconst result = [];\n\tfor (const child of children) if (Array.isArray(child)) result.push(...flattenChildren(child));\n\telse result.push(child);\n\treturn result;\n}\n\n//#endregion\n//#region src/jsx-runtime.ts\n/**\n* JSX automatic runtime.\n*\n* When tsconfig has `\"jsxImportSource\": \"@pyreon/core\"`, the TS/bundler compiler\n* rewrites JSX to imports from this file automatically:\n* <div class=\"x\" /> → jsx(\"div\", { class: \"x\" })\n*/\nfunction jsx(type, props, key) {\n\tconst { children, ...rest } = props;\n\tconst propsWithKey = key != null ? {\n\t\t...rest,\n\t\tkey\n\t} : rest;\n\tif (typeof type === \"function\") return h(type, children !== void 0 ? {\n\t\t...propsWithKey,\n\t\tchildren\n\t} : propsWithKey);\n\treturn h(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);\n}\nconst jsxs = jsx;\n\n//#endregion\nexport { Fragment, jsx, jsxs };\n//# sourceMappingURL=jsx-runtime.js.map","import type { VNodeChild } from '@pyreon/core'\nimport { createRef, onMount, onUnmount } from '@pyreon/core'\nimport { useIntersectionObserver } from './utils/use-intersection-observer'\n\n// ─── Script optimization component ─────────────────────────────────────────\n//\n// <Script> provides optimized third-party script loading:\n// - Defer loading until after hydration\n// - Load on idle (requestIdleCallback)\n// - Load on interaction (click, scroll, etc.)\n// - Load on viewport entry\n// - Worker offloading for analytics scripts\n\nexport interface ScriptProps {\n /** Script source URL. */\n src: string\n /** Loading strategy. Default: \"afterHydration\" */\n strategy?: ScriptStrategy\n /** Inline script content (alternative to src). */\n children?: string\n /** Script id for deduplication. */\n id?: string\n /** Async attribute. Default: true */\n async?: boolean\n /** onLoad callback. */\n onLoad?: () => void\n /** onError callback. */\n onError?: (error: Error) => void\n}\n\nexport type ScriptStrategy =\n | 'beforeHydration'\n | 'afterHydration'\n | 'onIdle'\n | 'onInteraction'\n | 'onViewport'\n\n/**\n * Optimized script loading component.\n *\n * @example\n * // Load analytics after page is interactive\n * <Script src=\"https://analytics.example.com/script.js\" strategy=\"onIdle\" />\n *\n * // Load chat widget when user scrolls\n * <Script src=\"/chat-widget.js\" strategy=\"onViewport\" />\n *\n * // Inline script with deferred execution\n * <Script strategy=\"afterHydration\">\n * {`console.log(\"App hydrated!\")`}\n * </Script>\n */\nexport function Script(props: ScriptProps): VNodeChild {\n function loadScript() {\n // Deduplication\n if (props.id && document.getElementById(props.id)) return\n\n const script = document.createElement('script')\n if (props.src) script.src = props.src\n if (props.id) script.id = props.id\n script.async = props.async !== false\n\n if (props.onLoad) script.onload = props.onLoad\n if (props.onError) {\n script.onerror = () =>\n props.onError?.(new Error(`Failed to load: ${props.src}`))\n }\n\n if (props.children && !props.src) {\n script.textContent = props.children\n }\n\n document.head.appendChild(script)\n }\n\n onMount(() => {\n const strategy = props.strategy ?? 'afterHydration'\n\n switch (strategy) {\n case 'beforeHydration':\n // Already in HTML — do nothing\n break\n\n case 'afterHydration':\n // Load immediately after mount (hydration is complete)\n loadScript()\n break\n\n case 'onIdle':\n if ('requestIdleCallback' in window) {\n requestIdleCallback(() => loadScript(), { timeout: 5000 })\n } else {\n setTimeout(loadScript, 200)\n }\n break\n\n case 'onInteraction': {\n const events = ['click', 'scroll', 'keydown', 'touchstart']\n function handler() {\n for (const e of events) document.removeEventListener(e, handler)\n loadScript()\n }\n for (const e of events) {\n document.addEventListener(e, handler, { once: true, passive: true })\n }\n onUnmount(() => {\n for (const e of events) document.removeEventListener(e, handler)\n })\n break\n }\n\n case 'onViewport':\n // Handled below via useIntersectionObserver on the sentinel element\n break\n }\n return undefined\n })\n\n const sentinelRef = createRef<HTMLElement>()\n const strategy = props.strategy ?? 'afterHydration'\n\n if (strategy === 'onViewport') {\n useIntersectionObserver(\n () => sentinelRef.current ?? undefined,\n () => loadScript(),\n )\n }\n\n if (strategy === 'onViewport') {\n return <div ref={sentinelRef} style=\"width:0;height:0;overflow:hidden\" />\n }\n\n return null\n}\n"],"x_google_ignoreList":[1],"mappings":"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"}
|
package/lib/theme.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { onMount, onUnmount } from "@pyreon/core";
|
|
2
2
|
import { effect, signal } from "@pyreon/reactivity";
|
|
3
3
|
|
|
4
|
-
//#region ../../node_modules/.bun/@pyreon+core@0.7.
|
|
4
|
+
//#region ../../node_modules/.bun/@pyreon+core@0.7.12/node_modules/@pyreon/core/lib/jsx-runtime.js
|
|
5
5
|
/**
|
|
6
6
|
* Hyperscript function — the compiled output of JSX.
|
|
7
7
|
* `<div class="x">hello</div>` → `h("div", { class: "x" }, "hello")`
|
|
@@ -113,7 +113,7 @@ function ThemeToggle(props) {
|
|
|
113
113
|
return /* @__PURE__ */ jsx("button", {
|
|
114
114
|
class: props.class,
|
|
115
115
|
style: props.style,
|
|
116
|
-
|
|
116
|
+
onClick: toggleTheme,
|
|
117
117
|
"aria-label": "Toggle theme",
|
|
118
118
|
title: "Toggle theme",
|
|
119
119
|
type: "button",
|
package/lib/theme.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme.js","names":[],"sources":["../../../node_modules/.bun/@pyreon+core@0.7.
|
|
1
|
+
{"version":3,"file":"theme.js","names":[],"sources":["../../../node_modules/.bun/@pyreon+core@0.7.12/node_modules/@pyreon/core/lib/jsx-runtime.js","../src/theme.tsx"],"sourcesContent":["//#region src/h.ts\n/** Marker for fragment nodes — renders children without a wrapper element */\nconst Fragment = Symbol(\"Pyreon.Fragment\");\n/**\n* Hyperscript function — the compiled output of JSX.\n* `<div class=\"x\">hello</div>` → `h(\"div\", { class: \"x\" }, \"hello\")`\n*\n* Generic on P so TypeScript validates props match the component's signature\n* at the call site, then stores the result in the loosely-typed VNode.\n*/\n/** Shared empty props sentinel — identity-checked in mountElement to skip applyProps. */\nconst EMPTY_PROPS = {};\nfunction h(type, props, ...children) {\n\treturn {\n\t\ttype,\n\t\tprops: props ?? EMPTY_PROPS,\n\t\tchildren: normalizeChildren(children),\n\t\tkey: props?.key ?? null\n\t};\n}\nfunction normalizeChildren(children) {\n\tfor (let i = 0; i < children.length; i++) if (Array.isArray(children[i])) return flattenChildren(children);\n\treturn children;\n}\nfunction flattenChildren(children) {\n\tconst result = [];\n\tfor (const child of children) if (Array.isArray(child)) result.push(...flattenChildren(child));\n\telse result.push(child);\n\treturn result;\n}\n\n//#endregion\n//#region src/jsx-runtime.ts\n/**\n* JSX automatic runtime.\n*\n* When tsconfig has `\"jsxImportSource\": \"@pyreon/core\"`, the TS/bundler compiler\n* rewrites JSX to imports from this file automatically:\n* <div class=\"x\" /> → jsx(\"div\", { class: \"x\" })\n*/\nfunction jsx(type, props, key) {\n\tconst { children, ...rest } = props;\n\tconst propsWithKey = key != null ? {\n\t\t...rest,\n\t\tkey\n\t} : rest;\n\tif (typeof type === \"function\") return h(type, children !== void 0 ? {\n\t\t...propsWithKey,\n\t\tchildren\n\t} : propsWithKey);\n\treturn h(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);\n}\nconst jsxs = jsx;\n\n//#endregion\nexport { Fragment, jsx, jsxs };\n//# sourceMappingURL=jsx-runtime.js.map","import type { VNodeChild } from '@pyreon/core'\nimport { onMount, onUnmount } from '@pyreon/core'\nimport { effect, signal } from '@pyreon/reactivity'\n\n// ─── Theme system ───────────────────────────────────────────────────────────\n//\n// Provides dark/light/system theme support with:\n// - System preference detection via matchMedia\n// - Persistent preference via localStorage\n// - No flash of wrong theme (inline script in HTML)\n// - Reactive theme signal for components\n\nexport type Theme = 'light' | 'dark' | 'system'\n\nconst STORAGE_KEY = 'zero-theme'\n\n/** Reactive theme signal. */\nexport const theme = signal<Theme>('system')\n\n/** Computed resolved theme (what's actually applied). */\nexport function resolvedTheme(): 'light' | 'dark' {\n const t = theme()\n if (t === 'system') {\n if (typeof window === 'undefined') return 'dark'\n return window.matchMedia('(prefers-color-scheme: dark)').matches\n ? 'dark'\n : 'light'\n }\n return t\n}\n\n/** Toggle between light and dark. */\nexport function toggleTheme() {\n const current = resolvedTheme()\n setTheme(current === 'dark' ? 'light' : 'dark')\n}\n\n/** Set theme explicitly. */\nexport function setTheme(t: Theme) {\n theme.set(t)\n if (typeof document !== 'undefined') {\n document.documentElement.dataset.theme = resolvedTheme()\n try {\n localStorage.setItem(STORAGE_KEY, t)\n } catch {\n // localStorage may not be available (SSR, private browsing)\n }\n }\n}\n\n/**\n * Initialize the theme system. Call once in your app entry or layout.\n * Reads from localStorage, listens for system preference changes.\n */\nexport function initTheme() {\n onMount(() => {\n // Read persisted preference\n try {\n const stored = localStorage.getItem(STORAGE_KEY) as Theme | null\n if (stored === 'light' || stored === 'dark' || stored === 'system') {\n theme.set(stored)\n }\n } catch {\n // localStorage may not be available\n }\n\n // Apply to document\n document.documentElement.dataset.theme = resolvedTheme()\n\n // Watch for system preference changes\n const mq = window.matchMedia('(prefers-color-scheme: dark)')\n function onChange() {\n if (theme() === 'system') {\n document.documentElement.dataset.theme = resolvedTheme()\n }\n }\n mq.addEventListener('change', onChange)\n onUnmount(() => mq.removeEventListener('change', onChange))\n\n // Re-apply when theme signal changes\n const dispose = effect(() => {\n document.documentElement.dataset.theme = resolvedTheme()\n })\n if (dispose) onUnmount(() => dispose.dispose())\n\n return undefined\n })\n}\n\n/**\n * Theme toggle button component.\n *\n * @example\n * import { ThemeToggle } from \"@pyreon/zero/theme\"\n * <ThemeToggle />\n */\nexport function ThemeToggle(props: { class?: string; style?: string }): VNodeChild {\n initTheme()\n\n return (\n <button\n class={props.class}\n style={props.style}\n onClick={toggleTheme}\n aria-label=\"Toggle theme\"\n title=\"Toggle theme\"\n type=\"button\"\n >\n {() =>\n resolvedTheme() === 'dark' ? (\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"5\" />\n <line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"3\" />\n <line x1=\"12\" y1=\"21\" x2=\"12\" y2=\"23\" />\n <line x1=\"4.22\" y1=\"4.22\" x2=\"5.64\" y2=\"5.64\" />\n <line x1=\"18.36\" y1=\"18.36\" x2=\"19.78\" y2=\"19.78\" />\n <line x1=\"1\" y1=\"12\" x2=\"3\" y2=\"12\" />\n <line x1=\"21\" y1=\"12\" x2=\"23\" y2=\"12\" />\n <line x1=\"4.22\" y1=\"19.78\" x2=\"5.64\" y2=\"18.36\" />\n <line x1=\"18.36\" y1=\"5.64\" x2=\"19.78\" y2=\"4.22\" />\n </svg>\n ) : (\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\" />\n </svg>\n )\n }\n </button>\n )\n}\n\n/**\n * Inline script to prevent flash of wrong theme.\n * Include this in your index.html <head> BEFORE any stylesheets.\n *\n * @example\n * // index.html\n * <head>\n * <script>{themeScript}</script>\n * ...\n * </head>\n */\nexport const themeScript = `(function(){try{var t=localStorage.getItem(\"${STORAGE_KEY}\");var r=t===\"light\"?\"light\":t===\"dark\"?\"dark\":window.matchMedia(\"(prefers-color-scheme:dark)\").matches?\"dark\":\"light\";document.documentElement.dataset.theme=r}catch(e){}})()`\n"],"x_google_ignoreList":[0],"mappings":"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"}
|
package/lib/types/image.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export interface ImageProps {
|
|
|
17
17
|
formats?: FormatSource[];
|
|
18
18
|
/** Loading strategy. "lazy" uses IntersectionObserver, "eager" loads immediately. Default: "lazy" */
|
|
19
19
|
loading?: 'lazy' | 'eager';
|
|
20
|
-
/** Mark as priority (LCP image). Disables lazy loading, adds
|
|
20
|
+
/** Mark as priority (LCP image). Disables lazy loading, adds fetchPriority="high". */
|
|
21
21
|
priority?: boolean;
|
|
22
22
|
/** Low-quality placeholder image URL or base64 data URI for blur-up effect. */
|
|
23
23
|
placeholder?: string;
|
package/lib/types/link.d.ts
CHANGED
|
@@ -62,7 +62,7 @@ export interface UseLinkReturn {
|
|
|
62
62
|
* function MyLink(props: LinkProps) {
|
|
63
63
|
* const link = useLink(props)
|
|
64
64
|
* return (
|
|
65
|
-
* <button ref={link.ref} class={link.classes()}
|
|
65
|
+
* <button ref={link.ref} class={link.classes()} onClick={link.handleClick}>
|
|
66
66
|
* {props.children}
|
|
67
67
|
* </button>
|
|
68
68
|
* )
|
|
@@ -81,8 +81,8 @@ export declare function useLink(props: LinkProps): UseLinkReturn;
|
|
|
81
81
|
* <button
|
|
82
82
|
* ref={props.ref}
|
|
83
83
|
* class={props.class}
|
|
84
|
-
*
|
|
85
|
-
*
|
|
84
|
+
* onClick={props.onClick}
|
|
85
|
+
* onMouseEnter={props.onMouseEnter}
|
|
86
86
|
* >
|
|
87
87
|
* {props.children}
|
|
88
88
|
* </button>
|
|
@@ -93,8 +93,8 @@ export declare function useLink(props: LinkProps): UseLinkReturn;
|
|
|
93
93
|
* <div
|
|
94
94
|
* ref={props.ref}
|
|
95
95
|
* class={`card ${props.isActive() ? "card--active" : ""}`}
|
|
96
|
-
*
|
|
97
|
-
*
|
|
96
|
+
* onClick={props.onClick}
|
|
97
|
+
* onMouseEnter={props.onMouseEnter}
|
|
98
98
|
* >
|
|
99
99
|
* {props.children}
|
|
100
100
|
* </div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/zero",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Pyreon Zero — zero-config full-stack framework powered by Pyreon and Vite",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Vit Bokisch",
|
|
@@ -115,17 +115,17 @@
|
|
|
115
115
|
"typecheck": "tsc --noEmit"
|
|
116
116
|
},
|
|
117
117
|
"dependencies": {
|
|
118
|
-
"@pyreon/core": "^0.7.
|
|
119
|
-
"@pyreon/head": "^0.7.
|
|
120
|
-
"@pyreon/meta": "^0.
|
|
121
|
-
"@pyreon/router": "^0.7.
|
|
122
|
-
"@pyreon/runtime-dom": "^0.7.
|
|
123
|
-
"@pyreon/runtime-server": "^0.7.
|
|
124
|
-
"@pyreon/server": "^0.7.
|
|
125
|
-
"@pyreon/vite-plugin": "^0.7.
|
|
118
|
+
"@pyreon/core": "^0.7.11",
|
|
119
|
+
"@pyreon/head": "^0.7.11",
|
|
120
|
+
"@pyreon/meta": "^0.5.0",
|
|
121
|
+
"@pyreon/router": "^0.7.11",
|
|
122
|
+
"@pyreon/runtime-dom": "^0.7.11",
|
|
123
|
+
"@pyreon/runtime-server": "^0.7.11",
|
|
124
|
+
"@pyreon/server": "^0.7.11",
|
|
125
|
+
"@pyreon/vite-plugin": "^0.7.11",
|
|
126
126
|
"vite": "^8.0.0"
|
|
127
127
|
},
|
|
128
128
|
"peerDependencies": {
|
|
129
|
-
"@pyreon/reactivity": "^0.7.
|
|
129
|
+
"@pyreon/reactivity": "^0.7.11"
|
|
130
130
|
}
|
|
131
131
|
}
|
package/src/image.tsx
CHANGED
|
@@ -31,7 +31,7 @@ export interface ImageProps {
|
|
|
31
31
|
formats?: FormatSource[]
|
|
32
32
|
/** Loading strategy. "lazy" uses IntersectionObserver, "eager" loads immediately. Default: "lazy" */
|
|
33
33
|
loading?: 'lazy' | 'eager'
|
|
34
|
-
/** Mark as priority (LCP image). Disables lazy loading, adds
|
|
34
|
+
/** Mark as priority (LCP image). Disables lazy loading, adds fetchPriority="high". */
|
|
35
35
|
priority?: boolean
|
|
36
36
|
/** Low-quality placeholder image URL or base64 data URI for blur-up effect. */
|
|
37
37
|
placeholder?: string
|
|
@@ -102,7 +102,7 @@ export function Image(props: ImageProps): VNodeChild {
|
|
|
102
102
|
const imgEl = (
|
|
103
103
|
<img
|
|
104
104
|
src={() => (inView() ? props.src : '')}
|
|
105
|
-
|
|
105
|
+
srcSet={() =>
|
|
106
106
|
!hasFormats && inView() && resolvedSrcset ? resolvedSrcset : ''
|
|
107
107
|
}
|
|
108
108
|
sizes={resolvedSrcset ? sizes : undefined}
|
|
@@ -111,8 +111,8 @@ export function Image(props: ImageProps): VNodeChild {
|
|
|
111
111
|
height={props.height}
|
|
112
112
|
loading={isEager ? 'eager' : 'lazy'}
|
|
113
113
|
decoding={props.decoding ?? 'async'}
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
fetchPriority={props.priority ? 'high' : undefined}
|
|
115
|
+
onLoad={() => loaded.set(true)}
|
|
116
116
|
style={() =>
|
|
117
117
|
[
|
|
118
118
|
'display: block',
|
|
@@ -154,7 +154,7 @@ export function Image(props: ImageProps): VNodeChild {
|
|
|
154
154
|
{props.formats?.map((fmt) => (
|
|
155
155
|
<source
|
|
156
156
|
type={fmt.type}
|
|
157
|
-
|
|
157
|
+
srcSet={() => inView() ? fmt.srcset ?? '' : ''}
|
|
158
158
|
sizes={sizes}
|
|
159
159
|
/>
|
|
160
160
|
))}
|
package/src/link.tsx
CHANGED
|
@@ -100,7 +100,7 @@ function doPrefetch(href: string) {
|
|
|
100
100
|
* function MyLink(props: LinkProps) {
|
|
101
101
|
* const link = useLink(props)
|
|
102
102
|
* return (
|
|
103
|
-
* <button ref={link.ref} class={link.classes()}
|
|
103
|
+
* <button ref={link.ref} class={link.classes()} onClick={link.handleClick}>
|
|
104
104
|
* {props.children}
|
|
105
105
|
* </button>
|
|
106
106
|
* )
|
|
@@ -191,8 +191,8 @@ export function useLink(props: LinkProps): UseLinkReturn {
|
|
|
191
191
|
* <button
|
|
192
192
|
* ref={props.ref}
|
|
193
193
|
* class={props.class}
|
|
194
|
-
*
|
|
195
|
-
*
|
|
194
|
+
* onClick={props.onClick}
|
|
195
|
+
* onMouseEnter={props.onMouseEnter}
|
|
196
196
|
* >
|
|
197
197
|
* {props.children}
|
|
198
198
|
* </button>
|
|
@@ -203,8 +203,8 @@ export function useLink(props: LinkProps): UseLinkReturn {
|
|
|
203
203
|
* <div
|
|
204
204
|
* ref={props.ref}
|
|
205
205
|
* class={`card ${props.isActive() ? "card--active" : ""}`}
|
|
206
|
-
*
|
|
207
|
-
*
|
|
206
|
+
* onClick={props.onClick}
|
|
207
|
+
* onMouseEnter={props.onMouseEnter}
|
|
208
208
|
* >
|
|
209
209
|
* {props.children}
|
|
210
210
|
* </div>
|
|
@@ -256,9 +256,9 @@ export const Link = createLink((props: LinkRenderProps) => (
|
|
|
256
256
|
{...(props.rel ? { rel: props.rel } : {})}
|
|
257
257
|
{...(props['aria-label'] ? { 'aria-label': props['aria-label'] } : {})}
|
|
258
258
|
{...(props.isExactActive() ? { 'aria-current': 'page' as const } : {})}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
259
|
+
onClick={props.onClick}
|
|
260
|
+
onMouseEnter={props.onMouseEnter}
|
|
261
|
+
onTouchStart={props.onTouchStart}
|
|
262
262
|
>
|
|
263
263
|
{props.children}
|
|
264
264
|
</a>
|
package/src/theme.tsx
CHANGED
|
@@ -101,7 +101,7 @@ export function ThemeToggle(props: { class?: string; style?: string }): VNodeChi
|
|
|
101
101
|
<button
|
|
102
102
|
class={props.class}
|
|
103
103
|
style={props.style}
|
|
104
|
-
|
|
104
|
+
onClick={toggleTheme}
|
|
105
105
|
aria-label="Toggle theme"
|
|
106
106
|
title="Toggle theme"
|
|
107
107
|
type="button"
|