@ai-matrx/kit 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/CHANGELOG.md +132 -0
- package/LICENSE +21 -0
- package/README.md +72 -0
- package/dist/autosave.cjs +92 -0
- package/dist/autosave.cjs.map +1 -0
- package/dist/autosave.d.cts +17 -0
- package/dist/autosave.d.ts +17 -0
- package/dist/autosave.js +72 -0
- package/dist/autosave.js.map +1 -0
- package/dist/clipboard.cjs +152 -0
- package/dist/clipboard.cjs.map +1 -0
- package/dist/clipboard.d.cts +17 -0
- package/dist/clipboard.d.ts +17 -0
- package/dist/clipboard.js +132 -0
- package/dist/clipboard.js.map +1 -0
- package/dist/color-util.cjs +790 -0
- package/dist/color-util.cjs.map +1 -0
- package/dist/color-util.d.cts +235 -0
- package/dist/color-util.d.ts +235 -0
- package/dist/color-util.js +767 -0
- package/dist/color-util.js.map +1 -0
- package/dist/concurrency.cjs +59 -0
- package/dist/concurrency.cjs.map +1 -0
- package/dist/concurrency.d.cts +19 -0
- package/dist/concurrency.d.ts +19 -0
- package/dist/concurrency.js +38 -0
- package/dist/concurrency.js.map +1 -0
- package/dist/confirm.cjs +344 -0
- package/dist/confirm.cjs.map +1 -0
- package/dist/confirm.d.cts +132 -0
- package/dist/confirm.d.ts +132 -0
- package/dist/confirm.js +312 -0
- package/dist/confirm.js.map +1 -0
- package/dist/delimiter-guard.cjs +172 -0
- package/dist/delimiter-guard.cjs.map +1 -0
- package/dist/delimiter-guard.d.cts +119 -0
- package/dist/delimiter-guard.d.ts +119 -0
- package/dist/delimiter-guard.js +151 -0
- package/dist/delimiter-guard.js.map +1 -0
- package/dist/drafts.cjs +223 -0
- package/dist/drafts.cjs.map +1 -0
- package/dist/drafts.d.cts +62 -0
- package/dist/drafts.d.ts +62 -0
- package/dist/drafts.js +201 -0
- package/dist/drafts.js.map +1 -0
- package/dist/idb-store.cjs +178 -0
- package/dist/idb-store.cjs.map +1 -0
- package/dist/idb-store.d.cts +111 -0
- package/dist/idb-store.d.ts +111 -0
- package/dist/idb-store.js +155 -0
- package/dist/idb-store.js.map +1 -0
- package/dist/idle-scheduler.cjs +268 -0
- package/dist/idle-scheduler.cjs.map +1 -0
- package/dist/idle-scheduler.d.cts +185 -0
- package/dist/idle-scheduler.d.ts +185 -0
- package/dist/idle-scheduler.js +246 -0
- package/dist/idle-scheduler.js.map +1 -0
- package/dist/index.cjs +3113 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +897 -0
- package/dist/index.d.ts +897 -0
- package/dist/index.js +3088 -0
- package/dist/index.js.map +1 -0
- package/dist/invalidation.cjs +64 -0
- package/dist/invalidation.cjs.map +1 -0
- package/dist/invalidation.d.cts +52 -0
- package/dist/invalidation.d.ts +52 -0
- package/dist/invalidation.js +43 -0
- package/dist/invalidation.js.map +1 -0
- package/dist/json-format.cjs +319 -0
- package/dist/json-format.cjs.map +1 -0
- package/dist/json-format.d.cts +148 -0
- package/dist/json-format.d.ts +148 -0
- package/dist/json-format.js +286 -0
- package/dist/json-format.js.map +1 -0
- package/dist/latest-request.cjs +36 -0
- package/dist/latest-request.cjs.map +1 -0
- package/dist/latest-request.d.cts +29 -0
- package/dist/latest-request.d.ts +29 -0
- package/dist/latest-request.js +16 -0
- package/dist/latest-request.js.map +1 -0
- package/dist/qr.cjs +109 -0
- package/dist/qr.cjs.map +1 -0
- package/dist/qr.d.cts +47 -0
- package/dist/qr.d.ts +47 -0
- package/dist/qr.js +78 -0
- package/dist/qr.js.map +1 -0
- package/dist/react-tree.cjs +69 -0
- package/dist/react-tree.cjs.map +1 -0
- package/dist/react-tree.d.cts +38 -0
- package/dist/react-tree.d.ts +38 -0
- package/dist/react-tree.js +38 -0
- package/dist/react-tree.js.map +1 -0
- package/dist/search-scoring.cjs +116 -0
- package/dist/search-scoring.cjs.map +1 -0
- package/dist/search-scoring.d.cts +77 -0
- package/dist/search-scoring.d.ts +77 -0
- package/dist/search-scoring.js +95 -0
- package/dist/search-scoring.js.map +1 -0
- package/dist/text-case.cjs +313 -0
- package/dist/text-case.cjs.map +1 -0
- package/dist/text-case.d.cts +53 -0
- package/dist/text-case.d.ts +53 -0
- package/dist/text-case.js +292 -0
- package/dist/text-case.js.map +1 -0
- package/dist/toast.cjs +74 -0
- package/dist/toast.cjs.map +1 -0
- package/dist/toast.d.cts +92 -0
- package/dist/toast.d.ts +92 -0
- package/dist/toast.js +54 -0
- package/dist/toast.js.map +1 -0
- package/dist/url-state.cjs +212 -0
- package/dist/url-state.cjs.map +1 -0
- package/dist/url-state.d.cts +90 -0
- package/dist/url-state.d.ts +90 -0
- package/dist/url-state.js +199 -0
- package/dist/url-state.js.map +1 -0
- package/package.json +286 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/latest-request.ts"],"sourcesContent":["\"use client\";\n\n/**\n * useLatestRequest — makes a superseded async response impossible to apply.\n *\n * THE BUG THIS KILLS: a surface refetches whenever some input changes (a\n * `?app=` scope, a search box, a selected id). Two fetches are then in flight\n * at once, and **responses do not arrive in the order they were sent.** The\n * older one lands last, calls `setRows`, and wins — so the table shows app A's\n * runs while the banner, the URL, and every label already say app B.\n *\n * That is not stale data, which announces itself. It is the WRONG RECORD under\n * a CONFIDENT label — the same failure `StaleDataNotice` exists to prevent on\n * the error path, arriving through the success path instead. Clearing rows when\n * a fetch FAILS (which this repo already does) closes only half the hole: a\n * fetch that succeeds late is just as capable of mislabeling the screen, and\n * nothing about it looks wrong.\n *\n * `AbortController` is the other half of the answer and a good thing to add on\n * top — but it is not a substitute. An abort races the response; the request may\n * already have resolved, and a non-`fetch` data source (a Supabase client call,\n * an RPC wrapper) often has no signal to give. This guard is source-agnostic and\n * final: it decides at APPLY time, which is the only moment that matters.\n *\n * ```ts\n * const beginRequest = useLatestRequest();\n *\n * const load = useCallback(async () => {\n * const isCurrent = beginRequest(); // claim this attempt\n * setLoading(true);\n * try {\n * const data = await fetchScopedRows(appId);\n * if (!isCurrent()) return; // a newer load already started\n * setRows(data);\n * } catch (err) {\n * if (!isCurrent()) return; // don't let an old failure blank new rows\n * setRows([]);\n * setLoadFailed(true);\n * } finally {\n * if (isCurrent()) setLoading(false);\n * }\n * }, [appId, beginRequest]);\n * ```\n *\n * **Guard the catch and the finally too, not just the success path.** A stale\n * REJECTION is the mirror-image bug: it wipes the rows the current request just\n * loaded and raises a \"couldn't load\" notice about a request nobody is waiting\n * for. And an early `setLoading(false)` from a superseded attempt reports the\n * surface as settled while the real one is still in flight.\n *\n * `begin()` returns the predicate rather than exposing a counter, so there is no\n * sequence number to compare wrongly and no way to ask \"is my request current?\"\n * without first having declared one.\n *\n * Three hand-rolled copies of this exact `requestSeq`/`reqIdRef` pattern predate\n * it — `useServerAgentSearch`, `useRagSearch`, `useContextPreview`. They are\n * correct; they are just the evidence that this is a class, not an incident. New\n * code uses this hook, and those three collapse onto it when next touched.\n */\n\nimport { useCallback, useRef } from \"react\";\n\n/**\n * Marks a new attempt as the current one and returns a predicate reporting\n * whether it still is. Call it once at the top of the async function, then\n * check the predicate before EVERY state write that follows an `await`.\n */\nexport type BeginRequest = () => () => boolean;\n\n/**\n * 🚨 RETURNS THE FUNCTION ITSELF, NOT AN OBJECT WRAPPING IT — deliberately, and\n * this is the whole reason the signature looks like that.\n *\n * The first version returned `{ begin }`. That object literal is a NEW\n * reference on every render, and the entire point of this hook is to be named\n * in the dependency array of the very `useCallback` that performs the fetch. An\n * unstable dependency there makes `load` unstable, which makes the\n * `useEffect(…, [load])` that calls it re-run on every render — an unbroken\n * refetch loop against the database with no user input at all. A guard against\n * a fetch race that instead causes infinite fetches is worse than the bug it\n * was written to fix, and it is a High-severity defect that shipped.\n *\n * Returning the `useCallback`-stable function directly removes the hazard by\n * construction: there is no object whose identity a caller could depend on. If\n * this ever needs to return more than one thing, it must be wrapped in\n * `useMemo` — never a bare literal. (Do not lean on the React Compiler to\n * memoize it for you: a primitive has to be correct on its own terms, and\n * correctness here is the difference between one fetch and unbounded ones.)\n */\nexport function useLatestRequest(): BeginRequest {\n const seqRef = useRef(0);\n\n return useCallback(() => {\n const mySeq = ++seqRef.current;\n return () => mySeq === seqRef.current;\n }, []);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA4DA,mBAAoC;AA6B7B,SAAS,mBAAiC;AAC/C,QAAM,aAAS,qBAAO,CAAC;AAEvB,aAAO,0BAAY,MAAM;AACvB,UAAM,QAAQ,EAAE,OAAO;AACvB,WAAO,MAAM,UAAU,OAAO;AAAA,EAChC,GAAG,CAAC,CAAC;AACP;","names":[]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marks a new attempt as the current one and returns a predicate reporting
|
|
3
|
+
* whether it still is. Call it once at the top of the async function, then
|
|
4
|
+
* check the predicate before EVERY state write that follows an `await`.
|
|
5
|
+
*/
|
|
6
|
+
type BeginRequest = () => () => boolean;
|
|
7
|
+
/**
|
|
8
|
+
* 🚨 RETURNS THE FUNCTION ITSELF, NOT AN OBJECT WRAPPING IT — deliberately, and
|
|
9
|
+
* this is the whole reason the signature looks like that.
|
|
10
|
+
*
|
|
11
|
+
* The first version returned `{ begin }`. That object literal is a NEW
|
|
12
|
+
* reference on every render, and the entire point of this hook is to be named
|
|
13
|
+
* in the dependency array of the very `useCallback` that performs the fetch. An
|
|
14
|
+
* unstable dependency there makes `load` unstable, which makes the
|
|
15
|
+
* `useEffect(…, [load])` that calls it re-run on every render — an unbroken
|
|
16
|
+
* refetch loop against the database with no user input at all. A guard against
|
|
17
|
+
* a fetch race that instead causes infinite fetches is worse than the bug it
|
|
18
|
+
* was written to fix, and it is a High-severity defect that shipped.
|
|
19
|
+
*
|
|
20
|
+
* Returning the `useCallback`-stable function directly removes the hazard by
|
|
21
|
+
* construction: there is no object whose identity a caller could depend on. If
|
|
22
|
+
* this ever needs to return more than one thing, it must be wrapped in
|
|
23
|
+
* `useMemo` — never a bare literal. (Do not lean on the React Compiler to
|
|
24
|
+
* memoize it for you: a primitive has to be correct on its own terms, and
|
|
25
|
+
* correctness here is the difference between one fetch and unbounded ones.)
|
|
26
|
+
*/
|
|
27
|
+
declare function useLatestRequest(): BeginRequest;
|
|
28
|
+
|
|
29
|
+
export { type BeginRequest, useLatestRequest };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marks a new attempt as the current one and returns a predicate reporting
|
|
3
|
+
* whether it still is. Call it once at the top of the async function, then
|
|
4
|
+
* check the predicate before EVERY state write that follows an `await`.
|
|
5
|
+
*/
|
|
6
|
+
type BeginRequest = () => () => boolean;
|
|
7
|
+
/**
|
|
8
|
+
* 🚨 RETURNS THE FUNCTION ITSELF, NOT AN OBJECT WRAPPING IT — deliberately, and
|
|
9
|
+
* this is the whole reason the signature looks like that.
|
|
10
|
+
*
|
|
11
|
+
* The first version returned `{ begin }`. That object literal is a NEW
|
|
12
|
+
* reference on every render, and the entire point of this hook is to be named
|
|
13
|
+
* in the dependency array of the very `useCallback` that performs the fetch. An
|
|
14
|
+
* unstable dependency there makes `load` unstable, which makes the
|
|
15
|
+
* `useEffect(…, [load])` that calls it re-run on every render — an unbroken
|
|
16
|
+
* refetch loop against the database with no user input at all. A guard against
|
|
17
|
+
* a fetch race that instead causes infinite fetches is worse than the bug it
|
|
18
|
+
* was written to fix, and it is a High-severity defect that shipped.
|
|
19
|
+
*
|
|
20
|
+
* Returning the `useCallback`-stable function directly removes the hazard by
|
|
21
|
+
* construction: there is no object whose identity a caller could depend on. If
|
|
22
|
+
* this ever needs to return more than one thing, it must be wrapped in
|
|
23
|
+
* `useMemo` — never a bare literal. (Do not lean on the React Compiler to
|
|
24
|
+
* memoize it for you: a primitive has to be correct on its own terms, and
|
|
25
|
+
* correctness here is the difference between one fetch and unbounded ones.)
|
|
26
|
+
*/
|
|
27
|
+
declare function useLatestRequest(): BeginRequest;
|
|
28
|
+
|
|
29
|
+
export { type BeginRequest, useLatestRequest };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
// src/latest-request.ts
|
|
5
|
+
import { useCallback, useRef } from "react";
|
|
6
|
+
function useLatestRequest() {
|
|
7
|
+
const seqRef = useRef(0);
|
|
8
|
+
return useCallback(() => {
|
|
9
|
+
const mySeq = ++seqRef.current;
|
|
10
|
+
return () => mySeq === seqRef.current;
|
|
11
|
+
}, []);
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
useLatestRequest
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=latest-request.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/latest-request.ts"],"sourcesContent":["\"use client\";\n\n/**\n * useLatestRequest — makes a superseded async response impossible to apply.\n *\n * THE BUG THIS KILLS: a surface refetches whenever some input changes (a\n * `?app=` scope, a search box, a selected id). Two fetches are then in flight\n * at once, and **responses do not arrive in the order they were sent.** The\n * older one lands last, calls `setRows`, and wins — so the table shows app A's\n * runs while the banner, the URL, and every label already say app B.\n *\n * That is not stale data, which announces itself. It is the WRONG RECORD under\n * a CONFIDENT label — the same failure `StaleDataNotice` exists to prevent on\n * the error path, arriving through the success path instead. Clearing rows when\n * a fetch FAILS (which this repo already does) closes only half the hole: a\n * fetch that succeeds late is just as capable of mislabeling the screen, and\n * nothing about it looks wrong.\n *\n * `AbortController` is the other half of the answer and a good thing to add on\n * top — but it is not a substitute. An abort races the response; the request may\n * already have resolved, and a non-`fetch` data source (a Supabase client call,\n * an RPC wrapper) often has no signal to give. This guard is source-agnostic and\n * final: it decides at APPLY time, which is the only moment that matters.\n *\n * ```ts\n * const beginRequest = useLatestRequest();\n *\n * const load = useCallback(async () => {\n * const isCurrent = beginRequest(); // claim this attempt\n * setLoading(true);\n * try {\n * const data = await fetchScopedRows(appId);\n * if (!isCurrent()) return; // a newer load already started\n * setRows(data);\n * } catch (err) {\n * if (!isCurrent()) return; // don't let an old failure blank new rows\n * setRows([]);\n * setLoadFailed(true);\n * } finally {\n * if (isCurrent()) setLoading(false);\n * }\n * }, [appId, beginRequest]);\n * ```\n *\n * **Guard the catch and the finally too, not just the success path.** A stale\n * REJECTION is the mirror-image bug: it wipes the rows the current request just\n * loaded and raises a \"couldn't load\" notice about a request nobody is waiting\n * for. And an early `setLoading(false)` from a superseded attempt reports the\n * surface as settled while the real one is still in flight.\n *\n * `begin()` returns the predicate rather than exposing a counter, so there is no\n * sequence number to compare wrongly and no way to ask \"is my request current?\"\n * without first having declared one.\n *\n * Three hand-rolled copies of this exact `requestSeq`/`reqIdRef` pattern predate\n * it — `useServerAgentSearch`, `useRagSearch`, `useContextPreview`. They are\n * correct; they are just the evidence that this is a class, not an incident. New\n * code uses this hook, and those three collapse onto it when next touched.\n */\n\nimport { useCallback, useRef } from \"react\";\n\n/**\n * Marks a new attempt as the current one and returns a predicate reporting\n * whether it still is. Call it once at the top of the async function, then\n * check the predicate before EVERY state write that follows an `await`.\n */\nexport type BeginRequest = () => () => boolean;\n\n/**\n * 🚨 RETURNS THE FUNCTION ITSELF, NOT AN OBJECT WRAPPING IT — deliberately, and\n * this is the whole reason the signature looks like that.\n *\n * The first version returned `{ begin }`. That object literal is a NEW\n * reference on every render, and the entire point of this hook is to be named\n * in the dependency array of the very `useCallback` that performs the fetch. An\n * unstable dependency there makes `load` unstable, which makes the\n * `useEffect(…, [load])` that calls it re-run on every render — an unbroken\n * refetch loop against the database with no user input at all. A guard against\n * a fetch race that instead causes infinite fetches is worse than the bug it\n * was written to fix, and it is a High-severity defect that shipped.\n *\n * Returning the `useCallback`-stable function directly removes the hazard by\n * construction: there is no object whose identity a caller could depend on. If\n * this ever needs to return more than one thing, it must be wrapped in\n * `useMemo` — never a bare literal. (Do not lean on the React Compiler to\n * memoize it for you: a primitive has to be correct on its own terms, and\n * correctness here is the difference between one fetch and unbounded ones.)\n */\nexport function useLatestRequest(): BeginRequest {\n const seqRef = useRef(0);\n\n return useCallback(() => {\n const mySeq = ++seqRef.current;\n return () => mySeq === seqRef.current;\n }, []);\n}\n"],"mappings":";;;;AA4DA,SAAS,aAAa,cAAc;AA6B7B,SAAS,mBAAiC;AAC/C,QAAM,SAAS,OAAO,CAAC;AAEvB,SAAO,YAAY,MAAM;AACvB,UAAM,QAAQ,EAAE,OAAO;AACvB,WAAO,MAAM,UAAU,OAAO;AAAA,EAChC,GAAG,CAAC,CAAC;AACP;","names":[]}
|
package/dist/qr.cjs
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/qr.ts
|
|
31
|
+
var qr_exports = {};
|
|
32
|
+
__export(qr_exports, {
|
|
33
|
+
decodeQrFromElement: () => decodeQrFromElement,
|
|
34
|
+
decodeQrFromImageData: () => decodeQrFromImageData,
|
|
35
|
+
decodeQrFromImageFile: () => decodeQrFromImageFile,
|
|
36
|
+
hasNativeQrDetector: () => hasNativeQrDetector
|
|
37
|
+
});
|
|
38
|
+
module.exports = __toCommonJS(qr_exports);
|
|
39
|
+
var MAX_EDGE = 1600;
|
|
40
|
+
function nativeDetector() {
|
|
41
|
+
const ctor = globalThis.BarcodeDetector;
|
|
42
|
+
return typeof ctor === "function" ? ctor : null;
|
|
43
|
+
}
|
|
44
|
+
function hasNativeQrDetector() {
|
|
45
|
+
return nativeDetector() !== null;
|
|
46
|
+
}
|
|
47
|
+
async function decodeNative(source) {
|
|
48
|
+
const Ctor = nativeDetector();
|
|
49
|
+
if (!Ctor) return null;
|
|
50
|
+
try {
|
|
51
|
+
const detector = new Ctor({ formats: ["qr_code"] });
|
|
52
|
+
const results = await detector.detect(source);
|
|
53
|
+
const value = results.find((r) => r.rawValue)?.rawValue;
|
|
54
|
+
return value ? value : null;
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function decodeWithJsQr(frame) {
|
|
60
|
+
const { default: jsQR } = await import("jsqr");
|
|
61
|
+
const both = jsQR(frame.data, frame.width, frame.height, {
|
|
62
|
+
inversionAttempts: "attemptBoth"
|
|
63
|
+
});
|
|
64
|
+
return both?.data ? both.data : null;
|
|
65
|
+
}
|
|
66
|
+
function toImageData(source, width, height) {
|
|
67
|
+
if (!width || !height) return null;
|
|
68
|
+
const scale = Math.min(1, MAX_EDGE / Math.max(width, height));
|
|
69
|
+
const w = Math.max(1, Math.round(width * scale));
|
|
70
|
+
const h = Math.max(1, Math.round(height * scale));
|
|
71
|
+
const canvas = document.createElement("canvas");
|
|
72
|
+
canvas.width = w;
|
|
73
|
+
canvas.height = h;
|
|
74
|
+
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
|
75
|
+
if (!ctx) return null;
|
|
76
|
+
ctx.drawImage(source, 0, 0, w, h);
|
|
77
|
+
return ctx.getImageData(0, 0, w, h);
|
|
78
|
+
}
|
|
79
|
+
async function decodeQrFromImageData(frame) {
|
|
80
|
+
return await decodeWithJsQr(frame) ?? null;
|
|
81
|
+
}
|
|
82
|
+
async function decodeQrFromElement(element) {
|
|
83
|
+
const width = element instanceof HTMLVideoElement ? element.videoWidth : element.width;
|
|
84
|
+
const height = element instanceof HTMLVideoElement ? element.videoHeight : element.height;
|
|
85
|
+
if (!width || !height) return null;
|
|
86
|
+
const native = await decodeNative(element);
|
|
87
|
+
if (native) return native;
|
|
88
|
+
const frame = toImageData(element, width, height);
|
|
89
|
+
return frame ? decodeQrFromImageData(frame) : null;
|
|
90
|
+
}
|
|
91
|
+
async function decodeQrFromImageFile(file) {
|
|
92
|
+
const direct = await decodeNative(file);
|
|
93
|
+
if (direct) return direct;
|
|
94
|
+
let bitmap = null;
|
|
95
|
+
try {
|
|
96
|
+
bitmap = await createImageBitmap(file);
|
|
97
|
+
} catch {
|
|
98
|
+
throw new Error("That file could not be read as an image.");
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const viaBitmap = await decodeNative(bitmap);
|
|
102
|
+
if (viaBitmap) return viaBitmap;
|
|
103
|
+
const frame = toImageData(bitmap, bitmap.width, bitmap.height);
|
|
104
|
+
return frame ? decodeQrFromImageData(frame) : null;
|
|
105
|
+
} finally {
|
|
106
|
+
bitmap.close();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=qr.cjs.map
|
package/dist/qr.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/qr.ts"],"sourcesContent":["/**\n * @ai-matrx/kit/qr — THE QR-code decoder (client-side, in memory). Ported\n * verbatim from matrx-frontend `lib/qr/decode.ts`.\n *\n * One primitive, three inputs (a File/Blob, an `ImageData` frame, a\n * `<video>`/`<canvas>` element), one answer: the text the QR encodes, or\n * `null` when no code is present. Nothing here uploads, stores, or persists\n * anything — the bytes live in a canvas for the length of one call.\n *\n * Engine order:\n * 1. `BarcodeDetector` — native, fast, handles rotation and poor contrast.\n * 2. `jsqr` — pure-JS fallback (lazily imported, so it only enters the\n * bundle of a surface that actually decodes), for Safari/Firefox where\n * the native detector does not exist.\n *\n * Runtime dependency of this subpath (and only when the fallback fires):\n * `jsqr` — a browser without `BarcodeDetector` decoding a pasted screenshot\n * IS the capability, not an optional extra; without the fallback the unit\n * silently does nothing on Safari/Firefox.\n *\n * Browser capability: decoding needs a DOM (`document`, canvas 2D,\n * `createImageBitmap`) at call time; importing is inert and SSR-safe, and\n * `hasNativeQrDetector()` is safe to call anywhere (it only probes\n * `globalThis`).\n *\n * 🚨 Reach for THIS, never a second decoder. If a surface needs a new input\n * shape, add an adapter here.\n */\n\n/** Longest edge we rasterise to. Big enough for a phone screenshot of a QR,\n * small enough that a 48MP photo cannot stall the main thread. */\nconst MAX_EDGE = 1600;\n\ntype NativeBarcodeDetector = {\n detect: (source: CanvasImageSource | ImageBitmap | Blob) => Promise<{ rawValue: string }[]>;\n};\n\ntype BarcodeDetectorCtor = {\n new (options?: { formats?: string[] }): NativeBarcodeDetector;\n getSupportedFormats?: () => Promise<string[]>;\n};\n\nfunction nativeDetector(): BarcodeDetectorCtor | null {\n const ctor = (globalThis as { BarcodeDetector?: BarcodeDetectorCtor })\n .BarcodeDetector;\n return typeof ctor === \"function\" ? ctor : null;\n}\n\n/** True when the browser can decode without downloading the JS fallback. */\nexport function hasNativeQrDetector(): boolean {\n return nativeDetector() !== null;\n}\n\nasync function decodeNative(\n source: CanvasImageSource | ImageBitmap | Blob,\n): Promise<string | null> {\n const Ctor = nativeDetector();\n if (!Ctor) return null;\n try {\n const detector = new Ctor({ formats: [\"qr_code\"] });\n const results = await detector.detect(source);\n const value = results.find((r) => r.rawValue)?.rawValue;\n return value ? value : null;\n } catch {\n // A detector that throws (unsupported format, decode error) is a miss,\n // never a crash — the jsqr fallback still gets its turn.\n return null;\n }\n}\n\nasync function decodeWithJsQr(frame: ImageData): Promise<string | null> {\n const { default: jsQR } = await import(\"jsqr\");\n const both = jsQR(frame.data, frame.width, frame.height, {\n inversionAttempts: \"attemptBoth\",\n });\n return both?.data ? both.data : null;\n}\n\n/** Draw any image source onto a canvas, capped at {@link MAX_EDGE}. */\nfunction toImageData(\n source: CanvasImageSource,\n width: number,\n height: number,\n): ImageData | null {\n if (!width || !height) return null;\n const scale = Math.min(1, MAX_EDGE / Math.max(width, height));\n const w = Math.max(1, Math.round(width * scale));\n const h = Math.max(1, Math.round(height * scale));\n const canvas = document.createElement(\"canvas\");\n canvas.width = w;\n canvas.height = h;\n const ctx = canvas.getContext(\"2d\", { willReadFrequently: true });\n if (!ctx) return null;\n ctx.drawImage(source, 0, 0, w, h);\n return ctx.getImageData(0, 0, w, h);\n}\n\n/** Decode a QR code out of an already-rasterised frame. */\nexport async function decodeQrFromImageData(\n frame: ImageData,\n): Promise<string | null> {\n return (await decodeWithJsQr(frame)) ?? null;\n}\n\n/**\n * Decode a QR code out of a live `<video>` (a camera preview) or a `<canvas>`.\n * Returns `null` when the current frame holds no code — call it on a tick.\n */\nexport async function decodeQrFromElement(\n element: HTMLVideoElement | HTMLCanvasElement,\n): Promise<string | null> {\n const width =\n element instanceof HTMLVideoElement ? element.videoWidth : element.width;\n const height =\n element instanceof HTMLVideoElement ? element.videoHeight : element.height;\n if (!width || !height) return null;\n\n const native = await decodeNative(element);\n if (native) return native;\n\n const frame = toImageData(element, width, height);\n return frame ? decodeQrFromImageData(frame) : null;\n}\n\n/**\n * Decode a QR code out of an image File/Blob — a pasted screenshot, a dropped\n * PNG, a photo from the OS camera sheet.\n *\n * Resolves `null` when the image holds no QR code. Throws only when the file\n * is not decodable as an image at all.\n */\nexport async function decodeQrFromImageFile(\n file: Blob,\n): Promise<string | null> {\n // The native detector accepts a Blob directly on Chromium — cheapest path.\n const direct = await decodeNative(file);\n if (direct) return direct;\n\n let bitmap: ImageBitmap | null = null;\n try {\n bitmap = await createImageBitmap(file);\n } catch {\n throw new Error(\"That file could not be read as an image.\");\n }\n try {\n const viaBitmap = await decodeNative(bitmap);\n if (viaBitmap) return viaBitmap;\n const frame = toImageData(bitmap, bitmap.width, bitmap.height);\n return frame ? decodeQrFromImageData(frame) : null;\n } finally {\n bitmap.close();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA+BA,IAAM,WAAW;AAWjB,SAAS,iBAA6C;AACpD,QAAM,OAAQ,WACX;AACH,SAAO,OAAO,SAAS,aAAa,OAAO;AAC7C;AAGO,SAAS,sBAA+B;AAC7C,SAAO,eAAe,MAAM;AAC9B;AAEA,eAAe,aACb,QACwB;AACxB,QAAM,OAAO,eAAe;AAC5B,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI;AACF,UAAM,WAAW,IAAI,KAAK,EAAE,SAAS,CAAC,SAAS,EAAE,CAAC;AAClD,UAAM,UAAU,MAAM,SAAS,OAAO,MAAM;AAC5C,UAAM,QAAQ,QAAQ,KAAK,CAAC,MAAM,EAAE,QAAQ,GAAG;AAC/C,WAAO,QAAQ,QAAQ;AAAA,EACzB,QAAQ;AAGN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,eAAe,OAA0C;AACtE,QAAM,EAAE,SAAS,KAAK,IAAI,MAAM,OAAO,MAAM;AAC7C,QAAM,OAAO,KAAK,MAAM,MAAM,MAAM,OAAO,MAAM,QAAQ;AAAA,IACvD,mBAAmB;AAAA,EACrB,CAAC;AACD,SAAO,MAAM,OAAO,KAAK,OAAO;AAClC;AAGA,SAAS,YACP,QACA,OACA,QACkB;AAClB,MAAI,CAAC,SAAS,CAAC,OAAQ,QAAO;AAC9B,QAAM,QAAQ,KAAK,IAAI,GAAG,WAAW,KAAK,IAAI,OAAO,MAAM,CAAC;AAC5D,QAAM,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,KAAK,CAAC;AAC/C,QAAM,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,SAAS,KAAK,CAAC;AAChD,QAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,SAAO,QAAQ;AACf,SAAO,SAAS;AAChB,QAAM,MAAM,OAAO,WAAW,MAAM,EAAE,oBAAoB,KAAK,CAAC;AAChE,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI,UAAU,QAAQ,GAAG,GAAG,GAAG,CAAC;AAChC,SAAO,IAAI,aAAa,GAAG,GAAG,GAAG,CAAC;AACpC;AAGA,eAAsB,sBACpB,OACwB;AACxB,SAAQ,MAAM,eAAe,KAAK,KAAM;AAC1C;AAMA,eAAsB,oBACpB,SACwB;AACxB,QAAM,QACJ,mBAAmB,mBAAmB,QAAQ,aAAa,QAAQ;AACrE,QAAM,SACJ,mBAAmB,mBAAmB,QAAQ,cAAc,QAAQ;AACtE,MAAI,CAAC,SAAS,CAAC,OAAQ,QAAO;AAE9B,QAAM,SAAS,MAAM,aAAa,OAAO;AACzC,MAAI,OAAQ,QAAO;AAEnB,QAAM,QAAQ,YAAY,SAAS,OAAO,MAAM;AAChD,SAAO,QAAQ,sBAAsB,KAAK,IAAI;AAChD;AASA,eAAsB,sBACpB,MACwB;AAExB,QAAM,SAAS,MAAM,aAAa,IAAI;AACtC,MAAI,OAAQ,QAAO;AAEnB,MAAI,SAA6B;AACjC,MAAI;AACF,aAAS,MAAM,kBAAkB,IAAI;AAAA,EACvC,QAAQ;AACN,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AACA,MAAI;AACF,UAAM,YAAY,MAAM,aAAa,MAAM;AAC3C,QAAI,UAAW,QAAO;AACtB,UAAM,QAAQ,YAAY,QAAQ,OAAO,OAAO,OAAO,MAAM;AAC7D,WAAO,QAAQ,sBAAsB,KAAK,IAAI;AAAA,EAChD,UAAE;AACA,WAAO,MAAM;AAAA,EACf;AACF;","names":[]}
|
package/dist/qr.d.cts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ai-matrx/kit/qr — THE QR-code decoder (client-side, in memory). Ported
|
|
3
|
+
* verbatim from matrx-frontend `lib/qr/decode.ts`.
|
|
4
|
+
*
|
|
5
|
+
* One primitive, three inputs (a File/Blob, an `ImageData` frame, a
|
|
6
|
+
* `<video>`/`<canvas>` element), one answer: the text the QR encodes, or
|
|
7
|
+
* `null` when no code is present. Nothing here uploads, stores, or persists
|
|
8
|
+
* anything — the bytes live in a canvas for the length of one call.
|
|
9
|
+
*
|
|
10
|
+
* Engine order:
|
|
11
|
+
* 1. `BarcodeDetector` — native, fast, handles rotation and poor contrast.
|
|
12
|
+
* 2. `jsqr` — pure-JS fallback (lazily imported, so it only enters the
|
|
13
|
+
* bundle of a surface that actually decodes), for Safari/Firefox where
|
|
14
|
+
* the native detector does not exist.
|
|
15
|
+
*
|
|
16
|
+
* Runtime dependency of this subpath (and only when the fallback fires):
|
|
17
|
+
* `jsqr` — a browser without `BarcodeDetector` decoding a pasted screenshot
|
|
18
|
+
* IS the capability, not an optional extra; without the fallback the unit
|
|
19
|
+
* silently does nothing on Safari/Firefox.
|
|
20
|
+
*
|
|
21
|
+
* Browser capability: decoding needs a DOM (`document`, canvas 2D,
|
|
22
|
+
* `createImageBitmap`) at call time; importing is inert and SSR-safe, and
|
|
23
|
+
* `hasNativeQrDetector()` is safe to call anywhere (it only probes
|
|
24
|
+
* `globalThis`).
|
|
25
|
+
*
|
|
26
|
+
* 🚨 Reach for THIS, never a second decoder. If a surface needs a new input
|
|
27
|
+
* shape, add an adapter here.
|
|
28
|
+
*/
|
|
29
|
+
/** True when the browser can decode without downloading the JS fallback. */
|
|
30
|
+
declare function hasNativeQrDetector(): boolean;
|
|
31
|
+
/** Decode a QR code out of an already-rasterised frame. */
|
|
32
|
+
declare function decodeQrFromImageData(frame: ImageData): Promise<string | null>;
|
|
33
|
+
/**
|
|
34
|
+
* Decode a QR code out of a live `<video>` (a camera preview) or a `<canvas>`.
|
|
35
|
+
* Returns `null` when the current frame holds no code — call it on a tick.
|
|
36
|
+
*/
|
|
37
|
+
declare function decodeQrFromElement(element: HTMLVideoElement | HTMLCanvasElement): Promise<string | null>;
|
|
38
|
+
/**
|
|
39
|
+
* Decode a QR code out of an image File/Blob — a pasted screenshot, a dropped
|
|
40
|
+
* PNG, a photo from the OS camera sheet.
|
|
41
|
+
*
|
|
42
|
+
* Resolves `null` when the image holds no QR code. Throws only when the file
|
|
43
|
+
* is not decodable as an image at all.
|
|
44
|
+
*/
|
|
45
|
+
declare function decodeQrFromImageFile(file: Blob): Promise<string | null>;
|
|
46
|
+
|
|
47
|
+
export { decodeQrFromElement, decodeQrFromImageData, decodeQrFromImageFile, hasNativeQrDetector };
|
package/dist/qr.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ai-matrx/kit/qr — THE QR-code decoder (client-side, in memory). Ported
|
|
3
|
+
* verbatim from matrx-frontend `lib/qr/decode.ts`.
|
|
4
|
+
*
|
|
5
|
+
* One primitive, three inputs (a File/Blob, an `ImageData` frame, a
|
|
6
|
+
* `<video>`/`<canvas>` element), one answer: the text the QR encodes, or
|
|
7
|
+
* `null` when no code is present. Nothing here uploads, stores, or persists
|
|
8
|
+
* anything — the bytes live in a canvas for the length of one call.
|
|
9
|
+
*
|
|
10
|
+
* Engine order:
|
|
11
|
+
* 1. `BarcodeDetector` — native, fast, handles rotation and poor contrast.
|
|
12
|
+
* 2. `jsqr` — pure-JS fallback (lazily imported, so it only enters the
|
|
13
|
+
* bundle of a surface that actually decodes), for Safari/Firefox where
|
|
14
|
+
* the native detector does not exist.
|
|
15
|
+
*
|
|
16
|
+
* Runtime dependency of this subpath (and only when the fallback fires):
|
|
17
|
+
* `jsqr` — a browser without `BarcodeDetector` decoding a pasted screenshot
|
|
18
|
+
* IS the capability, not an optional extra; without the fallback the unit
|
|
19
|
+
* silently does nothing on Safari/Firefox.
|
|
20
|
+
*
|
|
21
|
+
* Browser capability: decoding needs a DOM (`document`, canvas 2D,
|
|
22
|
+
* `createImageBitmap`) at call time; importing is inert and SSR-safe, and
|
|
23
|
+
* `hasNativeQrDetector()` is safe to call anywhere (it only probes
|
|
24
|
+
* `globalThis`).
|
|
25
|
+
*
|
|
26
|
+
* 🚨 Reach for THIS, never a second decoder. If a surface needs a new input
|
|
27
|
+
* shape, add an adapter here.
|
|
28
|
+
*/
|
|
29
|
+
/** True when the browser can decode without downloading the JS fallback. */
|
|
30
|
+
declare function hasNativeQrDetector(): boolean;
|
|
31
|
+
/** Decode a QR code out of an already-rasterised frame. */
|
|
32
|
+
declare function decodeQrFromImageData(frame: ImageData): Promise<string | null>;
|
|
33
|
+
/**
|
|
34
|
+
* Decode a QR code out of a live `<video>` (a camera preview) or a `<canvas>`.
|
|
35
|
+
* Returns `null` when the current frame holds no code — call it on a tick.
|
|
36
|
+
*/
|
|
37
|
+
declare function decodeQrFromElement(element: HTMLVideoElement | HTMLCanvasElement): Promise<string | null>;
|
|
38
|
+
/**
|
|
39
|
+
* Decode a QR code out of an image File/Blob — a pasted screenshot, a dropped
|
|
40
|
+
* PNG, a photo from the OS camera sheet.
|
|
41
|
+
*
|
|
42
|
+
* Resolves `null` when the image holds no QR code. Throws only when the file
|
|
43
|
+
* is not decodable as an image at all.
|
|
44
|
+
*/
|
|
45
|
+
declare function decodeQrFromImageFile(file: Blob): Promise<string | null>;
|
|
46
|
+
|
|
47
|
+
export { decodeQrFromElement, decodeQrFromImageData, decodeQrFromImageFile, hasNativeQrDetector };
|
package/dist/qr.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// src/qr.ts
|
|
2
|
+
var MAX_EDGE = 1600;
|
|
3
|
+
function nativeDetector() {
|
|
4
|
+
const ctor = globalThis.BarcodeDetector;
|
|
5
|
+
return typeof ctor === "function" ? ctor : null;
|
|
6
|
+
}
|
|
7
|
+
function hasNativeQrDetector() {
|
|
8
|
+
return nativeDetector() !== null;
|
|
9
|
+
}
|
|
10
|
+
async function decodeNative(source) {
|
|
11
|
+
const Ctor = nativeDetector();
|
|
12
|
+
if (!Ctor) return null;
|
|
13
|
+
try {
|
|
14
|
+
const detector = new Ctor({ formats: ["qr_code"] });
|
|
15
|
+
const results = await detector.detect(source);
|
|
16
|
+
const value = results.find((r) => r.rawValue)?.rawValue;
|
|
17
|
+
return value ? value : null;
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function decodeWithJsQr(frame) {
|
|
23
|
+
const { default: jsQR } = await import("jsqr");
|
|
24
|
+
const both = jsQR(frame.data, frame.width, frame.height, {
|
|
25
|
+
inversionAttempts: "attemptBoth"
|
|
26
|
+
});
|
|
27
|
+
return both?.data ? both.data : null;
|
|
28
|
+
}
|
|
29
|
+
function toImageData(source, width, height) {
|
|
30
|
+
if (!width || !height) return null;
|
|
31
|
+
const scale = Math.min(1, MAX_EDGE / Math.max(width, height));
|
|
32
|
+
const w = Math.max(1, Math.round(width * scale));
|
|
33
|
+
const h = Math.max(1, Math.round(height * scale));
|
|
34
|
+
const canvas = document.createElement("canvas");
|
|
35
|
+
canvas.width = w;
|
|
36
|
+
canvas.height = h;
|
|
37
|
+
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
|
38
|
+
if (!ctx) return null;
|
|
39
|
+
ctx.drawImage(source, 0, 0, w, h);
|
|
40
|
+
return ctx.getImageData(0, 0, w, h);
|
|
41
|
+
}
|
|
42
|
+
async function decodeQrFromImageData(frame) {
|
|
43
|
+
return await decodeWithJsQr(frame) ?? null;
|
|
44
|
+
}
|
|
45
|
+
async function decodeQrFromElement(element) {
|
|
46
|
+
const width = element instanceof HTMLVideoElement ? element.videoWidth : element.width;
|
|
47
|
+
const height = element instanceof HTMLVideoElement ? element.videoHeight : element.height;
|
|
48
|
+
if (!width || !height) return null;
|
|
49
|
+
const native = await decodeNative(element);
|
|
50
|
+
if (native) return native;
|
|
51
|
+
const frame = toImageData(element, width, height);
|
|
52
|
+
return frame ? decodeQrFromImageData(frame) : null;
|
|
53
|
+
}
|
|
54
|
+
async function decodeQrFromImageFile(file) {
|
|
55
|
+
const direct = await decodeNative(file);
|
|
56
|
+
if (direct) return direct;
|
|
57
|
+
let bitmap = null;
|
|
58
|
+
try {
|
|
59
|
+
bitmap = await createImageBitmap(file);
|
|
60
|
+
} catch {
|
|
61
|
+
throw new Error("That file could not be read as an image.");
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const viaBitmap = await decodeNative(bitmap);
|
|
65
|
+
if (viaBitmap) return viaBitmap;
|
|
66
|
+
const frame = toImageData(bitmap, bitmap.width, bitmap.height);
|
|
67
|
+
return frame ? decodeQrFromImageData(frame) : null;
|
|
68
|
+
} finally {
|
|
69
|
+
bitmap.close();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export {
|
|
73
|
+
decodeQrFromElement,
|
|
74
|
+
decodeQrFromImageData,
|
|
75
|
+
decodeQrFromImageFile,
|
|
76
|
+
hasNativeQrDetector
|
|
77
|
+
};
|
|
78
|
+
//# sourceMappingURL=qr.js.map
|
package/dist/qr.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/qr.ts"],"sourcesContent":["/**\n * @ai-matrx/kit/qr — THE QR-code decoder (client-side, in memory). Ported\n * verbatim from matrx-frontend `lib/qr/decode.ts`.\n *\n * One primitive, three inputs (a File/Blob, an `ImageData` frame, a\n * `<video>`/`<canvas>` element), one answer: the text the QR encodes, or\n * `null` when no code is present. Nothing here uploads, stores, or persists\n * anything — the bytes live in a canvas for the length of one call.\n *\n * Engine order:\n * 1. `BarcodeDetector` — native, fast, handles rotation and poor contrast.\n * 2. `jsqr` — pure-JS fallback (lazily imported, so it only enters the\n * bundle of a surface that actually decodes), for Safari/Firefox where\n * the native detector does not exist.\n *\n * Runtime dependency of this subpath (and only when the fallback fires):\n * `jsqr` — a browser without `BarcodeDetector` decoding a pasted screenshot\n * IS the capability, not an optional extra; without the fallback the unit\n * silently does nothing on Safari/Firefox.\n *\n * Browser capability: decoding needs a DOM (`document`, canvas 2D,\n * `createImageBitmap`) at call time; importing is inert and SSR-safe, and\n * `hasNativeQrDetector()` is safe to call anywhere (it only probes\n * `globalThis`).\n *\n * 🚨 Reach for THIS, never a second decoder. If a surface needs a new input\n * shape, add an adapter here.\n */\n\n/** Longest edge we rasterise to. Big enough for a phone screenshot of a QR,\n * small enough that a 48MP photo cannot stall the main thread. */\nconst MAX_EDGE = 1600;\n\ntype NativeBarcodeDetector = {\n detect: (source: CanvasImageSource | ImageBitmap | Blob) => Promise<{ rawValue: string }[]>;\n};\n\ntype BarcodeDetectorCtor = {\n new (options?: { formats?: string[] }): NativeBarcodeDetector;\n getSupportedFormats?: () => Promise<string[]>;\n};\n\nfunction nativeDetector(): BarcodeDetectorCtor | null {\n const ctor = (globalThis as { BarcodeDetector?: BarcodeDetectorCtor })\n .BarcodeDetector;\n return typeof ctor === \"function\" ? ctor : null;\n}\n\n/** True when the browser can decode without downloading the JS fallback. */\nexport function hasNativeQrDetector(): boolean {\n return nativeDetector() !== null;\n}\n\nasync function decodeNative(\n source: CanvasImageSource | ImageBitmap | Blob,\n): Promise<string | null> {\n const Ctor = nativeDetector();\n if (!Ctor) return null;\n try {\n const detector = new Ctor({ formats: [\"qr_code\"] });\n const results = await detector.detect(source);\n const value = results.find((r) => r.rawValue)?.rawValue;\n return value ? value : null;\n } catch {\n // A detector that throws (unsupported format, decode error) is a miss,\n // never a crash — the jsqr fallback still gets its turn.\n return null;\n }\n}\n\nasync function decodeWithJsQr(frame: ImageData): Promise<string | null> {\n const { default: jsQR } = await import(\"jsqr\");\n const both = jsQR(frame.data, frame.width, frame.height, {\n inversionAttempts: \"attemptBoth\",\n });\n return both?.data ? both.data : null;\n}\n\n/** Draw any image source onto a canvas, capped at {@link MAX_EDGE}. */\nfunction toImageData(\n source: CanvasImageSource,\n width: number,\n height: number,\n): ImageData | null {\n if (!width || !height) return null;\n const scale = Math.min(1, MAX_EDGE / Math.max(width, height));\n const w = Math.max(1, Math.round(width * scale));\n const h = Math.max(1, Math.round(height * scale));\n const canvas = document.createElement(\"canvas\");\n canvas.width = w;\n canvas.height = h;\n const ctx = canvas.getContext(\"2d\", { willReadFrequently: true });\n if (!ctx) return null;\n ctx.drawImage(source, 0, 0, w, h);\n return ctx.getImageData(0, 0, w, h);\n}\n\n/** Decode a QR code out of an already-rasterised frame. */\nexport async function decodeQrFromImageData(\n frame: ImageData,\n): Promise<string | null> {\n return (await decodeWithJsQr(frame)) ?? null;\n}\n\n/**\n * Decode a QR code out of a live `<video>` (a camera preview) or a `<canvas>`.\n * Returns `null` when the current frame holds no code — call it on a tick.\n */\nexport async function decodeQrFromElement(\n element: HTMLVideoElement | HTMLCanvasElement,\n): Promise<string | null> {\n const width =\n element instanceof HTMLVideoElement ? element.videoWidth : element.width;\n const height =\n element instanceof HTMLVideoElement ? element.videoHeight : element.height;\n if (!width || !height) return null;\n\n const native = await decodeNative(element);\n if (native) return native;\n\n const frame = toImageData(element, width, height);\n return frame ? decodeQrFromImageData(frame) : null;\n}\n\n/**\n * Decode a QR code out of an image File/Blob — a pasted screenshot, a dropped\n * PNG, a photo from the OS camera sheet.\n *\n * Resolves `null` when the image holds no QR code. Throws only when the file\n * is not decodable as an image at all.\n */\nexport async function decodeQrFromImageFile(\n file: Blob,\n): Promise<string | null> {\n // The native detector accepts a Blob directly on Chromium — cheapest path.\n const direct = await decodeNative(file);\n if (direct) return direct;\n\n let bitmap: ImageBitmap | null = null;\n try {\n bitmap = await createImageBitmap(file);\n } catch {\n throw new Error(\"That file could not be read as an image.\");\n }\n try {\n const viaBitmap = await decodeNative(bitmap);\n if (viaBitmap) return viaBitmap;\n const frame = toImageData(bitmap, bitmap.width, bitmap.height);\n return frame ? decodeQrFromImageData(frame) : null;\n } finally {\n bitmap.close();\n }\n}\n"],"mappings":";AA+BA,IAAM,WAAW;AAWjB,SAAS,iBAA6C;AACpD,QAAM,OAAQ,WACX;AACH,SAAO,OAAO,SAAS,aAAa,OAAO;AAC7C;AAGO,SAAS,sBAA+B;AAC7C,SAAO,eAAe,MAAM;AAC9B;AAEA,eAAe,aACb,QACwB;AACxB,QAAM,OAAO,eAAe;AAC5B,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI;AACF,UAAM,WAAW,IAAI,KAAK,EAAE,SAAS,CAAC,SAAS,EAAE,CAAC;AAClD,UAAM,UAAU,MAAM,SAAS,OAAO,MAAM;AAC5C,UAAM,QAAQ,QAAQ,KAAK,CAAC,MAAM,EAAE,QAAQ,GAAG;AAC/C,WAAO,QAAQ,QAAQ;AAAA,EACzB,QAAQ;AAGN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,eAAe,OAA0C;AACtE,QAAM,EAAE,SAAS,KAAK,IAAI,MAAM,OAAO,MAAM;AAC7C,QAAM,OAAO,KAAK,MAAM,MAAM,MAAM,OAAO,MAAM,QAAQ;AAAA,IACvD,mBAAmB;AAAA,EACrB,CAAC;AACD,SAAO,MAAM,OAAO,KAAK,OAAO;AAClC;AAGA,SAAS,YACP,QACA,OACA,QACkB;AAClB,MAAI,CAAC,SAAS,CAAC,OAAQ,QAAO;AAC9B,QAAM,QAAQ,KAAK,IAAI,GAAG,WAAW,KAAK,IAAI,OAAO,MAAM,CAAC;AAC5D,QAAM,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,KAAK,CAAC;AAC/C,QAAM,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,SAAS,KAAK,CAAC;AAChD,QAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,SAAO,QAAQ;AACf,SAAO,SAAS;AAChB,QAAM,MAAM,OAAO,WAAW,MAAM,EAAE,oBAAoB,KAAK,CAAC;AAChE,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI,UAAU,QAAQ,GAAG,GAAG,GAAG,CAAC;AAChC,SAAO,IAAI,aAAa,GAAG,GAAG,GAAG,CAAC;AACpC;AAGA,eAAsB,sBACpB,OACwB;AACxB,SAAQ,MAAM,eAAe,KAAK,KAAM;AAC1C;AAMA,eAAsB,oBACpB,SACwB;AACxB,QAAM,QACJ,mBAAmB,mBAAmB,QAAQ,aAAa,QAAQ;AACrE,QAAM,SACJ,mBAAmB,mBAAmB,QAAQ,cAAc,QAAQ;AACtE,MAAI,CAAC,SAAS,CAAC,OAAQ,QAAO;AAE9B,QAAM,SAAS,MAAM,aAAa,OAAO;AACzC,MAAI,OAAQ,QAAO;AAEnB,QAAM,QAAQ,YAAY,SAAS,OAAO,MAAM;AAChD,SAAO,QAAQ,sBAAsB,KAAK,IAAI;AAChD;AASA,eAAsB,sBACpB,MACwB;AAExB,QAAM,SAAS,MAAM,aAAa,IAAI;AACtC,MAAI,OAAQ,QAAO;AAEnB,MAAI,SAA6B;AACjC,MAAI;AACF,aAAS,MAAM,kBAAkB,IAAI;AAAA,EACvC,QAAQ;AACN,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AACA,MAAI;AACF,UAAM,YAAY,MAAM,aAAa,MAAM;AAC3C,QAAI,UAAW,QAAO;AACtB,UAAM,QAAQ,YAAY,QAAQ,OAAO,OAAO,OAAO,MAAM;AAC7D,WAAO,QAAQ,sBAAsB,KAAK,IAAI;AAAA,EAChD,UAAE;AACA,WAAO,MAAM;AAAA,EACf;AACF;","names":[]}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/react-tree.ts
|
|
31
|
+
var react_tree_exports = {};
|
|
32
|
+
__export(react_tree_exports, {
|
|
33
|
+
treeContainsComponent: () => treeContainsComponent
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(react_tree_exports);
|
|
36
|
+
var React = __toESM(require("react"), 1);
|
|
37
|
+
var REACT_PORTAL_TYPE = /* @__PURE__ */ Symbol.for("react.portal");
|
|
38
|
+
function treeContainsComponent(node, Component) {
|
|
39
|
+
if (node == null || typeof node === "boolean") return false;
|
|
40
|
+
if (Array.isArray(node)) {
|
|
41
|
+
return node.some((child) => treeContainsComponent(child, Component));
|
|
42
|
+
}
|
|
43
|
+
if (React.isValidElement(node)) {
|
|
44
|
+
if (node.type === Component) return true;
|
|
45
|
+
const props = node.props;
|
|
46
|
+
return props.children != null ? treeContainsComponent(props.children, Component) : false;
|
|
47
|
+
}
|
|
48
|
+
if (typeof node === "string" || typeof node === "number") return false;
|
|
49
|
+
if (typeof node === "object" && node.$$typeof === REACT_PORTAL_TYPE) {
|
|
50
|
+
return treeContainsComponent(
|
|
51
|
+
node.children,
|
|
52
|
+
Component
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
if (typeof node === "object" && Symbol.iterator in node) {
|
|
56
|
+
return Array.from(node).some(
|
|
57
|
+
(child) => treeContainsComponent(child, Component)
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
if (typeof process !== "undefined" && true) {
|
|
61
|
+
const keys = typeof node === "object" ? ` with keys {${Object.keys(node).join(", ")}}` : "";
|
|
62
|
+
console.error(
|
|
63
|
+
`[treeContainsComponent] A non-renderable value${keys} is being passed as a React child. React will throw 'Objects are not valid as a React child' at the real render site. Stringify it (e.g. JSON.stringify) before rendering.`,
|
|
64
|
+
node
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=react-tree.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/react-tree.ts"],"sourcesContent":["/**\n * @ai-matrx/kit/react-tree — safe React children-tree scanning.\n *\n * Ported from matrx-frontend `lib/react/treeContainsComponent.ts`; this\n * subpath is the ONE home of the scanner inside the kit (the `/confirm`\n * subpath's alert-dialog imports it from here — no duplicate bodies).\n *\n * Two deliberate divergences from the frontend original, both documented:\n * - the dev-mode scream checks `typeof process` first, since this package may\n * load in an unbundled browser context where `process` is undefined;\n * - a React PORTAL child (`createPortal(...)` passed as a child) is a valid\n * React child, but it is not an element, an iterable, or a primitive — the\n * original fell through to the non-renderable branch, screamed a false\n * positive in dev, and skipped the portal's content. Portals are now\n * recognized and their children traversed.\n */\n\nimport * as React from \"react\";\n\nconst REACT_PORTAL_TYPE = Symbol.for(\"react.portal\");\n\n/**\n * Returns true if `Component` appears anywhere in the React element tree under\n * `node`. Used to detect optional a11y children (e.g. DialogDescription)\n * without rendering duplicates.\n *\n * DEFENSIVE + LOUD. A non-renderable node — a plain object or function passed\n * as a React child — is a real bug: React throws \"Objects are not valid as a\n * React child\" the instant it renders one. This a11y probe must NOT be the\n * crash site. `React.Children.toArray` would throw HERE, producing a trace that\n * points at the dialog primitive instead of the component that leaked the\n * object (this misdirection has burned real debugging hours). So we walk the\n * tree by hand, SKIP any non-renderable node, and scream in dev with its keys —\n * then let React report the defect at the true render site with the offending\n * component in the stack. For every VALID tree the result is identical to the\n * old `React.Children.toArray(node).some(...)`.\n */\nexport function treeContainsComponent(\n node: React.ReactNode,\n Component: React.ElementType,\n): boolean {\n if (node == null || typeof node === \"boolean\") return false;\n\n if (Array.isArray(node)) {\n return node.some((child) => treeContainsComponent(child, Component));\n }\n\n if (React.isValidElement(node)) {\n if (node.type === Component) return true;\n const props = node.props as { children?: React.ReactNode };\n return props.children != null\n ? treeContainsComponent(props.children, Component)\n : false;\n }\n\n // Strings / numbers are valid leaf children but never the Component.\n if (typeof node === \"string\" || typeof node === \"number\") return false;\n\n // A portal is a valid child that is NOT an element: traverse its content.\n if (\n typeof node === \"object\" &&\n (node as { $$typeof?: unknown }).$$typeof === REACT_PORTAL_TYPE\n ) {\n return treeContainsComponent(\n (node as { children?: React.ReactNode }).children,\n Component,\n );\n }\n\n // Non-array iterables (Set, Map, generator) are valid React children — React\n // supports them — so traverse rather than reject.\n if (typeof node === \"object\" && Symbol.iterator in node) {\n return Array.from(node as Iterable<React.ReactNode>).some((child) =>\n treeContainsComponent(child, Component),\n );\n }\n\n // Anything else (a raw object, a function) is NOT a valid React child. React\n // will throw when it renders this; we must not throw first and hide the cause.\n if (\n typeof process !== \"undefined\" &&\n process.env.NODE_ENV !== \"production\"\n ) {\n const keys =\n typeof node === \"object\"\n ? ` with keys {${Object.keys(node).join(\", \")}}`\n : \"\";\n console.error(\n `[treeContainsComponent] A non-renderable value${keys} is being passed as a React child. ` +\n \"React will throw 'Objects are not valid as a React child' at the real render site. \" +\n \"Stringify it (e.g. JSON.stringify) before rendering.\",\n node,\n );\n }\n return false;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBA,YAAuB;AAEvB,IAAM,oBAAoB,uBAAO,IAAI,cAAc;AAkB5C,SAAS,sBACd,MACA,WACS;AACT,MAAI,QAAQ,QAAQ,OAAO,SAAS,UAAW,QAAO;AAEtD,MAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,WAAO,KAAK,KAAK,CAAC,UAAU,sBAAsB,OAAO,SAAS,CAAC;AAAA,EACrE;AAEA,MAAU,qBAAe,IAAI,GAAG;AAC9B,QAAI,KAAK,SAAS,UAAW,QAAO;AACpC,UAAM,QAAQ,KAAK;AACnB,WAAO,MAAM,YAAY,OACrB,sBAAsB,MAAM,UAAU,SAAS,IAC/C;AAAA,EACN;AAGA,MAAI,OAAO,SAAS,YAAY,OAAO,SAAS,SAAU,QAAO;AAGjE,MACE,OAAO,SAAS,YACf,KAAgC,aAAa,mBAC9C;AACA,WAAO;AAAA,MACJ,KAAwC;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AAIA,MAAI,OAAO,SAAS,YAAY,OAAO,YAAY,MAAM;AACvD,WAAO,MAAM,KAAK,IAAiC,EAAE;AAAA,MAAK,CAAC,UACzD,sBAAsB,OAAO,SAAS;AAAA,IACxC;AAAA,EACF;AAIA,MACE,OAAO,YAAY,eACnB,MACA;AACA,UAAM,OACJ,OAAO,SAAS,WACZ,eAAe,OAAO,KAAK,IAAI,EAAE,KAAK,IAAI,CAAC,MAC3C;AACN,YAAQ;AAAA,MACN,iDAAiD,IAAI;AAAA,MAGrD;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @ai-matrx/kit/react-tree — safe React children-tree scanning.
|
|
5
|
+
*
|
|
6
|
+
* Ported from matrx-frontend `lib/react/treeContainsComponent.ts`; this
|
|
7
|
+
* subpath is the ONE home of the scanner inside the kit (the `/confirm`
|
|
8
|
+
* subpath's alert-dialog imports it from here — no duplicate bodies).
|
|
9
|
+
*
|
|
10
|
+
* Two deliberate divergences from the frontend original, both documented:
|
|
11
|
+
* - the dev-mode scream checks `typeof process` first, since this package may
|
|
12
|
+
* load in an unbundled browser context where `process` is undefined;
|
|
13
|
+
* - a React PORTAL child (`createPortal(...)` passed as a child) is a valid
|
|
14
|
+
* React child, but it is not an element, an iterable, or a primitive — the
|
|
15
|
+
* original fell through to the non-renderable branch, screamed a false
|
|
16
|
+
* positive in dev, and skipped the portal's content. Portals are now
|
|
17
|
+
* recognized and their children traversed.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Returns true if `Component` appears anywhere in the React element tree under
|
|
22
|
+
* `node`. Used to detect optional a11y children (e.g. DialogDescription)
|
|
23
|
+
* without rendering duplicates.
|
|
24
|
+
*
|
|
25
|
+
* DEFENSIVE + LOUD. A non-renderable node — a plain object or function passed
|
|
26
|
+
* as a React child — is a real bug: React throws "Objects are not valid as a
|
|
27
|
+
* React child" the instant it renders one. This a11y probe must NOT be the
|
|
28
|
+
* crash site. `React.Children.toArray` would throw HERE, producing a trace that
|
|
29
|
+
* points at the dialog primitive instead of the component that leaked the
|
|
30
|
+
* object (this misdirection has burned real debugging hours). So we walk the
|
|
31
|
+
* tree by hand, SKIP any non-renderable node, and scream in dev with its keys —
|
|
32
|
+
* then let React report the defect at the true render site with the offending
|
|
33
|
+
* component in the stack. For every VALID tree the result is identical to the
|
|
34
|
+
* old `React.Children.toArray(node).some(...)`.
|
|
35
|
+
*/
|
|
36
|
+
declare function treeContainsComponent(node: React.ReactNode, Component: React.ElementType): boolean;
|
|
37
|
+
|
|
38
|
+
export { treeContainsComponent };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @ai-matrx/kit/react-tree — safe React children-tree scanning.
|
|
5
|
+
*
|
|
6
|
+
* Ported from matrx-frontend `lib/react/treeContainsComponent.ts`; this
|
|
7
|
+
* subpath is the ONE home of the scanner inside the kit (the `/confirm`
|
|
8
|
+
* subpath's alert-dialog imports it from here — no duplicate bodies).
|
|
9
|
+
*
|
|
10
|
+
* Two deliberate divergences from the frontend original, both documented:
|
|
11
|
+
* - the dev-mode scream checks `typeof process` first, since this package may
|
|
12
|
+
* load in an unbundled browser context where `process` is undefined;
|
|
13
|
+
* - a React PORTAL child (`createPortal(...)` passed as a child) is a valid
|
|
14
|
+
* React child, but it is not an element, an iterable, or a primitive — the
|
|
15
|
+
* original fell through to the non-renderable branch, screamed a false
|
|
16
|
+
* positive in dev, and skipped the portal's content. Portals are now
|
|
17
|
+
* recognized and their children traversed.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Returns true if `Component` appears anywhere in the React element tree under
|
|
22
|
+
* `node`. Used to detect optional a11y children (e.g. DialogDescription)
|
|
23
|
+
* without rendering duplicates.
|
|
24
|
+
*
|
|
25
|
+
* DEFENSIVE + LOUD. A non-renderable node — a plain object or function passed
|
|
26
|
+
* as a React child — is a real bug: React throws "Objects are not valid as a
|
|
27
|
+
* React child" the instant it renders one. This a11y probe must NOT be the
|
|
28
|
+
* crash site. `React.Children.toArray` would throw HERE, producing a trace that
|
|
29
|
+
* points at the dialog primitive instead of the component that leaked the
|
|
30
|
+
* object (this misdirection has burned real debugging hours). So we walk the
|
|
31
|
+
* tree by hand, SKIP any non-renderable node, and scream in dev with its keys —
|
|
32
|
+
* then let React report the defect at the true render site with the offending
|
|
33
|
+
* component in the stack. For every VALID tree the result is identical to the
|
|
34
|
+
* old `React.Children.toArray(node).some(...)`.
|
|
35
|
+
*/
|
|
36
|
+
declare function treeContainsComponent(node: React.ReactNode, Component: React.ElementType): boolean;
|
|
37
|
+
|
|
38
|
+
export { treeContainsComponent };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// src/react-tree.ts
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
var REACT_PORTAL_TYPE = /* @__PURE__ */ Symbol.for("react.portal");
|
|
4
|
+
function treeContainsComponent(node, Component) {
|
|
5
|
+
if (node == null || typeof node === "boolean") return false;
|
|
6
|
+
if (Array.isArray(node)) {
|
|
7
|
+
return node.some((child) => treeContainsComponent(child, Component));
|
|
8
|
+
}
|
|
9
|
+
if (React.isValidElement(node)) {
|
|
10
|
+
if (node.type === Component) return true;
|
|
11
|
+
const props = node.props;
|
|
12
|
+
return props.children != null ? treeContainsComponent(props.children, Component) : false;
|
|
13
|
+
}
|
|
14
|
+
if (typeof node === "string" || typeof node === "number") return false;
|
|
15
|
+
if (typeof node === "object" && node.$$typeof === REACT_PORTAL_TYPE) {
|
|
16
|
+
return treeContainsComponent(
|
|
17
|
+
node.children,
|
|
18
|
+
Component
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
if (typeof node === "object" && Symbol.iterator in node) {
|
|
22
|
+
return Array.from(node).some(
|
|
23
|
+
(child) => treeContainsComponent(child, Component)
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
if (typeof process !== "undefined" && true) {
|
|
27
|
+
const keys = typeof node === "object" ? ` with keys {${Object.keys(node).join(", ")}}` : "";
|
|
28
|
+
console.error(
|
|
29
|
+
`[treeContainsComponent] A non-renderable value${keys} is being passed as a React child. React will throw 'Objects are not valid as a React child' at the real render site. Stringify it (e.g. JSON.stringify) before rendering.`,
|
|
30
|
+
node
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
treeContainsComponent
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=react-tree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/react-tree.ts"],"sourcesContent":["/**\n * @ai-matrx/kit/react-tree — safe React children-tree scanning.\n *\n * Ported from matrx-frontend `lib/react/treeContainsComponent.ts`; this\n * subpath is the ONE home of the scanner inside the kit (the `/confirm`\n * subpath's alert-dialog imports it from here — no duplicate bodies).\n *\n * Two deliberate divergences from the frontend original, both documented:\n * - the dev-mode scream checks `typeof process` first, since this package may\n * load in an unbundled browser context where `process` is undefined;\n * - a React PORTAL child (`createPortal(...)` passed as a child) is a valid\n * React child, but it is not an element, an iterable, or a primitive — the\n * original fell through to the non-renderable branch, screamed a false\n * positive in dev, and skipped the portal's content. Portals are now\n * recognized and their children traversed.\n */\n\nimport * as React from \"react\";\n\nconst REACT_PORTAL_TYPE = Symbol.for(\"react.portal\");\n\n/**\n * Returns true if `Component` appears anywhere in the React element tree under\n * `node`. Used to detect optional a11y children (e.g. DialogDescription)\n * without rendering duplicates.\n *\n * DEFENSIVE + LOUD. A non-renderable node — a plain object or function passed\n * as a React child — is a real bug: React throws \"Objects are not valid as a\n * React child\" the instant it renders one. This a11y probe must NOT be the\n * crash site. `React.Children.toArray` would throw HERE, producing a trace that\n * points at the dialog primitive instead of the component that leaked the\n * object (this misdirection has burned real debugging hours). So we walk the\n * tree by hand, SKIP any non-renderable node, and scream in dev with its keys —\n * then let React report the defect at the true render site with the offending\n * component in the stack. For every VALID tree the result is identical to the\n * old `React.Children.toArray(node).some(...)`.\n */\nexport function treeContainsComponent(\n node: React.ReactNode,\n Component: React.ElementType,\n): boolean {\n if (node == null || typeof node === \"boolean\") return false;\n\n if (Array.isArray(node)) {\n return node.some((child) => treeContainsComponent(child, Component));\n }\n\n if (React.isValidElement(node)) {\n if (node.type === Component) return true;\n const props = node.props as { children?: React.ReactNode };\n return props.children != null\n ? treeContainsComponent(props.children, Component)\n : false;\n }\n\n // Strings / numbers are valid leaf children but never the Component.\n if (typeof node === \"string\" || typeof node === \"number\") return false;\n\n // A portal is a valid child that is NOT an element: traverse its content.\n if (\n typeof node === \"object\" &&\n (node as { $$typeof?: unknown }).$$typeof === REACT_PORTAL_TYPE\n ) {\n return treeContainsComponent(\n (node as { children?: React.ReactNode }).children,\n Component,\n );\n }\n\n // Non-array iterables (Set, Map, generator) are valid React children — React\n // supports them — so traverse rather than reject.\n if (typeof node === \"object\" && Symbol.iterator in node) {\n return Array.from(node as Iterable<React.ReactNode>).some((child) =>\n treeContainsComponent(child, Component),\n );\n }\n\n // Anything else (a raw object, a function) is NOT a valid React child. React\n // will throw when it renders this; we must not throw first and hide the cause.\n if (\n typeof process !== \"undefined\" &&\n process.env.NODE_ENV !== \"production\"\n ) {\n const keys =\n typeof node === \"object\"\n ? ` with keys {${Object.keys(node).join(\", \")}}`\n : \"\";\n console.error(\n `[treeContainsComponent] A non-renderable value${keys} is being passed as a React child. ` +\n \"React will throw 'Objects are not valid as a React child' at the real render site. \" +\n \"Stringify it (e.g. JSON.stringify) before rendering.\",\n node,\n );\n }\n return false;\n}\n"],"mappings":";AAiBA,YAAY,WAAW;AAEvB,IAAM,oBAAoB,uBAAO,IAAI,cAAc;AAkB5C,SAAS,sBACd,MACA,WACS;AACT,MAAI,QAAQ,QAAQ,OAAO,SAAS,UAAW,QAAO;AAEtD,MAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,WAAO,KAAK,KAAK,CAAC,UAAU,sBAAsB,OAAO,SAAS,CAAC;AAAA,EACrE;AAEA,MAAU,qBAAe,IAAI,GAAG;AAC9B,QAAI,KAAK,SAAS,UAAW,QAAO;AACpC,UAAM,QAAQ,KAAK;AACnB,WAAO,MAAM,YAAY,OACrB,sBAAsB,MAAM,UAAU,SAAS,IAC/C;AAAA,EACN;AAGA,MAAI,OAAO,SAAS,YAAY,OAAO,SAAS,SAAU,QAAO;AAGjE,MACE,OAAO,SAAS,YACf,KAAgC,aAAa,mBAC9C;AACA,WAAO;AAAA,MACJ,KAAwC;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AAIA,MAAI,OAAO,SAAS,YAAY,OAAO,YAAY,MAAM;AACvD,WAAO,MAAM,KAAK,IAAiC,EAAE;AAAA,MAAK,CAAC,UACzD,sBAAsB,OAAO,SAAS;AAAA,IACxC;AAAA,EACF;AAIA,MACE,OAAO,YAAY,eACnB,MACA;AACA,UAAM,OACJ,OAAO,SAAS,WACZ,eAAe,OAAO,KAAK,IAAI,EAAE,KAAK,IAAI,CAAC,MAC3C;AACN,YAAQ;AAAA,MACN,iDAAiD,IAAI;AAAA,MAGrD;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
|