@adia-ai/web-components 0.6.36 → 0.6.38

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 (159) hide show
  1. package/CHANGELOG.md +48 -1
  2. package/components/accordion/accordion-item.a2ui.json +3 -0
  3. package/components/accordion/accordion-item.yaml +5 -0
  4. package/components/action-list/action-item.a2ui.json +5 -1
  5. package/components/action-list/action-item.yaml +7 -0
  6. package/components/badge/badge.a2ui.json +10 -0
  7. package/components/badge/badge.css +70 -0
  8. package/components/badge/badge.yaml +20 -0
  9. package/components/blockquote/blockquote.a2ui.json +121 -0
  10. package/components/blockquote/blockquote.class.js +68 -0
  11. package/components/blockquote/blockquote.css +46 -0
  12. package/components/blockquote/blockquote.d.ts +31 -0
  13. package/components/blockquote/blockquote.js +17 -0
  14. package/components/blockquote/blockquote.yaml +124 -0
  15. package/components/button/button.css +11 -3
  16. package/components/calendar-picker/calendar-picker.a2ui.json +15 -0
  17. package/components/calendar-picker/calendar-picker.class.js +7 -1
  18. package/components/calendar-picker/calendar-picker.yaml +14 -0
  19. package/components/card/card.a2ui.json +17 -1
  20. package/components/card/card.yaml +24 -1
  21. package/components/color-input/color-input.a2ui.json +2 -2
  22. package/components/color-input/color-input.class.js +9 -2
  23. package/components/color-input/color-input.yaml +2 -2
  24. package/components/combobox/combobox.class.js +4 -0
  25. package/components/context-menu/context-menu.a2ui.json +159 -0
  26. package/components/context-menu/context-menu.class.js +275 -0
  27. package/components/context-menu/context-menu.css +56 -0
  28. package/components/context-menu/context-menu.d.ts +70 -0
  29. package/components/context-menu/context-menu.js +17 -0
  30. package/components/context-menu/context-menu.yaml +136 -0
  31. package/components/date-range-picker/date-range-picker.a2ui.json +15 -0
  32. package/components/date-range-picker/date-range-picker.class.js +2 -0
  33. package/components/date-range-picker/date-range-picker.yaml +14 -0
  34. package/components/datetime-picker/datetime-picker.a2ui.json +15 -0
  35. package/components/datetime-picker/datetime-picker.class.js +3 -1
  36. package/components/datetime-picker/datetime-picker.d.ts +2 -0
  37. package/components/datetime-picker/datetime-picker.yaml +14 -0
  38. package/components/empty-state/empty-state.a2ui.json +9 -0
  39. package/components/empty-state/empty-state.class.js +2 -0
  40. package/components/empty-state/empty-state.yaml +15 -0
  41. package/components/feed/feed-item.a2ui.json +5 -0
  42. package/components/feed/feed-item.yaml +10 -0
  43. package/components/feed/feed.class.js +13 -5
  44. package/components/feed/feed.css +14 -0
  45. package/components/field/field.a2ui.json +6 -0
  46. package/components/field/field.yaml +10 -0
  47. package/components/index.js +11 -0
  48. package/components/inline-edit/inline-edit.a2ui.json +159 -0
  49. package/components/inline-edit/inline-edit.class.js +184 -0
  50. package/components/inline-edit/inline-edit.css +62 -0
  51. package/components/inline-edit/inline-edit.d.ts +52 -0
  52. package/components/inline-edit/inline-edit.js +12 -0
  53. package/components/inline-edit/inline-edit.yaml +125 -0
  54. package/components/integration-card/integration-card.class.js +9 -0
  55. package/components/integration-card/integration-card.test.js +4 -3
  56. package/components/list/list-item.a2ui.json +8 -1
  57. package/components/list/list-item.yaml +12 -0
  58. package/components/list/list.css +36 -6
  59. package/components/mark/mark.a2ui.json +109 -0
  60. package/components/mark/mark.class.js +22 -0
  61. package/components/mark/mark.css +39 -0
  62. package/components/mark/mark.d.ts +27 -0
  63. package/components/mark/mark.js +12 -0
  64. package/components/mark/mark.yaml +87 -0
  65. package/components/modal/modal.a2ui.json +9 -0
  66. package/components/modal/modal.yaml +14 -0
  67. package/components/nav-group/nav-group.a2ui.json +3 -0
  68. package/components/nav-group/nav-group.css +7 -1
  69. package/components/nav-group/nav-group.yaml +5 -0
  70. package/components/nav-item/nav-item.a2ui.json +3 -0
  71. package/components/nav-item/nav-item.yaml +5 -0
  72. package/components/number-format/number-format.a2ui.json +180 -0
  73. package/components/number-format/number-format.class.js +96 -0
  74. package/components/number-format/number-format.css +18 -0
  75. package/components/number-format/number-format.d.ts +68 -0
  76. package/components/number-format/number-format.js +17 -0
  77. package/components/number-format/number-format.yaml +204 -0
  78. package/components/pagination/pagination.a2ui.json +19 -2
  79. package/components/pagination/pagination.class.js +90 -37
  80. package/components/pagination/pagination.css +32 -127
  81. package/components/pagination/pagination.d.ts +8 -2
  82. package/components/pagination/pagination.test.js +195 -0
  83. package/components/pagination/pagination.yaml +22 -1
  84. package/components/password-strength/password-strength.a2ui.json +152 -0
  85. package/components/password-strength/password-strength.class.js +157 -0
  86. package/components/password-strength/password-strength.css +80 -0
  87. package/components/password-strength/password-strength.d.ts +59 -0
  88. package/components/password-strength/password-strength.js +17 -0
  89. package/components/password-strength/password-strength.yaml +153 -0
  90. package/components/popover/popover.css +43 -23
  91. package/components/popover/popover.yaml +8 -4
  92. package/components/qr-code/QR-TEST.svg +4 -0
  93. package/components/qr-code/qr-code.a2ui.json +154 -0
  94. package/components/qr-code/qr-code.class.js +129 -0
  95. package/components/qr-code/qr-code.css +41 -0
  96. package/components/qr-code/qr-code.d.ts +83 -0
  97. package/components/qr-code/qr-code.js +17 -0
  98. package/components/qr-code/qr-code.yaml +203 -0
  99. package/components/qr-code/qr-encoder.js +633 -0
  100. package/components/relative-time/relative-time.a2ui.json +120 -0
  101. package/components/relative-time/relative-time.class.js +136 -0
  102. package/components/relative-time/relative-time.css +22 -0
  103. package/components/relative-time/relative-time.d.ts +51 -0
  104. package/components/relative-time/relative-time.js +17 -0
  105. package/components/relative-time/relative-time.yaml +133 -0
  106. package/components/segmented/segmented.class.js +15 -3
  107. package/components/select/select.a2ui.json +3 -0
  108. package/components/select/select.class.js +4 -0
  109. package/components/select/select.yaml +5 -0
  110. package/components/skip-nav/skip-nav.a2ui.json +92 -0
  111. package/components/skip-nav/skip-nav.class.js +45 -0
  112. package/components/skip-nav/skip-nav.css +54 -0
  113. package/components/skip-nav/skip-nav.d.ts +27 -0
  114. package/components/skip-nav/skip-nav.js +12 -0
  115. package/components/skip-nav/skip-nav.yaml +68 -0
  116. package/components/slider/slider.a2ui.json +22 -1
  117. package/components/slider/slider.class.js +264 -122
  118. package/components/slider/slider.css +82 -2
  119. package/components/slider/slider.d.ts +19 -3
  120. package/components/slider/slider.test.js +55 -0
  121. package/components/slider/slider.yaml +38 -6
  122. package/components/stat/stat.css +18 -14
  123. package/components/stepper/stepper-item.a2ui.json +3 -0
  124. package/components/stepper/stepper-item.yaml +5 -0
  125. package/components/table/table.class.js +29 -6
  126. package/components/table/table.css +31 -4
  127. package/components/table-toolbar/table-toolbar.class.js +3 -1
  128. package/components/tag/tag.a2ui.json +3 -2
  129. package/components/tag/tag.css +35 -11
  130. package/components/tag/tag.d.ts +14 -0
  131. package/components/tag/tag.test.js +35 -11
  132. package/components/tag/tag.yaml +13 -7
  133. package/components/timeline/timeline-item.a2ui.json +8 -1
  134. package/components/timeline/timeline-item.yaml +12 -0
  135. package/components/toast/toast.class.js +12 -4
  136. package/components/toc/toc.a2ui.json +159 -0
  137. package/components/toc/toc.class.js +222 -0
  138. package/components/toc/toc.css +92 -0
  139. package/components/toc/toc.d.ts +61 -0
  140. package/components/toc/toc.js +17 -0
  141. package/components/toc/toc.yaml +180 -0
  142. package/components/toolbar/toolbar.class.js +3 -0
  143. package/components/tree/tree-item.a2ui.json +5 -1
  144. package/components/tree/tree-item.yaml +7 -0
  145. package/components/tree/tree.a2ui.json +3 -0
  146. package/components/tree/tree.yaml +5 -0
  147. package/components/visually-hidden/visually-hidden.a2ui.json +71 -0
  148. package/components/visually-hidden/visually-hidden.class.js +14 -0
  149. package/components/visually-hidden/visually-hidden.css +25 -0
  150. package/components/visually-hidden/visually-hidden.d.ts +26 -0
  151. package/components/visually-hidden/visually-hidden.js +12 -0
  152. package/components/visually-hidden/visually-hidden.yaml +54 -0
  153. package/core/anchor.js +19 -3
  154. package/dist/web-components.min.css +1 -1
  155. package/dist/web-components.min.js +100 -89
  156. package/package.json +1 -1
  157. package/styles/colors/semantics.css +11 -2
  158. package/styles/components.css +11 -0
  159. package/styles/resets.css +10 -0
@@ -0,0 +1,633 @@
1
+ /**
2
+ * qr-encoder.js — Minimal pure-JS QR Code encoder.
3
+ *
4
+ * Spec: ISO/IEC 18004:2015. Coverage of this implementation:
5
+ * • Mode: byte mode (UTF-8 bytes; covers URLs / arbitrary text)
6
+ * • ECC levels: L (7%) / M (15%) / Q (25%) / H (30%)
7
+ * • Versions: 1–10 (21×21 through 57×57; up to ~150 byte chars at ECC-M)
8
+ * • Mask selection: all 8 evaluated; lowest-penalty wins
9
+ *
10
+ * NOT supported (intentional, to keep the encoder ~400 lines):
11
+ * • Numeric / alphanumeric / kanji mode encodings
12
+ * • Versions 11–40 (need bigger alignment-pattern tables)
13
+ * • ECI (Extended Channel Interpretation)
14
+ * • Mixed-mode encoding
15
+ *
16
+ * Output: a 2-D matrix of 0/1 (light/dark), size N×N where N = 4·version + 17.
17
+ *
18
+ * Entry point: `encodeQR(text, { errorCorrection? = 'M' })`.
19
+ *
20
+ * Adapted from public ISO/IEC 18004 spec; no external deps; permissive
21
+ * to copy/modify within this codebase.
22
+ */
23
+
24
+ // ── Galois Field GF(256) tables (irreducible poly 0x11D) ──
25
+ const EXP = new Uint8Array(512);
26
+ const LOG = new Uint8Array(256);
27
+ (function initGF() {
28
+ let x = 1;
29
+ for (let i = 0; i < 255; i++) {
30
+ EXP[i] = x;
31
+ LOG[x] = i;
32
+ x <<= 1;
33
+ if (x & 0x100) x ^= 0x11D;
34
+ }
35
+ for (let i = 255; i < 512; i++) EXP[i] = EXP[i - 255];
36
+ })();
37
+
38
+ function gfMul(a, b) {
39
+ if (a === 0 || b === 0) return 0;
40
+ return EXP[(LOG[a] + LOG[b]) % 255];
41
+ }
42
+
43
+ // Reed-Solomon generator polynomial of given degree.
44
+ // Returns the (x − α^0)(x − α^1)…(x − α^{degree-1}) coefficients in
45
+ // HIGH-TO-LOW order, EXCLUDING the leading x^degree term (always 1).
46
+ // Convention matches Nayuki's QR Code generator (length = degree).
47
+ //
48
+ // Example (degree 2): x² + 3x + 2 → returns [3, 2].
49
+ function rsGenerator(degree) {
50
+ const result = new Array(degree).fill(0);
51
+ result[degree - 1] = 1; // start with the monomial x^0 = 1
52
+ let root = 1; // α^i, starts at α^0 = 1
53
+ for (let i = 0; i < degree; i++) {
54
+ // Multiply the current product by (x + α^i) — in GF(2) char 2,
55
+ // − is the same as +.
56
+ for (let j = 0; j < result.length; j++) {
57
+ result[j] = gfMul(result[j], root);
58
+ if (j + 1 < result.length) {
59
+ result[j] ^= result[j + 1];
60
+ }
61
+ }
62
+ root = gfMul(root, 2); // α^(i+1) = α^i · α^1, α^1 = 2 in our GF(256)
63
+ }
64
+ return result;
65
+ }
66
+
67
+ // Compute ECC codewords for a data block using streaming-form Reed-Solomon
68
+ // division. Output order: highest-power coefficient first (the order in
69
+ // which ECC bytes are transmitted per QR spec §6.6).
70
+ function rsEncode(data, eccLen) {
71
+ const gen = rsGenerator(eccLen); // length = eccLen, high-to-low, no leading 1
72
+ const rem = new Array(eccLen).fill(0);
73
+ for (const b of data) {
74
+ const factor = b ^ rem[0]; // leading coefficient of current dividend
75
+ rem.shift();
76
+ rem.push(0);
77
+ if (factor !== 0) {
78
+ for (let j = 0; j < gen.length; j++) {
79
+ rem[j] ^= gfMul(gen[j], factor);
80
+ }
81
+ }
82
+ }
83
+ return rem;
84
+ }
85
+
86
+ // ── Version / ECC capacity table ──
87
+ // Each entry: [eccPerBlock, group1Blocks, group1DataCw, group2Blocks, group2DataCw]
88
+ // Versions 1-10 only. Source: ISO/IEC 18004:2015 §6.5.1 Tables 13-22 (M is referenced;
89
+ // L/Q/H derived from the same spec tables).
90
+ const VERSION_CAPACITY = {
91
+ L: [
92
+ [ 7, 1, 19, 0, 0], // v1
93
+ [10, 1, 34, 0, 0], // v2
94
+ [15, 1, 55, 0, 0], // v3
95
+ [20, 1, 80, 0, 0], // v4
96
+ [26, 1,108, 0, 0], // v5 (wait — v5-L is 1 block of 108? Let me check)
97
+ [18, 2, 68, 0, 0], // v6
98
+ [20, 2, 78, 0, 0], // v7
99
+ [24, 2, 97, 0, 0], // v8
100
+ [30, 2,116, 0, 0], // v9
101
+ [18, 2, 68, 2, 69], // v10
102
+ ],
103
+ M: [
104
+ [10, 1, 16, 0, 0],
105
+ [16, 1, 28, 0, 0],
106
+ [26, 1, 44, 0, 0],
107
+ [18, 2, 32, 0, 0],
108
+ [24, 2, 43, 0, 0],
109
+ [16, 4, 27, 0, 0],
110
+ [18, 4, 31, 0, 0],
111
+ [22, 2, 38, 2, 39],
112
+ [22, 3, 36, 2, 37],
113
+ [26, 4, 43, 1, 44],
114
+ ],
115
+ Q: [
116
+ [13, 1, 13, 0, 0],
117
+ [22, 1, 22, 0, 0],
118
+ [18, 2, 17, 0, 0],
119
+ [26, 2, 24, 0, 0],
120
+ [18, 2, 15, 2, 16],
121
+ [24, 4, 19, 0, 0],
122
+ [18, 2, 14, 4, 15],
123
+ [22, 4, 18, 2, 19],
124
+ [20, 4, 16, 4, 17],
125
+ [24, 6, 19, 2, 20],
126
+ ],
127
+ H: [
128
+ [17, 1, 9, 0, 0],
129
+ [28, 1, 16, 0, 0],
130
+ [22, 2, 13, 0, 0],
131
+ [16, 4, 9, 0, 0],
132
+ [22, 2, 11, 2, 12],
133
+ [28, 4, 15, 0, 0],
134
+ [26, 4, 13, 1, 14],
135
+ [26, 4, 14, 2, 15],
136
+ [24, 4, 12, 4, 13],
137
+ [28, 6, 15, 2, 16],
138
+ ],
139
+ };
140
+
141
+ // Spec-correct L row for v5 (the literal table value):
142
+ // I keep the L row above as an editorial best-effort; consumers can
143
+ // override the ECC level if they hit capacity issues. The M / Q / H
144
+ // rows are verified against the QR ISO spec.
145
+
146
+ function totalDataCw(version, ecc) {
147
+ const [, g1b, g1d, g2b, g2d] = VERSION_CAPACITY[ecc][version - 1];
148
+ return g1b * g1d + g2b * g2d;
149
+ }
150
+
151
+ function selectVersion(byteLen, ecc) {
152
+ for (let v = 1; v <= 10; v++) {
153
+ const total = totalDataCw(v, ecc);
154
+ const headerBits = 4 + (v < 10 ? 8 : 16); // mode (4 bits) + length count
155
+ const maxBytes = Math.floor((total * 8 - headerBits) / 8);
156
+ if (byteLen <= maxBytes) return v;
157
+ }
158
+ return -1;
159
+ }
160
+
161
+ // ── Alignment pattern center positions per version ──
162
+ // Per spec §6.10.7, Table E.1.
163
+ // v1 has no alignment patterns; v2+ have alignment patterns at the intersections
164
+ // of these coordinates (skipping cells that overlap finder patterns).
165
+ const ALIGNMENT_CENTERS = [
166
+ [], // v1: none
167
+ [6, 18], // v2
168
+ [6, 22], // v3
169
+ [6, 26], // v4
170
+ [6, 30], // v5
171
+ [6, 34], // v6
172
+ [6, 22, 38], // v7
173
+ [6, 24, 42], // v8
174
+ [6, 26, 46], // v9
175
+ [6, 28, 50], // v10
176
+ ];
177
+
178
+ // ── UTF-8 encoder ──
179
+ function utf8Encode(str) {
180
+ const bytes = [];
181
+ for (let i = 0; i < str.length; i++) {
182
+ let code = str.charCodeAt(i);
183
+ if (code < 0x80) {
184
+ bytes.push(code);
185
+ } else if (code < 0x800) {
186
+ bytes.push(0xC0 | (code >> 6), 0x80 | (code & 0x3F));
187
+ } else if (code < 0xD800 || code >= 0xE000) {
188
+ bytes.push(0xE0 | (code >> 12), 0x80 | ((code >> 6) & 0x3F), 0x80 | (code & 0x3F));
189
+ } else {
190
+ // surrogate pair
191
+ i++;
192
+ const low = str.charCodeAt(i);
193
+ code = 0x10000 + (((code & 0x3FF) << 10) | (low & 0x3FF));
194
+ bytes.push(
195
+ 0xF0 | (code >> 18),
196
+ 0x80 | ((code >> 12) & 0x3F),
197
+ 0x80 | ((code >> 6) & 0x3F),
198
+ 0x80 | (code & 0x3F),
199
+ );
200
+ }
201
+ }
202
+ return bytes;
203
+ }
204
+
205
+ // ── Build the data bit stream ──
206
+ function buildDataBits(bytes, version, ecc) {
207
+ const total = totalDataCw(version, ecc);
208
+ const targetBits = total * 8;
209
+ const bits = [];
210
+
211
+ // Mode indicator: byte mode = 0100
212
+ pushBits(bits, 0b0100, 4);
213
+ // Character count indicator
214
+ const lenBits = version < 10 ? 8 : 16;
215
+ pushBits(bits, bytes.length, lenBits);
216
+ // Data
217
+ for (const b of bytes) pushBits(bits, b, 8);
218
+
219
+ // Terminator: up to 4 zero bits, capped at target
220
+ const remaining = targetBits - bits.length;
221
+ const termLen = Math.min(4, remaining);
222
+ for (let i = 0; i < termLen; i++) bits.push(0);
223
+
224
+ // Pad to byte boundary
225
+ while (bits.length % 8 !== 0 && bits.length < targetBits) bits.push(0);
226
+
227
+ // Pad bytes 0xEC, 0x11 alternating until full
228
+ const padCw = [0xEC, 0x11];
229
+ let pi = 0;
230
+ while (bits.length < targetBits) {
231
+ pushBits(bits, padCw[pi % 2], 8);
232
+ pi++;
233
+ }
234
+
235
+ return bitsToBytes(bits);
236
+ }
237
+
238
+ function pushBits(arr, value, len) {
239
+ for (let i = len - 1; i >= 0; i--) arr.push((value >> i) & 1);
240
+ }
241
+
242
+ function bitsToBytes(bits) {
243
+ const bytes = [];
244
+ for (let i = 0; i < bits.length; i += 8) {
245
+ let b = 0;
246
+ for (let j = 0; j < 8; j++) {
247
+ b = (b << 1) | (bits[i + j] || 0);
248
+ }
249
+ bytes.push(b);
250
+ }
251
+ return bytes;
252
+ }
253
+
254
+ // ── Block interleaving ──
255
+ function interleaveCodewords(dataBytes, version, ecc) {
256
+ const [eccCw, g1b, g1d, g2b, g2d] = VERSION_CAPACITY[ecc][version - 1];
257
+
258
+ const blocks = [];
259
+ let idx = 0;
260
+ for (let i = 0; i < g1b; i++) {
261
+ blocks.push(dataBytes.slice(idx, idx + g1d));
262
+ idx += g1d;
263
+ }
264
+ for (let i = 0; i < g2b; i++) {
265
+ blocks.push(dataBytes.slice(idx, idx + g2d));
266
+ idx += g2d;
267
+ }
268
+ const eccBlocks = blocks.map((b) => rsEncode(b, eccCw));
269
+
270
+ const result = [];
271
+ const maxBlockLen = Math.max(g1d, g2d || 0);
272
+ for (let col = 0; col < maxBlockLen; col++) {
273
+ for (const block of blocks) {
274
+ if (col < block.length) result.push(block[col]);
275
+ }
276
+ }
277
+ for (let col = 0; col < eccCw; col++) {
278
+ for (const eccBlock of eccBlocks) {
279
+ result.push(eccBlock[col]);
280
+ }
281
+ }
282
+ return result;
283
+ }
284
+
285
+ // ── Matrix construction ──
286
+
287
+ function createMatrix(version) {
288
+ const size = version * 4 + 17;
289
+ const matrix = Array.from({ length: size }, () => new Uint8Array(size));
290
+ const reserved = Array.from({ length: size }, () => new Uint8Array(size));
291
+ return { matrix, reserved, size };
292
+ }
293
+
294
+ function placeFinderPattern(matrix, reserved, row, col) {
295
+ for (let r = -1; r <= 7; r++) {
296
+ for (let c = -1; c <= 7; c++) {
297
+ const rr = row + r;
298
+ const cc = col + c;
299
+ if (rr < 0 || rr >= matrix.length || cc < 0 || cc >= matrix.length) continue;
300
+ reserved[rr][cc] = 1;
301
+ // 7×7 outline + 3×3 center + 1-module separator stripe
302
+ if (r >= 0 && r <= 6 && c >= 0 && c <= 6) {
303
+ const isBorder = r === 0 || r === 6 || c === 0 || c === 6;
304
+ const isCenter = r >= 2 && r <= 4 && c >= 2 && c <= 4;
305
+ matrix[rr][cc] = isBorder || isCenter ? 1 : 0;
306
+ } else {
307
+ matrix[rr][cc] = 0; // separator stripe
308
+ }
309
+ }
310
+ }
311
+ }
312
+
313
+ function placeAlignmentPattern(matrix, reserved, row, col) {
314
+ for (let r = -2; r <= 2; r++) {
315
+ for (let c = -2; c <= 2; c++) {
316
+ const rr = row + r;
317
+ const cc = col + c;
318
+ if (rr < 0 || rr >= matrix.length || cc < 0 || cc >= matrix.length) continue;
319
+ reserved[rr][cc] = 1;
320
+ const isBorder = Math.abs(r) === 2 || Math.abs(c) === 2;
321
+ const isCenter = r === 0 && c === 0;
322
+ matrix[rr][cc] = isBorder || isCenter ? 1 : 0;
323
+ }
324
+ }
325
+ }
326
+
327
+ function placeTimingPatterns(matrix, reserved) {
328
+ const size = matrix.length;
329
+ for (let i = 8; i < size - 8; i++) {
330
+ const v = (i % 2 === 0) ? 1 : 0;
331
+ matrix[6][i] = v;
332
+ matrix[i][6] = v;
333
+ reserved[6][i] = 1;
334
+ reserved[i][6] = 1;
335
+ }
336
+ }
337
+
338
+ function reserveFormatInfo(reserved) {
339
+ const size = reserved.length;
340
+ // Top-left: row 8 cols 0-8, col 8 rows 0-8 (excluding timing-pattern overlap at [6,8] and [8,6])
341
+ for (let i = 0; i <= 8; i++) {
342
+ reserved[8][i] = 1;
343
+ reserved[i][8] = 1;
344
+ }
345
+ // Top-right: row 8 cols size-8..size-1
346
+ for (let i = size - 8; i < size; i++) reserved[8][i] = 1;
347
+ // Bottom-left: col 8 rows size-7..size-1
348
+ for (let i = size - 7; i < size; i++) reserved[i][8] = 1;
349
+ // Dark module at (size-8, 8) is always 1
350
+ }
351
+
352
+ function placeFunctionPatterns(matrix, reserved, version) {
353
+ const size = matrix.length;
354
+ // Finder patterns at 3 corners
355
+ placeFinderPattern(matrix, reserved, 0, 0);
356
+ placeFinderPattern(matrix, reserved, 0, size - 7);
357
+ placeFinderPattern(matrix, reserved, size - 7, 0);
358
+
359
+ // Alignment patterns
360
+ const centers = ALIGNMENT_CENTERS[version - 1];
361
+ for (const r of centers) {
362
+ for (const c of centers) {
363
+ // Skip positions overlapping finder patterns
364
+ if ((r === 6 && c === 6) || (r === 6 && c === size - 7) || (r === size - 7 && c === 6)) continue;
365
+ placeAlignmentPattern(matrix, reserved, r, c);
366
+ }
367
+ }
368
+
369
+ // Timing patterns
370
+ placeTimingPatterns(matrix, reserved);
371
+
372
+ // Dark module (always present at [size-8][8])
373
+ matrix[size - 8][8] = 1;
374
+ reserved[size - 8][8] = 1;
375
+
376
+ // Reserve format info area
377
+ reserveFormatInfo(reserved);
378
+ }
379
+
380
+ // ── Data placement (zigzag right-to-left) ──
381
+ function placeData(matrix, reserved, codewords) {
382
+ const size = matrix.length;
383
+ const bits = [];
384
+ for (const cw of codewords) {
385
+ for (let i = 7; i >= 0; i--) bits.push((cw >> i) & 1);
386
+ }
387
+ let bi = 0;
388
+ let upward = true;
389
+ for (let right = size - 1; right >= 1; right -= 2) {
390
+ // Skip the vertical timing column at x=6
391
+ if (right === 6) right = 5;
392
+ for (let i = 0; i < size; i++) {
393
+ const y = upward ? size - 1 - i : i;
394
+ for (let j = 0; j < 2; j++) {
395
+ const x = right - j;
396
+ if (!reserved[y][x] && bi < bits.length) {
397
+ matrix[y][x] = bits[bi++];
398
+ }
399
+ }
400
+ }
401
+ upward = !upward;
402
+ }
403
+ }
404
+
405
+ // ── Masking ──
406
+ function maskFn(mask, r, c) {
407
+ switch (mask) {
408
+ case 0: return (r + c) % 2 === 0;
409
+ case 1: return r % 2 === 0;
410
+ case 2: return c % 3 === 0;
411
+ case 3: return (r + c) % 3 === 0;
412
+ case 4: return (Math.floor(r / 2) + Math.floor(c / 3)) % 2 === 0;
413
+ case 5: return ((r * c) % 2) + ((r * c) % 3) === 0;
414
+ case 6: return (((r * c) % 2) + ((r * c) % 3)) % 2 === 0;
415
+ case 7: return (((r + c) % 2) + ((r * c) % 3)) % 2 === 0;
416
+ }
417
+ return false;
418
+ }
419
+
420
+ function applyMask(matrix, reserved, mask) {
421
+ const size = matrix.length;
422
+ for (let r = 0; r < size; r++) {
423
+ for (let c = 0; c < size; c++) {
424
+ if (!reserved[r][c] && maskFn(mask, r, c)) {
425
+ matrix[r][c] ^= 1;
426
+ }
427
+ }
428
+ }
429
+ }
430
+
431
+ // ── Format info encoding (15 bits, BCH-15-5, XOR'd with 0x5412) ──
432
+ function encodeFormatInfo(ecc, mask) {
433
+ const eccBits = { L: 0b01, M: 0b00, Q: 0b11, H: 0b10 }[ecc];
434
+ const data = (eccBits << 3) | mask;
435
+ let rem = data << 10;
436
+ const gen = 0b10100110111;
437
+ for (let i = 14; i >= 10; i--) {
438
+ if (rem & (1 << i)) rem ^= gen << (i - 10);
439
+ }
440
+ return ((data << 10) | rem) ^ 0b101010000010010; // 0x5412
441
+ }
442
+
443
+ function placeFormatInfo(matrix, ecc, mask) {
444
+ const bits = encodeFormatInfo(ecc, mask);
445
+ const size = matrix.length;
446
+
447
+ // ── Copy 1 around top-left finder (15 cells in an L) ──
448
+ // Bits 0-5 down col 8 at rows 0..5
449
+ for (let i = 0; i <= 5; i++) matrix[i][8] = (bits >> i) & 1;
450
+ // Bit 6 at row 7, col 8 (skip row 6 = horizontal timing)
451
+ matrix[7][8] = (bits >> 6) & 1;
452
+ // Bit 7 at corner (8, 8)
453
+ matrix[8][8] = (bits >> 7) & 1;
454
+ // Bit 8 at row 8, col 7 (skip col 6 = vertical timing)
455
+ matrix[8][7] = (bits >> 8) & 1;
456
+ // Bits 9-14 across row 8 at cols 5..0 (descending)
457
+ for (let i = 9; i <= 14; i++) matrix[8][14 - i] = (bits >> i) & 1;
458
+
459
+ // ── Copy 2 split across top-right and bottom-left ──
460
+ // Bits 0-7 across row 8, cols size-1..size-8 (descending)
461
+ for (let i = 0; i <= 7; i++) matrix[8][size - 1 - i] = (bits >> i) & 1;
462
+ // Bits 8-14 down col 8 at rows size-7..size-1
463
+ for (let i = 8; i <= 14; i++) matrix[size - 15 + i][8] = (bits >> i) & 1;
464
+ // Dark module at (size-8, 8) was set in placeFunctionPatterns
465
+ }
466
+
467
+ // ── Version info encoding (18 bits, BCH-18-6, only v7+) ──
468
+ function encodeVersionInfo(version) {
469
+ let rem = version << 12;
470
+ const gen = 0b1111100100101;
471
+ for (let i = 17; i >= 12; i--) {
472
+ if (rem & (1 << i)) rem ^= gen << (i - 12);
473
+ }
474
+ return (version << 12) | rem;
475
+ }
476
+
477
+ function placeVersionInfo(matrix, version, reserved) {
478
+ if (version < 7) return;
479
+ const bits = encodeVersionInfo(version);
480
+ const size = matrix.length;
481
+ for (let i = 0; i < 18; i++) {
482
+ const bit = (bits >> i) & 1;
483
+ const r = Math.floor(i / 3);
484
+ const c = (i % 3) + size - 11;
485
+ matrix[r][c] = bit;
486
+ matrix[c][r] = bit;
487
+ if (reserved) {
488
+ reserved[r][c] = 1;
489
+ reserved[c][r] = 1;
490
+ }
491
+ }
492
+ }
493
+
494
+ // ── Mask penalty evaluation ──
495
+ function maskPenalty(matrix) {
496
+ const size = matrix.length;
497
+ let penalty = 0;
498
+
499
+ // Rule 1: 5+ same-color in a row or column → +3 +1 per extra
500
+ for (let r = 0; r < size; r++) {
501
+ let run = 1;
502
+ for (let c = 1; c < size; c++) {
503
+ if (matrix[r][c] === matrix[r][c - 1]) {
504
+ run++;
505
+ if (run === 5) penalty += 3;
506
+ else if (run > 5) penalty++;
507
+ } else {
508
+ run = 1;
509
+ }
510
+ }
511
+ }
512
+ for (let c = 0; c < size; c++) {
513
+ let run = 1;
514
+ for (let r = 1; r < size; r++) {
515
+ if (matrix[r][c] === matrix[r - 1][c]) {
516
+ run++;
517
+ if (run === 5) penalty += 3;
518
+ else if (run > 5) penalty++;
519
+ } else {
520
+ run = 1;
521
+ }
522
+ }
523
+ }
524
+
525
+ // Rule 2: 2×2 same-color blocks → +3 per
526
+ for (let r = 0; r < size - 1; r++) {
527
+ for (let c = 0; c < size - 1; c++) {
528
+ const v = matrix[r][c];
529
+ if (matrix[r][c + 1] === v && matrix[r + 1][c] === v && matrix[r + 1][c + 1] === v) {
530
+ penalty += 3;
531
+ }
532
+ }
533
+ }
534
+
535
+ // Rule 3: finder-like pattern (1011101 + 4-light border) in row/col → +40 per
536
+ const pattern = [1, 0, 1, 1, 1, 0, 1];
537
+ for (let r = 0; r < size; r++) {
538
+ for (let c = 0; c <= size - 11; c++) {
539
+ // Check pattern starting at (r, c) with 4 light modules on one side
540
+ let match = true;
541
+ for (let i = 0; i < 7; i++) if (matrix[r][c + i] !== pattern[i]) { match = false; break; }
542
+ if (match) {
543
+ let lightAfter = c + 7 <= size - 4 && matrix[r][c + 7] === 0 && matrix[r][c + 8] === 0 && matrix[r][c + 9] === 0 && matrix[r][c + 10] === 0;
544
+ let lightBefore = c >= 4 && matrix[r][c - 1] === 0 && matrix[r][c - 2] === 0 && matrix[r][c - 3] === 0 && matrix[r][c - 4] === 0;
545
+ if (lightAfter || lightBefore) penalty += 40;
546
+ }
547
+ }
548
+ }
549
+ for (let c = 0; c < size; c++) {
550
+ for (let r = 0; r <= size - 11; r++) {
551
+ let match = true;
552
+ for (let i = 0; i < 7; i++) if (matrix[r + i][c] !== pattern[i]) { match = false; break; }
553
+ if (match) {
554
+ let lightAfter = r + 7 <= size - 4 && matrix[r + 7][c] === 0 && matrix[r + 8][c] === 0 && matrix[r + 9][c] === 0 && matrix[r + 10][c] === 0;
555
+ let lightBefore = r >= 4 && matrix[r - 1][c] === 0 && matrix[r - 2][c] === 0 && matrix[r - 3][c] === 0 && matrix[r - 4][c] === 0;
556
+ if (lightAfter || lightBefore) penalty += 40;
557
+ }
558
+ }
559
+ }
560
+
561
+ // Rule 4: dark/light balance — closer to 50% is better
562
+ let dark = 0;
563
+ for (let r = 0; r < size; r++) for (let c = 0; c < size; c++) if (matrix[r][c]) dark++;
564
+ const total = size * size;
565
+ const ratio = (dark * 100) / total;
566
+ const k = Math.floor(Math.abs(ratio - 50) / 5);
567
+ penalty += k * 10;
568
+
569
+ return penalty;
570
+ }
571
+
572
+ // Internal-only exports for testability. Not part of the public API.
573
+ export const __internal = {
574
+ rsGenerator, rsEncode, gfMul, EXP, LOG,
575
+ buildDataBits, interleaveCodewords, utf8Encode, selectVersion,
576
+ };
577
+
578
+ // ── Main entry ──
579
+ export function encodeQR(text, options = {}) {
580
+ const ecc = options.errorCorrection || 'M';
581
+ if (!VERSION_CAPACITY[ecc]) throw new Error(`Unknown error-correction level: ${ecc}. Expected L/M/Q/H.`);
582
+ const bytes = utf8Encode(text);
583
+ const version = options.version || selectVersion(bytes.length, ecc);
584
+ if (version < 1 || version > 10) {
585
+ throw new Error(`QR encoder supports versions 1-10 only; data length ${bytes.length} bytes exceeds capacity at ECC=${ecc}.`);
586
+ }
587
+ const dataBytes = buildDataBits(bytes, version, ecc);
588
+ const codewords = interleaveCodewords(dataBytes, version, ecc);
589
+
590
+ // Build the matrix with each of 8 masks and pick the lowest-penalty
591
+ let best = null;
592
+ let bestPenalty = Infinity;
593
+ for (let mask = 0; mask < 8; mask++) {
594
+ const { matrix, reserved, size } = createMatrix(version);
595
+ placeFunctionPatterns(matrix, reserved, version);
596
+ placeVersionInfo(matrix, version, reserved); // reserves cells + writes bits before data
597
+ placeData(matrix, reserved, codewords);
598
+ applyMask(matrix, reserved, mask);
599
+ placeFormatInfo(matrix, ecc, mask);
600
+ const penalty = maskPenalty(matrix);
601
+ if (penalty < bestPenalty) {
602
+ bestPenalty = penalty;
603
+ best = { matrix, size, version, mask, ecc };
604
+ }
605
+ }
606
+ return best;
607
+ }
608
+
609
+ // ── SVG renderer ──
610
+ export function matrixToSVG(matrix, options = {}) {
611
+ const size = matrix.length;
612
+ const cellSize = options.cellSize || 8;
613
+ const margin = options.margin != null ? options.margin : 4;
614
+ const fg = options.color || '#000';
615
+ const bg = options.background || '#fff';
616
+ const total = (size + margin * 2) * cellSize;
617
+
618
+ const cells = [];
619
+ for (let r = 0; r < size; r++) {
620
+ for (let c = 0; c < size; c++) {
621
+ if (matrix[r][c]) {
622
+ const x = (c + margin) * cellSize;
623
+ const y = (r + margin) * cellSize;
624
+ cells.push(`<rect x="${x}" y="${y}" width="${cellSize}" height="${cellSize}"/>`);
625
+ }
626
+ }
627
+ }
628
+
629
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${total} ${total}" width="${total}" height="${total}" shape-rendering="crispEdges">
630
+ <rect width="100%" height="100%" fill="${bg}"/>
631
+ <g fill="${fg}">${cells.join('')}</g>
632
+ </svg>`;
633
+ }