@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.
Files changed (163) hide show
  1. package/dist/components/admin.d.ts.map +1 -1
  2. package/dist/components/admin.js +31 -21
  3. package/dist/components/admin.js.map +1 -1
  4. package/dist/components/agents.d.ts.map +1 -1
  5. package/dist/components/agents.js +21 -18
  6. package/dist/components/agents.js.map +1 -1
  7. package/dist/components/app-chrome.d.ts.map +1 -1
  8. package/dist/components/app-chrome.js +5 -5
  9. package/dist/components/app-chrome.js.map +1 -1
  10. package/dist/components/approvals.d.ts.map +1 -1
  11. package/dist/components/approvals.js +2 -2
  12. package/dist/components/approvals.js.map +1 -1
  13. package/dist/components/building-blocks.d.ts.map +1 -1
  14. package/dist/components/building-blocks.js +13 -10
  15. package/dist/components/building-blocks.js.map +1 -1
  16. package/dist/components/charts.d.ts +2 -2
  17. package/dist/components/charts.d.ts.map +1 -1
  18. package/dist/components/charts.js +14 -11
  19. package/dist/components/charts.js.map +1 -1
  20. package/dist/components/chat.d.ts +2 -2
  21. package/dist/components/chat.d.ts.map +1 -1
  22. package/dist/components/chat.js +22 -18
  23. package/dist/components/chat.js.map +1 -1
  24. package/dist/components/commerce.d.ts.map +1 -1
  25. package/dist/components/commerce.js +20 -16
  26. package/dist/components/commerce.js.map +1 -1
  27. package/dist/components/data-display.d.ts +0 -5
  28. package/dist/components/data-display.d.ts.map +1 -1
  29. package/dist/components/data-display.js +41 -22
  30. package/dist/components/data-display.js.map +1 -1
  31. package/dist/components/data-table.d.ts +2 -2
  32. package/dist/components/data-table.d.ts.map +1 -1
  33. package/dist/components/data-table.js +3 -3
  34. package/dist/components/data-table.js.map +1 -1
  35. package/dist/components/developer.d.ts.map +1 -1
  36. package/dist/components/developer.js +7 -6
  37. package/dist/components/developer.js.map +1 -1
  38. package/dist/components/feedback.d.ts.map +1 -1
  39. package/dist/components/feedback.js +5 -4
  40. package/dist/components/feedback.js.map +1 -1
  41. package/dist/components/forms.d.ts.map +1 -1
  42. package/dist/components/forms.js +63 -21
  43. package/dist/components/forms.js.map +1 -1
  44. package/dist/components/foundation.d.ts.map +1 -1
  45. package/dist/components/foundation.js +13 -9
  46. package/dist/components/foundation.js.map +1 -1
  47. package/dist/components/household.d.ts +19 -0
  48. package/dist/components/household.d.ts.map +1 -0
  49. package/dist/components/household.js +137 -0
  50. package/dist/components/household.js.map +1 -0
  51. package/dist/components/index.d.ts +4 -1
  52. package/dist/components/index.d.ts.map +1 -1
  53. package/dist/components/index.js +4 -1
  54. package/dist/components/index.js.map +1 -1
  55. package/dist/components/inputs.d.ts.map +1 -1
  56. package/dist/components/inputs.js +33 -8
  57. package/dist/components/inputs.js.map +1 -1
  58. package/dist/components/life.d.ts +26 -0
  59. package/dist/components/life.d.ts.map +1 -0
  60. package/dist/components/life.js +186 -0
  61. package/dist/components/life.js.map +1 -0
  62. package/dist/components/mail.d.ts.map +1 -1
  63. package/dist/components/mail.js +5 -5
  64. package/dist/components/mail.js.map +1 -1
  65. package/dist/components/media.d.ts.map +1 -1
  66. package/dist/components/media.js +62 -11
  67. package/dist/components/media.js.map +1 -1
  68. package/dist/components/navigation.d.ts +3 -1
  69. package/dist/components/navigation.d.ts.map +1 -1
  70. package/dist/components/navigation.js +287 -20
  71. package/dist/components/navigation.js.map +1 -1
  72. package/dist/components/operations.d.ts.map +1 -1
  73. package/dist/components/operations.js +10 -10
  74. package/dist/components/operations.js.map +1 -1
  75. package/dist/components/overlays.d.ts.map +1 -1
  76. package/dist/components/overlays.js +2 -2
  77. package/dist/components/overlays.js.map +1 -1
  78. package/dist/components/previews.js +2 -2
  79. package/dist/components/previews.js.map +1 -1
  80. package/dist/components/primitives.js +2 -2
  81. package/dist/components/primitives.js.map +1 -1
  82. package/dist/components/qr-code.d.ts +8 -0
  83. package/dist/components/qr-code.d.ts.map +1 -0
  84. package/dist/components/qr-code.js +18 -0
  85. package/dist/components/qr-code.js.map +1 -0
  86. package/dist/components/real-estate.d.ts.map +1 -1
  87. package/dist/components/real-estate.js +5 -5
  88. package/dist/components/real-estate.js.map +1 -1
  89. package/dist/components/rich-text-editor.d.ts.map +1 -1
  90. package/dist/components/rich-text-editor.js +1 -1
  91. package/dist/components/rich-text-editor.js.map +1 -1
  92. package/dist/components/select-menu.js +1 -1
  93. package/dist/components/select-menu.js.map +1 -1
  94. package/dist/components/shell.d.ts +0 -4
  95. package/dist/components/shell.d.ts.map +1 -1
  96. package/dist/components/shell.js +100 -7
  97. package/dist/components/shell.js.map +1 -1
  98. package/dist/components/site.d.ts.map +1 -1
  99. package/dist/components/site.js +8 -6
  100. package/dist/components/site.js.map +1 -1
  101. package/dist/components/status.d.ts.map +1 -1
  102. package/dist/components/status.js +2 -2
  103. package/dist/components/status.js.map +1 -1
  104. package/dist/components/system.d.ts.map +1 -1
  105. package/dist/components/system.js +3 -2
  106. package/dist/components/system.js.map +1 -1
  107. package/dist/components/types.d.ts +589 -18
  108. package/dist/components/types.d.ts.map +1 -1
  109. package/dist/components/workspace.d.ts.map +1 -1
  110. package/dist/components/workspace.js +15 -12
  111. package/dist/components/workspace.js.map +1 -1
  112. package/dist/index.d.ts +1 -1
  113. package/dist/index.d.ts.map +1 -1
  114. package/dist/index.js +1 -1
  115. package/dist/index.js.map +1 -1
  116. package/dist/qr.d.ts +14 -0
  117. package/dist/qr.d.ts.map +1 -0
  118. package/dist/qr.js +298 -0
  119. package/dist/qr.js.map +1 -0
  120. package/dist/strings.d.ts +126 -4
  121. package/dist/strings.d.ts.map +1 -1
  122. package/dist/strings.js +88 -1
  123. package/dist/strings.js.map +1 -1
  124. package/dist/themes.css +15592 -10463
  125. package/package.json +2 -1
  126. package/src/components/admin.tsx +66 -23
  127. package/src/components/agents.tsx +22 -20
  128. package/src/components/app-chrome.tsx +9 -5
  129. package/src/components/approvals.tsx +3 -1
  130. package/src/components/building-blocks.tsx +24 -13
  131. package/src/components/charts.tsx +17 -12
  132. package/src/components/chat.tsx +54 -31
  133. package/src/components/commerce.tsx +58 -24
  134. package/src/components/data-display.tsx +106 -38
  135. package/src/components/data-table.tsx +11 -7
  136. package/src/components/developer.tsx +15 -6
  137. package/src/components/feedback.tsx +5 -3
  138. package/src/components/forms.tsx +101 -23
  139. package/src/components/foundation.tsx +9 -5
  140. package/src/components/household.tsx +489 -0
  141. package/src/components/index.ts +32 -1
  142. package/src/components/inputs.tsx +44 -7
  143. package/src/components/life.tsx +672 -0
  144. package/src/components/mail.tsx +7 -3
  145. package/src/components/media.tsx +88 -16
  146. package/src/components/navigation.tsx +651 -71
  147. package/src/components/operations.tsx +17 -8
  148. package/src/components/overlays.tsx +7 -2
  149. package/src/components/previews.tsx +2 -2
  150. package/src/components/primitives.tsx +2 -2
  151. package/src/components/qr-code.tsx +30 -0
  152. package/src/components/real-estate.tsx +7 -4
  153. package/src/components/rich-text-editor.tsx +7 -1
  154. package/src/components/select-menu.tsx +1 -1
  155. package/src/components/shell.tsx +237 -11
  156. package/src/components/site.tsx +18 -4
  157. package/src/components/status.tsx +2 -0
  158. package/src/components/system.tsx +3 -2
  159. package/src/components/types.ts +583 -18
  160. package/src/components/workspace.tsx +37 -15
  161. package/src/index.ts +2 -0
  162. package/src/qr.ts +300 -0
  163. 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 className="tf-subnav" aria-label={t.sectionPages(p.eyebrow)}>
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 className="tf-notes" role="region" aria-label={t.notifications}>
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 type="button" className="tf-filecard" onClick={p.onClick}>
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 className="tf-wizard" role="group" aria-label={p.label || t.setup}>
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
@@ -29,6 +29,8 @@ export {
29
29
  FEEDBACK_STRINGS,
30
30
  FORM_STRINGS,
31
31
  FORMS_STRINGS,
32
+ HOUSEHOLD_STRINGS,
33
+ LIFE_STRINGS,
32
34
  MAIL_STRINGS,
33
35
  MEDIA_STRINGS,
34
36
  NAVIGATION_STRINGS,
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
+ }