flet-box 0.0.0-stage → 1.0.2

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 (178) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +190 -2
  3. package/bin/cli.js +394 -0
  4. package/bin/commands/buildAndroid.js +200 -0
  5. package/bin/commands/buildIOS.js +237 -0
  6. package/bin/commands/create.js +406 -0
  7. package/bin/commands/createBundle.js +375 -0
  8. package/bin/commands/createComponent.js +115 -0
  9. package/bin/commands/createScreen.js +161 -0
  10. package/bin/commands/killServer.js +107 -0
  11. package/bin/commands/package.js +131 -0
  12. package/bin/commands/release.js +189 -0
  13. package/bin/commands/runBundle.js +62 -0
  14. package/bin/commands/runCloudflare.js +89 -0
  15. package/bin/commands/runNative.js +109 -0
  16. package/bin/commands/runServer.js +390 -0
  17. package/bin/ota/loader.js +361 -0
  18. package/bin/utils/bundleConfig.js +178 -0
  19. package/bin/utils/capacitor.js +137 -0
  20. package/bin/utils/colors.js +215 -0
  21. package/bin/utils/helpers.js +80 -0
  22. package/bin/utils/spinner.js +96 -0
  23. package/bin/utils/templates.js +1824 -0
  24. package/bin/utils/version.js +108 -0
  25. package/package.json +58 -4
  26. package/src/animations/AnimatedBox.js +607 -0
  27. package/src/animations/AnimatedText.js +105 -0
  28. package/src/animations/MatrixRain.js +220 -0
  29. package/src/animations/ParallaxBox.js +369 -0
  30. package/src/animations/animate.js +332 -0
  31. package/src/animations/index.js +25 -0
  32. package/src/app.js +41 -0
  33. package/src/assets/icon-1024-maskable.png +0 -0
  34. package/src/assets/icon-192-maskable.png +0 -0
  35. package/src/assets/icon-192.png +0 -0
  36. package/src/assets/icon-512-maskable.png +0 -0
  37. package/src/assets/icon-512.png +0 -0
  38. package/src/assets/logo.png +0 -0
  39. package/src/core/App.js +44 -0
  40. package/src/core/hmr-client.js +142 -0
  41. package/src/core/index.js +34 -0
  42. package/src/core/pwa.js +100 -0
  43. package/src/core/runApp.js +430 -0
  44. package/src/create-index.sh +237 -0
  45. package/src/fonts/MaterialIcons-Regular.woff2 +0 -0
  46. package/src/fonts/icons.css +23 -0
  47. package/src/index.d.ts +1930 -0
  48. package/src/index.js +288 -0
  49. package/src/navigations/AdaptiveScaffold.js +149 -0
  50. package/src/navigations/AppBar.js +331 -0
  51. package/src/navigations/BottomNavigation.js +298 -0
  52. package/src/navigations/CollapsibleSideBar.js +139 -0
  53. package/src/navigations/Drawer.js +333 -0
  54. package/src/navigations/DrawerItem.js +260 -0
  55. package/src/navigations/Router.js +475 -0
  56. package/src/navigations/Scaffold.js +537 -0
  57. package/src/navigations/Tabs.js +384 -0
  58. package/src/navigations/index.js +57 -0
  59. package/src/services/RamStore.js +213 -0
  60. package/src/services/Session.js +310 -0
  61. package/src/services/Storage.js +309 -0
  62. package/src/services/http.js +350 -0
  63. package/src/services/index.js +57 -0
  64. package/src/tools/animation.js +42 -0
  65. package/src/tools/array.js +95 -0
  66. package/src/tools/border.js +28 -0
  67. package/src/tools/clipboard.js +104 -0
  68. package/src/tools/color.js +329 -0
  69. package/src/tools/createList.js +215 -0
  70. package/src/tools/delay.js +55 -0
  71. package/src/tools/device.js +138 -0
  72. package/src/tools/dict.js +297 -0
  73. package/src/tools/dimensions.js +118 -0
  74. package/src/tools/filter.js +41 -0
  75. package/src/tools/flex.js +26 -0
  76. package/src/tools/gradient.js +34 -0
  77. package/src/tools/grid.js +57 -0
  78. package/src/tools/index.js +38 -0
  79. package/src/tools/mapList.js +107 -0
  80. package/src/tools/margin.js +93 -0
  81. package/src/tools/memo.js +60 -0
  82. package/src/tools/os.js +104 -0
  83. package/src/tools/padding.js +93 -0
  84. package/src/tools/print.js +69 -0
  85. package/src/tools/random.js +299 -0
  86. package/src/tools/ref.js +70 -0
  87. package/src/tools/rgba.js +4 -0
  88. package/src/tools/shadow.js +23 -0
  89. package/src/tools/string.js +85 -0
  90. package/src/tools/time.js +674 -0
  91. package/src/tools/transform.js +68 -0
  92. package/src/tools/transition.js +48 -0
  93. package/src/tools/useState.js +268 -0
  94. package/src/tools/uuid.js +42 -0
  95. package/src/utils/TextInputValidator.js +256 -0
  96. package/src/utils/composeUpdate.js +65 -0
  97. package/src/utils/getWidgetProps.js +35 -0
  98. package/src/utils/index.js +76 -0
  99. package/src/utils/markdownParser.js +368 -0
  100. package/src/utils/mediaTime.js +79 -0
  101. package/src/utils/navigation.js +263 -0
  102. package/src/utils/scriptInjector.js +73 -0
  103. package/src/utils/stackPosition.js +31 -0
  104. package/src/utils/stopWebRefresh.js +77 -0
  105. package/src/utils/styleInjector.js +38 -0
  106. package/src/utils/syntaxHighlight.js +339 -0
  107. package/src/utils/themes.js +347 -0
  108. package/src/utils/units.js +87 -0
  109. package/src/utils/visualEffects.js +169 -0
  110. package/src/widget-factory/addChildren.js +114 -0
  111. package/src/widget-factory/assignProps.js +256 -0
  112. package/src/widget-factory/createWidget.js +28 -0
  113. package/src/widget-factory/effects.js +132 -0
  114. package/src/widget-factory/index.js +7 -0
  115. package/src/widget-factory/lifecycle.js +210 -0
  116. package/src/widget-factory/makeParentable.js +89 -0
  117. package/src/widget-factory/pixTool.js +40 -0
  118. package/src/widget-factory/processProps.js +122 -0
  119. package/src/widget-factory/reactivity.js +98 -0
  120. package/src/widget-factory/remTool.js +34 -0
  121. package/src/widget-factory/stackPosition.js +87 -0
  122. package/src/widget-factory/stringTool.js +26 -0
  123. package/src/widget-factory/tools.js +249 -0
  124. package/src/widget-factory/translateProps.js +1049 -0
  125. package/src/widget-factory/warnings.js +284 -0
  126. package/src/widget-factory/widgetFactory.js +200 -0
  127. package/src/widgets/Accordion.js +562 -0
  128. package/src/widgets/AdMob.js +315 -0
  129. package/src/widgets/AdSense.js +268 -0
  130. package/src/widgets/AlertDialog.js +162 -0
  131. package/src/widgets/Audio.js +311 -0
  132. package/src/widgets/Avatar.js +210 -0
  133. package/src/widgets/Badge.js +189 -0
  134. package/src/widgets/BottomSheet.js +287 -0
  135. package/src/widgets/Button.js +247 -0
  136. package/src/widgets/Card.js +33 -0
  137. package/src/widgets/Carousel.js +301 -0
  138. package/src/widgets/Chart.js +632 -0
  139. package/src/widgets/Checkbox.js +106 -0
  140. package/src/widgets/Chip.js +153 -0
  141. package/src/widgets/CircularBar.js +352 -0
  142. package/src/widgets/CircularChart.js +549 -0
  143. package/src/widgets/CodeViewer.js +236 -0
  144. package/src/widgets/Column.js +29 -0
  145. package/src/widgets/Container.js +31 -0
  146. package/src/widgets/DataTable.js +257 -0
  147. package/src/widgets/Divider.js +93 -0
  148. package/src/widgets/DraggBox.js +115 -0
  149. package/src/widgets/Dropdown.js +505 -0
  150. package/src/widgets/DroppBox.js +180 -0
  151. package/src/widgets/FloatingActionButton.js +91 -0
  152. package/src/widgets/GridView.js +20 -0
  153. package/src/widgets/Icon.js +33 -0
  154. package/src/widgets/Image.js +15 -0
  155. package/src/widgets/Input.js +565 -0
  156. package/src/widgets/Inspector.js +254 -0
  157. package/src/widgets/InstallButton.js +110 -0
  158. package/src/widgets/ListTile.js +224 -0
  159. package/src/widgets/ListView.js +503 -0
  160. package/src/widgets/Markdown.js +1236 -0
  161. package/src/widgets/Modal.js +496 -0
  162. package/src/widgets/Pagination.js +195 -0
  163. package/src/widgets/ProgressBar.js +178 -0
  164. package/src/widgets/QRCode.js +467 -0
  165. package/src/widgets/Radio.js +132 -0
  166. package/src/widgets/Rating.js +261 -0
  167. package/src/widgets/Row.js +29 -0
  168. package/src/widgets/Skeleton.js +243 -0
  169. package/src/widgets/Slider.js +527 -0
  170. package/src/widgets/SnackBar.js +227 -0
  171. package/src/widgets/Stack.js +29 -0
  172. package/src/widgets/Stepper.js +296 -0
  173. package/src/widgets/Switch.js +102 -0
  174. package/src/widgets/Text.js +98 -0
  175. package/src/widgets/Tooltip.js +267 -0
  176. package/src/widgets/TreeView.js +370 -0
  177. package/src/widgets/Video.js +92 -0
  178. package/src/widgets/index.js +52 -0
@@ -0,0 +1,178 @@
1
+ /**
2
+ * @file ProgressBar.js
3
+ * @description Linear progress bar (determinate/indeterminate) using shared keyframes.
4
+ */
5
+ import { WidgetFactory } from "../widget-factory/index.js";
6
+ import { colors } from "../utils/themes.js";
7
+ import { injectKeyframes } from "../utils/styleInjector.js";
8
+
9
+ export const ProgressBar = (props) => {
10
+ const {
11
+ value = 0,
12
+ max = 100,
13
+ height = 8,
14
+ width = "100%",
15
+ color = colors.primary,
16
+ backgroundColor = colors.border,
17
+ borderRadius,
18
+ label,
19
+ showValue = false,
20
+ valuePosition = "right",
21
+ indeterminate = false,
22
+ striped = false,
23
+ animatedStripes = false,
24
+ ...rest
25
+ } = props;
26
+
27
+ let progressValue = Math.min(Math.max(value, 0), max);
28
+ let percentage = (progressValue / max) * 100;
29
+ const finalBorderRadius =
30
+ borderRadius !== undefined ? borderRadius : height / 2;
31
+ const isColumn = valuePosition === "top" || valuePosition === "bottom";
32
+
33
+ // Createste fill bar
34
+ const fill = WidgetFactory({
35
+ width: indeterminate ? "100%" : `${percentage}%`,
36
+ height: "100%",
37
+ backgroundColor: color,
38
+ borderRadius:
39
+ typeof finalBorderRadius === "number"
40
+ ? `${finalBorderRadius}px`
41
+ : finalBorderRadius,
42
+ transition: indeterminate ? "none" : "width 0.3s ease",
43
+ position: "relative",
44
+ ...(striped || indeterminate
45
+ ? {
46
+ backgroundImage: `repeating-linear-gradient(45deg, rgba(255,255,255,0.15) 0px, rgba(255,255,255,0.15) 10px, transparent 10px, transparent 20px)`,
47
+ backgroundSize: "28px 28px",
48
+ }
49
+ : {}),
50
+ });
51
+
52
+ // Animated stripes
53
+ if (animatedStripes || indeterminate) {
54
+ fill.style.animation = `progress-stripes ${indeterminate ? "1.5s" : "0.5s"} linear infinite`;
55
+ injectKeyframes(
56
+ "progress-stripes",
57
+ "0% { background-position: 0 0; } 100% { background-position: 28px 0; }",
58
+ );
59
+ }
60
+
61
+ // Indeterminate animation
62
+ if (indeterminate) {
63
+ fill.style.width = "50%";
64
+ fill.style.transform = "translateX(-100%)";
65
+ fill.style.animation = "indeterminate-progress 1.5s ease-in-out infinite";
66
+ injectKeyframes(
67
+ "indeterminate-progress",
68
+ "0% { transform: translateX(-100%); } 50% { transform: translateX(0%); } 100% { transform: translateX(100%); }",
69
+ "indeterminate-progress-style",
70
+ );
71
+ }
72
+
73
+ // Track background
74
+ const track = WidgetFactory({
75
+ flex: 1,
76
+ height: typeof height === "number" ? `${height}px` : height,
77
+ backgroundColor: backgroundColor,
78
+ borderRadius:
79
+ typeof finalBorderRadius === "number"
80
+ ? `${finalBorderRadius}px`
81
+ : finalBorderRadius,
82
+ overflow: "hidden",
83
+ position: "relative",
84
+ child: fill,
85
+ });
86
+
87
+ // Build children array for main container
88
+ const children = [];
89
+
90
+ // Label (left or top)
91
+ if (label && (valuePosition === "left" || valuePosition === "top")) {
92
+ children.push(
93
+ WidgetFactory({
94
+ tag: "span",
95
+ textContent: label,
96
+ fontSize: "12px",
97
+ color: colors.textSecondary,
98
+ whiteSpace: "nowrap",
99
+ }),
100
+ );
101
+ }
102
+
103
+ children.push(track);
104
+
105
+ // Label (right or bottom)
106
+ if (label && (valuePosition === "right" || valuePosition === "bottom")) {
107
+ children.push(
108
+ WidgetFactory({
109
+ tag: "span",
110
+ textContent: label,
111
+ fontSize: "12px",
112
+ color: colors.textSecondary,
113
+ whiteSpace: "nowrap",
114
+ }),
115
+ );
116
+ }
117
+
118
+ // Value percentage
119
+ if (showValue && !indeterminate) {
120
+ const valueEl = WidgetFactory({
121
+ tag: "span",
122
+ textContent: `${Math.round(percentage)}%`,
123
+ fontSize: "12px",
124
+ color: colors.textSecondary,
125
+ fontWeight: "500",
126
+ whiteSpace: "nowrap",
127
+ minWidth: "40px",
128
+ textAlign: "right",
129
+ });
130
+
131
+ if (valuePosition === "left" || valuePosition === "top") {
132
+ children.unshift(valueEl);
133
+ } else {
134
+ children.push(valueEl);
135
+ }
136
+ }
137
+
138
+ // Main container
139
+ const container = WidgetFactory({
140
+ widgetName: "ProgressBar",
141
+ display: "flex",
142
+ flexDirection: isColumn ? "column" : "row",
143
+ alignItems: "center",
144
+ gap: "8px",
145
+ width: typeof width === "number" ? `${width}px` : width,
146
+ children: children,
147
+ ...rest,
148
+ });
149
+
150
+ // Public methods
151
+ const updateProgress = (newValue) => {
152
+ if (indeterminate) return;
153
+ progressValue = Math.min(Math.max(newValue, 0), max);
154
+ percentage = (progressValue / max) * 100;
155
+ fill.style.width = `${percentage}%`;
156
+
157
+ if (showValue) {
158
+ const valueEl = container.querySelector("span:last-child");
159
+ if (valueEl && valueEl !== label) {
160
+ valueEl.textContent = `${Math.round(percentage)}%`;
161
+ }
162
+ }
163
+ };
164
+
165
+ Object.defineProperty(container, "value", {
166
+ get: () => progressValue,
167
+ set: (newVal) => updateProgress(newVal),
168
+ enumerable: true,
169
+ });
170
+
171
+ container.updateProgress = updateProgress;
172
+ container.getValue = () => progressValue;
173
+ container.setValue = updateProgress;
174
+
175
+ return container;
176
+ };
177
+
178
+ export default ProgressBar;
@@ -0,0 +1,467 @@
1
+ /**
2
+ * @file QRCode.js
3
+ * @description QR code widget that generates real, scannable QR codes entirely
4
+ * in-browser with zero external dependencies. Implements QR Code Model 2
5
+ * (versions 1–10, byte mode, error correction L/M/Q/H) using:
6
+ * - ISO 18004-compliant data encoding (byte mode)
7
+ * - Reed-Solomon error correction
8
+ * - Standard function patterns (finder, timing, alignment, format info)
9
+ * - Data masking (all 8 masks evaluated, best chosen by penalty score)
10
+ * - Column-pair zigzag data placement
11
+ */
12
+
13
+ import { WidgetFactory } from "../widget-factory/index.js";
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // QR constants
17
+ // ---------------------------------------------------------------------------
18
+
19
+ /** Error correction capacity tables [version][ecLevel] = { totalCodewords, ecCodewordsPerBlock, blocks } */
20
+ const EC_TABLE = {
21
+ // [totalDataCodewords, ecPerBlock, blocks]
22
+ 1: { L: [19, 7, 1], M: [16, 10, 1], Q: [13, 13, 1], H: [9, 17, 1] },
23
+ 2: { L: [34, 10, 1], M: [28, 16, 1], Q: [22, 22, 1], H: [16, 28, 1] },
24
+ 3: { L: [55, 15, 1], M: [44, 26, 1], Q: [34, 18, 2], H: [26, 22, 2] },
25
+ 4: { L: [80, 20, 1], M: [64, 18, 2], Q: [48, 26, 2], H: [36, 16, 4] },
26
+ 5: { L: [108,26, 1], M: [86, 24, 2], Q: [62, 18, 4], H: [46, 22, 2] }, // simplified
27
+ 6: { L: [136,18, 2], M: [108,16, 4], Q: [76, 24, 4], H: [60, 28, 4] },
28
+ 7: { L: [156,20, 2], M: [124,18, 4], Q: [88, 18, 6], H: [66, 26, 4] },
29
+ 8: { L: [194,24, 2], M: [154,22, 2], Q: [110,22, 4], H: [86, 26, 4] },
30
+ 9: { L: [232,30, 2], M: [182,22, 3], Q: [132,20, 4], H: [100,24, 4] },
31
+ 10: { L: [274,18, 4], M: [216,26, 4], Q: [154,24, 6], H: [122,28, 6] },
32
+ };
33
+
34
+ /** Alignment pattern center positions per version (version 1 has none) */
35
+ const ALIGN_POS = {
36
+ 1: [], 2: [6,18], 3: [6,22], 4: [6,26], 5: [6,30],
37
+ 6: [6,34], 7: [6,22,38], 8: [6,24,42], 9: [6,26,46], 10: [6,28,50],
38
+ };
39
+
40
+ /** GF(256) tables for Reed-Solomon (generator polynomial x^8+x^4+x^3+x^2+1 = 0x11D) */
41
+ const GF_EXP = new Uint8Array(512);
42
+ const GF_LOG = new Uint8Array(256);
43
+ (function buildGF() {
44
+ let x = 1;
45
+ for (let i = 0; i < 255; i++) {
46
+ GF_EXP[i] = x;
47
+ GF_LOG[x] = i;
48
+ x <<= 1;
49
+ if (x & 0x100) x ^= 0x11D;
50
+ }
51
+ for (let i = 255; i < 512; i++) GF_EXP[i] = GF_EXP[i - 255];
52
+ })();
53
+
54
+ const gfMul = (a, b) => (a === 0 || b === 0) ? 0 : GF_EXP[GF_LOG[a] + GF_LOG[b]];
55
+
56
+ /** Generate Reed-Solomon generator polynomial of degree `n` */
57
+ function rsGenerator(n) {
58
+ let g = [1];
59
+ for (let i = 0; i < n; i++) {
60
+ const factor = [1, GF_EXP[i]];
61
+ const result = new Array(g.length + factor.length - 1).fill(0);
62
+ for (let j = 0; j < g.length; j++)
63
+ for (let k = 0; k < factor.length; k++)
64
+ result[j + k] ^= gfMul(g[j], factor[k]);
65
+ g = result;
66
+ }
67
+ return g;
68
+ }
69
+
70
+ /** Compute `ecCount` Reed-Solomon error correction codewords for `data` */
71
+ function rsEncode(data, ecCount) {
72
+ const gen = rsGenerator(ecCount);
73
+ const msg = [...data, ...new Array(ecCount).fill(0)];
74
+ for (let i = 0; i < data.length; i++) {
75
+ const coeff = msg[i];
76
+ if (coeff !== 0) {
77
+ for (let j = 1; j < gen.length; j++) {
78
+ msg[i + j] ^= gfMul(gen[j], coeff);
79
+ }
80
+ }
81
+ }
82
+ return msg.slice(data.length);
83
+ }
84
+
85
+ // ---------------------------------------------------------------------------
86
+ // QR matrix builder
87
+ // ---------------------------------------------------------------------------
88
+
89
+ /**
90
+ * Build a complete, scannable QR code matrix.
91
+ * @param {string} text - The text to encode.
92
+ * @param {'L'|'M'|'Q'|'H'} ecLevel - Error correction level.
93
+ * @returns {{ matrix: number[][], size: number }}
94
+ */
95
+ function buildQRMatrix(text, ecLevel = "M") {
96
+ // 1. Encode text as UTF-8 bytes
97
+ const encoder = new TextEncoder();
98
+ const bytes = encoder.encode(text);
99
+
100
+ // 2. Choose smallest version that fits
101
+ let version = 1;
102
+ for (; version <= 10; version++) {
103
+ const [dataCodewords] = EC_TABLE[version][ecLevel];
104
+ // Byte mode header: 4 (mode) + 8 (char count) + 8*N bits, rounded to codewords
105
+ const totalBits = 4 + 8 + bytes.length * 8;
106
+ if (Math.ceil(totalBits / 8) <= dataCodewords) break;
107
+ }
108
+ if (version > 10) version = 10; // clamp; long text will be truncated
109
+
110
+ const size = version * 4 + 17;
111
+ const [dataCodewords, ecPerBlock, numBlocks] = EC_TABLE[version][ecLevel];
112
+
113
+ // 3. Build bit stream (byte mode)
114
+ const bits = [];
115
+ const pushBits = (val, len) => {
116
+ for (let i = len - 1; i >= 0; i--) bits.push((val >> i) & 1);
117
+ };
118
+ pushBits(0b0100, 4); // mode indicator: byte
119
+ pushBits(bytes.length, 8); // character count
120
+ for (const b of bytes) pushBits(b, 8);
121
+ // Terminator
122
+ for (let i = 0; i < 4 && bits.length < dataCodewords * 8; i++) bits.push(0);
123
+ // Pad to byte boundary
124
+ while (bits.length % 8 !== 0) bits.push(0);
125
+ // Pad codewords
126
+ const padBytes = [0xEC, 0x11];
127
+ let padIdx = 0;
128
+ while (bits.length < dataCodewords * 8) {
129
+ pushBits(padBytes[padIdx++ % 2], 8);
130
+ }
131
+
132
+ // 4. Convert bits to data codewords
133
+ const dataCW = [];
134
+ for (let i = 0; i < bits.length; i += 8) {
135
+ let b = 0;
136
+ for (let j = 0; j < 8; j++) b = (b << 1) | (bits[i + j] || 0);
137
+ dataCW.push(b);
138
+ }
139
+
140
+ // 5. Split into blocks and compute EC codewords
141
+ const blockSize = Math.floor(dataCodewords / numBlocks);
142
+ const extraBlocks = dataCodewords - blockSize * numBlocks;
143
+ const dataBlocks = [];
144
+ const ecBlocks = [];
145
+ let offset = 0;
146
+ for (let b = 0; b < numBlocks; b++) {
147
+ const len = b < numBlocks - extraBlocks ? blockSize : blockSize + 1;
148
+ const block = dataCW.slice(offset, offset + len);
149
+ offset += len;
150
+ dataBlocks.push(block);
151
+ ecBlocks.push(rsEncode(block, ecPerBlock));
152
+ }
153
+
154
+ // 6. Interleave data codewords, then EC codewords
155
+ const finalCW = [];
156
+ const maxData = Math.max(...dataBlocks.map(b => b.length));
157
+ for (let i = 0; i < maxData; i++)
158
+ for (const block of dataBlocks) if (i < block.length) finalCW.push(block[i]);
159
+ for (let i = 0; i < ecPerBlock; i++)
160
+ for (const block of ecBlocks) finalCW.push(block[i]);
161
+
162
+ // 7. Build final bit sequence
163
+ const finalBits = [];
164
+ for (const cw of finalCW) pushBits(cw, 8);
165
+ // Remainder bits (version-dependent, versions 2–6 need 7)
166
+ const remainderBitsCount = version >= 2 && version <= 6 ? 7 : 0;
167
+ for (let i = 0; i < remainderBitsCount; i++) finalBits.push(0);
168
+
169
+ // 8. Initialize matrix with -1 (unfilled)
170
+ const mat = Array.from({ length: size }, () => new Int8Array(size).fill(-1));
171
+ const reserved = Array.from({ length: size }, () => new Uint8Array(size));
172
+
173
+ const setModule = (r, c, val) => { mat[r][c] = val; reserved[r][c] = 1; };
174
+
175
+ // 9. Place function patterns
176
+
177
+ // Finder patterns (7×7) + separators
178
+ const addFinder = (row, col) => {
179
+ for (let r = -1; r <= 7; r++) {
180
+ for (let c = -1; c <= 7; c++) {
181
+ const nr = row + r, nc = col + c;
182
+ if (nr < 0 || nr >= size || nc < 0 || nc >= size) continue;
183
+ const isInner = r >= 0 && r <= 6 && c >= 0 && c <= 6;
184
+ let val = 0;
185
+ if (isInner) {
186
+ val = (r === 0 || r === 6 || c === 0 || c === 6) ? 1
187
+ : (r >= 2 && r <= 4 && c >= 2 && c <= 4) ? 1 : 0;
188
+ }
189
+ setModule(nr, nc, val);
190
+ }
191
+ }
192
+ };
193
+ addFinder(0, 0);
194
+ addFinder(0, size - 7);
195
+ addFinder(size - 7, 0);
196
+
197
+ // Timing patterns
198
+ for (let i = 8; i < size - 8; i++) {
199
+ setModule(6, i, i % 2 === 0 ? 1 : 0);
200
+ setModule(i, 6, i % 2 === 0 ? 1 : 0);
201
+ }
202
+
203
+ // Dark module
204
+ setModule(size - 8, 8, 1);
205
+
206
+ // Alignment patterns
207
+ const ap = ALIGN_POS[version];
208
+ for (let ai = 0; ai < ap.length; ai++) {
209
+ for (let aj = 0; aj < ap.length; aj++) {
210
+ const r = ap[ai], c = ap[aj];
211
+ if (reserved[r][c]) continue; // skip if overlaps finder
212
+ for (let dr = -2; dr <= 2; dr++) {
213
+ for (let dc = -2; dc <= 2; dc++) {
214
+ const v = (Math.abs(dr) === 2 || Math.abs(dc) === 2) ? 1
215
+ : (dr === 0 && dc === 0) ? 1 : 0;
216
+ setModule(r + dr, c + dc, v);
217
+ }
218
+ }
219
+ }
220
+ }
221
+
222
+ // Reserve format info areas (filled later)
223
+ for (let i = 0; i < 9; i++) {
224
+ reserved[8][i] = 1; reserved[i][8] = 1;
225
+ }
226
+ for (let i = size - 8; i < size; i++) {
227
+ reserved[8][i] = 1; reserved[i][8] = 1;
228
+ }
229
+
230
+ // 10. Place data bits using zigzag column-pair scan
231
+ let bitIdx = 0;
232
+ let goingUp = true;
233
+ let col = size - 1;
234
+ while (col > 0) {
235
+ if (col === 6) col--; // skip timing column
236
+ for (let rowOffset = 0; rowOffset < size; rowOffset++) {
237
+ const row = goingUp ? size - 1 - rowOffset : rowOffset;
238
+ for (let c = col; c >= col - 1; c--) {
239
+ if (!reserved[row][c]) {
240
+ mat[row][c] = bitIdx < finalBits.length ? finalBits[bitIdx++] : 0;
241
+ }
242
+ }
243
+ }
244
+ goingUp = !goingUp;
245
+ col -= 2;
246
+ }
247
+
248
+ // 11. Apply best mask and write format info
249
+ const ecBits = { L: 0b01, M: 0b00, Q: 0b11, H: 0b10 }[ecLevel];
250
+
251
+ const applyMask = (maskId, m) => {
252
+ const fn = [
253
+ (r, c) => (r + c) % 2 === 0,
254
+ (r, c) => r % 2 === 0,
255
+ (r, c) => c % 3 === 0,
256
+ (r, c) => (r + c) % 3 === 0,
257
+ (r, c) => (Math.floor(r / 2) + Math.floor(c / 3)) % 2 === 0,
258
+ (r, c) => ((r * c) % 2 + (r * c) % 3) === 0,
259
+ (r, c) => ((r * c) % 2 + (r * c) % 3) % 2 === 0,
260
+ (r, c) => ((r + c) % 2 + (r * c) % 3) % 2 === 0,
261
+ ][maskId];
262
+ for (let r = 0; r < size; r++)
263
+ for (let c = 0; c < size; c++)
264
+ if (!reserved[r][c] && fn(r, c)) m[r][c] ^= 1;
265
+ };
266
+
267
+ const penaltyScore = (m) => {
268
+ let score = 0;
269
+ // Rule 1: 5+ in a row
270
+ for (let r = 0; r < size; r++) {
271
+ for (let isCol = 0; isCol < 2; isCol++) {
272
+ let run = 1;
273
+ for (let i = 1; i < size; i++) {
274
+ const prev = isCol ? m[i-1][r] : m[r][i-1];
275
+ const cur = isCol ? m[i][r] : m[r][i];
276
+ if (cur === prev) { run++; if (run === 5) score += 3; else if (run > 5) score++; }
277
+ else run = 1;
278
+ }
279
+ }
280
+ }
281
+ // Rule 2: 2×2 blocks
282
+ for (let r = 0; r < size - 1; r++)
283
+ for (let c = 0; c < size - 1; c++)
284
+ if (m[r][c] === m[r][c+1] && m[r][c] === m[r+1][c] && m[r][c] === m[r+1][c+1])
285
+ score += 3;
286
+ // Rule 3: finder-like patterns
287
+ const pat1 = [1,0,1,1,1,0,1,0,0,0,0], pat2 = [0,0,0,0,1,0,1,1,1,0,1];
288
+ for (let r = 0; r < size; r++) {
289
+ for (let c = 0; c <= size - 11; c++) {
290
+ let m1 = true, m2 = true;
291
+ for (let k = 0; k < 11; k++) {
292
+ if (m[r][c+k] !== pat1[k]) m1 = false;
293
+ if (m[r][c+k] !== pat2[k]) m2 = false;
294
+ }
295
+ if (m1 || m2) score += 40;
296
+ m1 = true; m2 = true;
297
+ for (let k = 0; k < 11; k++) {
298
+ if (m[c+k][r] !== pat1[k]) m1 = false;
299
+ if (m[c+k][r] !== pat2[k]) m2 = false;
300
+ }
301
+ if (m1 || m2) score += 40;
302
+ }
303
+ }
304
+ // Rule 4: dark/light ratio
305
+ let dark = 0;
306
+ for (let r = 0; r < size; r++) for (let c = 0; c < size; c++) if (m[r][c] === 1) dark++;
307
+ const pct = (dark / (size * size)) * 100;
308
+ const prev5 = Math.floor(Math.abs(pct - 50) / 5) * 5;
309
+ score += Math.min(prev5, prev5 + 5) * 2;
310
+ return score;
311
+ };
312
+
313
+ // Evaluate all 8 masks, pick lowest penalty
314
+ let bestMask = 0, bestScore = Infinity;
315
+ for (let mask = 0; mask < 8; mask++) {
316
+ const copy = mat.map(r => Int8Array.from(r));
317
+ applyMask(mask, copy);
318
+ const s = penaltyScore(copy);
319
+ if (s < bestScore) { bestScore = s; bestMask = mask; }
320
+ }
321
+ applyMask(bestMask, mat);
322
+
323
+ // Write format information
324
+ const writeFormat = (bits15) => {
325
+ const fmtPositions = [
326
+ [[8,0],[8,1],[8,2],[8,3],[8,4],[8,5],[8,7],[8,8],[7,8],[5,8],[4,8],[3,8],[2,8],[1,8],[0,8]],
327
+ [[size-1,8],[size-2,8],[size-3,8],[size-4,8],[size-5,8],[size-6,8],[size-7,8],[8,size-8],[8,size-7],[8,size-6],[8,size-5],[8,size-4],[8,size-3],[8,size-2],[8,size-1]],
328
+ ];
329
+ for (let copy = 0; copy < 2; copy++)
330
+ for (let i = 0; i < 15; i++)
331
+ mat[fmtPositions[copy][i][0]][fmtPositions[copy][i][1]] = (bits15 >> (14 - i)) & 1;
332
+ };
333
+
334
+ // Format info: ec(2 bits) + mask(3 bits), BCH(10 bits), XOR 101010000010010
335
+ let fmtData = (ecBits << 3) | bestMask;
336
+ let fmtBCH = fmtData << 10;
337
+ const poly = 0b10100110111;
338
+ for (let i = 14; i >= 10; i--)
339
+ if ((fmtBCH >> i) & 1) fmtBCH ^= poly << (i - 10);
340
+ writeFormat(((fmtData << 10) | fmtBCH) ^ 0b101010000010010);
341
+
342
+ return { matrix: mat, size };
343
+ }
344
+
345
+ // ---------------------------------------------------------------------------
346
+ // Widget
347
+ // ---------------------------------------------------------------------------
348
+
349
+ /**
350
+ * Creates a QRCode widget that renders a real, scannable QR code on a canvas.
351
+ *
352
+ * @param {Object} props
353
+ * @param {string} props.value - The text or URL to encode. Required.
354
+ * @param {number} [props.size=200] - Canvas size in pixels (square).
355
+ * @param {string} [props.bgColor=#ffffff] - Background (light module) color.
356
+ * @param {string} [props.fgColor=#000000] - Foreground (dark module) color.
357
+ * @param {'L'|'M'|'Q'|'H'} [props.errorCorrection=M] - Error correction level.
358
+ * L=7%, M=15%, Q=25%, H=30% of codewords recoverable.
359
+ * @param {number} [props.margin=4] - Quiet zone in modules around the QR.
360
+ * @returns {HTMLElement|null} The canvas container element, or `null` when
361
+ * `value` is empty (nothing to render).
362
+ */
363
+ export const QRCode = (props) => {
364
+ const {
365
+ value = "",
366
+ size = 200,
367
+ bgColor = "#ffffff",
368
+ fgColor = "#000000",
369
+ errorCorrection = "M",
370
+ margin = 4,
371
+ ...rest
372
+ } = props;
373
+
374
+ if (!value) return null;
375
+
376
+ let currentValue = value;
377
+ let canvasRef = null;
378
+
379
+ const drawQR = () => {
380
+ if (!canvasRef) return;
381
+ const canvas = canvasRef;
382
+ const ctx = canvas.getContext("2d");
383
+
384
+ let result;
385
+ try {
386
+ result = buildQRMatrix(currentValue, errorCorrection);
387
+ } catch (e) {
388
+ // Fallback: show error text
389
+ canvas.width = size;
390
+ canvas.height = size;
391
+ ctx.fillStyle = bgColor;
392
+ ctx.fillRect(0, 0, size, size);
393
+ ctx.fillStyle = "#cc0000";
394
+ ctx.font = "12px sans-serif";
395
+ ctx.textAlign = "center";
396
+ ctx.fillText("QR error", size / 2, size / 2);
397
+ return;
398
+ }
399
+
400
+ const { matrix, size: matSize } = result;
401
+ const totalModules = matSize + margin * 2;
402
+ const cellSize = size / totalModules;
403
+
404
+ canvas.width = size;
405
+ canvas.height = size;
406
+
407
+ ctx.fillStyle = bgColor;
408
+ ctx.fillRect(0, 0, size, size);
409
+
410
+ ctx.fillStyle = fgColor;
411
+ for (let row = 0; row < matSize; row++) {
412
+ for (let col = 0; col < matSize; col++) {
413
+ if (matrix[row][col] === 1) {
414
+ const x = (col + margin) * cellSize;
415
+ const y = (row + margin) * cellSize;
416
+ ctx.fillRect(
417
+ Math.round(x),
418
+ Math.round(y),
419
+ Math.ceil(cellSize),
420
+ Math.ceil(cellSize)
421
+ );
422
+ }
423
+ }
424
+ }
425
+ };
426
+
427
+ const canvas = document.createElement("canvas");
428
+ canvas.width = size;
429
+ canvas.height = size;
430
+ canvas.style.width = `${size}px`;
431
+ canvas.style.height = `${size}px`;
432
+ canvas.style.display = "block";
433
+ canvasRef = canvas;
434
+
435
+ // Draw on next tick so the canvas is in the DOM
436
+ setTimeout(() => drawQR(), 0);
437
+
438
+ const container = WidgetFactory({
439
+ widgetName: "QRCode",
440
+ display: "inline-block",
441
+ ...rest,
442
+ });
443
+
444
+ container.appendChild(canvas);
445
+
446
+ /** Update the encoded value and redraw */
447
+ container.updateValue = (newValue) => {
448
+ currentValue = newValue;
449
+ drawQR();
450
+ };
451
+
452
+ /** Trigger a PNG download of the current QR code */
453
+ container.download = (filename = "qrcode.png") => {
454
+ const link = document.createElement("a");
455
+ link.download = filename;
456
+ link.href = canvas.toDataURL("image/png");
457
+ link.click();
458
+ };
459
+
460
+ container.getDataURL = () => canvas.toDataURL("image/png");
461
+ container.getCanvas = () => canvas;
462
+ container.getValue = () => currentValue;
463
+
464
+ return container;
465
+ };
466
+
467
+ export default QRCode;