@livenetworks/ashlar 2.0.6 → 2.0.8
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/LICENSE +641 -641
- package/components/COMPONENTS.md +1116 -1116
- package/components/index.js +81 -81
- package/components/ln-accordion/ln-accordion-dev.scss +8 -8
- package/components/ln-accordion/ln-accordion.js +1 -1
- package/components/ln-accordion/ln-accordion.schema.json +30 -30
- package/components/ln-accordion/src/ln-accordion.js +1 -0
- package/components/ln-ajax/ln-ajax-dev.scss +8 -8
- package/components/ln-ajax/ln-ajax.js +1 -1
- package/components/ln-ajax/ln-ajax.schema.json +30 -30
- package/components/ln-api-connector/ln-api-connector.js +1 -1
- package/components/ln-api-connector/ln-api-connector.schema.json +96 -96
- package/components/ln-api-connector/src/ln-api-connector.js +1 -0
- package/components/ln-api-queue/ln-api-queue.js +1 -1
- package/components/ln-api-queue/ln-api-queue.schema.json +34 -34
- package/components/ln-api-queue/src/ln-api-queue.js +3 -13
- package/components/ln-autoresize/ln-autoresize-dev.scss +8 -8
- package/components/ln-autoresize/ln-autoresize.js +1 -1
- package/components/ln-autoresize/ln-autoresize.schema.json +22 -22
- package/components/ln-autosave/ln-autosave-dev.scss +13 -13
- package/components/ln-autosave/ln-autosave.js +1 -1
- package/components/ln-autosave/ln-autosave.schema.json +48 -48
- package/components/ln-autosave/src/autosave-model.js +34 -34
- package/components/ln-chart/ln-chart.js +1 -1
- package/components/ln-chart/ln-chart.schema.json +146 -146
- package/components/ln-chart/src/chart-model.js +154 -154
- package/components/ln-circular-progress/ln-circular-progress.js +1 -1
- package/components/ln-circular-progress/ln-circular-progress.schema.json +42 -42
- package/components/ln-confirm/ln-confirm-dev.scss +24 -24
- package/components/ln-confirm/ln-confirm.js +1 -1
- package/components/ln-confirm/ln-confirm.schema.json +66 -66
- package/components/ln-confirm/src/ln-confirm.js +178 -178
- package/components/ln-core/crypto.js +286 -265
- package/components/ln-core/dom.js +115 -115
- package/components/ln-core/form.js +136 -136
- package/components/ln-core/helpers.js +33 -33
- package/components/ln-core/http.js +48 -48
- package/components/ln-core/index.js +1 -1
- package/components/ln-core/ln-core.schema.json +109 -109
- package/components/ln-core/locale.js +55 -55
- package/components/ln-core/matching.js +78 -78
- package/components/ln-core/number.js +125 -125
- package/components/ln-core/template.js +206 -206
- package/components/ln-couchdb-connector/README.md +4 -0
- package/components/ln-couchdb-connector/ln-couchdb-connector.js +1 -1
- package/components/ln-couchdb-connector/ln-couchdb-connector.schema.json +50 -50
- package/components/ln-couchdb-connector/src/ln-couchdb-connector.js +19 -1
- package/components/ln-data-coordinator/README.md +9 -38
- package/components/ln-data-coordinator/ln-data-coordinator-dev.scss +8 -8
- package/components/ln-data-coordinator/ln-data-coordinator.js +1 -1
- package/components/ln-data-coordinator/ln-data-coordinator.schema.json +16 -16
- package/components/ln-data-coordinator/src/data-read-policy.js +0 -7
- package/components/ln-data-coordinator/src/ln-data-coordinator.js +428 -1299
- package/components/ln-data-store/README.md +3 -2
- package/components/ln-data-store/ln-data-store-dev.scss +13 -13
- package/components/ln-data-store/ln-data-store.js +1 -1
- package/components/ln-data-store/ln-data-store.schema.json +84 -84
- package/components/ln-data-store/src/data-store-model.js +184 -155
- package/components/ln-data-store/src/ln-data-store.js +58 -11
- package/components/ln-date/README.md +4 -0
- package/components/ln-date/ln-date.js +1 -1
- package/components/ln-date/ln-date.schema.json +92 -92
- package/components/ln-date/src/date-model.js +186 -186
- package/components/ln-date/src/ln-date.js +3 -3
- package/components/ln-debug/ln-debug.js +1 -1
- package/components/ln-debug/ln-debug.schema.json +28 -28
- package/components/ln-debug/src/console-sink.js +19 -19
- package/components/ln-debug/src/gate.js +95 -95
- package/components/ln-debug/src/generated-attributes.js +2 -2
- package/components/ln-dropdown/ln-dropdown-dev.scss +13 -13
- package/components/ln-dropdown/ln-dropdown.js +1 -1
- package/components/ln-dropdown/ln-dropdown.schema.json +90 -90
- package/components/ln-editor/ln-editor.js +1 -1
- package/components/ln-editor/ln-editor.schema.json +58 -58
- package/components/ln-external-links/ln-external-links.js +1 -1
- package/components/ln-external-links/ln-external-links.schema.json +18 -18
- package/components/ln-fill/ln-fill.js +1 -1
- package/components/ln-fill/ln-fill.schema.json +23 -23
- package/components/ln-filter/ln-filter.js +1 -1
- package/components/ln-filter/ln-filter.schema.json +153 -153
- package/components/ln-filter/src/filter-model.js +185 -185
- package/components/ln-form/ln-form-dev.scss +8 -8
- package/components/ln-form/ln-form.js +1 -1
- package/components/ln-form/ln-form.schema.json +45 -45
- package/components/ln-http/ln-http.js +1 -1
- package/components/ln-http/ln-http.schema.json +6 -6
- package/components/ln-icon/ln-icon.schema.json +6 -6
- package/components/ln-include/ln-include.js +1 -1
- package/components/ln-include/ln-include.schema.json +23 -23
- package/components/ln-key/ln-key-dev.scss +18 -18
- package/components/ln-key/ln-key.js +1 -1
- package/components/ln-key/ln-key.schema.json +56 -56
- package/components/ln-key/src/ln-key.js +10 -10
- package/components/ln-link/ln-link-dev.scss +8 -8
- package/components/ln-link/ln-link.js +1 -1
- package/components/ln-link/ln-link.schema.json +22 -22
- package/components/ln-list/README.md +152 -152
- package/components/ln-list/ln-list-dev.scss +8 -8
- package/components/ln-list/ln-list.js +1 -1
- package/components/ln-list/ln-list.schema.json +166 -166
- package/components/ln-list/src/ln-list.js +4 -4
- package/components/ln-modal/README.md +4 -6
- package/components/ln-modal/ln-modal.js +1 -1
- package/components/ln-modal/ln-modal.schema.json +65 -65
- package/components/ln-modal/src/ln-modal.js +28 -8
- package/components/ln-nav/ln-nav.js +1 -1
- package/components/ln-nav/ln-nav.schema.json +25 -25
- package/components/ln-number/ln-number.js +1 -1
- package/components/ln-number/ln-number.schema.json +63 -63
- package/components/ln-number/src/number-model.js +31 -31
- package/components/ln-obfuscator/README.md +60 -60
- package/components/ln-obfuscator/ln-obfuscator.js +1 -1
- package/components/ln-obfuscator/ln-obfuscator.schema.json +49 -49
- package/components/ln-obfuscator/src/ln-obfuscator.js +159 -159
- package/components/ln-obfuscator/src/obfuscator-model.js +250 -250
- package/components/ln-options/ln-options.js +1 -1
- package/components/ln-options/ln-options.schema.json +41 -41
- package/components/ln-options/src/ln-options.js +1 -0
- package/components/ln-persist/README.md +151 -151
- package/components/ln-persist/ln-persist.js +1 -1
- package/components/ln-persist/ln-persist.schema.json +28 -28
- package/components/ln-persist/src/ln-persist.js +83 -83
- package/components/ln-picklist/README.md +230 -230
- package/components/ln-picklist/ln-picklist-dev.scss +22 -22
- package/components/ln-picklist/ln-picklist.js +1 -1
- package/components/ln-picklist/ln-picklist.schema.json +49 -49
- package/components/ln-picklist/src/ln-picklist.js +228 -228
- package/components/ln-popover/README.md +4 -0
- package/components/ln-popover/ln-popover-dev.scss +13 -13
- package/components/ln-popover/ln-popover.js +1 -1
- package/components/ln-popover/ln-popover.schema.json +82 -82
- package/components/ln-popover/src/ln-popover.js +9 -0
- package/components/ln-progress/ln-progress-dev.scss +8 -8
- package/components/ln-progress/ln-progress.js +1 -1
- package/components/ln-progress/ln-progress.schema.json +40 -40
- package/components/ln-router/ln-router.js +1 -1
- package/components/ln-router/ln-router.schema.json +76 -76
- package/components/ln-scroll/README.md +45 -45
- package/components/ln-scroll/ln-scroll.js +1 -1
- package/components/ln-scroll/ln-scroll.schema.json +85 -85
- package/components/ln-scroll/src/ln-scroll.js +150 -150
- package/components/ln-search/README.md +188 -188
- package/components/ln-search/ln-search.js +1 -1
- package/components/ln-search/ln-search.schema.json +103 -103
- package/components/ln-slug/ln-slug.js +1 -1
- package/components/ln-slug/ln-slug.schema.json +23 -23
- package/components/ln-sort/ln-sort.js +1 -1
- package/components/ln-sort/ln-sort.schema.json +104 -104
- package/components/ln-sort/src/ln-sort.js +321 -321
- package/components/ln-sort/src/sort-model.js +117 -117
- package/components/ln-sortable/ln-sortable-dev.scss +13 -13
- package/components/ln-sortable/ln-sortable.js +1 -1
- package/components/ln-sortable/ln-sortable.schema.json +36 -36
- package/components/ln-stat/ln-stat-dev.scss +8 -8
- package/components/ln-stat/ln-stat.js +1 -1
- package/components/ln-stat/ln-stat.schema.json +32 -32
- package/components/ln-stat/src/stat-model.js +28 -28
- package/components/ln-table/ln-table.js +1 -1
- package/components/ln-table/ln-table.schema.json +211 -211
- package/components/ln-table/src/ln-table.js +2 -0
- package/components/ln-table/src/table-model.js +98 -98
- package/components/ln-table-coordinator/ln-table-coordinator.js +1 -1
- package/components/ln-table-coordinator/ln-table-coordinator.schema.json +93 -93
- package/components/ln-table-coordinator/src/ln-table-coordinator.js +3 -3
- package/components/ln-tabs/README.md +2 -2
- package/components/ln-tabs/ln-tabs.js +1 -1
- package/components/ln-tabs/ln-tabs.schema.json +75 -75
- package/components/ln-tabs/src/ln-tabs.js +208 -207
- package/components/ln-tabs/src/tabs-model.js +82 -82
- package/components/ln-time/ln-time-dev.scss +13 -13
- package/components/ln-time/ln-time.js +1 -1
- package/components/ln-time/ln-time.schema.json +38 -38
- package/components/ln-toast/ln-toast.js +1 -1
- package/components/ln-toast/ln-toast.schema.json +65 -65
- package/components/ln-toggle/ln-toggle.js +1 -1
- package/components/ln-toggle/ln-toggle.schema.json +52 -52
- package/components/ln-tooltip/ln-tooltip.js +1 -1
- package/components/ln-tooltip/ln-tooltip.schema.json +86 -86
- package/components/ln-translations/ln-translations.js +1 -1
- package/components/ln-translations/ln-translations.schema.json +120 -120
- package/components/ln-ui-coordinator/README.md +62 -62
- package/components/ln-ui-coordinator/ln-ui-coordinator.js +1 -1
- package/components/ln-ui-coordinator/ln-ui-coordinator.schema.json +28 -28
- package/components/ln-ui-coordinator/src/ln-ui-coordinator.js +140 -140
- package/components/ln-upload/ln-upload.js +1 -1
- package/components/ln-upload/ln-upload.schema.json +193 -193
- package/components/ln-upload/src/upload-model.js +78 -78
- package/components/ln-validate/ln-validate.js +1 -1
- package/components/ln-validate/ln-validate.schema.json +50 -50
- package/components/ln-validate/src/validate-model.js +64 -64
- package/components/ln-websocket-connector/README.md +166 -166
- package/components/ln-websocket-connector/ln-websocket-connector.js +1 -1
- package/components/ln-websocket-connector/ln-websocket-connector.schema.json +29 -29
- package/components/ln-websocket-connector/src/ln-websocket-connector.js +283 -279
- package/ln-ashlar.html-data.json +7 -8
- package/package.json +84 -84
- package/theme/brand.css +73 -73
- package/theme/components/_confirm.scss +6 -6
- package/theme/components/_picklist.scss +3 -3
- package/theme/config/_palette.scss +274 -274
- package/theme/config/_theme.scss +292 -292
- package/theme/config/_tokens.scss +285 -285
- package/theme/config/mixins/_index.scss +62 -62
- package/theme/config/mixins/_picklist.scss +66 -66
- package/theme/ln-ashlar-dev.scss +66 -66
- package/theme/ln-ashlar-landing.scss +56 -56
- package/theme/ln-ashlar-theme.scss +63 -63
- package/theme/presentation/_bento.scss +151 -151
- package/theme/presentation/_cta.scss +111 -111
- package/theme/presentation/_effects.scss +216 -216
- package/theme/presentation/_features.scss +108 -108
- package/theme/presentation/_footer.scss +129 -129
- package/theme/presentation/_hero.scss +158 -158
- package/theme/presentation/_layout.scss +170 -170
- package/theme/presentation/_navbar.scss +140 -140
- package/theme/presentation/_pricing.scss +262 -262
- package/theme/presentation/_stats.scss +87 -87
- package/theme/presentation/_testimonials.scss +124 -124
- package/theme/presentation/_tokens.scss +39 -39
- package/theme/presentation/_typography.scss +163 -163
- package/web-types.json +12 -12
|
@@ -1,125 +1,125 @@
|
|
|
1
|
-
const _formatterCache = {};
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Extract group and decimal separators dynamically from Intl.NumberFormat.
|
|
5
|
-
* Supports standard commas, dots, and unicode narrow no-break space (U+202F) for fr-FR.
|
|
6
|
-
*
|
|
7
|
-
* @param {string} locale BCP 47 locale tag (e.g. 'en-US', 'mk-MK', 'fr-FR')
|
|
8
|
-
* @returns {{ groupSep: string, decimalSep: string, fmt: Intl.NumberFormat }}
|
|
9
|
-
*/
|
|
10
|
-
export function getSeparators(locale) {
|
|
11
|
-
const key = locale || 'default';
|
|
12
|
-
if (!_formatterCache[key]) {
|
|
13
|
-
const fmt = new Intl.NumberFormat(locale, { useGrouping: true });
|
|
14
|
-
const parts = fmt.formatToParts(1234.5);
|
|
15
|
-
let groupSep = '';
|
|
16
|
-
let decimalSep = '.';
|
|
17
|
-
for (let i = 0; i < parts.length; i++) {
|
|
18
|
-
if (parts[i].type === 'group') groupSep = parts[i].value;
|
|
19
|
-
if (parts[i].type === 'decimal') decimalSep = parts[i].value;
|
|
20
|
-
}
|
|
21
|
-
_formatterCache[key] = { groupSep, decimalSep, fmt };
|
|
22
|
-
}
|
|
23
|
-
return _formatterCache[key];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Clean a numeric string by removing group separators, unicode spaces,
|
|
28
|
-
* and normalizing the decimal separator to a period ('.').
|
|
29
|
-
*
|
|
30
|
-
* @param {string} raw Raw user input or string representation
|
|
31
|
-
* @param {string} groupSep Group (thousands) separator
|
|
32
|
-
* @param {string} decimalSep Decimal separator
|
|
33
|
-
* @returns {string} Standardized numeric string (e.g. "1234.56" or "-10")
|
|
34
|
-
*/
|
|
35
|
-
export function cleanNumericString(raw, groupSep, decimalSep) {
|
|
36
|
-
if (raw == null || typeof raw !== 'string') return '';
|
|
37
|
-
let str = raw.trim();
|
|
38
|
-
if (str === '') return '';
|
|
39
|
-
|
|
40
|
-
// Remove common currency symbols
|
|
41
|
-
str = str.replace(/[$€£¥]/g, '');
|
|
42
|
-
|
|
43
|
-
// Remove group separators if present
|
|
44
|
-
if (groupSep) {
|
|
45
|
-
str = str.split(groupSep).join('');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Remove all Unicode whitespace characters (covers space, U+00A0, U+202F, etc.)
|
|
49
|
-
str = str.replace(/\s/g, '');
|
|
50
|
-
|
|
51
|
-
// Normalize decimal separator to '.'
|
|
52
|
-
if (decimalSep && decimalSep !== '.') {
|
|
53
|
-
str = str.replace(decimalSep, '.');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Strip any remaining characters except digits, minus, and dot
|
|
57
|
-
str = str.replace(/[^\d.-]/g, '');
|
|
58
|
-
|
|
59
|
-
return str;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Parse a localized numeric string or raw value into a valid floating-point number.
|
|
64
|
-
*
|
|
65
|
-
* @param {unknown} raw Raw string or number
|
|
66
|
-
* @param {string} locale BCP 47 locale tag
|
|
67
|
-
* @returns {number} Parsed float or NaN if invalid
|
|
68
|
-
*/
|
|
69
|
-
export function parseNumber(raw, locale) {
|
|
70
|
-
if (typeof raw === 'number') return isNaN(raw) ? NaN : raw;
|
|
71
|
-
if (raw == null || typeof raw !== 'string') return NaN;
|
|
72
|
-
const trimmed = raw.trim();
|
|
73
|
-
if (trimmed === '' || trimmed === '-') return NaN;
|
|
74
|
-
|
|
75
|
-
const info = getSeparators(locale);
|
|
76
|
-
const cleaned = cleanNumericString(trimmed, info.groupSep, info.decimalSep);
|
|
77
|
-
if (cleaned === '' || cleaned === '-') return NaN;
|
|
78
|
-
|
|
79
|
-
const num = parseFloat(cleaned);
|
|
80
|
-
return isNaN(num) ? NaN : num;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Format a number using Intl.NumberFormat according to specified locale and options.
|
|
85
|
-
*
|
|
86
|
-
* @param {number} num Number to format
|
|
87
|
-
* @param {string} [locale='default'] BCP 47 locale tag
|
|
88
|
-
* @param {Object} [options] Formatting options
|
|
89
|
-
* @param {number|null} [options.maxDecimals] Maximum fraction digits limit
|
|
90
|
-
* @param {number} [options.userDecimals] Exact fraction digits to preserve during typing
|
|
91
|
-
* @returns {string} Formatted number string
|
|
92
|
-
*/
|
|
93
|
-
export function formatNumber(num, locale, options = {}) {
|
|
94
|
-
if (typeof num !== 'number' || isNaN(num) || !Number.isFinite(num)) return '';
|
|
95
|
-
|
|
96
|
-
const loc = locale || 'default';
|
|
97
|
-
const maxDecimals = options.maxDecimals != null ? parseInt(options.maxDecimals, 10) : null;
|
|
98
|
-
const userDecimals = options.userDecimals != null ? options.userDecimals : null;
|
|
99
|
-
|
|
100
|
-
if (maxDecimals !== null) {
|
|
101
|
-
const cacheKey = loc + '|max:' + maxDecimals;
|
|
102
|
-
if (!_formatterCache[cacheKey]) {
|
|
103
|
-
_formatterCache[cacheKey] = new Intl.NumberFormat(locale, {
|
|
104
|
-
useGrouping: true,
|
|
105
|
-
minimumFractionDigits: 0,
|
|
106
|
-
maximumFractionDigits: maxDecimals
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
return _formatterCache[cacheKey].format(num);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (userDecimals !== null && userDecimals > 0) {
|
|
113
|
-
const cacheKey = loc + '|exact:' + userDecimals;
|
|
114
|
-
if (!_formatterCache[cacheKey]) {
|
|
115
|
-
_formatterCache[cacheKey] = new Intl.NumberFormat(locale, {
|
|
116
|
-
useGrouping: true,
|
|
117
|
-
minimumFractionDigits: userDecimals,
|
|
118
|
-
maximumFractionDigits: userDecimals
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
return _formatterCache[cacheKey].format(num);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return getSeparators(locale).fmt.format(num);
|
|
125
|
-
}
|
|
1
|
+
const _formatterCache = {};
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extract group and decimal separators dynamically from Intl.NumberFormat.
|
|
5
|
+
* Supports standard commas, dots, and unicode narrow no-break space (U+202F) for fr-FR.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} locale BCP 47 locale tag (e.g. 'en-US', 'mk-MK', 'fr-FR')
|
|
8
|
+
* @returns {{ groupSep: string, decimalSep: string, fmt: Intl.NumberFormat }}
|
|
9
|
+
*/
|
|
10
|
+
export function getSeparators(locale) {
|
|
11
|
+
const key = locale || 'default';
|
|
12
|
+
if (!_formatterCache[key]) {
|
|
13
|
+
const fmt = new Intl.NumberFormat(locale, { useGrouping: true });
|
|
14
|
+
const parts = fmt.formatToParts(1234.5);
|
|
15
|
+
let groupSep = '';
|
|
16
|
+
let decimalSep = '.';
|
|
17
|
+
for (let i = 0; i < parts.length; i++) {
|
|
18
|
+
if (parts[i].type === 'group') groupSep = parts[i].value;
|
|
19
|
+
if (parts[i].type === 'decimal') decimalSep = parts[i].value;
|
|
20
|
+
}
|
|
21
|
+
_formatterCache[key] = { groupSep, decimalSep, fmt };
|
|
22
|
+
}
|
|
23
|
+
return _formatterCache[key];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Clean a numeric string by removing group separators, unicode spaces,
|
|
28
|
+
* and normalizing the decimal separator to a period ('.').
|
|
29
|
+
*
|
|
30
|
+
* @param {string} raw Raw user input or string representation
|
|
31
|
+
* @param {string} groupSep Group (thousands) separator
|
|
32
|
+
* @param {string} decimalSep Decimal separator
|
|
33
|
+
* @returns {string} Standardized numeric string (e.g. "1234.56" or "-10")
|
|
34
|
+
*/
|
|
35
|
+
export function cleanNumericString(raw, groupSep, decimalSep) {
|
|
36
|
+
if (raw == null || typeof raw !== 'string') return '';
|
|
37
|
+
let str = raw.trim();
|
|
38
|
+
if (str === '') return '';
|
|
39
|
+
|
|
40
|
+
// Remove common currency symbols
|
|
41
|
+
str = str.replace(/[$€£¥]/g, '');
|
|
42
|
+
|
|
43
|
+
// Remove group separators if present
|
|
44
|
+
if (groupSep) {
|
|
45
|
+
str = str.split(groupSep).join('');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Remove all Unicode whitespace characters (covers space, U+00A0, U+202F, etc.)
|
|
49
|
+
str = str.replace(/\s/g, '');
|
|
50
|
+
|
|
51
|
+
// Normalize decimal separator to '.'
|
|
52
|
+
if (decimalSep && decimalSep !== '.') {
|
|
53
|
+
str = str.replace(decimalSep, '.');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Strip any remaining characters except digits, minus, and dot
|
|
57
|
+
str = str.replace(/[^\d.-]/g, '');
|
|
58
|
+
|
|
59
|
+
return str;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Parse a localized numeric string or raw value into a valid floating-point number.
|
|
64
|
+
*
|
|
65
|
+
* @param {unknown} raw Raw string or number
|
|
66
|
+
* @param {string} locale BCP 47 locale tag
|
|
67
|
+
* @returns {number} Parsed float or NaN if invalid
|
|
68
|
+
*/
|
|
69
|
+
export function parseNumber(raw, locale) {
|
|
70
|
+
if (typeof raw === 'number') return isNaN(raw) ? NaN : raw;
|
|
71
|
+
if (raw == null || typeof raw !== 'string') return NaN;
|
|
72
|
+
const trimmed = raw.trim();
|
|
73
|
+
if (trimmed === '' || trimmed === '-') return NaN;
|
|
74
|
+
|
|
75
|
+
const info = getSeparators(locale);
|
|
76
|
+
const cleaned = cleanNumericString(trimmed, info.groupSep, info.decimalSep);
|
|
77
|
+
if (cleaned === '' || cleaned === '-') return NaN;
|
|
78
|
+
|
|
79
|
+
const num = parseFloat(cleaned);
|
|
80
|
+
return isNaN(num) ? NaN : num;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Format a number using Intl.NumberFormat according to specified locale and options.
|
|
85
|
+
*
|
|
86
|
+
* @param {number} num Number to format
|
|
87
|
+
* @param {string} [locale='default'] BCP 47 locale tag
|
|
88
|
+
* @param {Object} [options] Formatting options
|
|
89
|
+
* @param {number|null} [options.maxDecimals] Maximum fraction digits limit
|
|
90
|
+
* @param {number} [options.userDecimals] Exact fraction digits to preserve during typing
|
|
91
|
+
* @returns {string} Formatted number string
|
|
92
|
+
*/
|
|
93
|
+
export function formatNumber(num, locale, options = {}) {
|
|
94
|
+
if (typeof num !== 'number' || isNaN(num) || !Number.isFinite(num)) return '';
|
|
95
|
+
|
|
96
|
+
const loc = locale || 'default';
|
|
97
|
+
const maxDecimals = options.maxDecimals != null ? parseInt(options.maxDecimals, 10) : null;
|
|
98
|
+
const userDecimals = options.userDecimals != null ? options.userDecimals : null;
|
|
99
|
+
|
|
100
|
+
if (maxDecimals !== null) {
|
|
101
|
+
const cacheKey = loc + '|max:' + maxDecimals;
|
|
102
|
+
if (!_formatterCache[cacheKey]) {
|
|
103
|
+
_formatterCache[cacheKey] = new Intl.NumberFormat(locale, {
|
|
104
|
+
useGrouping: true,
|
|
105
|
+
minimumFractionDigits: 0,
|
|
106
|
+
maximumFractionDigits: maxDecimals
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return _formatterCache[cacheKey].format(num);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (userDecimals !== null && userDecimals > 0) {
|
|
113
|
+
const cacheKey = loc + '|exact:' + userDecimals;
|
|
114
|
+
if (!_formatterCache[cacheKey]) {
|
|
115
|
+
_formatterCache[cacheKey] = new Intl.NumberFormat(locale, {
|
|
116
|
+
useGrouping: true,
|
|
117
|
+
minimumFractionDigits: userDecimals,
|
|
118
|
+
maximumFractionDigits: userDecimals
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return _formatterCache[cacheKey].format(num);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return getSeparators(locale).fmt.format(num);
|
|
125
|
+
}
|
|
@@ -1,206 +1,206 @@
|
|
|
1
|
-
// ─── Template Cache ────────────────────────────────────────
|
|
2
|
-
const _tmplCache = {};
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Clone a <template data-ln-template="name"> element.
|
|
6
|
-
* Cached after first lookup.
|
|
7
|
-
*/
|
|
8
|
-
export function cloneTemplate(name, componentTag) {
|
|
9
|
-
if (!_tmplCache[name]) {
|
|
10
|
-
_tmplCache[name] = document.querySelector('[data-ln-template="' + name + '"]');
|
|
11
|
-
}
|
|
12
|
-
const tmpl = _tmplCache[name];
|
|
13
|
-
if (!tmpl) {
|
|
14
|
-
console.warn('[' + (componentTag || 'ln-core') + '] Template "' + name + '" not found');
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
return tmpl.content.cloneNode(true);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function cloneTemplateScoped(root, name, componentTag) {
|
|
21
|
-
if (root) {
|
|
22
|
-
const local = root.querySelector('[data-ln-template="' + name + '"]');
|
|
23
|
-
if (local) return local.content.cloneNode(true);
|
|
24
|
-
}
|
|
25
|
-
return cloneTemplate(name, componentTag);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// ─── Declarative DOM Binding ───────────────────────────────
|
|
29
|
-
|
|
30
|
-
export function fill(root, data) {
|
|
31
|
-
if (!root || !data) return root;
|
|
32
|
-
|
|
33
|
-
const fields = root.querySelectorAll('[data-ln-field]');
|
|
34
|
-
for (let i = 0; i < fields.length; i++) {
|
|
35
|
-
const el = fields[i];
|
|
36
|
-
const prop = el.getAttribute('data-ln-field');
|
|
37
|
-
if (data[prop] != null) {
|
|
38
|
-
el.textContent = data[prop];
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const attrs = root.querySelectorAll('[data-ln-attr]');
|
|
43
|
-
for (let i = 0; i < attrs.length; i++) {
|
|
44
|
-
const el = attrs[i];
|
|
45
|
-
const pairs = el.getAttribute('data-ln-attr').split(',');
|
|
46
|
-
for (let j = 0; j < pairs.length; j++) {
|
|
47
|
-
const parts = pairs[j].trim().split(':');
|
|
48
|
-
if (parts.length !== 2) continue;
|
|
49
|
-
const attr = parts[0].trim();
|
|
50
|
-
const prop = parts[1].trim();
|
|
51
|
-
if (data[prop] != null) {
|
|
52
|
-
el.setAttribute(attr, data[prop]);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const shows = root.querySelectorAll('[data-ln-show]');
|
|
58
|
-
for (let i = 0; i < shows.length; i++) {
|
|
59
|
-
const el = shows[i];
|
|
60
|
-
const prop = el.getAttribute('data-ln-show');
|
|
61
|
-
if (prop in data) {
|
|
62
|
-
el.classList.toggle('hidden', !data[prop]);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const classes = root.querySelectorAll('[data-ln-class]');
|
|
67
|
-
for (let i = 0; i < classes.length; i++) {
|
|
68
|
-
const el = classes[i];
|
|
69
|
-
const pairs = el.getAttribute('data-ln-class').split(',');
|
|
70
|
-
for (let j = 0; j < pairs.length; j++) {
|
|
71
|
-
const parts = pairs[j].trim().split(':');
|
|
72
|
-
if (parts.length !== 2) continue;
|
|
73
|
-
const cls = parts[0].trim();
|
|
74
|
-
const prop = parts[1].trim();
|
|
75
|
-
if (prop in data) {
|
|
76
|
-
el.classList.toggle(cls, !!data[prop]);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return root;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// ─── Event-driven fill fan-out ─────────────────────────────
|
|
85
|
-
|
|
86
|
-
export function lnFill(container, record) {
|
|
87
|
-
if (container.matches && container.matches('[data-ln-form], [data-ln-fillable]')) {
|
|
88
|
-
if (window.lnCore._debugSink) window.lnCore._debugSink('event', 'ln-fill', container, record ?? null);
|
|
89
|
-
container.dispatchEvent(new CustomEvent('ln-fill', { detail: record ?? null, bubbles: true }));
|
|
90
|
-
}
|
|
91
|
-
const targets = container.querySelectorAll('[data-ln-form], [data-ln-fillable]');
|
|
92
|
-
for (let i = 0; i < targets.length; i++) {
|
|
93
|
-
if (window.lnCore._debugSink) window.lnCore._debugSink('event', 'ln-fill', targets[i], record ?? null);
|
|
94
|
-
targets[i].dispatchEvent(new CustomEvent('ln-fill', { detail: record ?? null, bubbles: true }));
|
|
95
|
-
}
|
|
96
|
-
return container;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (typeof window !== 'undefined') {
|
|
100
|
-
window.lnCore = window.lnCore || {};
|
|
101
|
-
if (!window.lnCore._fillBound) {
|
|
102
|
-
window.lnCore._fillBound = true;
|
|
103
|
-
document.addEventListener('ln-fill', function (e) {
|
|
104
|
-
if (!e.target.matches || !e.target.matches('[data-ln-fillable]')) return;
|
|
105
|
-
if (e.detail) {
|
|
106
|
-
fill(e.target, e.detail);
|
|
107
|
-
} else {
|
|
108
|
-
const fields = e.target.querySelectorAll('[data-ln-field]');
|
|
109
|
-
for (let i = 0; i < fields.length; i++) {
|
|
110
|
-
fields[i].textContent = '';
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// ─── Template Text-Node Placeholders ──────────────────────
|
|
118
|
-
|
|
119
|
-
export function fillTemplate(clone, data) {
|
|
120
|
-
if (!clone || !data) return clone;
|
|
121
|
-
|
|
122
|
-
const walker = document.createTreeWalker(clone, NodeFilter.SHOW_TEXT);
|
|
123
|
-
while (walker.nextNode()) {
|
|
124
|
-
const node = walker.currentNode;
|
|
125
|
-
if (node.textContent.indexOf('{{') !== -1) {
|
|
126
|
-
node.textContent = node.textContent.replace(
|
|
127
|
-
/\{\{\s*(\w+)\s*\}\}/g,
|
|
128
|
-
function (_, key) { return data[key] !== undefined ? data[key] : ''; }
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const _replaceTokens = function (_, key) { return data[key] !== undefined ? data[key] : ''; };
|
|
134
|
-
const elements = Array.from(clone.querySelectorAll('*'));
|
|
135
|
-
if (clone.nodeType === 1) elements.push(clone);
|
|
136
|
-
for (let i = 0; i < elements.length; i++) {
|
|
137
|
-
const el = elements[i];
|
|
138
|
-
const attrs = el.attributes;
|
|
139
|
-
for (let j = 0; j < attrs.length; j++) {
|
|
140
|
-
const attr = attrs[j];
|
|
141
|
-
if (attr.value.indexOf('{{') !== -1) {
|
|
142
|
-
el.setAttribute(attr.name, attr.value.replace(/\{\{\s*(\w+)\s*\}\}/g, _replaceTokens));
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return clone;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// ─── Keyed List Rendering ──────────────────────────────────
|
|
151
|
-
|
|
152
|
-
export function renderList(container, items, templateName, keyFn, fillFn, componentTag) {
|
|
153
|
-
const existingByKey = {};
|
|
154
|
-
for (let i = 0; i < container.children.length; i++) {
|
|
155
|
-
const child = container.children[i];
|
|
156
|
-
const key = child.getAttribute('data-ln-render-key');
|
|
157
|
-
if (key) existingByKey[key] = child;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const frag = document.createDocumentFragment();
|
|
161
|
-
|
|
162
|
-
for (let i = 0; i < items.length; i++) {
|
|
163
|
-
const item = items[i];
|
|
164
|
-
const key = String(keyFn(item));
|
|
165
|
-
let el = existingByKey[key];
|
|
166
|
-
|
|
167
|
-
if (el) {
|
|
168
|
-
fillFn(el, item, i);
|
|
169
|
-
} else {
|
|
170
|
-
const clone = cloneTemplate(templateName, componentTag);
|
|
171
|
-
if (!clone) continue;
|
|
172
|
-
fillTemplate(clone, item);
|
|
173
|
-
el = clone.firstElementChild;
|
|
174
|
-
if (!el) continue;
|
|
175
|
-
el.setAttribute('data-ln-render-key', key);
|
|
176
|
-
fillFn(el, item, i);
|
|
177
|
-
}
|
|
178
|
-
frag.appendChild(el);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
container.textContent = '';
|
|
182
|
-
container.appendChild(frag);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// ─── Dictionary (i18n) ────────────────────────────────────
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Build a plain object from hidden dictionary elements.
|
|
189
|
-
*/
|
|
190
|
-
export function buildDict(root, selector) {
|
|
191
|
-
const dict = {};
|
|
192
|
-
const els = root.querySelectorAll('[' + selector + ']');
|
|
193
|
-
for (let i = 0; i < els.length; i++) {
|
|
194
|
-
dict[els[i].getAttribute(selector)] = els[i].textContent;
|
|
195
|
-
els[i].remove();
|
|
196
|
-
}
|
|
197
|
-
return dict;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (typeof window !== 'undefined') {
|
|
201
|
-
window.lnCore = window.lnCore || {};
|
|
202
|
-
window.lnCore.fillTemplate = fillTemplate;
|
|
203
|
-
window.lnCore.fill = fill;
|
|
204
|
-
window.lnCore.lnFill = lnFill;
|
|
205
|
-
window.lnCore.renderList = renderList;
|
|
206
|
-
}
|
|
1
|
+
// ─── Template Cache ────────────────────────────────────────
|
|
2
|
+
const _tmplCache = {};
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Clone a <template data-ln-template="name"> element.
|
|
6
|
+
* Cached after first lookup.
|
|
7
|
+
*/
|
|
8
|
+
export function cloneTemplate(name, componentTag) {
|
|
9
|
+
if (!_tmplCache[name]) {
|
|
10
|
+
_tmplCache[name] = document.querySelector('[data-ln-template="' + name + '"]');
|
|
11
|
+
}
|
|
12
|
+
const tmpl = _tmplCache[name];
|
|
13
|
+
if (!tmpl) {
|
|
14
|
+
console.warn('[' + (componentTag || 'ln-core') + '] Template "' + name + '" not found');
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return tmpl.content.cloneNode(true);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function cloneTemplateScoped(root, name, componentTag) {
|
|
21
|
+
if (root) {
|
|
22
|
+
const local = root.querySelector('[data-ln-template="' + name + '"]');
|
|
23
|
+
if (local) return local.content.cloneNode(true);
|
|
24
|
+
}
|
|
25
|
+
return cloneTemplate(name, componentTag);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ─── Declarative DOM Binding ───────────────────────────────
|
|
29
|
+
|
|
30
|
+
export function fill(root, data) {
|
|
31
|
+
if (!root || !data) return root;
|
|
32
|
+
|
|
33
|
+
const fields = root.querySelectorAll('[data-ln-field]');
|
|
34
|
+
for (let i = 0; i < fields.length; i++) {
|
|
35
|
+
const el = fields[i];
|
|
36
|
+
const prop = el.getAttribute('data-ln-field');
|
|
37
|
+
if (data[prop] != null) {
|
|
38
|
+
el.textContent = data[prop];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const attrs = root.querySelectorAll('[data-ln-attr]');
|
|
43
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
44
|
+
const el = attrs[i];
|
|
45
|
+
const pairs = el.getAttribute('data-ln-attr').split(',');
|
|
46
|
+
for (let j = 0; j < pairs.length; j++) {
|
|
47
|
+
const parts = pairs[j].trim().split(':');
|
|
48
|
+
if (parts.length !== 2) continue;
|
|
49
|
+
const attr = parts[0].trim();
|
|
50
|
+
const prop = parts[1].trim();
|
|
51
|
+
if (data[prop] != null) {
|
|
52
|
+
el.setAttribute(attr, data[prop]);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const shows = root.querySelectorAll('[data-ln-show]');
|
|
58
|
+
for (let i = 0; i < shows.length; i++) {
|
|
59
|
+
const el = shows[i];
|
|
60
|
+
const prop = el.getAttribute('data-ln-show');
|
|
61
|
+
if (prop in data) {
|
|
62
|
+
el.classList.toggle('hidden', !data[prop]);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const classes = root.querySelectorAll('[data-ln-class]');
|
|
67
|
+
for (let i = 0; i < classes.length; i++) {
|
|
68
|
+
const el = classes[i];
|
|
69
|
+
const pairs = el.getAttribute('data-ln-class').split(',');
|
|
70
|
+
for (let j = 0; j < pairs.length; j++) {
|
|
71
|
+
const parts = pairs[j].trim().split(':');
|
|
72
|
+
if (parts.length !== 2) continue;
|
|
73
|
+
const cls = parts[0].trim();
|
|
74
|
+
const prop = parts[1].trim();
|
|
75
|
+
if (prop in data) {
|
|
76
|
+
el.classList.toggle(cls, !!data[prop]);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return root;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ─── Event-driven fill fan-out ─────────────────────────────
|
|
85
|
+
|
|
86
|
+
export function lnFill(container, record) {
|
|
87
|
+
if (container.matches && container.matches('[data-ln-form], [data-ln-fillable]')) {
|
|
88
|
+
if (window.lnCore._debugSink) window.lnCore._debugSink('event', 'ln-fill', container, record ?? null);
|
|
89
|
+
container.dispatchEvent(new CustomEvent('ln-fill', { detail: record ?? null, bubbles: true }));
|
|
90
|
+
}
|
|
91
|
+
const targets = container.querySelectorAll('[data-ln-form], [data-ln-fillable]');
|
|
92
|
+
for (let i = 0; i < targets.length; i++) {
|
|
93
|
+
if (window.lnCore._debugSink) window.lnCore._debugSink('event', 'ln-fill', targets[i], record ?? null);
|
|
94
|
+
targets[i].dispatchEvent(new CustomEvent('ln-fill', { detail: record ?? null, bubbles: true }));
|
|
95
|
+
}
|
|
96
|
+
return container;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (typeof window !== 'undefined') {
|
|
100
|
+
window.lnCore = window.lnCore || {};
|
|
101
|
+
if (!window.lnCore._fillBound) {
|
|
102
|
+
window.lnCore._fillBound = true;
|
|
103
|
+
document.addEventListener('ln-fill', function (e) {
|
|
104
|
+
if (!e.target.matches || !e.target.matches('[data-ln-fillable]')) return;
|
|
105
|
+
if (e.detail) {
|
|
106
|
+
fill(e.target, e.detail);
|
|
107
|
+
} else {
|
|
108
|
+
const fields = e.target.querySelectorAll('[data-ln-field]');
|
|
109
|
+
for (let i = 0; i < fields.length; i++) {
|
|
110
|
+
fields[i].textContent = '';
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ─── Template Text-Node Placeholders ──────────────────────
|
|
118
|
+
|
|
119
|
+
export function fillTemplate(clone, data) {
|
|
120
|
+
if (!clone || !data) return clone;
|
|
121
|
+
|
|
122
|
+
const walker = document.createTreeWalker(clone, NodeFilter.SHOW_TEXT);
|
|
123
|
+
while (walker.nextNode()) {
|
|
124
|
+
const node = walker.currentNode;
|
|
125
|
+
if (node.textContent.indexOf('{{') !== -1) {
|
|
126
|
+
node.textContent = node.textContent.replace(
|
|
127
|
+
/\{\{\s*(\w+)\s*\}\}/g,
|
|
128
|
+
function (_, key) { return data[key] !== undefined ? data[key] : ''; }
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const _replaceTokens = function (_, key) { return data[key] !== undefined ? data[key] : ''; };
|
|
134
|
+
const elements = Array.from(clone.querySelectorAll('*'));
|
|
135
|
+
if (clone.nodeType === 1) elements.push(clone);
|
|
136
|
+
for (let i = 0; i < elements.length; i++) {
|
|
137
|
+
const el = elements[i];
|
|
138
|
+
const attrs = el.attributes;
|
|
139
|
+
for (let j = 0; j < attrs.length; j++) {
|
|
140
|
+
const attr = attrs[j];
|
|
141
|
+
if (attr.value.indexOf('{{') !== -1) {
|
|
142
|
+
el.setAttribute(attr.name, attr.value.replace(/\{\{\s*(\w+)\s*\}\}/g, _replaceTokens));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return clone;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ─── Keyed List Rendering ──────────────────────────────────
|
|
151
|
+
|
|
152
|
+
export function renderList(container, items, templateName, keyFn, fillFn, componentTag) {
|
|
153
|
+
const existingByKey = {};
|
|
154
|
+
for (let i = 0; i < container.children.length; i++) {
|
|
155
|
+
const child = container.children[i];
|
|
156
|
+
const key = child.getAttribute('data-ln-render-key');
|
|
157
|
+
if (key) existingByKey[key] = child;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const frag = document.createDocumentFragment();
|
|
161
|
+
|
|
162
|
+
for (let i = 0; i < items.length; i++) {
|
|
163
|
+
const item = items[i];
|
|
164
|
+
const key = String(keyFn(item));
|
|
165
|
+
let el = existingByKey[key];
|
|
166
|
+
|
|
167
|
+
if (el) {
|
|
168
|
+
fillFn(el, item, i);
|
|
169
|
+
} else {
|
|
170
|
+
const clone = cloneTemplate(templateName, componentTag);
|
|
171
|
+
if (!clone) continue;
|
|
172
|
+
fillTemplate(clone, item);
|
|
173
|
+
el = clone.firstElementChild;
|
|
174
|
+
if (!el) continue;
|
|
175
|
+
el.setAttribute('data-ln-render-key', key);
|
|
176
|
+
fillFn(el, item, i);
|
|
177
|
+
}
|
|
178
|
+
frag.appendChild(el);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
container.textContent = '';
|
|
182
|
+
container.appendChild(frag);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ─── Dictionary (i18n) ────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Build a plain object from hidden dictionary elements.
|
|
189
|
+
*/
|
|
190
|
+
export function buildDict(root, selector) {
|
|
191
|
+
const dict = {};
|
|
192
|
+
const els = root.querySelectorAll('[' + selector + ']');
|
|
193
|
+
for (let i = 0; i < els.length; i++) {
|
|
194
|
+
dict[els[i].getAttribute(selector)] = els[i].textContent;
|
|
195
|
+
els[i].remove();
|
|
196
|
+
}
|
|
197
|
+
return dict;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (typeof window !== 'undefined') {
|
|
201
|
+
window.lnCore = window.lnCore || {};
|
|
202
|
+
window.lnCore.fillTemplate = fillTemplate;
|
|
203
|
+
window.lnCore.fill = fill;
|
|
204
|
+
window.lnCore.lnFill = lnFill;
|
|
205
|
+
window.lnCore.renderList = renderList;
|
|
206
|
+
}
|
|
@@ -177,3 +177,7 @@ Ingress: every fetched/created/updated document gets `id = _id` before reaching
|
|
|
177
177
|
### Envelope unwrap
|
|
178
178
|
|
|
179
179
|
Same `{message, content}` presence-check as `ln-api-connector` (see its README) — `content` replaces the bare CouchDB body when present, `message` rides opaquely on the `:created`/`:updated`/`:deleted`/`:bulk-deleted` event detail. Raw CouchDB never sends this envelope, so a direct CouchDB backend never toasts; only a proxy/gateway that wraps responses can opt in.
|
|
180
|
+
|
|
181
|
+
### Async Cancellation & Lifecycle (`destroy()`)
|
|
182
|
+
|
|
183
|
+
In `destroy()`, any in-flight delta changes feed fetch (`fetchDelta`) is aborted via an internal `AbortController` (`_deltaController`), and its aborted promise rejection is safely ignored to prevent uncaught errors or stale event dispatches after element teardown. In accordance with offline sync data integrity rules, mutation requests (POST/PUT/DELETE) are not aborted mid-flight to avoid ambiguous states where the remote database commits a write while the client treats it as aborted.
|