bitwrench 1.2.16 → 2.0.7

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 (130) hide show
  1. package/README.md +160 -158
  2. package/bin/bitwrench.js +3 -0
  3. package/dist/bitwrench-code-edit.cjs.js +639 -0
  4. package/dist/bitwrench-code-edit.es5.js +875 -0
  5. package/dist/bitwrench-code-edit.es5.min.js +15 -0
  6. package/dist/bitwrench-code-edit.esm.js +628 -0
  7. package/dist/bitwrench-code-edit.esm.min.js +15 -0
  8. package/dist/bitwrench-code-edit.umd.js +645 -0
  9. package/dist/bitwrench-code-edit.umd.min.js +15 -0
  10. package/dist/bitwrench.cjs.js +6983 -0
  11. package/dist/bitwrench.cjs.min.js +62 -0
  12. package/dist/bitwrench.css +5100 -0
  13. package/dist/bitwrench.es5.js +8446 -0
  14. package/dist/bitwrench.es5.min.js +31 -0
  15. package/dist/bitwrench.esm.js +6981 -0
  16. package/dist/bitwrench.esm.min.js +62 -0
  17. package/dist/bitwrench.umd.js +6989 -0
  18. package/dist/bitwrench.umd.min.js +62 -0
  19. package/dist/builds.json +127 -0
  20. package/dist/sri.json +18 -0
  21. package/package.json +86 -24
  22. package/readme.html +288 -0
  23. package/src/bitwrench-code-edit.js +627 -0
  24. package/src/bitwrench-color-utils.js +311 -0
  25. package/src/bitwrench-component-base.js +736 -0
  26. package/src/bitwrench-components-inline.js +374 -0
  27. package/src/bitwrench-components-v2.js +1879 -0
  28. package/src/bitwrench-components.js +610 -0
  29. package/src/bitwrench-styles.js +3240 -0
  30. package/src/bitwrench.js +3367 -0
  31. package/src/cli/convert.js +205 -0
  32. package/src/cli/index.js +122 -0
  33. package/src/cli/inject.js +55 -0
  34. package/src/cli/layout-default.js +142 -0
  35. package/src/generate-css.js +381 -0
  36. package/src/vendor/quikdown.js +654 -0
  37. package/src/version.js +16 -0
  38. package/.eslintrc.json +0 -27
  39. package/.github/workflows/codeql-analysis.yml +0 -72
  40. package/.travis.yml +0 -34
  41. package/bitwrench.css +0 -92
  42. package/bitwrench.js +0 -3348
  43. package/bitwrench.js_sri.txt +0 -1
  44. package/bitwrench.min.js +0 -1
  45. package/bitwrench.min.js_sri.txt +0 -1
  46. package/bitwrench_ESM.js +0 -3207
  47. package/bitwrench_ESM.js_sri.txt +0 -1
  48. package/bitwrench_ESM.min.js +0 -1
  49. package/bitwrench_ESM.min.js_sri.txt +0 -1
  50. package/dev/bitwrench-todo.md +0 -215
  51. package/dev/css-arrows.md +0 -23
  52. package/dev/docStringDev.js +0 -124
  53. package/dev/docStringParseDev.js +0 -171
  54. package/dev/example11-load-mjs-page.html +0 -17
  55. package/dev/figures.html +0 -37
  56. package/dev/html_gen.js +0 -349
  57. package/dev/htmld.md +0 -250
  58. package/dev/htmldev.html +0 -45
  59. package/dev/index-old.html +0 -87
  60. package/dev/misc-notes.md +0 -21
  61. package/dev/norm.css +0 -30
  62. package/dev/notes.md +0 -2
  63. package/dev/pageData.mjs +0 -69
  64. package/dev/sizes.html +0 -49
  65. package/dev/universal-js-module.js +0 -37
  66. package/examples/example1.html +0 -78
  67. package/examples/example10.html +0 -84
  68. package/examples/example11.html +0 -17
  69. package/examples/example12.html +0 -18
  70. package/examples/example2.html +0 -44
  71. package/examples/example3.html +0 -50
  72. package/examples/example4.html +0 -22
  73. package/examples/example5.html +0 -82
  74. package/examples/example6.html +0 -128
  75. package/examples/example7.html +0 -91
  76. package/examples/example8.html +0 -27
  77. package/examples/example9.html +0 -102
  78. package/examples/examplePageData12.mjs +0 -73
  79. package/examples/pageData.mjs +0 -69
  80. package/examples/pico.min.css +0 -5
  81. package/icon/bitwrench-dark-tall.png +0 -0
  82. package/icon/bitwrench-dark.png +0 -0
  83. package/icon/bitwrench-icon-lt-grey.png +0 -0
  84. package/icon/bitwrench-icon.vsd +0 -0
  85. package/icon/bitwrench-logo-dark.png +0 -0
  86. package/icon/bitwrench-logo-full.png +0 -0
  87. package/icon/bitwrench-logo-green.png +0 -0
  88. package/icon/bitwrench-logo-grey.png +0 -0
  89. package/icon/bitwrench-logo-white.png +0 -0
  90. package/icon/bitwrench-logos-colors.png +0 -0
  91. package/icon/bitwrench-thick-logo.png +0 -0
  92. package/icon/bitwrench-thick-teal/android-chrome-192x192.png +0 -0
  93. package/icon/bitwrench-thick-teal/android-chrome-512x512.png +0 -0
  94. package/icon/bitwrench-thick-teal/apple-touch-icon.png +0 -0
  95. package/icon/bitwrench-thick-teal/browserconfig.xml +0 -9
  96. package/icon/bitwrench-thick-teal/favicon-16x16.png +0 -0
  97. package/icon/bitwrench-thick-teal/favicon-32x32.png +0 -0
  98. package/icon/bitwrench-thick-teal/favicon.ico +0 -0
  99. package/icon/bitwrench-thick-teal/mstile-144x144.png +0 -0
  100. package/icon/bitwrench-thick-teal/mstile-150x150.png +0 -0
  101. package/icon/bitwrench-thick-teal/mstile-310x150.png +0 -0
  102. package/icon/bitwrench-thick-teal/mstile-310x310.png +0 -0
  103. package/icon/bitwrench-thick-teal/mstile-70x70.png +0 -0
  104. package/icon/bitwrench-thick-teal/site.webmanifest +0 -19
  105. package/icon/bitwrench-thick-teal.ico +0 -0
  106. package/icon/bitwrench-thick-teal.svg +0 -44
  107. package/icon/bitwrench-thick-teal.zip +0 -0
  108. package/icon/favicon-test.html +0 -20
  109. package/icon/logos-test.PNG +0 -0
  110. package/images/bitwrench-512x512.png +0 -0
  111. package/images/bitwrench-logo-med.png +0 -0
  112. package/images/bitwrench-thick-logo.png +0 -0
  113. package/images/bitwrench-thick-logo.svg +0 -64
  114. package/images/bitwrench-thick-teal.ico +0 -0
  115. package/images/favicon.ico +0 -0
  116. package/index.html +0 -282
  117. package/instr_tmp/bitwrench.js +0 -1350
  118. package/karma.conf.js +0 -140
  119. package/makefile +0 -21
  120. package/quick-docs.html +0 -206
  121. package/test/bitwrench_test.js +0 -1255
  122. package/test/karma-test.js +0 -1081
  123. package/tools/bw_deprecatedNames.js +0 -19
  124. package/tools/bwconsole.js +0 -20
  125. package/tools/createSimpleHTMLPage.js +0 -41
  126. package/tools/emitreadme.sh +0 -4
  127. package/tools/export-bw-default-css.js +0 -41
  128. package/tools/umd2ModuleHack.js +0 -32
  129. package/tools/update-bw-package.js +0 -36
  130. package/tools/updatereadme.js +0 -34
@@ -0,0 +1,627 @@
1
+ /**
2
+ * bitwrench-code-edit.js - syntax-highlighted contenteditable code editor addon
3
+ *
4
+ * Provides bw.highlight() for tokenizing JS/CSS/HTML into TACO spans,
5
+ * and bw.codeEditor() for a live editable code block with syntax coloring.
6
+ *
7
+ * Can be loaded standalone (browser script tag after bitwrench.umd.js),
8
+ * or imported as an ES module / CJS module.
9
+ *
10
+ * @module bitwrench-code-edit
11
+ * @license BSD-2-Clause
12
+ */
13
+
14
+ // -- CSS (injected once) ----------------------------------------------
15
+ var _cssInjected = false;
16
+ var CSS_TEXT =
17
+ '.bw-ce{background:#1e293b;border-radius:6px;border:1px solid rgba(255,255,255,0.08);overflow:auto}' +
18
+ '.bw-ce pre{margin:0;padding:0}' +
19
+ '.bw-ce code{font-family:"SF Mono",Monaco,"Cascadia Code",Consolas,monospace;font-size:0.875rem;line-height:1.6;color:#e2e8f0;outline:none;white-space:pre-wrap;display:block;padding:0.75rem 1rem}' +
20
+ '.bw-ce code:empty::before{content:"\\200b"}' +
21
+ '.bw-ce .bw-ce-keyword{color:#c792ea}' +
22
+ '.bw-ce .bw-ce-string{color:#c3e88d}' +
23
+ '.bw-ce .bw-ce-comment{color:#546e7a;font-style:italic}' +
24
+ '.bw-ce .bw-ce-number{color:#f78c6c}' +
25
+ '.bw-ce .bw-ce-operator{color:#89ddff}' +
26
+ '.bw-ce .bw-ce-punctuation{color:#89ddff}' +
27
+ '.bw-ce .bw-ce-property{color:#82aaff}' +
28
+ '.bw-ce .bw-ce-function{color:#82aaff}' +
29
+ '.bw-ce .bw-ce-tag{color:#f07178}' +
30
+ '.bw-ce .bw-ce-attr-name{color:#ffcb6b}' +
31
+ '.bw-ce .bw-ce-attr-value{color:#c3e88d}' +
32
+ '.bw-ce .bw-ce-selector{color:#c792ea}' +
33
+ '.bw-ce .bw-ce-css-prop{color:#82aaff}' +
34
+ '.bw-ce .bw-ce-css-value{color:#f78c6c}' +
35
+ '.bw-ce .bw-ce-at-rule{color:#c792ea;font-style:italic}' +
36
+ '.bw-ce .bw-ce-color{color:#f78c6c}' +
37
+ '.bw-ce .bw-ce-template-interp{color:#89ddff}' +
38
+ // Light theme
39
+ '.bw-ce-light.bw-ce{background:#fafafa;border-color:#d8d8d8}' +
40
+ '.bw-ce-light.bw-ce code{color:#1a1a1a}' +
41
+ '.bw-ce-light.bw-ce .bw-ce-keyword{color:#7c3aed}' +
42
+ '.bw-ce-light.bw-ce .bw-ce-string{color:#16a34a}' +
43
+ '.bw-ce-light.bw-ce .bw-ce-comment{color:#9ca3af;font-style:italic}' +
44
+ '.bw-ce-light.bw-ce .bw-ce-number{color:#ea580c}' +
45
+ '.bw-ce-light.bw-ce .bw-ce-operator{color:#0891b2}' +
46
+ '.bw-ce-light.bw-ce .bw-ce-punctuation{color:#6b7280}' +
47
+ '.bw-ce-light.bw-ce .bw-ce-property{color:#2563eb}' +
48
+ '.bw-ce-light.bw-ce .bw-ce-function{color:#2563eb}' +
49
+ '.bw-ce-light.bw-ce .bw-ce-tag{color:#dc2626}' +
50
+ '.bw-ce-light.bw-ce .bw-ce-attr-name{color:#d97706}' +
51
+ '.bw-ce-light.bw-ce .bw-ce-attr-value{color:#16a34a}' +
52
+ '.bw-ce-light.bw-ce .bw-ce-selector{color:#7c3aed}' +
53
+ '.bw-ce-light.bw-ce .bw-ce-css-prop{color:#2563eb}' +
54
+ '.bw-ce-light.bw-ce .bw-ce-css-value{color:#ea580c}' +
55
+ '.bw-ce-light.bw-ce .bw-ce-at-rule{color:#7c3aed}' +
56
+ '.bw-ce-light.bw-ce .bw-ce-color{color:#ea580c}' +
57
+ '.bw-ce-light.bw-ce .bw-ce-template-interp{color:#0891b2}';
58
+
59
+ function ensureCSS(bw) {
60
+ if (_cssInjected) return;
61
+ _cssInjected = true;
62
+ if (bw && bw.injectCSS) {
63
+ bw.injectCSS(CSS_TEXT, { id: 'bw-code-edit-styles' });
64
+ }
65
+ }
66
+
67
+ // -- JS keywords ------------------------------------------------------
68
+ var JS_KEYWORDS = {};
69
+ 'var,const,let,function,return,if,else,for,while,do,switch,case,break,continue,new,typeof,instanceof,this,class,extends,import,export,default,from,true,false,null,undefined,try,catch,throw,finally,async,await,yield,of,in,delete,void,with,super,static,get,set,debugger'.split(',').forEach(function(k) { JS_KEYWORDS[k] = true; });
70
+
71
+ // -- JS Tokenizer -----------------------------------------------------
72
+ function tokenizeJS(code) {
73
+ var tokens = [];
74
+ var i = 0, len = code.length;
75
+ var buf = '';
76
+
77
+ function flush(type) {
78
+ if (buf.length) { tokens.push({ type: type || 'plain', text: buf }); buf = ''; }
79
+ }
80
+
81
+ while (i < len) {
82
+ var ch = code[i];
83
+ var next = code[i + 1];
84
+
85
+ // Line comment
86
+ if (ch === '/' && next === '/') {
87
+ flush('plain');
88
+ var end = code.indexOf('\n', i);
89
+ if (end === -1) end = len;
90
+ tokens.push({ type: 'comment', text: code.substring(i, end) });
91
+ i = end;
92
+ continue;
93
+ }
94
+
95
+ // Block comment
96
+ if (ch === '/' && next === '*') {
97
+ flush('plain');
98
+ var end2 = code.indexOf('*/', i + 2);
99
+ if (end2 === -1) end2 = len - 2;
100
+ tokens.push({ type: 'comment', text: code.substring(i, end2 + 2) });
101
+ i = end2 + 2;
102
+ continue;
103
+ }
104
+
105
+ // Regex (simple heuristic: / after operator or start-of-line)
106
+ if (ch === '/' && next !== '/' && next !== '*') {
107
+ var prevToken = tokens.length ? tokens[tokens.length - 1] : null;
108
+ var prevBuf = buf.trim();
109
+ var isRegex = false;
110
+ if (!prevBuf.length) {
111
+ if (!prevToken) isRegex = true;
112
+ else if (prevToken.type === 'operator' || prevToken.type === 'punctuation' || prevToken.type === 'keyword') isRegex = true;
113
+ }
114
+ if (isRegex) {
115
+ flush('plain');
116
+ var rBuf = '/';
117
+ var ri = i + 1;
118
+ var escaped = false;
119
+ var inCharClass = false;
120
+ while (ri < len) {
121
+ var rc = code[ri];
122
+ if (escaped) { rBuf += rc; escaped = false; ri++; continue; }
123
+ if (rc === '\\') { escaped = true; rBuf += rc; ri++; continue; }
124
+ if (rc === '[') { inCharClass = true; rBuf += rc; ri++; continue; }
125
+ if (rc === ']') { inCharClass = false; rBuf += rc; ri++; continue; }
126
+ if (rc === '/' && !inCharClass) { rBuf += rc; ri++; break; }
127
+ if (rc === '\n') break;
128
+ rBuf += rc; ri++;
129
+ }
130
+ // Flags
131
+ while (ri < len && /[gimsuvy]/.test(code[ri])) { rBuf += code[ri]; ri++; }
132
+ tokens.push({ type: 'string', text: rBuf });
133
+ i = ri;
134
+ continue;
135
+ }
136
+ }
137
+
138
+ // Strings
139
+ if (ch === '"' || ch === "'" || ch === '`') {
140
+ flush('plain');
141
+ var quote = ch;
142
+ var sBuf = ch;
143
+ var si = i + 1;
144
+ if (quote === '`') {
145
+ // Template literal
146
+ while (si < len) {
147
+ var sc = code[si];
148
+ if (sc === '\\') { sBuf += sc + (code[si + 1] || ''); si += 2; continue; }
149
+ if (sc === '$' && code[si + 1] === '{') {
150
+ // Flush string part so far
151
+ if (sBuf.length) tokens.push({ type: 'string', text: sBuf }); sBuf = '';
152
+ // Find matching brace (simple: count braces)
153
+ tokens.push({ type: 'template-interp', text: '${' });
154
+ si += 2;
155
+ var depth = 1;
156
+ var interpBuf = '';
157
+ while (si < len && depth > 0) {
158
+ if (code[si] === '{') depth++;
159
+ else if (code[si] === '}') { depth--; if (depth === 0) break; }
160
+ interpBuf += code[si]; si++;
161
+ }
162
+ // Tokenize the interpolation content recursively
163
+ var interpTokens = tokenizeJS(interpBuf);
164
+ tokens = tokens.concat(interpTokens);
165
+ tokens.push({ type: 'template-interp', text: '}' });
166
+ si++; // skip closing }
167
+ continue;
168
+ }
169
+ if (sc === '`') { sBuf += sc; si++; break; }
170
+ sBuf += sc; si++;
171
+ }
172
+ if (sBuf.length) tokens.push({ type: 'string', text: sBuf });
173
+ } else {
174
+ while (si < len) {
175
+ var sc2 = code[si];
176
+ if (sc2 === '\\') { sBuf += sc2 + (code[si + 1] || ''); si += 2; continue; }
177
+ if (sc2 === quote) { sBuf += sc2; si++; break; }
178
+ if (sc2 === '\n') break;
179
+ sBuf += sc2; si++;
180
+ }
181
+ tokens.push({ type: 'string', text: sBuf });
182
+ }
183
+ i = si;
184
+ continue;
185
+ }
186
+
187
+ // Numbers
188
+ if (/[0-9]/.test(ch) || (ch === '.' && next && /[0-9]/.test(next))) {
189
+ flush('plain');
190
+ var nBuf = '';
191
+ if (ch === '0' && next && /[xXbBoO]/.test(next)) {
192
+ nBuf = ch + next; i += 2;
193
+ while (i < len && /[0-9a-fA-F_]/.test(code[i])) { nBuf += code[i]; i++; }
194
+ } else {
195
+ while (i < len && /[0-9._eE]/.test(code[i])) {
196
+ if ((code[i] === 'e' || code[i] === 'E') && code[i + 1] && /[+\-0-9]/.test(code[i + 1])) {
197
+ nBuf += code[i] + code[i + 1]; i += 2; continue;
198
+ }
199
+ nBuf += code[i]; i++;
200
+ }
201
+ }
202
+ tokens.push({ type: 'number', text: nBuf });
203
+ continue;
204
+ }
205
+
206
+ // Identifiers / keywords
207
+ if (/[a-zA-Z_$]/.test(ch)) {
208
+ flush('plain');
209
+ var wBuf = '';
210
+ while (i < len && /[a-zA-Z0-9_$]/.test(code[i])) { wBuf += code[i]; i++; }
211
+ // Look-ahead: is it a function call?
212
+ var la = i;
213
+ while (la < len && (code[la] === ' ' || code[la] === '\t')) la++;
214
+ // Look-back: is it a property (after dot)?
215
+ var prevTok = tokens.length ? tokens[tokens.length - 1] : null;
216
+ var isDot = prevTok && prevTok.type === 'punctuation' && prevTok.text === '.';
217
+
218
+ if (JS_KEYWORDS[wBuf]) {
219
+ tokens.push({ type: 'keyword', text: wBuf });
220
+ } else if (isDot) {
221
+ if (la < len && code[la] === '(') {
222
+ tokens.push({ type: 'function', text: wBuf });
223
+ } else {
224
+ tokens.push({ type: 'property', text: wBuf });
225
+ }
226
+ } else if (la < len && code[la] === '(') {
227
+ tokens.push({ type: 'function', text: wBuf });
228
+ } else {
229
+ tokens.push({ type: 'plain', text: wBuf });
230
+ }
231
+ continue;
232
+ }
233
+
234
+ // Operators
235
+ if ('=+-*/%!<>&|^~?:'.indexOf(ch) !== -1) {
236
+ flush('plain');
237
+ // Consume multi-char operators
238
+ var opBuf = ch;
239
+ i++;
240
+ if (i < len && '=+-*/%!<>&|^~?:'.indexOf(code[i]) !== -1) {
241
+ opBuf += code[i]; i++;
242
+ if (i < len && '=>&|'.indexOf(code[i]) !== -1) { opBuf += code[i]; i++; }
243
+ }
244
+ tokens.push({ type: 'operator', text: opBuf });
245
+ continue;
246
+ }
247
+
248
+ // Punctuation
249
+ if ('(){}[];,.'.indexOf(ch) !== -1) {
250
+ flush('plain');
251
+ tokens.push({ type: 'punctuation', text: ch });
252
+ i++;
253
+ continue;
254
+ }
255
+
256
+ // Plain (whitespace + anything else)
257
+ buf += ch;
258
+ i++;
259
+ }
260
+ flush('plain');
261
+ return tokens;
262
+ }
263
+
264
+ // -- CSS Tokenizer ----------------------------------------------------
265
+ function tokenizeCSS(code) {
266
+ var tokens = [];
267
+ var i = 0, len = code.length;
268
+ var state = 'selector'; // selector | prop | value
269
+ var buf = '';
270
+
271
+ function flush(type) {
272
+ if (buf.length) { tokens.push({ type: type || 'plain', text: buf }); buf = ''; }
273
+ }
274
+
275
+ while (i < len) {
276
+ var ch = code[i];
277
+ var next = code[i + 1];
278
+
279
+ // Block comment
280
+ if (ch === '/' && next === '*') {
281
+ flush(state === 'selector' ? 'selector' : state === 'prop' ? 'css-prop' : 'css-value');
282
+ var end = code.indexOf('*/', i + 2);
283
+ if (end === -1) end = len - 2;
284
+ tokens.push({ type: 'comment', text: code.substring(i, end + 2) });
285
+ i = end + 2;
286
+ continue;
287
+ }
288
+
289
+ // Strings in values
290
+ if ((ch === '"' || ch === "'") && (state === 'value' || state === 'selector')) {
291
+ flush(state === 'selector' ? 'selector' : 'css-value');
292
+ var quote = ch;
293
+ var sBuf = ch;
294
+ i++;
295
+ while (i < len) {
296
+ if (code[i] === '\\') { sBuf += code[i] + (code[i + 1] || ''); i += 2; continue; }
297
+ if (code[i] === quote) { sBuf += code[i]; i++; break; }
298
+ sBuf += code[i]; i++;
299
+ }
300
+ tokens.push({ type: 'string', text: sBuf });
301
+ continue;
302
+ }
303
+
304
+ // At-rules
305
+ if (ch === '@' && state === 'selector') {
306
+ flush('selector');
307
+ var aBuf = '@';
308
+ i++;
309
+ while (i < len && /[a-zA-Z\-]/.test(code[i])) { aBuf += code[i]; i++; }
310
+ tokens.push({ type: 'at-rule', text: aBuf });
311
+ continue;
312
+ }
313
+
314
+ // Hex colors in values
315
+ if (ch === '#' && state === 'value') {
316
+ flush('css-value');
317
+ var hBuf = '#';
318
+ i++;
319
+ while (i < len && /[0-9a-fA-F]/.test(code[i])) { hBuf += code[i]; i++; }
320
+ tokens.push({ type: 'color', text: hBuf });
321
+ continue;
322
+ }
323
+
324
+ // Numbers in values
325
+ if (state === 'value' && /[0-9]/.test(ch)) {
326
+ flush('css-value');
327
+ var nBuf2 = '';
328
+ while (i < len && /[0-9.]/.test(code[i])) { nBuf2 += code[i]; i++; }
329
+ // Unit
330
+ var uBuf = '';
331
+ while (i < len && /[a-zA-Z%]/.test(code[i])) { uBuf += code[i]; i++; }
332
+ tokens.push({ type: 'number', text: nBuf2 + uBuf });
333
+ continue;
334
+ }
335
+
336
+ // Punctuation and state transitions
337
+ if (ch === '{') {
338
+ flush(state === 'selector' ? 'selector' : 'plain');
339
+ tokens.push({ type: 'punctuation', text: ch });
340
+ state = 'prop';
341
+ i++; continue;
342
+ }
343
+ if (ch === '}') {
344
+ flush(state === 'prop' ? 'css-prop' : state === 'value' ? 'css-value' : 'plain');
345
+ tokens.push({ type: 'punctuation', text: ch });
346
+ state = 'selector';
347
+ i++; continue;
348
+ }
349
+ if (ch === ':' && state === 'prop') {
350
+ flush('css-prop');
351
+ tokens.push({ type: 'punctuation', text: ch });
352
+ state = 'value';
353
+ i++; continue;
354
+ }
355
+ if (ch === ';') {
356
+ flush(state === 'value' ? 'css-value' : 'plain');
357
+ tokens.push({ type: 'punctuation', text: ch });
358
+ state = 'prop';
359
+ i++; continue;
360
+ }
361
+ if (ch === ',') {
362
+ flush(state === 'selector' ? 'selector' : state === 'value' ? 'css-value' : 'plain');
363
+ tokens.push({ type: 'punctuation', text: ch });
364
+ i++; continue;
365
+ }
366
+
367
+ // Accumulate into buffer
368
+ buf += ch;
369
+ i++;
370
+ }
371
+ flush(state === 'selector' ? 'selector' : state === 'prop' ? 'css-prop' : 'css-value');
372
+ return tokens;
373
+ }
374
+
375
+ // -- HTML Tokenizer ---------------------------------------------------
376
+ function tokenizeHTML(code) {
377
+ var tokens = [];
378
+ var i = 0, len = code.length;
379
+ var buf = '';
380
+
381
+ function flush(type) {
382
+ if (buf.length) { tokens.push({ type: type || 'plain', text: buf }); buf = ''; }
383
+ }
384
+
385
+ while (i < len) {
386
+ var ch = code[i];
387
+
388
+ // Comment
389
+ if (ch === '<' && code.substring(i, i + 4) === '<!--') {
390
+ flush('plain');
391
+ var end = code.indexOf('-->', i + 4);
392
+ if (end === -1) end = len - 3;
393
+ tokens.push({ type: 'comment', text: code.substring(i, end + 3) });
394
+ i = end + 3;
395
+ continue;
396
+ }
397
+
398
+ // Tag
399
+ if (ch === '<') {
400
+ flush('plain');
401
+ // Consume < and optional /
402
+ var tBuf = '<';
403
+ i++;
404
+ if (i < len && code[i] === '/') { tBuf += '/'; i++; }
405
+ // Tag name
406
+ while (i < len && /[a-zA-Z0-9\-]/.test(code[i])) { tBuf += code[i]; i++; }
407
+ tokens.push({ type: 'tag', text: tBuf });
408
+
409
+ // Attributes
410
+ while (i < len && code[i] !== '>' && !(code[i] === '/' && code[i + 1] === '>')) {
411
+ // Whitespace
412
+ if (/\s/.test(code[i])) {
413
+ var wBuf = '';
414
+ while (i < len && /\s/.test(code[i])) { wBuf += code[i]; i++; }
415
+ tokens.push({ type: 'plain', text: wBuf });
416
+ continue;
417
+ }
418
+ // Attribute name
419
+ if (/[a-zA-Z_\-@:]/.test(code[i])) {
420
+ var aBuf = '';
421
+ while (i < len && /[a-zA-Z0-9_\-@:]/.test(code[i])) { aBuf += code[i]; i++; }
422
+ tokens.push({ type: 'attr-name', text: aBuf });
423
+ // = sign
424
+ if (i < len && code[i] === '=') {
425
+ tokens.push({ type: 'punctuation', text: '=' });
426
+ i++;
427
+ // Attribute value
428
+ if (i < len && (code[i] === '"' || code[i] === "'")) {
429
+ var q = code[i];
430
+ var vBuf = q;
431
+ i++;
432
+ while (i < len && code[i] !== q) { vBuf += code[i]; i++; }
433
+ if (i < len) { vBuf += code[i]; i++; }
434
+ tokens.push({ type: 'attr-value', text: vBuf });
435
+ } else {
436
+ // Unquoted value
437
+ var uBuf2 = '';
438
+ while (i < len && !/[\s>]/.test(code[i])) { uBuf2 += code[i]; i++; }
439
+ tokens.push({ type: 'attr-value', text: uBuf2 });
440
+ }
441
+ }
442
+ continue;
443
+ }
444
+ // Anything else in tag
445
+ buf += code[i]; i++;
446
+ flush('plain');
447
+ }
448
+
449
+ // Close of tag
450
+ var closeBuf = '';
451
+ if (i < len && code[i] === '/') { closeBuf += '/'; i++; }
452
+ if (i < len && code[i] === '>') { closeBuf += '>'; i++; }
453
+ if (closeBuf) tokens.push({ type: 'tag', text: closeBuf });
454
+ continue;
455
+ }
456
+
457
+ // Entity
458
+ if (ch === '&') {
459
+ flush('plain');
460
+ var eBuf = '&';
461
+ i++;
462
+ while (i < len && code[i] !== ';' && /[a-zA-Z0-9#]/.test(code[i])) { eBuf += code[i]; i++; }
463
+ if (i < len && code[i] === ';') { eBuf += ';'; i++; }
464
+ tokens.push({ type: 'string', text: eBuf });
465
+ continue;
466
+ }
467
+
468
+ // Plain text
469
+ buf += ch;
470
+ i++;
471
+ }
472
+ flush('plain');
473
+ return tokens;
474
+ }
475
+
476
+ // -- Token to TACO conversion -----------------------------------------
477
+ var TOKENIZERS = { js: tokenizeJS, javascript: tokenizeJS, css: tokenizeCSS, html: tokenizeHTML };
478
+
479
+ function tokensToTACO(tokArr) {
480
+ var result = [];
481
+ for (var i = 0; i < tokArr.length; i++) {
482
+ var tok = tokArr[i];
483
+ if (tok.type === 'plain') {
484
+ result.push(tok.text);
485
+ } else {
486
+ result.push({ t: 'span', a: { class: 'bw-ce-' + tok.type }, c: tok.text });
487
+ }
488
+ }
489
+ return result;
490
+ }
491
+
492
+ // -- Public: highlight ------------------------------------------------
493
+ function highlight(code, lang) {
494
+ var tokenizer = TOKENIZERS[lang] || tokenizeJS;
495
+ var tokens = tokenizer(code);
496
+ return tokensToTACO(tokens);
497
+ }
498
+
499
+ // -- Caret save/restore -----------------------------------------------
500
+ function getCaretOffset(el) {
501
+ var sel = window.getSelection();
502
+ if (!sel.rangeCount) return 0;
503
+ var range = sel.getRangeAt(0).cloneRange();
504
+ range.selectNodeContents(el);
505
+ range.setEnd(sel.getRangeAt(0).startContainer, sel.getRangeAt(0).startOffset);
506
+ return range.toString().length;
507
+ }
508
+
509
+ function setCaretOffset(el, offset) {
510
+ var sel = window.getSelection();
511
+ var range = document.createRange();
512
+ var walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null, false);
513
+ var pos = 0;
514
+ var node;
515
+ while ((node = walker.nextNode())) {
516
+ var nodeLen = node.textContent.length;
517
+ if (pos + nodeLen >= offset) {
518
+ range.setStart(node, offset - pos);
519
+ range.collapse(true);
520
+ sel.removeAllRanges();
521
+ sel.addRange(range);
522
+ return;
523
+ }
524
+ pos += nodeLen;
525
+ }
526
+ // If offset exceeds content, place at end
527
+ range.selectNodeContents(el);
528
+ range.collapse(false);
529
+ sel.removeAllRanges();
530
+ sel.addRange(range);
531
+ }
532
+
533
+ // -- Public: codeEditor -----------------------------------------------
534
+ function codeEditor(opts) {
535
+ opts = opts || {};
536
+ var code = opts.code || '';
537
+ var lang = opts.lang || 'js';
538
+ var height = opts.height || '180px';
539
+ var readOnly = !!opts.readOnly;
540
+ var className = 'bw-ce' + (opts.className ? ' ' + opts.className : '');
541
+
542
+ var highlighted = highlight(code, lang);
543
+
544
+ var codeAttrs = {
545
+ spellcheck: 'false',
546
+ class: 'bw-ce-code'
547
+ };
548
+ if (!readOnly) {
549
+ codeAttrs.contenteditable = 'true';
550
+ }
551
+
552
+ return {
553
+ t: 'div',
554
+ a: { class: className, style: 'max-height:' + height + ';overflow:auto' },
555
+ c: [
556
+ { t: 'pre', c: { t: 'code', a: codeAttrs, c: highlighted } }
557
+ ],
558
+ o: {
559
+ mounted: function(el) {
560
+ var codeEl = el.querySelector('.bw-ce-code');
561
+ if (!codeEl) return;
562
+
563
+ var currentCode = code;
564
+ var debounceTimer = null;
565
+
566
+ // Resolve bw from global or import context
567
+ var bw = (typeof window !== 'undefined' && window.bw) || {};
568
+
569
+ function getValue() { return codeEl.textContent || ''; }
570
+
571
+ function setValue(newCode) {
572
+ currentCode = newCode;
573
+ var tacos = highlight(newCode, lang);
574
+ if (bw.html) codeEl.innerHTML = bw.html({ t: 'span', c: tacos });
575
+ }
576
+
577
+ // Expose API on the element
578
+ el._bwCodeEdit = { getValue: getValue, setValue: setValue };
579
+
580
+ if (readOnly) return;
581
+
582
+ function rehighlight() {
583
+ var newCode = getValue();
584
+ if (newCode === currentCode) return;
585
+ currentCode = newCode;
586
+ var offset = getCaretOffset(codeEl);
587
+ var tacos = highlight(newCode, lang);
588
+ if (bw.html) codeEl.innerHTML = bw.html({ t: 'span', c: tacos });
589
+ setCaretOffset(codeEl, offset);
590
+ if (opts.onChange) opts.onChange(newCode);
591
+ }
592
+
593
+ codeEl.addEventListener('input', function() {
594
+ clearTimeout(debounceTimer);
595
+ debounceTimer = setTimeout(rehighlight, 50);
596
+ });
597
+
598
+ // Tab key: insert 2 spaces
599
+ codeEl.addEventListener('keydown', function(e) {
600
+ if (e.key === 'Tab') {
601
+ e.preventDefault();
602
+ document.execCommand('insertText', false, ' ');
603
+ }
604
+ });
605
+ }
606
+ }
607
+ };
608
+ }
609
+
610
+ // -- Auto-attach to bw when loaded as script tag ----------------------
611
+ function install(bw) {
612
+ if (!bw) return;
613
+ bw.highlight = highlight;
614
+ bw.codeEditor = function(opts) {
615
+ ensureCSS(bw);
616
+ return codeEditor(opts);
617
+ };
618
+ }
619
+
620
+ // Auto-install if bw is on window (script tag usage)
621
+ if (typeof window !== 'undefined' && window.bw) {
622
+ install(window.bw);
623
+ }
624
+
625
+ // -- Exports ----------------------------------------------------------
626
+ export { highlight, codeEditor, install, CSS_TEXT, tokenizeJS, tokenizeCSS, tokenizeHTML };
627
+ export default { highlight, codeEditor, install, CSS_TEXT };