@twentyfirstmedia/ui-core 0.5.0 → 0.7.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/dist/components/admin.d.ts.map +1 -1
- package/dist/components/admin.js +31 -21
- package/dist/components/admin.js.map +1 -1
- package/dist/components/agents.d.ts.map +1 -1
- package/dist/components/agents.js +21 -18
- package/dist/components/agents.js.map +1 -1
- package/dist/components/app-chrome.d.ts.map +1 -1
- package/dist/components/app-chrome.js +5 -5
- package/dist/components/app-chrome.js.map +1 -1
- package/dist/components/approvals.d.ts.map +1 -1
- package/dist/components/approvals.js +2 -2
- package/dist/components/approvals.js.map +1 -1
- package/dist/components/building-blocks.d.ts.map +1 -1
- package/dist/components/building-blocks.js +13 -10
- package/dist/components/building-blocks.js.map +1 -1
- package/dist/components/charts.d.ts +2 -2
- package/dist/components/charts.d.ts.map +1 -1
- package/dist/components/charts.js +14 -11
- package/dist/components/charts.js.map +1 -1
- package/dist/components/chat.d.ts +2 -2
- package/dist/components/chat.d.ts.map +1 -1
- package/dist/components/chat.js +22 -18
- package/dist/components/chat.js.map +1 -1
- package/dist/components/commerce.d.ts.map +1 -1
- package/dist/components/commerce.js +20 -16
- package/dist/components/commerce.js.map +1 -1
- package/dist/components/data-display.d.ts +0 -5
- package/dist/components/data-display.d.ts.map +1 -1
- package/dist/components/data-display.js +41 -22
- package/dist/components/data-display.js.map +1 -1
- package/dist/components/data-table.d.ts +2 -2
- package/dist/components/data-table.d.ts.map +1 -1
- package/dist/components/data-table.js +3 -3
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/developer.d.ts.map +1 -1
- package/dist/components/developer.js +7 -6
- package/dist/components/developer.js.map +1 -1
- package/dist/components/feedback.d.ts.map +1 -1
- package/dist/components/feedback.js +5 -4
- package/dist/components/feedback.js.map +1 -1
- package/dist/components/forms.d.ts.map +1 -1
- package/dist/components/forms.js +63 -21
- package/dist/components/forms.js.map +1 -1
- package/dist/components/foundation.d.ts.map +1 -1
- package/dist/components/foundation.js +13 -9
- package/dist/components/foundation.js.map +1 -1
- package/dist/components/household.d.ts +19 -0
- package/dist/components/household.d.ts.map +1 -0
- package/dist/components/household.js +137 -0
- package/dist/components/household.js.map +1 -0
- package/dist/components/index.d.ts +4 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +4 -1
- package/dist/components/index.js.map +1 -1
- package/dist/components/inputs.d.ts.map +1 -1
- package/dist/components/inputs.js +33 -8
- package/dist/components/inputs.js.map +1 -1
- package/dist/components/life.d.ts +26 -0
- package/dist/components/life.d.ts.map +1 -0
- package/dist/components/life.js +186 -0
- package/dist/components/life.js.map +1 -0
- package/dist/components/mail.d.ts.map +1 -1
- package/dist/components/mail.js +5 -5
- package/dist/components/mail.js.map +1 -1
- package/dist/components/media.d.ts.map +1 -1
- package/dist/components/media.js +62 -11
- package/dist/components/media.js.map +1 -1
- package/dist/components/navigation.d.ts +3 -1
- package/dist/components/navigation.d.ts.map +1 -1
- package/dist/components/navigation.js +287 -20
- package/dist/components/navigation.js.map +1 -1
- package/dist/components/operations.d.ts.map +1 -1
- package/dist/components/operations.js +10 -10
- package/dist/components/operations.js.map +1 -1
- package/dist/components/overlays.d.ts.map +1 -1
- package/dist/components/overlays.js +2 -2
- package/dist/components/overlays.js.map +1 -1
- package/dist/components/previews.js +2 -2
- package/dist/components/previews.js.map +1 -1
- package/dist/components/primitives.js +2 -2
- package/dist/components/primitives.js.map +1 -1
- package/dist/components/qr-code.d.ts +8 -0
- package/dist/components/qr-code.d.ts.map +1 -0
- package/dist/components/qr-code.js +18 -0
- package/dist/components/qr-code.js.map +1 -0
- package/dist/components/real-estate.d.ts.map +1 -1
- package/dist/components/real-estate.js +5 -5
- package/dist/components/real-estate.js.map +1 -1
- package/dist/components/rich-text-editor.d.ts.map +1 -1
- package/dist/components/rich-text-editor.js +1 -1
- package/dist/components/rich-text-editor.js.map +1 -1
- package/dist/components/select-menu.js +1 -1
- package/dist/components/select-menu.js.map +1 -1
- package/dist/components/shell.d.ts +0 -4
- package/dist/components/shell.d.ts.map +1 -1
- package/dist/components/shell.js +100 -7
- package/dist/components/shell.js.map +1 -1
- package/dist/components/site.d.ts.map +1 -1
- package/dist/components/site.js +8 -6
- package/dist/components/site.js.map +1 -1
- package/dist/components/status.d.ts.map +1 -1
- package/dist/components/status.js +2 -2
- package/dist/components/status.js.map +1 -1
- package/dist/components/system.d.ts.map +1 -1
- package/dist/components/system.js +3 -2
- package/dist/components/system.js.map +1 -1
- package/dist/components/types.d.ts +589 -18
- package/dist/components/types.d.ts.map +1 -1
- package/dist/components/workspace.d.ts.map +1 -1
- package/dist/components/workspace.js +15 -12
- package/dist/components/workspace.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/qr.d.ts +14 -0
- package/dist/qr.d.ts.map +1 -0
- package/dist/qr.js +298 -0
- package/dist/qr.js.map +1 -0
- package/dist/strings.d.ts +126 -4
- package/dist/strings.d.ts.map +1 -1
- package/dist/strings.js +88 -1
- package/dist/strings.js.map +1 -1
- package/dist/themes.css +15592 -10463
- package/package.json +2 -1
- package/src/components/admin.tsx +66 -23
- package/src/components/agents.tsx +22 -20
- package/src/components/app-chrome.tsx +9 -5
- package/src/components/approvals.tsx +3 -1
- package/src/components/building-blocks.tsx +24 -13
- package/src/components/charts.tsx +17 -12
- package/src/components/chat.tsx +54 -31
- package/src/components/commerce.tsx +58 -24
- package/src/components/data-display.tsx +106 -38
- package/src/components/data-table.tsx +11 -7
- package/src/components/developer.tsx +15 -6
- package/src/components/feedback.tsx +5 -3
- package/src/components/forms.tsx +101 -23
- package/src/components/foundation.tsx +9 -5
- package/src/components/household.tsx +489 -0
- package/src/components/index.ts +32 -1
- package/src/components/inputs.tsx +44 -7
- package/src/components/life.tsx +672 -0
- package/src/components/mail.tsx +7 -3
- package/src/components/media.tsx +88 -16
- package/src/components/navigation.tsx +651 -71
- package/src/components/operations.tsx +17 -8
- package/src/components/overlays.tsx +7 -2
- package/src/components/previews.tsx +2 -2
- package/src/components/primitives.tsx +2 -2
- package/src/components/qr-code.tsx +30 -0
- package/src/components/real-estate.tsx +7 -4
- package/src/components/rich-text-editor.tsx +7 -1
- package/src/components/select-menu.tsx +1 -1
- package/src/components/shell.tsx +237 -11
- package/src/components/site.tsx +18 -4
- package/src/components/status.tsx +2 -0
- package/src/components/system.tsx +3 -2
- package/src/components/types.ts +583 -18
- package/src/components/workspace.tsx +37 -15
- package/src/index.ts +2 -0
- package/src/qr.ts +300 -0
- package/src/strings.ts +297 -1
|
@@ -31,7 +31,11 @@ import type {
|
|
|
31
31
|
export function SubNav(p: SubNavProps) {
|
|
32
32
|
const t = useStrings(WORKSPACE_STRINGS);
|
|
33
33
|
return (
|
|
34
|
-
<nav
|
|
34
|
+
<nav
|
|
35
|
+
style={p.style}
|
|
36
|
+
className={cx("tf-subnav", p.className)}
|
|
37
|
+
aria-label={t.sectionPages(p.eyebrow)}
|
|
38
|
+
>
|
|
35
39
|
<div className="tf-subnav-head">
|
|
36
40
|
{p.eyebrow && <span className="tf-subnav-eyebrow">{p.eyebrow}</span>}
|
|
37
41
|
{p.action && (
|
|
@@ -74,8 +78,9 @@ export function Drawer(p: DrawerProps) {
|
|
|
74
78
|
if (!pr.mounted) return null;
|
|
75
79
|
return (
|
|
76
80
|
<div
|
|
81
|
+
style={p.style}
|
|
77
82
|
{...leaving(pr.closing)}
|
|
78
|
-
className={cx("tf-drawer-wrap", p.inline && "inline")}
|
|
83
|
+
className={cx("tf-drawer-wrap", p.inline && "inline", p.className)}
|
|
79
84
|
onMouseDown={(e) => {
|
|
80
85
|
if (e.target === e.currentTarget && p.onClose) p.onClose?.();
|
|
81
86
|
}}
|
|
@@ -180,8 +185,9 @@ export function CommandPalette(p: CommandPaletteProps) {
|
|
|
180
185
|
if (!pr.mounted) return null;
|
|
181
186
|
return (
|
|
182
187
|
<div
|
|
188
|
+
style={p.style}
|
|
183
189
|
{...leaving(pr.closing)}
|
|
184
|
-
className={cx("tf-palette-wrap", p.inline && "inline")}
|
|
190
|
+
className={cx("tf-palette-wrap", p.inline && "inline", p.className)}
|
|
185
191
|
onMouseDown={(e) => {
|
|
186
192
|
if (e.target === e.currentTarget && p.onClose) p.onClose?.();
|
|
187
193
|
}}
|
|
@@ -273,7 +279,12 @@ export function NotificationList(p: NotificationListProps) {
|
|
|
273
279
|
const f = React.useState<"all" | "unread">("all"),
|
|
274
280
|
items = (p.items || []).filter((n) => f[0] === "all" || n.unread);
|
|
275
281
|
return (
|
|
276
|
-
<div
|
|
282
|
+
<div
|
|
283
|
+
style={p.style}
|
|
284
|
+
className={cx("tf-notes", p.className)}
|
|
285
|
+
role="region"
|
|
286
|
+
aria-label={t.notifications}
|
|
287
|
+
>
|
|
277
288
|
<div
|
|
278
289
|
className="tf-header"
|
|
279
290
|
style={{
|
|
@@ -353,12 +364,13 @@ export function Flow(p: FlowProps) {
|
|
|
353
364
|
const t = useStrings(WORKSPACE_STRINGS);
|
|
354
365
|
return (
|
|
355
366
|
<ol
|
|
356
|
-
className="tf-flow tf-stagger"
|
|
357
|
-
style={
|
|
358
|
-
{
|
|
367
|
+
className={cx("tf-flow tf-stagger", p.className)}
|
|
368
|
+
style={{
|
|
369
|
+
...({
|
|
359
370
|
"--base": "120ms",
|
|
360
|
-
} as React.CSSProperties
|
|
361
|
-
|
|
371
|
+
} as React.CSSProperties),
|
|
372
|
+
...p.style,
|
|
373
|
+
}}
|
|
362
374
|
>
|
|
363
375
|
{(p.steps || []).map((s, i) => {
|
|
364
376
|
const steps = STEP(),
|
|
@@ -467,7 +479,7 @@ export function FileThumb(p: FileThumbProps) {
|
|
|
467
479
|
</span>
|
|
468
480
|
);
|
|
469
481
|
return (
|
|
470
|
-
<span className="tf-t-doc">
|
|
482
|
+
<span style={p.style} className={cx("tf-t-doc", p.className)}>
|
|
471
483
|
<b>{(p.title || "").replace(/\.[^.]+$/, "")}</b>
|
|
472
484
|
{Array.from(
|
|
473
485
|
{
|
|
@@ -489,7 +501,12 @@ export function FileCard(p: FileCardProps) {
|
|
|
489
501
|
const t = useStrings(WORKSPACE_STRINGS);
|
|
490
502
|
const k = FILE_KIND[p.kind] || FILE_KIND.doc;
|
|
491
503
|
return (
|
|
492
|
-
<button
|
|
504
|
+
<button
|
|
505
|
+
style={p.style}
|
|
506
|
+
type="button"
|
|
507
|
+
className={cx("tf-filecard", p.className)}
|
|
508
|
+
onClick={p.onClick}
|
|
509
|
+
>
|
|
493
510
|
<span className={"tf-thumb tf-bg-" + k[1]}>
|
|
494
511
|
<FileThumb kind={p.kind} title={p.name} />
|
|
495
512
|
</span>
|
|
@@ -507,7 +524,12 @@ export function Wizard(p: WizardProps) {
|
|
|
507
524
|
cur = p.current || 0,
|
|
508
525
|
done = p.done || {};
|
|
509
526
|
return (
|
|
510
|
-
<div
|
|
527
|
+
<div
|
|
528
|
+
style={p.style}
|
|
529
|
+
className={cx("tf-wizard", p.className)}
|
|
530
|
+
role="group"
|
|
531
|
+
aria-label={p.label || t.setup}
|
|
532
|
+
>
|
|
511
533
|
<aside className="tf-wizard-rail">
|
|
512
534
|
{p.brand && (
|
|
513
535
|
<span
|
|
@@ -570,7 +592,7 @@ export function PermissionMatrix(p: PermissionMatrixProps) {
|
|
|
570
592
|
const box = React.useRef<HTMLDivElement>(null);
|
|
571
593
|
useScrollFocus(box);
|
|
572
594
|
return (
|
|
573
|
-
<div ref={box} className="tf-scroll">
|
|
595
|
+
<div style={p.style} ref={box} className={cx("tf-scroll", p.className)}>
|
|
574
596
|
<table className="tf-table tf-perm">
|
|
575
597
|
<thead>
|
|
576
598
|
<tr>
|
|
@@ -648,7 +670,7 @@ export function InvoicePreview(p: InvoicePreviewProps) {
|
|
|
648
670
|
});
|
|
649
671
|
const fmt = p.format || money2;
|
|
650
672
|
return (
|
|
651
|
-
<div className="tf-paper">
|
|
673
|
+
<div style={p.style} className={cx("tf-paper", p.className)}>
|
|
652
674
|
<div className="tf-paper-top">
|
|
653
675
|
<div>
|
|
654
676
|
{p.brand && (
|
|
@@ -720,7 +742,7 @@ export function TransactionRow(p: TransactionRowProps) {
|
|
|
720
742
|
const open = React.useState(false),
|
|
721
743
|
inc = p.amount > 0;
|
|
722
744
|
return (
|
|
723
|
-
<div className={cx("tf-tx", !p.matched && "unmatched")}>
|
|
745
|
+
<div style={p.style} className={cx("tf-tx", !p.matched && "unmatched", p.className)}>
|
|
724
746
|
<div className="tf-tx-row">
|
|
725
747
|
<TintIcon icon={inc ? "download" : "send"} tint={inc ? "mint" : "plain"} />
|
|
726
748
|
<div className="tf-tx-text">
|
package/src/index.ts
CHANGED
package/src/qr.ts
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small QR code encoder (ISO/IEC 18004): byte mode, versions 1–40, error correction L/M/Q/H,
|
|
3
|
+
* the mask with the lowest penalty. Enough for otpauth:// links, URLs and short texts; it runs
|
|
4
|
+
* locally, so nothing leaves the app.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type QrLevel = "L" | "M" | "Q" | "H";
|
|
8
|
+
|
|
9
|
+
const LEVEL_BITS: Record<QrLevel, number> = { L: 1, M: 0, Q: 3, H: 2 };
|
|
10
|
+
const LEVEL_INDEX: Record<QrLevel, number> = { L: 0, M: 1, Q: 2, H: 3 };
|
|
11
|
+
|
|
12
|
+
// error correction codewords per block, and number of blocks, by level (L, M, Q, H) and version
|
|
13
|
+
// prettier-ignore
|
|
14
|
+
const ECC_PER_BLOCK: number[][] = [
|
|
15
|
+
[-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30],
|
|
16
|
+
[-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28],
|
|
17
|
+
[-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30],
|
|
18
|
+
[-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30],
|
|
19
|
+
];
|
|
20
|
+
// prettier-ignore
|
|
21
|
+
const BLOCKS: number[][] = [
|
|
22
|
+
[-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25],
|
|
23
|
+
[-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49],
|
|
24
|
+
[-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68],
|
|
25
|
+
[-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81],
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
/** Modules of all function patterns and data for a version. */
|
|
29
|
+
function rawModules(ver: number): number {
|
|
30
|
+
let r = (16 * ver + 128) * ver + 64;
|
|
31
|
+
if (ver >= 2) {
|
|
32
|
+
const n = Math.floor(ver / 7) + 2;
|
|
33
|
+
r -= (25 * n - 10) * n - 55;
|
|
34
|
+
if (ver >= 7) r -= 36;
|
|
35
|
+
}
|
|
36
|
+
return r;
|
|
37
|
+
}
|
|
38
|
+
function dataCodewords(ver: number, lvl: number): number {
|
|
39
|
+
return Math.floor(rawModules(ver) / 8) - ECC_PER_BLOCK[lvl]![ver]! * BLOCKS[lvl]![ver]!;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Reed–Solomon over GF(256) with the polynomial 0x11d */
|
|
43
|
+
function mul(x: number, y: number): number {
|
|
44
|
+
let z = 0;
|
|
45
|
+
for (let i = 7; i >= 0; i--) {
|
|
46
|
+
z = (z << 1) ^ ((z >>> 7) * 0x11d);
|
|
47
|
+
z ^= ((y >>> i) & 1) * x;
|
|
48
|
+
}
|
|
49
|
+
return z & 0xff;
|
|
50
|
+
}
|
|
51
|
+
function divisor(degree: number): number[] {
|
|
52
|
+
const r = new Array<number>(degree).fill(0);
|
|
53
|
+
r[degree - 1] = 1;
|
|
54
|
+
let root = 1;
|
|
55
|
+
for (let i = 0; i < degree; i++) {
|
|
56
|
+
for (let j = 0; j < r.length; j++) {
|
|
57
|
+
r[j] = mul(r[j]!, root);
|
|
58
|
+
if (j + 1 < r.length) r[j] = r[j]! ^ r[j + 1]!;
|
|
59
|
+
}
|
|
60
|
+
root = mul(root, 0x02);
|
|
61
|
+
}
|
|
62
|
+
return r;
|
|
63
|
+
}
|
|
64
|
+
function remainder(data: number[], div: number[]): number[] {
|
|
65
|
+
const r = new Array<number>(div.length).fill(0);
|
|
66
|
+
for (const b of data) {
|
|
67
|
+
const f = b ^ r.shift()!;
|
|
68
|
+
r.push(0);
|
|
69
|
+
div.forEach((c, i) => (r[i] = r[i]! ^ mul(c, f)));
|
|
70
|
+
}
|
|
71
|
+
return r;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function alignment(ver: number): number[] {
|
|
75
|
+
if (ver === 1) return [];
|
|
76
|
+
const n = Math.floor(ver / 7) + 2;
|
|
77
|
+
const step = ver === 32 ? 26 : Math.ceil((ver * 4 + 4) / (n * 2 - 2)) * 2;
|
|
78
|
+
const out = [6];
|
|
79
|
+
for (let pos = ver * 4 + 10; out.length < n; pos -= step) out.splice(1, 0, pos);
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Encodes text as a QR code: a square of booleans (true = dark), without the quiet zone.
|
|
85
|
+
* Throws when the text does not fit version 40.
|
|
86
|
+
*/
|
|
87
|
+
export function qrMatrix(text: string, level: QrLevel = "M"): boolean[][] {
|
|
88
|
+
const bytes = Array.from(new TextEncoder().encode(text));
|
|
89
|
+
const lvl = LEVEL_INDEX[level];
|
|
90
|
+
let ver = 1;
|
|
91
|
+
for (; ; ver++) {
|
|
92
|
+
if (ver > 40) throw new Error("Text too long for a QR code");
|
|
93
|
+
const countBits = ver < 10 ? 8 : 16;
|
|
94
|
+
if (4 + countBits + bytes.length * 8 <= dataCodewords(ver, lvl) * 8) break;
|
|
95
|
+
}
|
|
96
|
+
const size = ver * 4 + 17;
|
|
97
|
+
|
|
98
|
+
// data bits: byte mode, count, bytes, terminator, padding
|
|
99
|
+
const bits: number[] = [];
|
|
100
|
+
const put = (v: number, n: number) => {
|
|
101
|
+
for (let i = n - 1; i >= 0; i--) bits.push((v >>> i) & 1);
|
|
102
|
+
};
|
|
103
|
+
put(0b0100, 4);
|
|
104
|
+
put(bytes.length, ver < 10 ? 8 : 16);
|
|
105
|
+
bytes.forEach((b) => put(b, 8));
|
|
106
|
+
const cap = dataCodewords(ver, lvl) * 8;
|
|
107
|
+
put(0, Math.min(4, cap - bits.length));
|
|
108
|
+
put(0, (8 - (bits.length % 8)) % 8);
|
|
109
|
+
for (let pad = 0xec; bits.length < cap; pad ^= 0xec ^ 0x11) put(pad, 8);
|
|
110
|
+
const data: number[] = [];
|
|
111
|
+
for (let i = 0; i < bits.length; i += 8) data.push(parseInt(bits.slice(i, i + 8).join(""), 2));
|
|
112
|
+
|
|
113
|
+
// blocks with error correction, interleaved
|
|
114
|
+
const nBlocks = BLOCKS[lvl]![ver]!,
|
|
115
|
+
eccLen = ECC_PER_BLOCK[lvl]![ver]!,
|
|
116
|
+
raw = Math.floor(rawModules(ver) / 8),
|
|
117
|
+
shortBlocks = nBlocks - (raw % nBlocks),
|
|
118
|
+
shortLen = Math.floor(raw / nBlocks);
|
|
119
|
+
const div = divisor(eccLen);
|
|
120
|
+
const blocks: number[][] = [];
|
|
121
|
+
for (let i = 0, k = 0; i < nBlocks; i++) {
|
|
122
|
+
const len = shortLen - eccLen + (i < shortBlocks ? 0 : 1);
|
|
123
|
+
const d = data.slice(k, k + len);
|
|
124
|
+
k += len;
|
|
125
|
+
const ecc = remainder(d, div);
|
|
126
|
+
if (i < shortBlocks) d.push(0);
|
|
127
|
+
blocks.push(d.concat(ecc));
|
|
128
|
+
}
|
|
129
|
+
const codewords: number[] = [];
|
|
130
|
+
for (let i = 0; i < blocks[0]!.length; i++)
|
|
131
|
+
blocks.forEach((b, j) => {
|
|
132
|
+
if (i !== shortLen - eccLen || j >= shortBlocks) codewords.push(b[i]!);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// function patterns
|
|
136
|
+
const m: boolean[][] = Array.from({ length: size }, () => new Array<boolean>(size).fill(false));
|
|
137
|
+
const fn: boolean[][] = Array.from({ length: size }, () => new Array<boolean>(size).fill(false));
|
|
138
|
+
const set = (x: number, y: number, dark: boolean) => {
|
|
139
|
+
m[y]![x] = dark;
|
|
140
|
+
fn[y]![x] = true;
|
|
141
|
+
};
|
|
142
|
+
for (let i = 0; i < size; i++) {
|
|
143
|
+
set(6, i, i % 2 === 0);
|
|
144
|
+
set(i, 6, i % 2 === 0);
|
|
145
|
+
}
|
|
146
|
+
const finder = (cx: number, cy: number) => {
|
|
147
|
+
for (let dy = -4; dy <= 4; dy++)
|
|
148
|
+
for (let dx = -4; dx <= 4; dx++) {
|
|
149
|
+
const d = Math.max(Math.abs(dx), Math.abs(dy)),
|
|
150
|
+
x = cx + dx,
|
|
151
|
+
y = cy + dy;
|
|
152
|
+
if (x >= 0 && x < size && y >= 0 && y < size) set(x, y, d !== 2 && d !== 4);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
finder(3, 3);
|
|
156
|
+
finder(size - 4, 3);
|
|
157
|
+
finder(3, size - 4);
|
|
158
|
+
const al = alignment(ver);
|
|
159
|
+
al.forEach((ay, i) =>
|
|
160
|
+
al.forEach((ax, j) => {
|
|
161
|
+
if (
|
|
162
|
+
(i === 0 && j === 0) ||
|
|
163
|
+
(i === 0 && j === al.length - 1) ||
|
|
164
|
+
(i === al.length - 1 && j === 0)
|
|
165
|
+
)
|
|
166
|
+
return;
|
|
167
|
+
for (let dy = -2; dy <= 2; dy++)
|
|
168
|
+
for (let dx = -2; dx <= 2; dx++)
|
|
169
|
+
set(ax + dx, ay + dy, Math.max(Math.abs(dx), Math.abs(dy)) !== 1);
|
|
170
|
+
}),
|
|
171
|
+
);
|
|
172
|
+
const format = (mask: number) => {
|
|
173
|
+
const d = (LEVEL_BITS[level] << 3) | mask;
|
|
174
|
+
let r = d;
|
|
175
|
+
for (let i = 0; i < 10; i++) r = (r << 1) ^ ((r >>> 9) * 0x537);
|
|
176
|
+
const b = ((d << 10) | r) ^ 0x5412;
|
|
177
|
+
const bit = (i: number) => ((b >>> i) & 1) !== 0;
|
|
178
|
+
for (let i = 0; i <= 5; i++) set(8, i, bit(i));
|
|
179
|
+
set(8, 7, bit(6));
|
|
180
|
+
set(8, 8, bit(7));
|
|
181
|
+
set(7, 8, bit(8));
|
|
182
|
+
for (let i = 9; i < 15; i++) set(14 - i, 8, bit(i));
|
|
183
|
+
for (let i = 0; i < 8; i++) set(size - 1 - i, 8, bit(i));
|
|
184
|
+
for (let i = 8; i < 15; i++) set(8, size - 15 + i, bit(i));
|
|
185
|
+
set(8, size - 8, true);
|
|
186
|
+
};
|
|
187
|
+
format(0); // reserve the format areas
|
|
188
|
+
if (ver >= 7) {
|
|
189
|
+
let r = ver;
|
|
190
|
+
for (let i = 0; i < 12; i++) r = (r << 1) ^ ((r >>> 11) * 0x1f25);
|
|
191
|
+
const b = (ver << 12) | r;
|
|
192
|
+
for (let i = 0; i < 18; i++) {
|
|
193
|
+
const dark = ((b >>> i) & 1) !== 0,
|
|
194
|
+
a = size - 11 + (i % 3),
|
|
195
|
+
c = Math.floor(i / 3);
|
|
196
|
+
set(a, c, dark);
|
|
197
|
+
set(c, a, dark);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// data in the zigzag
|
|
202
|
+
let i = 0;
|
|
203
|
+
for (let right = size - 1; right >= 1; right -= 2) {
|
|
204
|
+
if (right === 6) right = 5;
|
|
205
|
+
for (let vert = 0; vert < size; vert++)
|
|
206
|
+
for (let j = 0; j < 2; j++) {
|
|
207
|
+
const x = right - j,
|
|
208
|
+
up = ((right + 1) & 2) === 0,
|
|
209
|
+
y = up ? size - 1 - vert : vert;
|
|
210
|
+
if (!fn[y]![x] && i < codewords.length * 8) {
|
|
211
|
+
m[y]![x] = ((codewords[i >>> 3]! >>> (7 - (i & 7))) & 1) !== 0;
|
|
212
|
+
i++;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const masked = (mask: number) => {
|
|
218
|
+
const out = m.map((row) => row.slice());
|
|
219
|
+
for (let y = 0; y < size; y++)
|
|
220
|
+
for (let x = 0; x < size; x++) {
|
|
221
|
+
if (fn[y]![x]) continue;
|
|
222
|
+
const flip = [
|
|
223
|
+
(x + y) % 2 === 0,
|
|
224
|
+
y % 2 === 0,
|
|
225
|
+
x % 3 === 0,
|
|
226
|
+
(x + y) % 3 === 0,
|
|
227
|
+
(Math.floor(x / 3) + Math.floor(y / 2)) % 2 === 0,
|
|
228
|
+
((x * y) % 2) + ((x * y) % 3) === 0,
|
|
229
|
+
(((x * y) % 2) + ((x * y) % 3)) % 2 === 0,
|
|
230
|
+
(((x + y) % 2) + ((x * y) % 3)) % 2 === 0,
|
|
231
|
+
][mask];
|
|
232
|
+
if (flip) out[y]![x] = !out[y]![x];
|
|
233
|
+
}
|
|
234
|
+
return out;
|
|
235
|
+
};
|
|
236
|
+
const penalty = (g: boolean[][]) => {
|
|
237
|
+
let p = 0;
|
|
238
|
+
const lines = (get: (a: number, b: number) => boolean) => {
|
|
239
|
+
for (let a = 0; a < size; a++) {
|
|
240
|
+
let run = 1;
|
|
241
|
+
const seq: boolean[] = [];
|
|
242
|
+
for (let b = 0; b < size; b++) {
|
|
243
|
+
seq.push(get(a, b));
|
|
244
|
+
if (b > 0 && get(a, b) === get(a, b - 1)) {
|
|
245
|
+
run++;
|
|
246
|
+
if (run === 5) p += 3;
|
|
247
|
+
else if (run > 5) p++;
|
|
248
|
+
} else run = 1;
|
|
249
|
+
}
|
|
250
|
+
// finder-like patterns: 1:1:3:1:1 with four light modules on one side
|
|
251
|
+
const s = seq.map((v) => (v ? "1" : "0")).join("");
|
|
252
|
+
for (const pat of ["10111010000", "00001011101"]) {
|
|
253
|
+
let k = s.indexOf(pat);
|
|
254
|
+
while (k !== -1) {
|
|
255
|
+
p += 40;
|
|
256
|
+
k = s.indexOf(pat, k + 1);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
lines((a, b) => g[a]![b]!);
|
|
262
|
+
lines((a, b) => g[b]![a]!);
|
|
263
|
+
for (let y = 0; y < size - 1; y++)
|
|
264
|
+
for (let x = 0; x < size - 1; x++) {
|
|
265
|
+
const c = g[y]![x];
|
|
266
|
+
if (c === g[y]![x + 1] && c === g[y + 1]![x] && c === g[y + 1]![x + 1]) p += 3;
|
|
267
|
+
}
|
|
268
|
+
const dark = g.reduce((s, row) => s + row.filter(Boolean).length, 0);
|
|
269
|
+
const total = size * size;
|
|
270
|
+
p += Math.max(0, Math.ceil(Math.abs(dark * 20 - total * 10) / total) - 1) * 10;
|
|
271
|
+
return p;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
let best: boolean[][] = m,
|
|
275
|
+
bestScore = Infinity;
|
|
276
|
+
for (let mask = 0; mask < 8; mask++) {
|
|
277
|
+
format(mask);
|
|
278
|
+
const g = masked(mask);
|
|
279
|
+
// the format bits were written into m; copy them into the masked grid
|
|
280
|
+
for (let y = 0; y < size; y++)
|
|
281
|
+
for (let x = 0; x < size; x++) if (fn[y]![x]) g[y]![x] = m[y]![x]!;
|
|
282
|
+
const score = penalty(g);
|
|
283
|
+
if (score < bestScore) {
|
|
284
|
+
bestScore = score;
|
|
285
|
+
best = g;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return best;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** SVG path data for a QR matrix: one 1×1 square per dark module, offset by the quiet zone. */
|
|
292
|
+
export function qrPath(matrix: boolean[][], quiet = 4): string {
|
|
293
|
+
let d = "";
|
|
294
|
+
matrix.forEach((row, y) =>
|
|
295
|
+
row.forEach((dark, x) => {
|
|
296
|
+
if (dark) d += `M${x + quiet} ${y + quiet}h1v1h-1z`;
|
|
297
|
+
}),
|
|
298
|
+
);
|
|
299
|
+
return d;
|
|
300
|
+
}
|