@livenetworks/ashlar 2.0.7 → 2.0.9
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-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 +9 -0
- package/components/ln-api-connector/src/ln-api-connector.js +2 -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/attrs.js +13 -0
- package/components/ln-core/crypto.js +286 -265
- package/components/ln-core/dom.js +115 -115
- package/components/ln-core/form.js +146 -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/lifecycle.js +5 -0
- package/components/ln-core/ln-core.schema.json +3 -2
- 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/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 +1 -0
- 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 +432 -1308
- 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 -184
- package/components/ln-data-store/src/ln-data-store.js +1 -1
- 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-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 +4 -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 +6 -0
- package/components/ln-form/src/ln-form.js +21 -0
- 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-modal/ln-modal.js +1 -1
- package/components/ln-modal/ln-modal.schema.json +65 -65
- 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/ln-number.js +26 -0
- 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 +6 -0
- package/components/ln-options/src/ln-options.js +4 -4
- 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/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-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-slug/src/ln-slug.js +25 -1
- 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/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-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 -208
- 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 +15 -8
- package/package.json +85 -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 +26 -12
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Build a URL from segments, stripping duplicate slashes.
|
|
3
|
-
*/
|
|
4
|
-
export function buildUrl(...segments) {
|
|
5
|
-
return segments
|
|
6
|
-
.filter(x => x !== undefined && x !== null && x !== '')
|
|
7
|
-
.map((part, index) => {
|
|
8
|
-
if (index === 0) return part.replace(/\/+$/, '');
|
|
9
|
-
return part.replace(/^\/+/, '').replace(/\/+$/, '');
|
|
10
|
-
})
|
|
11
|
-
.filter(Boolean)
|
|
12
|
-
.join('/');
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Compile standard JSON request headers.
|
|
17
|
-
*/
|
|
18
|
-
export function getHeaders(customHeaders, auth) {
|
|
19
|
-
return Object.assign({
|
|
20
|
-
'Content-Type': 'application/json',
|
|
21
|
-
'Accept': 'application/json'
|
|
22
|
-
}, customHeaders, auth ? { 'Authorization': auth } : null);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Safely parse headers from JSON string.
|
|
27
|
-
*/
|
|
28
|
-
export function parseHeaders(str, componentName = 'ln-core') {
|
|
29
|
-
try { return str ? JSON.parse(str) : {}; }
|
|
30
|
-
catch (e) { return console.error(`[${componentName}] Invalid headers JSON:`, e), {}; }
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// ─── Domain Data Mapper Registry ───────────────────────────
|
|
34
|
-
const _dataMappers = {};
|
|
35
|
-
|
|
36
|
-
export function registerDataMapper(name, mapper) {
|
|
37
|
-
_dataMappers[name] = mapper;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function getDataMapper(name) {
|
|
41
|
-
return _dataMappers[name] || { ingress: r => r, egress: r => r };
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (typeof window !== 'undefined') {
|
|
45
|
-
window.lnCore = window.lnCore || {};
|
|
46
|
-
window.lnCore.registerDataMapper = registerDataMapper;
|
|
47
|
-
window.lnCore.getDataMapper = getDataMapper;
|
|
48
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Build a URL from segments, stripping duplicate slashes.
|
|
3
|
+
*/
|
|
4
|
+
export function buildUrl(...segments) {
|
|
5
|
+
return segments
|
|
6
|
+
.filter(x => x !== undefined && x !== null && x !== '')
|
|
7
|
+
.map((part, index) => {
|
|
8
|
+
if (index === 0) return part.replace(/\/+$/, '');
|
|
9
|
+
return part.replace(/^\/+/, '').replace(/\/+$/, '');
|
|
10
|
+
})
|
|
11
|
+
.filter(Boolean)
|
|
12
|
+
.join('/');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Compile standard JSON request headers.
|
|
17
|
+
*/
|
|
18
|
+
export function getHeaders(customHeaders, auth) {
|
|
19
|
+
return Object.assign({
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
'Accept': 'application/json'
|
|
22
|
+
}, customHeaders, auth ? { 'Authorization': auth } : null);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Safely parse headers from JSON string.
|
|
27
|
+
*/
|
|
28
|
+
export function parseHeaders(str, componentName = 'ln-core') {
|
|
29
|
+
try { return str ? JSON.parse(str) : {}; }
|
|
30
|
+
catch (e) { return console.error(`[${componentName}] Invalid headers JSON:`, e), {}; }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ─── Domain Data Mapper Registry ───────────────────────────
|
|
34
|
+
const _dataMappers = {};
|
|
35
|
+
|
|
36
|
+
export function registerDataMapper(name, mapper) {
|
|
37
|
+
_dataMappers[name] = mapper;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getDataMapper(name) {
|
|
41
|
+
return _dataMappers[name] || { ingress: r => r, egress: r => r };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (typeof window !== 'undefined') {
|
|
45
|
+
window.lnCore = window.lnCore || {};
|
|
46
|
+
window.lnCore.registerDataMapper = registerDataMapper;
|
|
47
|
+
window.lnCore.getDataMapper = getDataMapper;
|
|
48
|
+
}
|
|
@@ -3,7 +3,7 @@ export { reactiveState, deepReactive, createBatcher } from './reactive.js';
|
|
|
3
3
|
export { createWindowCache } from './window-cache.js';
|
|
4
4
|
export { hashParse, hashGet, hashSet, hashLinkClick, resolveHashNamespace, hashSortEncode, hashSortDecode, hashFilterEncode, hashFilterDecode } from './hash.js';
|
|
5
5
|
export { computePlacement, measureHidden } from './positioning.js';
|
|
6
|
-
export { setCryptoKey, getCryptoKey, clearCryptoKey, hasCryptoKey, deriveCryptoKey, encryptData, decryptData, uint8ToBase64, base64ToUint8 } from './crypto.js';
|
|
6
|
+
export { setCryptoKey, getCryptoKey, clearCryptoKey, hasCryptoKey, deriveCryptoKey, encryptData, decryptData, uint8ToBase64, base64ToUint8, uuid } from './crypto.js';
|
|
7
7
|
export { calculateProgress } from './progress.js';
|
|
8
8
|
export { parseDateInput, formatDateToISO } from './date.js';
|
|
9
9
|
export { getSeparators, cleanNumericString, parseNumber, formatNumber } from './number.js';
|
|
@@ -385,6 +385,11 @@ export function registerComponent(selector, attribute, ComponentFn, componentTag
|
|
|
385
385
|
|
|
386
386
|
window[attribute] = constructor;
|
|
387
387
|
|
|
388
|
+
if (typeof window !== 'undefined') {
|
|
389
|
+
window.lnCore = window.lnCore || {};
|
|
390
|
+
window.lnCore.registerComponent = registerComponent;
|
|
391
|
+
}
|
|
392
|
+
|
|
388
393
|
function boot() {
|
|
389
394
|
if (pendingCount() > 0) {
|
|
390
395
|
queueBoot(function () { constructor(document.body); });
|
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
import { dispatch } from './events.js';
|
|
2
|
-
import { guardBody } from './dom.js';
|
|
3
|
-
|
|
4
|
-
// ─── Locale Detection ─────────────────────────────────────
|
|
5
|
-
|
|
6
|
-
export function getLocale(el) {
|
|
7
|
-
const langEl = el ? el.closest('[lang]') : null;
|
|
8
|
-
const raw = (langEl ? (langEl.getAttribute('lang') || langEl.lang) : null)
|
|
9
|
-
|| (typeof document !== 'undefined' && document.documentElement ? (document.documentElement.getAttribute('lang') || document.documentElement.lang) : null)
|
|
10
|
-
|| (typeof navigator !== 'undefined' ? navigator.language : null);
|
|
11
|
-
return raw ? raw.trim() : 'en-US';
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// ─── Shared Locale-Change Broadcaster ─────────────────────
|
|
15
|
-
|
|
16
|
-
export function ensureLocaleObserver() {
|
|
17
|
-
if (typeof window === 'undefined') return;
|
|
18
|
-
window.lnCore = window.lnCore || {};
|
|
19
|
-
if (window.lnCore._localeObserverBound) return;
|
|
20
|
-
window.lnCore._localeObserverBound = true;
|
|
21
|
-
|
|
22
|
-
guardBody(function () {
|
|
23
|
-
const observer = new MutationObserver(function () {
|
|
24
|
-
dispatch(document, 'ln-core:locale-change', {});
|
|
25
|
-
});
|
|
26
|
-
observer.observe(document.documentElement, {
|
|
27
|
-
attributes: true,
|
|
28
|
-
attributeFilter: ['lang'],
|
|
29
|
-
subtree: true
|
|
30
|
-
});
|
|
31
|
-
}, 'ln-core');
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// ─── Locale Fallback Registry ───────────────────────────────
|
|
35
|
-
const _localeFallbacks = {};
|
|
36
|
-
|
|
37
|
-
export function registerLocaleFallback(langPrefix, dictionary) {
|
|
38
|
-
if (!langPrefix || typeof dictionary !== 'object') return;
|
|
39
|
-
const prefix = langPrefix.toLowerCase().split('-')[0];
|
|
40
|
-
_localeFallbacks[prefix] = dictionary;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function getLocaleFallback(lang) {
|
|
44
|
-
if (!lang) return null;
|
|
45
|
-
const prefix = lang.toLowerCase().split('-')[0];
|
|
46
|
-
return _localeFallbacks[prefix] || null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (typeof window !== 'undefined') {
|
|
51
|
-
window.lnCore = window.lnCore || {};
|
|
52
|
-
window.lnCore.registerLocaleFallback = registerLocaleFallback;
|
|
53
|
-
window.lnCore.getLocaleFallback = getLocaleFallback;
|
|
54
|
-
window.lnCore.ensureLocaleObserver = ensureLocaleObserver;
|
|
55
|
-
}
|
|
1
|
+
import { dispatch } from './events.js';
|
|
2
|
+
import { guardBody } from './dom.js';
|
|
3
|
+
|
|
4
|
+
// ─── Locale Detection ─────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
export function getLocale(el) {
|
|
7
|
+
const langEl = el ? el.closest('[lang]') : null;
|
|
8
|
+
const raw = (langEl ? (langEl.getAttribute('lang') || langEl.lang) : null)
|
|
9
|
+
|| (typeof document !== 'undefined' && document.documentElement ? (document.documentElement.getAttribute('lang') || document.documentElement.lang) : null)
|
|
10
|
+
|| (typeof navigator !== 'undefined' ? navigator.language : null);
|
|
11
|
+
return raw ? raw.trim() : 'en-US';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// ─── Shared Locale-Change Broadcaster ─────────────────────
|
|
15
|
+
|
|
16
|
+
export function ensureLocaleObserver() {
|
|
17
|
+
if (typeof window === 'undefined') return;
|
|
18
|
+
window.lnCore = window.lnCore || {};
|
|
19
|
+
if (window.lnCore._localeObserverBound) return;
|
|
20
|
+
window.lnCore._localeObserverBound = true;
|
|
21
|
+
|
|
22
|
+
guardBody(function () {
|
|
23
|
+
const observer = new MutationObserver(function () {
|
|
24
|
+
dispatch(document, 'ln-core:locale-change', {});
|
|
25
|
+
});
|
|
26
|
+
observer.observe(document.documentElement, {
|
|
27
|
+
attributes: true,
|
|
28
|
+
attributeFilter: ['lang'],
|
|
29
|
+
subtree: true
|
|
30
|
+
});
|
|
31
|
+
}, 'ln-core');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ─── Locale Fallback Registry ───────────────────────────────
|
|
35
|
+
const _localeFallbacks = {};
|
|
36
|
+
|
|
37
|
+
export function registerLocaleFallback(langPrefix, dictionary) {
|
|
38
|
+
if (!langPrefix || typeof dictionary !== 'object') return;
|
|
39
|
+
const prefix = langPrefix.toLowerCase().split('-')[0];
|
|
40
|
+
_localeFallbacks[prefix] = dictionary;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getLocaleFallback(lang) {
|
|
44
|
+
if (!lang) return null;
|
|
45
|
+
const prefix = lang.toLowerCase().split('-')[0];
|
|
46
|
+
return _localeFallbacks[prefix] || null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
if (typeof window !== 'undefined') {
|
|
51
|
+
window.lnCore = window.lnCore || {};
|
|
52
|
+
window.lnCore.registerLocaleFallback = registerLocaleFallback;
|
|
53
|
+
window.lnCore.getLocaleFallback = getLocaleFallback;
|
|
54
|
+
window.lnCore.ensureLocaleObserver = ensureLocaleObserver;
|
|
55
|
+
}
|
|
@@ -1,78 +1,78 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Normalizes a search query string for matching.
|
|
3
|
-
* @param {unknown} value
|
|
4
|
-
* @returns {string}
|
|
5
|
-
*/
|
|
6
|
-
export function normalizeSearchTerm(value) {
|
|
7
|
-
return String(value || '').trim().toLowerCase();
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Splits query string into individual tokens for AND-based substring matching.
|
|
12
|
-
* @param {unknown} term
|
|
13
|
-
* @returns {string[]}
|
|
14
|
-
*/
|
|
15
|
-
export function tokenizeSearchQuery(term) {
|
|
16
|
-
const normalized = normalizeSearchTerm(term);
|
|
17
|
-
if (!normalized) return [];
|
|
18
|
-
return normalized.split(/\s+/).filter(Boolean);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Parses comma-separated field selectors forwarded to consumers.
|
|
23
|
-
* @param {unknown} rawFields
|
|
24
|
-
* @returns {string[]|null}
|
|
25
|
-
*/
|
|
26
|
-
export function parseSearchFields(rawFields) {
|
|
27
|
-
if (rawFields === null || rawFields === undefined) return null;
|
|
28
|
-
const fields = String(rawFields)
|
|
29
|
-
.split(',')
|
|
30
|
-
.map(f => f.trim())
|
|
31
|
-
.filter(Boolean);
|
|
32
|
-
return fields.length ? fields : null;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Checks if text contains all tokens (AND-matching, order-independent).
|
|
37
|
-
* @param {string} text
|
|
38
|
-
* @param {string[]} tokens
|
|
39
|
-
* @returns {boolean}
|
|
40
|
-
*/
|
|
41
|
-
export function matchesSearchTokens(text, tokens) {
|
|
42
|
-
if (!tokens || tokens.length === 0) return true;
|
|
43
|
-
if (!text) return false;
|
|
44
|
-
const lower = String(text).toLowerCase();
|
|
45
|
-
for (let i = 0; i < tokens.length; i++) {
|
|
46
|
-
if (lower.indexOf(tokens[i]) === -1) return false;
|
|
47
|
-
}
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Collapses collected text parts into normalized search index text.
|
|
53
|
-
* @param {string[]} parts
|
|
54
|
-
* @returns {string}
|
|
55
|
-
*/
|
|
56
|
-
export function collapseSearchParts(parts) {
|
|
57
|
-
if (!parts || parts.length === 0) return '';
|
|
58
|
-
return parts.join(' ').replace(/\s+/g, ' ').trim().toLowerCase();
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Evaluates whether a cell or attribute value matches any of the filter's values (OR logic).
|
|
63
|
-
* @param {unknown} value
|
|
64
|
-
* @param {string[]} filterValues
|
|
65
|
-
* @returns {boolean}
|
|
66
|
-
*/
|
|
67
|
-
export function matchesFilterValues(value, filterValues) {
|
|
68
|
-
if (!filterValues || filterValues.length === 0) return true;
|
|
69
|
-
if (value === null || value === undefined) return false;
|
|
70
|
-
|
|
71
|
-
const lowerVal = String(value).trim().toLowerCase();
|
|
72
|
-
for (let i = 0; i < filterValues.length; i++) {
|
|
73
|
-
if (String(filterValues[i]).trim().toLowerCase() === lowerVal) {
|
|
74
|
-
return true;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes a search query string for matching.
|
|
3
|
+
* @param {unknown} value
|
|
4
|
+
* @returns {string}
|
|
5
|
+
*/
|
|
6
|
+
export function normalizeSearchTerm(value) {
|
|
7
|
+
return String(value || '').trim().toLowerCase();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Splits query string into individual tokens for AND-based substring matching.
|
|
12
|
+
* @param {unknown} term
|
|
13
|
+
* @returns {string[]}
|
|
14
|
+
*/
|
|
15
|
+
export function tokenizeSearchQuery(term) {
|
|
16
|
+
const normalized = normalizeSearchTerm(term);
|
|
17
|
+
if (!normalized) return [];
|
|
18
|
+
return normalized.split(/\s+/).filter(Boolean);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parses comma-separated field selectors forwarded to consumers.
|
|
23
|
+
* @param {unknown} rawFields
|
|
24
|
+
* @returns {string[]|null}
|
|
25
|
+
*/
|
|
26
|
+
export function parseSearchFields(rawFields) {
|
|
27
|
+
if (rawFields === null || rawFields === undefined) return null;
|
|
28
|
+
const fields = String(rawFields)
|
|
29
|
+
.split(',')
|
|
30
|
+
.map(f => f.trim())
|
|
31
|
+
.filter(Boolean);
|
|
32
|
+
return fields.length ? fields : null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Checks if text contains all tokens (AND-matching, order-independent).
|
|
37
|
+
* @param {string} text
|
|
38
|
+
* @param {string[]} tokens
|
|
39
|
+
* @returns {boolean}
|
|
40
|
+
*/
|
|
41
|
+
export function matchesSearchTokens(text, tokens) {
|
|
42
|
+
if (!tokens || tokens.length === 0) return true;
|
|
43
|
+
if (!text) return false;
|
|
44
|
+
const lower = String(text).toLowerCase();
|
|
45
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
46
|
+
if (lower.indexOf(tokens[i]) === -1) return false;
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Collapses collected text parts into normalized search index text.
|
|
53
|
+
* @param {string[]} parts
|
|
54
|
+
* @returns {string}
|
|
55
|
+
*/
|
|
56
|
+
export function collapseSearchParts(parts) {
|
|
57
|
+
if (!parts || parts.length === 0) return '';
|
|
58
|
+
return parts.join(' ').replace(/\s+/g, ' ').trim().toLowerCase();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Evaluates whether a cell or attribute value matches any of the filter's values (OR logic).
|
|
63
|
+
* @param {unknown} value
|
|
64
|
+
* @param {string[]} filterValues
|
|
65
|
+
* @returns {boolean}
|
|
66
|
+
*/
|
|
67
|
+
export function matchesFilterValues(value, filterValues) {
|
|
68
|
+
if (!filterValues || filterValues.length === 0) return true;
|
|
69
|
+
if (value === null || value === undefined) return false;
|
|
70
|
+
|
|
71
|
+
const lowerVal = String(value).trim().toLowerCase();
|
|
72
|
+
for (let i = 0; i < filterValues.length; i++) {
|
|
73
|
+
if (String(filterValues[i]).trim().toLowerCase() === lowerVal) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
@@ -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
|
+
}
|