bitwrench 2.0.13 → 2.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/bitwrench-code-edit.cjs.js +46 -46
- package/dist/bitwrench-code-edit.cjs.min.js +16 -0
- package/dist/bitwrench-code-edit.es5.js +8 -8
- package/dist/bitwrench-code-edit.es5.min.js +2 -2
- package/dist/bitwrench-code-edit.esm.js +46 -46
- package/dist/bitwrench-code-edit.esm.min.js +2 -2
- package/dist/bitwrench-code-edit.umd.js +46 -46
- package/dist/bitwrench-code-edit.umd.min.js +2 -2
- package/dist/bitwrench-lean.cjs.js +5011 -3419
- package/dist/bitwrench-lean.cjs.min.js +35 -6
- package/dist/bitwrench-lean.es5.js +6218 -4272
- package/dist/bitwrench-lean.es5.min.js +32 -3
- package/dist/bitwrench-lean.esm.js +5011 -3419
- package/dist/bitwrench-lean.esm.min.js +35 -6
- package/dist/bitwrench-lean.umd.js +5011 -3419
- package/dist/bitwrench-lean.umd.min.js +35 -6
- package/dist/bitwrench.cjs.js +6966 -4662
- package/dist/bitwrench.cjs.min.js +38 -8
- package/dist/bitwrench.css +2453 -4784
- package/dist/bitwrench.es5.js +9592 -6813
- package/dist/bitwrench.es5.min.js +34 -5
- package/dist/bitwrench.esm.js +6966 -4662
- package/dist/bitwrench.esm.min.js +38 -8
- package/dist/bitwrench.min.css +1 -0
- package/dist/bitwrench.umd.js +6966 -4662
- package/dist/bitwrench.umd.min.js +38 -8
- package/dist/builds.json +89 -67
- package/dist/sri.json +28 -26
- package/package.json +7 -5
- package/readme.html +14 -14
- package/src/{bitwrench-components-v2.js → bitwrench-bccl.js} +1311 -600
- package/src/bitwrench-code-edit.js +45 -45
- package/src/bitwrench-color-utils.js +154 -27
- package/src/bitwrench-components-stub.js +4 -1
- package/src/bitwrench-file-ops.js +180 -0
- package/src/bitwrench-lean.js +2 -2
- package/src/bitwrench-styles.js +1468 -3494
- package/src/bitwrench-utils.js +458 -0
- package/src/bitwrench.js +1795 -1349
- package/src/cli/layout-default.js +18 -18
- package/src/generate-css.js +73 -53
- package/src/version.js +3 -3
- package/src/bitwrench-component-base.js +0 -736
- package/src/bitwrench-components-inline.js +0 -374
- package/src/bitwrench-components.js +0 -610
|
@@ -14,53 +14,53 @@
|
|
|
14
14
|
// -- CSS (injected once) ----------------------------------------------
|
|
15
15
|
var _cssInjected = false;
|
|
16
16
|
var CSS_TEXT =
|
|
17
|
-
'.
|
|
18
|
-
'.
|
|
19
|
-
'.
|
|
20
|
-
'.
|
|
21
|
-
'.
|
|
22
|
-
'.
|
|
23
|
-
'.
|
|
24
|
-
'.
|
|
25
|
-
'.
|
|
26
|
-
'.
|
|
27
|
-
'.
|
|
28
|
-
'.
|
|
29
|
-
'.
|
|
30
|
-
'.
|
|
31
|
-
'.
|
|
32
|
-
'.
|
|
33
|
-
'.
|
|
34
|
-
'.
|
|
35
|
-
'.
|
|
36
|
-
'.
|
|
37
|
-
'.
|
|
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
38
|
// Light theme
|
|
39
|
-
'.
|
|
40
|
-
'.
|
|
41
|
-
'.
|
|
42
|
-
'.
|
|
43
|
-
'.
|
|
44
|
-
'.
|
|
45
|
-
'.
|
|
46
|
-
'.
|
|
47
|
-
'.
|
|
48
|
-
'.
|
|
49
|
-
'.
|
|
50
|
-
'.
|
|
51
|
-
'.
|
|
52
|
-
'.
|
|
53
|
-
'.
|
|
54
|
-
'.
|
|
55
|
-
'.
|
|
56
|
-
'.
|
|
57
|
-
'.
|
|
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
58
|
|
|
59
59
|
function ensureCSS(bw) {
|
|
60
60
|
if (_cssInjected) return;
|
|
61
61
|
_cssInjected = true;
|
|
62
62
|
if (bw && bw.injectCSS) {
|
|
63
|
-
bw.injectCSS(CSS_TEXT, { id: '
|
|
63
|
+
bw.injectCSS(CSS_TEXT, { id: 'bw_code_edit_styles' });
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
@@ -483,7 +483,7 @@ function tokensToTACO(tokArr) {
|
|
|
483
483
|
if (tok.type === 'plain') {
|
|
484
484
|
result.push(tok.text);
|
|
485
485
|
} else {
|
|
486
|
-
result.push({ t: 'span', a: { class: '
|
|
486
|
+
result.push({ t: 'span', a: { class: 'bw_ce_' + tok.type }, c: tok.text });
|
|
487
487
|
}
|
|
488
488
|
}
|
|
489
489
|
return result;
|
|
@@ -537,13 +537,13 @@ function codeEditor(opts) {
|
|
|
537
537
|
var lang = opts.lang || 'js';
|
|
538
538
|
var height = opts.height || '180px';
|
|
539
539
|
var readOnly = !!opts.readOnly;
|
|
540
|
-
var className = '
|
|
540
|
+
var className = 'bw_ce' + (opts.className ? ' ' + opts.className : '');
|
|
541
541
|
|
|
542
542
|
var highlighted = highlight(code, lang);
|
|
543
543
|
|
|
544
544
|
var codeAttrs = {
|
|
545
545
|
spellcheck: 'false',
|
|
546
|
-
class: '
|
|
546
|
+
class: 'bw_ce_code'
|
|
547
547
|
};
|
|
548
548
|
if (!readOnly) {
|
|
549
549
|
codeAttrs.contenteditable = 'true';
|
|
@@ -557,7 +557,7 @@ function codeEditor(opts) {
|
|
|
557
557
|
],
|
|
558
558
|
o: {
|
|
559
559
|
mounted: function(el) {
|
|
560
|
-
var codeEl = el.querySelector('.
|
|
560
|
+
var codeEl = el.querySelector('.bw_ce_code');
|
|
561
561
|
if (!codeEl) return;
|
|
562
562
|
|
|
563
563
|
var currentCode = code;
|
|
@@ -6,9 +6,13 @@
|
|
|
6
6
|
* bw.colorParse, bw.colorRgbToHsl, etc.
|
|
7
7
|
*
|
|
8
8
|
* @module bitwrench-color-utils
|
|
9
|
-
* @license BSD-2-Clause
|
|
9
|
+
* @license BSD-2-Clause
|
|
10
|
+
* @copy Manu Chatterjee @deftio
|
|
10
11
|
*/
|
|
11
12
|
|
|
13
|
+
function _xs (x) {
|
|
14
|
+
return ('0' + x.toString(16)).slice(-2)
|
|
15
|
+
}
|
|
12
16
|
/**
|
|
13
17
|
* Clamp a value between min and max.
|
|
14
18
|
* @param {number} val
|
|
@@ -200,10 +204,7 @@ export function hexToHsl(hex) {
|
|
|
200
204
|
*/
|
|
201
205
|
export function hslToHex(hsl) {
|
|
202
206
|
var rgb = colorHslToRgb(hsl[0], hsl[1], hsl[2], 255, true);
|
|
203
|
-
return '#' +
|
|
204
|
-
('0' + rgb[0].toString(16)).slice(-2) +
|
|
205
|
-
('0' + rgb[1].toString(16)).slice(-2) +
|
|
206
|
-
('0' + rgb[2].toString(16)).slice(-2);
|
|
207
|
+
return '#' + _xs(rgb[0])+_xs(rgb[1])+_xs(rgb[2]);
|
|
207
208
|
}
|
|
208
209
|
|
|
209
210
|
/**
|
|
@@ -232,10 +233,7 @@ export function mixColor(hex1, hex2, ratio) {
|
|
|
232
233
|
var r = Math.round(c1[0] + (c2[0] - c1[0]) * ratio);
|
|
233
234
|
var g = Math.round(c1[1] + (c2[1] - c1[1]) * ratio);
|
|
234
235
|
var b = Math.round(c1[2] + (c2[2] - c1[2]) * ratio);
|
|
235
|
-
return '#' +
|
|
236
|
-
('0' + r.toString(16)).slice(-2) +
|
|
237
|
-
('0' + g.toString(16)).slice(-2) +
|
|
238
|
-
('0' + b.toString(16)).slice(-2);
|
|
236
|
+
return '#' + _xs(r) + _xs(g) + _xs(b);
|
|
239
237
|
}
|
|
240
238
|
|
|
241
239
|
/**
|
|
@@ -261,6 +259,29 @@ export function textOnColor(hex) {
|
|
|
261
259
|
return relativeLuminance(hex) > 0.179 ? '#000' : '#fff';
|
|
262
260
|
}
|
|
263
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Shift a color's hue toward a target hue by a given amount.
|
|
264
|
+
* Uses shortest-arc interpolation on the hue wheel.
|
|
265
|
+
* @param {string} sourceHex - Color to shift
|
|
266
|
+
* @param {string} targetHex - Color whose hue to shift toward
|
|
267
|
+
* @param {number} [amount=0.20] - 0 = no shift, 1 = full shift to target hue
|
|
268
|
+
* @returns {string} Harmonized hex color
|
|
269
|
+
*/
|
|
270
|
+
export function harmonize(sourceHex, targetHex, amount) {
|
|
271
|
+
if (amount === undefined) amount = 0.20;
|
|
272
|
+
if (amount === 0) return sourceHex;
|
|
273
|
+
var srcHsl = hexToHsl(sourceHex);
|
|
274
|
+
var tgtHsl = hexToHsl(targetHex);
|
|
275
|
+
|
|
276
|
+
// Shortest-arc hue interpolation
|
|
277
|
+
var diff = tgtHsl[0] - srcHsl[0];
|
|
278
|
+
if (diff > 180) diff -= 360;
|
|
279
|
+
if (diff < -180) diff += 360;
|
|
280
|
+
|
|
281
|
+
var newHue = (srcHsl[0] + diff * amount + 360) % 360;
|
|
282
|
+
return hslToHex([newHue, srcHsl[1], srcHsl[2]]);
|
|
283
|
+
}
|
|
284
|
+
|
|
264
285
|
/**
|
|
265
286
|
* Derive a full shade palette for a single semantic color.
|
|
266
287
|
* @param {string} hex - Base color hex
|
|
@@ -280,31 +301,137 @@ export function deriveShades(hex) {
|
|
|
280
301
|
};
|
|
281
302
|
}
|
|
282
303
|
|
|
304
|
+
/**
|
|
305
|
+
* Derive the alternate (luminance-inverted) version of a single seed color.
|
|
306
|
+
* Preserves hue, mirrors lightness, adjusts saturation for readability.
|
|
307
|
+
* @param {string} hex - Seed hex color
|
|
308
|
+
* @returns {string} Alternate hex color
|
|
309
|
+
*/
|
|
310
|
+
export function deriveAlternateSeed(hex) {
|
|
311
|
+
var hsl = hexToHsl(hex);
|
|
312
|
+
var h = hsl[0], s = hsl[1], l = hsl[2];
|
|
313
|
+
var altL, altS;
|
|
314
|
+
|
|
315
|
+
if (l > 50) {
|
|
316
|
+
// Light color → make dark. Map 50-100 → 30-10 range
|
|
317
|
+
altL = clip(100 - l - 10, 8, 40);
|
|
318
|
+
// Reduce saturation slightly — vivid colors at low lightness look garish
|
|
319
|
+
altS = clip(s * 0.85, 0, 100);
|
|
320
|
+
} else {
|
|
321
|
+
// Dark color → make light. Map 0-50 → 65-92 range
|
|
322
|
+
altL = clip(100 - l + 10, 60, 92);
|
|
323
|
+
// Slightly increase saturation for light variant
|
|
324
|
+
altS = clip(s * 1.1, 0, 100);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return hslToHex([h, altS, altL]);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Determine whether a palette config is "light-flavored" based on
|
|
332
|
+
* the average luminance of its seed colors.
|
|
333
|
+
* @param {Object} config - Theme config with primary, secondary hex colors
|
|
334
|
+
* @returns {boolean} true if the seeds are predominantly light
|
|
335
|
+
*/
|
|
336
|
+
export function isLightPalette(config) {
|
|
337
|
+
var lum = relativeLuminance(config.primary);
|
|
338
|
+
if (config.secondary) lum = (lum + relativeLuminance(config.secondary)) / 2;
|
|
339
|
+
if (config.tertiary) lum = (lum * 2 + relativeLuminance(config.tertiary)) / 3;
|
|
340
|
+
return lum > 0.179;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Derive a complete alternate config from a primary theme config.
|
|
345
|
+
* Each seed color is luminance-inverted; semantic colors are adjusted for
|
|
346
|
+
* the new luminance context.
|
|
347
|
+
* @param {Object} config - Primary theme config
|
|
348
|
+
* @returns {Object} Alternate theme config (same shape, inverted lightness)
|
|
349
|
+
*/
|
|
350
|
+
export function deriveAlternateConfig(config) {
|
|
351
|
+
var alt = {};
|
|
352
|
+
// Invert the user's seed colors
|
|
353
|
+
alt.primary = deriveAlternateSeed(config.primary);
|
|
354
|
+
alt.secondary = deriveAlternateSeed(config.secondary);
|
|
355
|
+
alt.tertiary = config.tertiary ? deriveAlternateSeed(config.tertiary) : alt.primary;
|
|
356
|
+
|
|
357
|
+
// Derive alternate surface colors from primary hue
|
|
358
|
+
var priHsl = hexToHsl(config.primary);
|
|
359
|
+
var h = priHsl[0];
|
|
360
|
+
var isLight = isLightPalette(config);
|
|
361
|
+
|
|
362
|
+
if (isLight) {
|
|
363
|
+
// Primary is light → alternate needs dark surfaces
|
|
364
|
+
alt.light = hslToHex([h, Math.min(priHsl[1], 15), 15]);
|
|
365
|
+
alt.dark = hslToHex([h, 5, 88]);
|
|
366
|
+
} else {
|
|
367
|
+
// Primary is dark → alternate needs light surfaces
|
|
368
|
+
alt.light = hslToHex([h, Math.min(priHsl[1], 10), 96]);
|
|
369
|
+
alt.dark = hslToHex([h, 10, 18]);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Semantic colors: harmonize toward primary, then invert for alternate
|
|
373
|
+
var amt = config.harmonize !== undefined ? config.harmonize : 0.20;
|
|
374
|
+
var semanticDefaults = {
|
|
375
|
+
success: '#198754', danger: '#dc3545',
|
|
376
|
+
warning: '#f0ad4e', info: '#17a2b8'
|
|
377
|
+
};
|
|
378
|
+
var semantics = ['success', 'danger', 'warning', 'info'];
|
|
379
|
+
for (var i = 0; i < semantics.length; i++) {
|
|
380
|
+
var key = semantics[i];
|
|
381
|
+
var seed = config[key] || semanticDefaults[key];
|
|
382
|
+
var harmonized = harmonize(seed, config.primary, amt);
|
|
383
|
+
alt[key] = deriveAlternateSeed(harmonized);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Semantic colors are already harmonized+inverted — don't re-harmonize in derivePalette
|
|
387
|
+
alt.harmonize = 0;
|
|
388
|
+
|
|
389
|
+
return alt;
|
|
390
|
+
}
|
|
391
|
+
|
|
283
392
|
/**
|
|
284
393
|
* Derive complete palette from a theme config object.
|
|
394
|
+
* Semantic colors are harmonized toward the primary hue (configurable).
|
|
395
|
+
* Light/dark surface colors are tinted with the primary hue.
|
|
285
396
|
* @param {Object} config - Theme config with primary, secondary, tertiary, etc.
|
|
286
|
-
* @
|
|
397
|
+
* @param {number} [config.harmonize=0.20] - Hue shift amount for semantic colors (0-1)
|
|
398
|
+
* @returns {Object} Full palette with shades for all 9 semantic colors
|
|
287
399
|
*/
|
|
288
400
|
export function derivePalette(config) {
|
|
289
|
-
var
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
401
|
+
var amt = config.harmonize !== undefined ? config.harmonize : 0.20;
|
|
402
|
+
var pri = config.primary;
|
|
403
|
+
var priHsl = hexToHsl(pri);
|
|
404
|
+
var h = priHsl[0];
|
|
405
|
+
|
|
406
|
+
// Semantic defaults — harmonized toward primary hue
|
|
407
|
+
var successBase = harmonize(config.success || '#198754', pri, amt);
|
|
408
|
+
var dangerBase = harmonize(config.danger || '#dc3545', pri, amt);
|
|
409
|
+
var warningBase = harmonize(config.warning || '#f0ad4e', pri, amt);
|
|
410
|
+
var infoBase = harmonize(config.info || '#17a2b8', pri, amt);
|
|
411
|
+
|
|
412
|
+
// Light/dark: derive from primary hue with low saturation (if not user-supplied)
|
|
413
|
+
var lightBase = config.light || hslToHex([h, 8, 97]);
|
|
414
|
+
var darkBase = config.dark || hslToHex([h, 10, 13]);
|
|
415
|
+
|
|
416
|
+
// Background & surface tokens — default to light (white/near-white).
|
|
417
|
+
// Dark backgrounds require explicit config.background / config.surface.
|
|
418
|
+
// Primary/secondary colors are accents, not page backgrounds, so
|
|
419
|
+
// isLightPalette should NOT drive bg/surface defaults.
|
|
420
|
+
var bgBase = config.background || '#ffffff';
|
|
421
|
+
var surfBase = config.surface || '#f8f9fa';
|
|
297
422
|
|
|
298
423
|
var palette = {
|
|
299
|
-
primary:
|
|
300
|
-
secondary:
|
|
301
|
-
tertiary:
|
|
302
|
-
success:
|
|
303
|
-
danger:
|
|
304
|
-
warning:
|
|
305
|
-
info:
|
|
306
|
-
light:
|
|
307
|
-
dark:
|
|
424
|
+
primary: deriveShades(config.primary),
|
|
425
|
+
secondary: deriveShades(config.secondary),
|
|
426
|
+
tertiary: deriveShades(config.tertiary),
|
|
427
|
+
success: deriveShades(successBase),
|
|
428
|
+
danger: deriveShades(dangerBase),
|
|
429
|
+
warning: deriveShades(warningBase),
|
|
430
|
+
info: deriveShades(infoBase),
|
|
431
|
+
light: deriveShades(lightBase),
|
|
432
|
+
dark: deriveShades(darkBase),
|
|
433
|
+
background: bgBase,
|
|
434
|
+
surface: surfBase
|
|
308
435
|
};
|
|
309
436
|
|
|
310
437
|
return palette;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Empty stub for bitwrench-
|
|
2
|
+
* Empty stub for bitwrench-bccl.js.
|
|
3
3
|
* Used by the lean build to exclude all BCCL component code.
|
|
4
4
|
*/
|
|
5
5
|
export const componentHandles = {};
|
|
6
|
+
export function variantClass() { return ''; }
|
|
7
|
+
export var BCCL = {};
|
|
8
|
+
export function make() { throw new Error('bw.make() requires the full bitwrench build (not lean)'); }
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bitwrench v2 File I/O Functions
|
|
3
|
+
*
|
|
4
|
+
* Save/load files in both Node.js and browser environments.
|
|
5
|
+
* Node uses fs module, browser uses Blob/XHR/FileReader.
|
|
6
|
+
*
|
|
7
|
+
* Called via bindFileOps(bw) which attaches all functions to the bw namespace.
|
|
8
|
+
* This preserves the same public API (bw.saveClientFile, bw.loadClientFile, etc.)
|
|
9
|
+
* while keeping the implementation in a separate module.
|
|
10
|
+
*
|
|
11
|
+
* @module bitwrench-file-ops
|
|
12
|
+
* @license BSD-2-Clause
|
|
13
|
+
* @author M A Chatterjee <deftio [at] deftio [dot] com>
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Attach all file I/O functions to the bitwrench namespace.
|
|
18
|
+
*
|
|
19
|
+
* @param {Object} bw - Bitwrench namespace object
|
|
20
|
+
*/
|
|
21
|
+
export function bindFileOps(bw) {
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Save data to a file. Works in both Node.js (fs.writeFile) and browser (download link).
|
|
25
|
+
*
|
|
26
|
+
* @param {string} fname - Filename to save as
|
|
27
|
+
* @param {*} data - Data to save (string or buffer)
|
|
28
|
+
* @category File I/O
|
|
29
|
+
*/
|
|
30
|
+
bw.saveClientFile = function(fname, data) {
|
|
31
|
+
if (bw.isNodeJS()) {
|
|
32
|
+
bw._getFs().then(function(fs) {
|
|
33
|
+
if (!fs) { console.error('bw.saveClientFile: fs module not available'); return; }
|
|
34
|
+
fs.writeFile(fname, data, function(err) {
|
|
35
|
+
if (err) {
|
|
36
|
+
console.error("Error saving file:", err);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
} else {
|
|
41
|
+
var blob = new Blob([data], { type: "application/octet-stream" });
|
|
42
|
+
var url = window.URL.createObjectURL(blob);
|
|
43
|
+
var a = bw.createDOM({
|
|
44
|
+
t: 'a',
|
|
45
|
+
a: {
|
|
46
|
+
href: url,
|
|
47
|
+
download: fname,
|
|
48
|
+
style: 'display: none'
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
document.body.appendChild(a);
|
|
52
|
+
a.click();
|
|
53
|
+
window.URL.revokeObjectURL(url);
|
|
54
|
+
document.body.removeChild(a);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Save data as a JSON file with pretty formatting.
|
|
60
|
+
*
|
|
61
|
+
* @param {string} fname - Filename to save as
|
|
62
|
+
* @param {*} data - Data to serialize as JSON
|
|
63
|
+
* @category File I/O
|
|
64
|
+
*/
|
|
65
|
+
bw.saveClientJSON = function(fname, data) {
|
|
66
|
+
bw.saveClientFile(fname, JSON.stringify(data, null, 2));
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Load a file by path (Node.js) or URL (browser via XHR).
|
|
71
|
+
*
|
|
72
|
+
* @param {string} fname - File path (Node) or URL (browser)
|
|
73
|
+
* @param {Function} callback - Called with (data, error). data is null on error.
|
|
74
|
+
* @param {Object} [options] - Options
|
|
75
|
+
* @param {string} [options.parser="raw"] - "raw" for string, "JSON" to auto-parse
|
|
76
|
+
* @returns {string} "BW_OK"
|
|
77
|
+
* @category File I/O
|
|
78
|
+
*/
|
|
79
|
+
bw.loadClientFile = function(fname, callback, options) {
|
|
80
|
+
var opts = { parser: 'raw' };
|
|
81
|
+
if (options && options.parser) { opts.parser = options.parser; }
|
|
82
|
+
var parse = (opts.parser === 'JSON') ? JSON.parse : function(s) { return s; };
|
|
83
|
+
|
|
84
|
+
if (bw.isNodeJS()) {
|
|
85
|
+
bw._getFs().then(function(fs) {
|
|
86
|
+
if (!fs) { callback(null, new Error('fs module not available')); return; }
|
|
87
|
+
fs.readFile(fname, 'utf8', function(err, data) {
|
|
88
|
+
if (err) { callback(null, err); }
|
|
89
|
+
else {
|
|
90
|
+
try { callback(parse(data), null); }
|
|
91
|
+
catch (e) { callback(null, e); }
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
} else {
|
|
96
|
+
var x = new XMLHttpRequest();
|
|
97
|
+
x.open('GET', fname, true);
|
|
98
|
+
x.onreadystatechange = function() {
|
|
99
|
+
if (x.readyState === 4) {
|
|
100
|
+
if (x.status >= 200 && x.status < 300) {
|
|
101
|
+
try { callback(parse(x.responseText), null); }
|
|
102
|
+
catch (e) { callback(null, e); }
|
|
103
|
+
} else {
|
|
104
|
+
callback(null, new Error('HTTP ' + x.status + ': ' + fname));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
x.send(null);
|
|
109
|
+
}
|
|
110
|
+
return 'BW_OK';
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Load a JSON file by path (Node.js) or URL (browser).
|
|
115
|
+
*
|
|
116
|
+
* @param {string} fname - File path (Node) or URL (browser)
|
|
117
|
+
* @param {Function} callback - Called with (parsedData, error)
|
|
118
|
+
* @returns {string} "BW_OK"
|
|
119
|
+
* @category File I/O
|
|
120
|
+
*/
|
|
121
|
+
bw.loadClientJSON = function(fname, callback) {
|
|
122
|
+
return bw.loadClientFile(fname, callback, { parser: 'JSON' });
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Prompt user to pick a local file via file dialog (browser only).
|
|
127
|
+
*
|
|
128
|
+
* @param {Function} callback - Called with (data, filename, error)
|
|
129
|
+
* @param {Object} [options] - Options
|
|
130
|
+
* @param {string} [options.accept] - File type filter (e.g. ".json,.txt")
|
|
131
|
+
* @param {string} [options.parser="raw"] - "raw" for string, "JSON" to auto-parse
|
|
132
|
+
* @category File I/O
|
|
133
|
+
*/
|
|
134
|
+
bw.loadLocalFile = function(callback, options) {
|
|
135
|
+
var opts = { parser: 'raw', accept: '' };
|
|
136
|
+
if (options) {
|
|
137
|
+
if (options.parser) { opts.parser = options.parser; }
|
|
138
|
+
if (options.accept) { opts.accept = options.accept; }
|
|
139
|
+
}
|
|
140
|
+
var parse = (opts.parser === 'JSON') ? JSON.parse : function(s) { return s; };
|
|
141
|
+
|
|
142
|
+
if (bw.isNodeJS()) {
|
|
143
|
+
callback(null, '', new Error('bw.loadLocalFile is browser-only. Use bw.loadClientFile() in Node.'));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
var input = bw.createDOM({
|
|
148
|
+
t: 'input',
|
|
149
|
+
a: {
|
|
150
|
+
type: 'file',
|
|
151
|
+
accept: opts.accept,
|
|
152
|
+
style: 'display: none'
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
input.addEventListener('change', function() {
|
|
156
|
+
var file = input.files[0];
|
|
157
|
+
if (!file) { callback(null, '', new Error('No file selected')); return; }
|
|
158
|
+
var reader = new FileReader();
|
|
159
|
+
reader.onload = function(e) {
|
|
160
|
+
try { callback(parse(e.target.result), file.name, null); }
|
|
161
|
+
catch (err) { callback(null, file.name, err); }
|
|
162
|
+
};
|
|
163
|
+
reader.onerror = function() { callback(null, file.name, reader.error); };
|
|
164
|
+
reader.readAsText(file);
|
|
165
|
+
input.remove();
|
|
166
|
+
});
|
|
167
|
+
document.body.appendChild(input);
|
|
168
|
+
input.click();
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Prompt user to pick a local JSON file via file dialog (browser only).
|
|
173
|
+
*
|
|
174
|
+
* @param {Function} callback - Called with (parsedData, filename, error)
|
|
175
|
+
* @category File I/O
|
|
176
|
+
*/
|
|
177
|
+
bw.loadLocalJSON = function(callback) {
|
|
178
|
+
bw.loadLocalFile(callback, { parser: 'JSON', accept: '.json' });
|
|
179
|
+
};
|
|
180
|
+
}
|
package/src/bitwrench-lean.js
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* bitwrench-lean.js — Entry point for the lean build.
|
|
3
3
|
*
|
|
4
4
|
* This is identical to bitwrench.js but the Rollup config redirects
|
|
5
|
-
* the bitwrench-
|
|
5
|
+
* the bitwrench-bccl.js import to an empty stub, so no
|
|
6
6
|
* BCCL component code is included in the output.
|
|
7
7
|
*
|
|
8
8
|
* Includes: HTML/DOM generation, CSS generation, color utilities,
|
|
9
9
|
* state management, pub/sub, file I/O, random/lorem,
|
|
10
10
|
* cookies, URL params, logging, makeTable, makeDataTable.
|
|
11
|
-
* Excludes: All make* component helpers from bitwrench-
|
|
11
|
+
* Excludes: All make* component helpers from bitwrench-bccl.js
|
|
12
12
|
* (makeButton, makeCard, makeAlert, makeTabs, etc.)
|
|
13
13
|
*/
|
|
14
14
|
export { default } from './bitwrench.js';
|