lightview 2.0.9 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/build-bundles.mjs +109 -0
  2. package/cdom/helpers/array.js +70 -0
  3. package/cdom/helpers/compare.js +26 -0
  4. package/cdom/helpers/conditional.js +34 -0
  5. package/cdom/helpers/datetime.js +54 -0
  6. package/cdom/helpers/format.js +20 -0
  7. package/cdom/helpers/logic.js +24 -0
  8. package/cdom/helpers/lookup.js +25 -0
  9. package/cdom/helpers/math.js +34 -0
  10. package/cdom/helpers/network.js +41 -0
  11. package/cdom/helpers/state.js +77 -0
  12. package/cdom/helpers/stats.js +39 -0
  13. package/cdom/helpers/string.js +49 -0
  14. package/cdom/parser.js +602 -0
  15. package/components/actions/button.js +16 -3
  16. package/components/actions/swap.js +26 -3
  17. package/components/daisyui.js +1 -1
  18. package/components/data-display/alert.js +13 -3
  19. package/components/data-display/badge.js +11 -3
  20. package/components/data-display/kbd.js +9 -3
  21. package/components/data-display/loading.js +11 -3
  22. package/components/data-display/progress.js +11 -3
  23. package/components/data-display/radial-progress.js +12 -3
  24. package/components/data-display/tooltip.js +17 -0
  25. package/components/layout/divider.js +21 -1
  26. package/components/layout/indicator.js +14 -0
  27. package/components/navigation/tabs.js +291 -16
  28. package/docs/api/elements.html +125 -49
  29. package/docs/api/hypermedia.html +29 -2
  30. package/docs/api/index.html +6 -2
  31. package/docs/api/nav.html +18 -4
  32. package/docs/cdom-nav.html +29 -0
  33. package/docs/cdom.html +362 -0
  34. package/docs/components/alert.html +8 -8
  35. package/docs/components/badge.html +55 -0
  36. package/docs/components/button.html +78 -92
  37. package/docs/components/component-nav.html +1 -1
  38. package/docs/components/divider.html +65 -21
  39. package/docs/components/indicator.html +85 -31
  40. package/docs/components/kbd.html +64 -25
  41. package/docs/components/loading.html +55 -39
  42. package/docs/components/progress.html +44 -3
  43. package/docs/components/radial-progress.html +32 -12
  44. package/docs/components/swap.html +183 -100
  45. package/docs/components/tabs.html +146 -278
  46. package/docs/components/tooltip.html +71 -31
  47. package/docs/getting-started/index.html +7 -5
  48. package/docs/index.html +1 -1
  49. package/docs/syntax-nav.html +10 -0
  50. package/docs/syntax.html +8 -6
  51. package/index.html +2 -2
  52. package/lightview-all.js +1 -0
  53. package/lightview-cdom.js +1 -0
  54. package/lightview-x.js +1 -1608
  55. package/lightview.js +1 -766
  56. package/lightview.js.bak +1 -0
  57. package/package.json +6 -2
  58. package/src/lightview-all.js +10 -0
  59. package/src/lightview-cdom.js +305 -0
  60. package/src/lightview-x.js +1581 -0
  61. package/src/lightview.js +694 -0
  62. package/src/reactivity/signal.js +133 -0
  63. package/src/reactivity/state.js +217 -0
  64. package/test-text-tag.js +6 -0
  65. package/tests/cdom/fixtures/helpers.cdomc +62 -0
  66. package/tests/cdom/fixtures/user.cdom +14 -0
  67. package/tests/cdom/fixtures/user.cdomc +12 -0
  68. package/tests/cdom/fixtures/user.odom +18 -0
  69. package/tests/cdom/fixtures/user.vdom +11 -0
  70. package/tests/cdom/helpers.test.js +121 -0
  71. package/tests/cdom/loader.test.js +125 -0
  72. package/tests/cdom/parser.test.js +108 -0
  73. package/tests/cdom/reactivity.test.js +186 -0
  74. package/tests/text-tag.test.js +77 -0
  75. package/vite.config.mjs +52 -0
  76. package/components/data-display/skeleton.js +0 -66
  77. package/docs/components/skeleton.html +0 -447
@@ -0,0 +1,109 @@
1
+ import { build } from 'vite';
2
+ import { resolve } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { copyFileSync, rmSync, existsSync } from 'fs';
5
+
6
+ const __dirname = fileURLToPath(new URL('.', import.meta.url));
7
+ const isWatch = process.argv.includes('--watch');
8
+
9
+ const builds = [
10
+ { entry: 'src/lightview.js', name: 'lightview', globalName: 'Lightview' },
11
+ { entry: 'src/lightview-x.js', name: 'lightview-x', globalName: 'LightviewX' },
12
+ { entry: 'src/lightview-cdom.js', name: 'lightview-cdom', globalName: 'LightviewCDOM' },
13
+ { entry: 'src/lightview-all.js', name: 'lightview-all', globalName: 'LightviewAll' }
14
+ ];
15
+
16
+ let building = false;
17
+ let queued = false;
18
+
19
+ async function runBuilds() {
20
+ if (building) {
21
+ queued = true;
22
+ return;
23
+ }
24
+ building = true;
25
+
26
+ console.log(isWatch ? 'Change detected. Rebuilding bundled files...' : 'Building bundles...');
27
+
28
+ try {
29
+ for (const b of builds) {
30
+ // console.log(`Building ${b.name}...`);
31
+ await build({
32
+ configFile: false,
33
+ logLevel: 'silent', // Reduce noise
34
+ build: {
35
+ lib: {
36
+ entry: resolve(__dirname, b.entry),
37
+ name: b.globalName,
38
+ formats: ['iife'],
39
+ fileName: () => `${b.name}.js`
40
+ },
41
+ outDir: 'build_tmp',
42
+ // Don't clean here, clean manually
43
+ emptyOutDir: false,
44
+ rollupOptions: {
45
+ external: (id) => id.includes('/components/') || id.includes('/docs/')
46
+ },
47
+ minify: 'terser',
48
+ terserOptions: {
49
+ compress: {
50
+ drop_console: false
51
+ }
52
+ }
53
+ }
54
+ });
55
+ }
56
+
57
+ // Copy files
58
+ for (const b of builds) {
59
+ try {
60
+ const src = resolve(__dirname, `build_tmp/${b.name}.js`);
61
+ const dest = resolve(__dirname, `${b.name}.js`);
62
+ if (existsSync(src)) {
63
+ copyFileSync(src, dest);
64
+ console.log(`Updated ${b.name}.js`);
65
+ } else {
66
+ console.error(`Missing built file: ${src}`);
67
+ }
68
+ } catch (e) {
69
+ console.error(`Failed to copy ${b.name}.js`, e);
70
+ }
71
+ }
72
+
73
+ // Cleanup
74
+ if (existsSync(resolve(__dirname, 'build_tmp'))) {
75
+ rmSync(resolve(__dirname, 'build_tmp'), { recursive: true, force: true });
76
+ }
77
+ } catch (e) {
78
+ console.error('Build error:', e);
79
+ }
80
+
81
+ building = false;
82
+ if (queued) {
83
+ queued = false;
84
+ runBuilds();
85
+ } else {
86
+ if (isWatch) console.log('Waiting for changes...');
87
+ }
88
+ }
89
+
90
+ // Initial run
91
+ runBuilds();
92
+
93
+ if (isWatch) {
94
+ console.log('Watching src/ for changes...');
95
+ // Dynamic import to avoid errors if run in environment without fs (unlikely here but good practice)
96
+ const { watch } = await import('fs');
97
+ let debounceTimer;
98
+
99
+ // Watch src directory
100
+ // Note: recursive option for Linux requires Node 20+, Windows/macOS supported earlier.
101
+ watch(resolve(__dirname, 'src'), { recursive: true }, (event, filename) => {
102
+ if (filename && !filename.includes('~')) {
103
+ clearTimeout(debounceTimer);
104
+ debounceTimer = setTimeout(() => {
105
+ runBuilds();
106
+ }, 300); // 300ms debounce
107
+ }
108
+ });
109
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * cdom ARRAY HELPERS
3
+ */
4
+
5
+ export const count = (...args) => args.length;
6
+
7
+ export const filter = (arr, predicate) => {
8
+ if (!Array.isArray(arr)) return [];
9
+ if (typeof predicate === 'function' && predicate.isLazy) {
10
+ return arr.filter(item => predicate.resolve(item));
11
+ }
12
+ return arr.filter(item => !!item);
13
+ };
14
+
15
+ export const map = (arr, transform) => {
16
+ if (!Array.isArray(arr)) return [];
17
+ if (typeof transform === 'string') {
18
+ return arr.map(item => (item && typeof item === 'object') ? item[transform] : item);
19
+ }
20
+ if (transform && transform.isLazy) {
21
+ return arr.map(item => transform.resolve(item));
22
+ }
23
+ return arr;
24
+ };
25
+
26
+ export const find = (arr, predicate) => {
27
+ if (!Array.isArray(arr)) return undefined;
28
+ if (predicate && predicate.isLazy) {
29
+ return arr.find(item => predicate.resolve(item));
30
+ }
31
+ return arr.find(item => !!item);
32
+ };
33
+
34
+ export const unique = (arr) => Array.isArray(arr) ? [...new Set(arr)] : [];
35
+
36
+ export const sort = (arr, order = 'asc') => {
37
+ if (!Array.isArray(arr)) return [];
38
+ const sorted = [...arr];
39
+ sorted.sort((a, b) => {
40
+ if (a < b) return order === 'asc' ? -1 : 1;
41
+ if (a > b) return order === 'asc' ? 1 : -1;
42
+ return 0;
43
+ });
44
+ return sorted;
45
+ };
46
+
47
+ export const reverse = (arr) => Array.isArray(arr) ? [...arr].reverse() : [];
48
+ export const first = (arr) => Array.isArray(arr) ? arr[0] : undefined;
49
+ export const last = (arr) => Array.isArray(arr) ? arr[arr.length - 1] : undefined;
50
+ export const slice = (arr, start, end) => Array.isArray(arr) ? arr.slice(start, end) : [];
51
+ export const flatten = (arr) => Array.isArray(arr) ? arr.flat(Infinity) : [];
52
+ export const join = (arr, sep = ',') => Array.isArray(arr) ? arr.join(String(sep)) : '';
53
+ export const length = (arg) => Array.isArray(arg) ? arg.length : (arg ? String(arg).length : 0);
54
+
55
+ export const registerArrayHelpers = (register) => {
56
+ register('count', count);
57
+ register('filter', filter);
58
+ register('map', map);
59
+ register('find', find);
60
+ register('unique', unique);
61
+ register('sort', sort);
62
+ register('reverse', reverse);
63
+ register('first', first);
64
+ register('last', last);
65
+ register('slice', slice);
66
+ register('flatten', flatten);
67
+ register('join', join);
68
+ register('len', length);
69
+ register('length', length);
70
+ };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * cdom COMPARISON HELPERS
3
+ */
4
+
5
+ export const gt = (a, b) => a > b;
6
+ export const lt = (a, b) => a < b;
7
+ export const gte = (a, b) => a >= b;
8
+ export const lte = (a, b) => a <= b;
9
+ export const neq = (a, b) => a !== b;
10
+ export const between = (val, min, max) => val >= min && val <= max;
11
+ export const contains = (arr, val) => Array.isArray(arr) && arr.includes(val);
12
+
13
+ export const registerCompareHelpers = (register) => {
14
+ register('gt', gt);
15
+ register('>', gt);
16
+ register('lt', lt);
17
+ register('<', lt);
18
+ register('gte', gte);
19
+ register('>=', gte);
20
+ register('lte', lte);
21
+ register('<=', lte);
22
+ register('neq', neq);
23
+ register('!=', neq);
24
+ register('between', between);
25
+ register('in', contains);
26
+ };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * cdom CONDITIONAL AGGREGATE HELPERS
3
+ */
4
+
5
+ export const sumIf = (arr, predicate) => {
6
+ if (!Array.isArray(arr)) return 0;
7
+ const filtered = (predicate && predicate.isLazy)
8
+ ? arr.filter(item => predicate.resolve(item))
9
+ : arr;
10
+ return filtered.reduce((a, b) => a + (Number(b) || 0), 0);
11
+ };
12
+
13
+ export const countIf = (arr, predicate) => {
14
+ if (!Array.isArray(arr)) return 0;
15
+ if (predicate && predicate.isLazy) {
16
+ return arr.filter(item => predicate.resolve(item)).length;
17
+ }
18
+ return arr.filter(item => !!item).length;
19
+ };
20
+
21
+ export const avgIf = (arr, predicate) => {
22
+ if (!Array.isArray(arr)) return 0;
23
+ const filtered = (predicate && predicate.isLazy)
24
+ ? arr.filter(item => predicate.resolve(item))
25
+ : arr;
26
+ if (filtered.length === 0) return 0;
27
+ return filtered.reduce((a, b) => a + (Number(b) || 0), 0) / filtered.length;
28
+ };
29
+
30
+ export const registerConditionalHelpers = (register) => {
31
+ register('sumIf', sumIf);
32
+ register('countIf', countIf);
33
+ register('avgIf', avgIf);
34
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * cdom DATE/TIME HELPERS
3
+ */
4
+
5
+ export const now = () => new Date().getTime();
6
+ export const today = () => {
7
+ const d = new Date();
8
+ d.setHours(0, 0, 0, 0);
9
+ return d.getTime();
10
+ };
11
+
12
+ export const date = (val) => new Date(val).getTime();
13
+
14
+ export const formatDate = (val, format) => {
15
+ const d = new Date(val);
16
+ if (isNaN(d.getTime())) return '';
17
+
18
+ // Minimal formatter, can be expanded
19
+ const options = { year: 'numeric', month: '2-digit', day: '2-digit' };
20
+ if (format === 'long') options.month = 'long';
21
+ return d.toLocaleDateString(undefined, options);
22
+ };
23
+
24
+ export const year = (val) => new Date(val).getFullYear();
25
+ export const month = (val) => new Date(val).getMonth() + 1;
26
+ export const day = (val) => new Date(val).getDate();
27
+ export const weekday = (val) => new Date(val).getDay();
28
+
29
+ export const addDays = (val, days) => {
30
+ const d = new Date(val);
31
+ d.setDate(d.getDate() + Number(days));
32
+ return d.getTime();
33
+ };
34
+
35
+ export const dateDiff = (d1, d2, unit = 'days') => {
36
+ const diff = Math.abs(new Date(d1) - new Date(d2));
37
+ if (unit === 'seconds') return diff / 1000;
38
+ if (unit === 'minutes') return diff / (1000 * 60);
39
+ if (unit === 'hours') return diff / (1000 * 60 * 60);
40
+ return diff / (1000 * 60 * 60 * 24);
41
+ };
42
+
43
+ export const registerDateTimeHelpers = (register) => {
44
+ register('now', now);
45
+ register('today', today);
46
+ register('date', date);
47
+ register('formatDate', formatDate);
48
+ register('year', year);
49
+ register('month', month);
50
+ register('day', day);
51
+ register('weekday', weekday);
52
+ register('addDays', addDays);
53
+ register('dateDiff', dateDiff);
54
+ };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * cdom FORMATTING HELPERS
3
+ */
4
+
5
+ export const number = (val, decimals = 2) => Number(val).toFixed(decimals);
6
+
7
+ export const currency = (val, symbol = '$', decimals = 2) => {
8
+ return symbol + Number(val).toFixed(decimals).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
9
+ };
10
+
11
+ export const percent = (val, decimals = 0) => (Number(val) * 100).toFixed(decimals) + '%';
12
+
13
+ export const thousands = (val) => String(val).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
14
+
15
+ export const registerFormatHelpers = (register) => {
16
+ register('number', number);
17
+ register('currency', currency);
18
+ register('percent', percent);
19
+ register('thousands', thousands);
20
+ };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * cdom LOGIC HELPERS
3
+ */
4
+
5
+ export const ifHelper = (condition, thenVal, elseVal) => condition ? thenVal : elseVal;
6
+ export const andHelper = (...args) => args.every(Boolean);
7
+ export const orHelper = (...args) => args.some(Boolean);
8
+ export const notHelper = (val) => !val;
9
+ export const eqHelper = (a, b) => a === b;
10
+ export const neqHelper = (a, b) => a !== b;
11
+
12
+ export const registerLogicHelpers = (register) => {
13
+ register('if', ifHelper);
14
+ register('and', andHelper);
15
+ register('&&', andHelper);
16
+ register('or', orHelper);
17
+ register('||', orHelper);
18
+ register('not', notHelper);
19
+ register('!', notHelper);
20
+ register('eq', eqHelper);
21
+ register('==', eqHelper);
22
+ register('===', eqHelper);
23
+ register('neq', neqHelper);
24
+ };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * cdom LOOKUP HELPERS
3
+ */
4
+
5
+ export const lookup = (val, searchArr, resultArr) => {
6
+ if (!Array.isArray(searchArr)) return undefined;
7
+ const idx = searchArr.indexOf(val);
8
+ return idx !== -1 && Array.isArray(resultArr) ? resultArr[idx] : undefined;
9
+ };
10
+
11
+ export const vlookup = (val, table, colIdx) => {
12
+ if (!Array.isArray(table)) return undefined;
13
+ const row = table.find(r => Array.isArray(r) && r[0] === val);
14
+ return row ? row[colIdx - 1] : undefined;
15
+ };
16
+
17
+ export const index = (arr, idx) => Array.isArray(arr) ? arr[idx] : undefined;
18
+ export const match = (val, arr) => Array.isArray(arr) ? arr.indexOf(val) : -1;
19
+
20
+ export const registerLookupHelpers = (register) => {
21
+ register('lookup', lookup);
22
+ register('vlookup', vlookup);
23
+ register('index', index);
24
+ register('match', match);
25
+ };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * cdom MATH HELPERS
3
+ */
4
+
5
+ export const add = (...args) => args.reduce((a, b) => Number(a) + Number(b), 0);
6
+ export const subtract = (a, b) => Number(a) - Number(b);
7
+ export const multiply = (...args) => args.reduce((a, b) => Number(a) * Number(b), 1);
8
+ export const divide = (a, b) => Number(a) / Number(b);
9
+
10
+ export const round = (val, decimals = 0) => Number(Math.round(val + 'e' + decimals) + 'e-' + decimals);
11
+ export const ceil = (val) => Math.ceil(val);
12
+ export const floor = (val) => Math.floor(val);
13
+ export const abs = (val) => Math.abs(val);
14
+ export const mod = (a, b) => a % b;
15
+ export const pow = (a, b) => Math.pow(a, b);
16
+ export const sqrt = (val) => Math.sqrt(val);
17
+
18
+ export const registerMathHelpers = (register) => {
19
+ register('+', add);
20
+ register('add', add);
21
+ register('-', subtract);
22
+ register('sub', subtract);
23
+ register('*', multiply);
24
+ register('mul', multiply);
25
+ register('/', divide);
26
+ register('div', divide);
27
+ register('round', round);
28
+ register('ceil', ceil);
29
+ register('floor', floor);
30
+ register('abs', abs);
31
+ register('mod', mod);
32
+ register('pow', pow);
33
+ register('sqrt', sqrt);
34
+ };
@@ -0,0 +1,41 @@
1
+ /**
2
+ * cdom NETWORK HELPERS
3
+ */
4
+
5
+ /**
6
+ * A wrapper around the native fetch API that handles body serialization and
7
+ * Content-Type headers based on the body type.
8
+ *
9
+ * @param {string} url - The URL to fetch
10
+ * @param {object} options - Fetch options (method, headers, body)
11
+ */
12
+ export const fetchHelper = (url, options = {}) => {
13
+ const fetchOptions = { ...options };
14
+ const headers = { ...fetchOptions.headers };
15
+
16
+ let body = fetchOptions.body;
17
+ if (body !== undefined) {
18
+ if (body !== null && typeof body === 'object') {
19
+ // Automatically stringify objects
20
+ body = JSON.stringify(body);
21
+ if (!headers['Content-Type']) {
22
+ headers['Content-Type'] = 'application/json';
23
+ }
24
+ } else {
25
+ // Convert everything else to string
26
+ body = String(body);
27
+ if (!headers['Content-Type']) {
28
+ headers['Content-Type'] = 'text/plain';
29
+ }
30
+ }
31
+ }
32
+
33
+ fetchOptions.body = body;
34
+ fetchOptions.headers = headers;
35
+
36
+ return globalThis.fetch(url, fetchOptions);
37
+ };
38
+
39
+ export const registerNetworkHelpers = (register) => {
40
+ register('fetch', fetchHelper);
41
+ };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * cdom STATE/MUTATION HELPERS
3
+ */
4
+
5
+ export const set = (target, val) => {
6
+ if (target && typeof target === 'object' && 'value' in target) {
7
+ target.value = val;
8
+ } else if (target && typeof target === 'function' && 'value' in target) {
9
+ target.value = val;
10
+ } else if (target && typeof target === 'object' && val && typeof val === 'object') {
11
+ Object.assign(target, val);
12
+ }
13
+ return val;
14
+ };
15
+
16
+ export const increment = (target, by = 1) => {
17
+ const current = (target && typeof target === 'object' && 'value' in target) ? target.value : 0;
18
+ const next = Number(current) + Number(by);
19
+ return set(target, next);
20
+ };
21
+
22
+ export const decrement = (target, by = 1) => {
23
+ const current = (target && typeof target === 'object' && 'value' in target) ? target.value : 0;
24
+ const next = Number(current) - Number(by);
25
+ return set(target, next);
26
+ };
27
+
28
+ export const toggle = (target) => {
29
+ const current = (target && typeof target === 'object' && 'value' in target) ? target.value : false;
30
+ return set(target, !current);
31
+ };
32
+
33
+ export const push = (target, item) => {
34
+ const current = (target && typeof target === 'object' && 'value' in target) ? target.value : [];
35
+ if (Array.isArray(current)) {
36
+ const next = [...current, item];
37
+ return set(target, next);
38
+ }
39
+ return current;
40
+ };
41
+
42
+ export const pop = (target) => {
43
+ const current = (target && typeof target === 'object' && 'value' in target) ? target.value : [];
44
+ if (Array.isArray(current) && current.length > 0) {
45
+ const next = current.slice(0, -1);
46
+ set(target, next);
47
+ }
48
+ return current;
49
+ };
50
+
51
+ export const assign = (target, obj) => {
52
+ const current = (target && typeof target === 'object' && 'value' in target) ? target.value : {};
53
+ const next = { ...current, ...obj };
54
+ return set(target, next);
55
+ };
56
+
57
+ export const clear = (target) => {
58
+ const current = (target && typeof target === 'object' && 'value' in target) ? target.value : null;
59
+ if (Array.isArray(current)) return set(target, []);
60
+ if (typeof current === 'object' && current !== null) return set(target, {});
61
+ return set(target, null);
62
+ };
63
+
64
+ export const registerStateHelpers = (register) => {
65
+ const opts = { pathAware: true };
66
+ register('set', set, opts);
67
+ register('increment', increment, opts);
68
+ register('++', increment, opts);
69
+ register('decrement', decrement, opts);
70
+ register('--', decrement, opts);
71
+ register('toggle', toggle, opts);
72
+ register('!!', toggle, opts);
73
+ register('push', push, opts);
74
+ register('pop', pop, opts);
75
+ register('assign', assign, opts);
76
+ register('clear', clear, opts);
77
+ };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * cdom STATISTICAL HELPERS
3
+ */
4
+
5
+ export const sum = (...args) => args.reduce((a, b) => a + (Number(b) || 0), 0);
6
+ export const avg = (...args) => args.length === 0 ? 0 : sum(...args) / args.length;
7
+ export const min = (...args) => Math.min(...args);
8
+ export const max = (...args) => Math.max(...args);
9
+
10
+ export const median = (...args) => {
11
+ if (args.length === 0) return 0;
12
+ const sorted = [...args].sort((a, b) => a - b);
13
+ const mid = Math.floor(sorted.length / 2);
14
+ return sorted.length % 2 !== 0 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
15
+ };
16
+
17
+ export const stdev = (...args) => {
18
+ if (args.length === 0) return 0;
19
+ const mean = avg(...args);
20
+ const squareDiffs = args.map(value => Math.pow(value - mean, 2));
21
+ return Math.sqrt(avg(...squareDiffs));
22
+ };
23
+
24
+ export const variance = (...args) => {
25
+ if (args.length === 0) return 0;
26
+ const mean = avg(...args);
27
+ const squareDiffs = args.map(value => Math.pow(value - mean, 2));
28
+ return avg(...squareDiffs);
29
+ };
30
+
31
+ export const registerStatsHelpers = (register) => {
32
+ register('sum', sum);
33
+ register('avg', avg);
34
+ register('min', min);
35
+ register('max', max);
36
+ register('median', median);
37
+ register('stdev', stdev);
38
+ register('var', variance);
39
+ };
@@ -0,0 +1,49 @@
1
+ /**
2
+ * cdom STRING HELPERS
3
+ */
4
+
5
+ export const join = (...args) => {
6
+ const separator = args[args.length - 1];
7
+ const items = args.slice(0, -1);
8
+ return items.join(separator);
9
+ };
10
+
11
+ export const concat = (...args) => args.join('');
12
+ export const upper = (s) => String(s).toUpperCase();
13
+ export const lower = (s) => String(s).toLowerCase();
14
+ export const trim = (s) => String(s).trim();
15
+ export const len = (s) => String(s).length;
16
+ export const replace = (s, search, replacement) => String(s).replace(search, replacement);
17
+ export const split = (s, separator) => String(s).split(separator);
18
+
19
+ export const capitalize = (s) => {
20
+ const str = String(s);
21
+ return str.charAt(0).toUpperCase() + str.slice(1);
22
+ };
23
+
24
+ export const titleCase = (s) => {
25
+ return String(s).toLowerCase().split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
26
+ };
27
+
28
+ export const contains = (s, search) => String(s).includes(search);
29
+ export const startsWith = (s, prefix) => String(s).startsWith(prefix);
30
+ export const endsWith = (s, suffix) => String(s).endsWith(suffix);
31
+
32
+ export const defaultHelper = (val, fallback) => (val !== undefined && val !== null) ? val : fallback;
33
+
34
+ export const registerStringHelpers = (register) => {
35
+ register('join', join);
36
+ register('concat', concat);
37
+ register('upper', upper);
38
+ register('lower', lower);
39
+ register('trim', trim);
40
+ register('len', len);
41
+ register('replace', replace);
42
+ register('split', split);
43
+ register('capitalize', capitalize);
44
+ register('titleCase', titleCase);
45
+ register('contains', contains);
46
+ register('startsWith', startsWith);
47
+ register('endsWith', endsWith);
48
+ register('default', defaultHelper);
49
+ };