@mk-kit/ui 0.48.0 → 0.50.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.
- package/README.md +4 -2
- package/fesm2022/mk-kit-ui-core.mjs +9 -380
- package/fesm2022/mk-kit-ui-core.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-data.mjs +6 -842
- package/fesm2022/mk-kit-ui-data.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-datetime.mjs +4 -247
- package/fesm2022/mk-kit-ui-datetime.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-embed.mjs +24 -5
- package/fesm2022/mk-kit-ui-embed.mjs.map +1 -1
- package/package.json +2 -1
- package/types/mk-kit-ui-core.d.ts +7 -162
- package/types/mk-kit-ui-data.d.ts +6 -173
- package/types/mk-kit-ui-datetime.d.ts +5 -77
- package/types/mk-kit-ui-embed.d.ts +19 -2
package/README.md
CHANGED
|
@@ -7,8 +7,10 @@ date & time pickers, editors, kanban, overlays, an app shell — written for
|
|
|
7
7
|
Angular 22 with signals and `OnPush` from day one. Every visual value is a
|
|
8
8
|
`--mk-*` CSS custom property; light and dark ship out of the box; WCAG 2.1 AA
|
|
9
9
|
is the target, not the marketing. MIT licensed. No third-party runtime
|
|
10
|
-
dependencies — only Angular and the sibling `@mk-kit/
|
|
11
|
-
|
|
10
|
+
dependencies — only Angular and the sibling packages `@mk-kit/core` (the
|
|
11
|
+
framework-free engines: diff, markdown, anchored positioning, query, dates)
|
|
12
|
+
and `@mk-kit/validators` (the identifier checks) — both zero-dependency and
|
|
13
|
+
tree-shakeable.
|
|
12
14
|
|
|
13
15
|
- **Docs & live demos:** <https://mk-kit.dev>
|
|
14
16
|
- **API reference:** <https://mk-kit.dev/api> — also as [api.json](https://mk-kit.dev/api.json), [llms.txt](https://mk-kit.dev/llms.txt) and [llms-full.txt](https://mk-kit.dev/llms-full.txt) for tools and AI assistants
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { inject, DOCUMENT, DestroyRef, PLATFORM_ID, signal, computed, effect, Injectable, InjectionToken, ApplicationRef, EnvironmentInjector, createComponent, Injector, ElementRef, input, output, Directive, untracked } from '@angular/core';
|
|
3
3
|
import { isPlatformBrowser } from '@angular/common';
|
|
4
|
+
import { mkComputeAnchoredPosition } from '@mk-kit/core';
|
|
5
|
+
export { MK_QUERY_OPERATORS, MK_QUERY_TEXT_EN, MK_QUERY_UNARY, mkComputeAnchoredPosition, mkCreateQueryGroup, mkCreateQueryRule, mkHighlight, mkHighlightJson, mkIsQueryGroup, mkQueryCompact, mkQueryIsEmpty, mkQueryOperatorLabel, mkQueryOperatorsFor, mkQueryRuleCount, mkQueryRuleIsComplete, mkQueryRuleMatches, mkQueryToPredicate, mkQueryToText, mkUniqueId } from '@mk-kit/core';
|
|
4
6
|
import { Observable } from 'rxjs';
|
|
5
7
|
import { FORM_FIELD } from '@angular/forms/signals';
|
|
6
8
|
|
|
@@ -551,17 +553,7 @@ class MkFocusTrap {
|
|
|
551
553
|
}
|
|
552
554
|
}
|
|
553
555
|
|
|
554
|
-
|
|
555
|
-
/**
|
|
556
|
-
* Generates a stable, unique DOM id for wiring `aria-*` relationships
|
|
557
|
-
* (labels, descriptions, controls). Prefer this over `Math.random()` so
|
|
558
|
-
* ids are deterministic within a render and SSR-safe.
|
|
559
|
-
*
|
|
560
|
-
* @param prefix short semantic prefix, e.g. `mk-input`.
|
|
561
|
-
*/
|
|
562
|
-
function mkUniqueId(prefix = 'mk') {
|
|
563
|
-
return `${prefix}-${++counter}`;
|
|
564
|
-
}
|
|
556
|
+
/** Delegates to `@mk-kit/core` — the exported name is unchanged. */
|
|
565
557
|
|
|
566
558
|
/** Injection token exposing the data passed to an overlay component. */
|
|
567
559
|
const MK_OVERLAY_DATA = new InjectionToken('MK_OVERLAY_DATA');
|
|
@@ -943,88 +935,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
943
935
|
* dropdown is unusable; at that point overflowing beats collapsing.
|
|
944
936
|
*/
|
|
945
937
|
const MIN_SIZE_CAP = 120;
|
|
946
|
-
function sideOf(placement) {
|
|
947
|
-
if (placement.startsWith('top'))
|
|
948
|
-
return 'top';
|
|
949
|
-
if (placement.startsWith('bottom'))
|
|
950
|
-
return 'bottom';
|
|
951
|
-
return placement.startsWith('left') ? 'left' : 'right';
|
|
952
|
-
}
|
|
953
|
-
function alignOf(placement) {
|
|
954
|
-
if (placement.endsWith('-start'))
|
|
955
|
-
return 'start';
|
|
956
|
-
if (placement.endsWith('-end'))
|
|
957
|
-
return 'end';
|
|
958
|
-
return 'center';
|
|
959
|
-
}
|
|
960
|
-
/**
|
|
961
|
-
* Pure viewport-positioning maths shared by every anchored overlay
|
|
962
|
-
* ({@link MkAnchoredPanel} and the tooltip). Given the anchor rect, the panel
|
|
963
|
-
* size and the viewport size, returns the top/left for a `position: fixed`
|
|
964
|
-
* panel — flipping to the opposite side when it would overflow and clamping
|
|
965
|
-
* back inside the viewport.
|
|
966
|
-
*/
|
|
967
|
-
function mkComputeAnchoredPosition(anchor, panel, viewport, opts) {
|
|
968
|
-
const { gap, flip, clamp } = opts;
|
|
969
|
-
let side = sideOf(opts.placement);
|
|
970
|
-
const align = alignOf(opts.placement);
|
|
971
|
-
const { width: w, height: h } = panel;
|
|
972
|
-
const { width: vw, height: vh } = viewport;
|
|
973
|
-
// Vertical flip for top/bottom placements.
|
|
974
|
-
if (flip && (side === 'top' || side === 'bottom')) {
|
|
975
|
-
const fitsBelow = anchor.bottom + gap + h <= vh;
|
|
976
|
-
const fitsAbove = anchor.top - gap - h >= 0;
|
|
977
|
-
if (side === 'bottom' && !fitsBelow && fitsAbove)
|
|
978
|
-
side = 'top';
|
|
979
|
-
else if (side === 'top' && !fitsAbove && fitsBelow)
|
|
980
|
-
side = 'bottom';
|
|
981
|
-
}
|
|
982
|
-
// Horizontal flip for left/right placements.
|
|
983
|
-
if (flip && (side === 'left' || side === 'right')) {
|
|
984
|
-
const fitsRight = anchor.right + gap + w <= vw;
|
|
985
|
-
const fitsLeft = anchor.left - gap - w >= 0;
|
|
986
|
-
if (side === 'right' && !fitsRight && fitsLeft)
|
|
987
|
-
side = 'left';
|
|
988
|
-
else if (side === 'left' && !fitsLeft && fitsRight)
|
|
989
|
-
side = 'right';
|
|
990
|
-
}
|
|
991
|
-
let top = 0;
|
|
992
|
-
let left = 0;
|
|
993
|
-
if (side === 'bottom')
|
|
994
|
-
top = anchor.bottom + gap;
|
|
995
|
-
else if (side === 'top')
|
|
996
|
-
top = anchor.top - h - gap;
|
|
997
|
-
else if (side === 'right')
|
|
998
|
-
left = anchor.right + gap;
|
|
999
|
-
else
|
|
1000
|
-
left = anchor.left - w - gap;
|
|
1001
|
-
if (side === 'top' || side === 'bottom') {
|
|
1002
|
-
// Cross axis is horizontal; in RTL, inline-start is the anchor's right edge.
|
|
1003
|
-
const startLeft = opts.rtl ? anchor.right - w : anchor.left;
|
|
1004
|
-
const endLeft = opts.rtl ? anchor.left : anchor.right - w;
|
|
1005
|
-
if (align === 'start')
|
|
1006
|
-
left = startLeft;
|
|
1007
|
-
else if (align === 'end')
|
|
1008
|
-
left = endLeft;
|
|
1009
|
-
else
|
|
1010
|
-
left = anchor.left + anchor.width / 2 - w / 2;
|
|
1011
|
-
}
|
|
1012
|
-
else {
|
|
1013
|
-
// Left/right: the cross axis is vertical. `-start` tops the panel with the
|
|
1014
|
-
// anchor (a submenu beside its item), `-end` bottoms it, else centre.
|
|
1015
|
-
if (align === 'start')
|
|
1016
|
-
top = anchor.top;
|
|
1017
|
-
else if (align === 'end')
|
|
1018
|
-
top = anchor.bottom - h;
|
|
1019
|
-
else
|
|
1020
|
-
top = anchor.top + anchor.height / 2 - h / 2;
|
|
1021
|
-
}
|
|
1022
|
-
if (clamp) {
|
|
1023
|
-
left = Math.max(gap, Math.min(left, vw - w - gap));
|
|
1024
|
-
top = Math.max(gap, Math.min(top, vh - h - gap));
|
|
1025
|
-
}
|
|
1026
|
-
return { top: Math.round(top), left: Math.round(left), placement: `${side}${align === 'center' ? '' : `-${align}`}` };
|
|
1027
|
-
}
|
|
1028
938
|
/**
|
|
1029
939
|
* Anchored-overlay directive. Apply it to a floating panel element (a dropdown
|
|
1030
940
|
* list, calendar, menu, …) that is rendered inside its component's own template
|
|
@@ -1817,45 +1727,7 @@ function provideMkI18n(overrides, base = MK_DEFAULT_I18N) {
|
|
|
1817
1727
|
class MkFieldContext {
|
|
1818
1728
|
}
|
|
1819
1729
|
|
|
1820
|
-
/**
|
|
1821
|
-
* Dependency-free syntax highlighting for {@link MkCodeEditor}. Each function
|
|
1822
|
-
* takes source text and returns an HTML string of `<span class="mk-tok-…">`
|
|
1823
|
-
* tokens. Input is HTML-escaped first, so the result is safe to render.
|
|
1824
|
-
*/
|
|
1825
|
-
/** Escape the three characters that could break out of HTML text content. */
|
|
1826
|
-
function mkEscapeHtml(src) {
|
|
1827
|
-
return src.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1828
|
-
}
|
|
1829
|
-
/**
|
|
1830
|
-
* Highlight a JSON document. Recognises object keys, strings, numbers, the
|
|
1831
|
-
* `true`/`false`/`null` literals and structural punctuation. Invalid JSON is
|
|
1832
|
-
* still highlighted token-by-token (the editor validates separately).
|
|
1833
|
-
*/
|
|
1834
|
-
function mkHighlightJson(src) {
|
|
1835
|
-
const esc = mkEscapeHtml(src);
|
|
1836
|
-
return esc.replace(
|
|
1837
|
-
// 1: string (+ optional following colon → key) · 2: colon
|
|
1838
|
-
// 3: keyword · 4: number · 5: punctuation
|
|
1839
|
-
/("(?:\\.|[^"\\])*")(\s*:)?|\b(true|false|null)\b|(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)|([{}\[\],:])/g, (match, str, colon, keyword, num, punc) => {
|
|
1840
|
-
if (str !== undefined) {
|
|
1841
|
-
if (colon !== undefined) {
|
|
1842
|
-
return `<span class="mk-tok-key">${str}</span><span class="mk-tok-punc">${colon}</span>`;
|
|
1843
|
-
}
|
|
1844
|
-
return `<span class="mk-tok-str">${str}</span>`;
|
|
1845
|
-
}
|
|
1846
|
-
if (keyword !== undefined)
|
|
1847
|
-
return `<span class="mk-tok-kw">${keyword}</span>`;
|
|
1848
|
-
if (num !== undefined)
|
|
1849
|
-
return `<span class="mk-tok-num">${num}</span>`;
|
|
1850
|
-
if (punc !== undefined)
|
|
1851
|
-
return `<span class="mk-tok-punc">${punc}</span>`;
|
|
1852
|
-
return match;
|
|
1853
|
-
});
|
|
1854
|
-
}
|
|
1855
|
-
/** Highlight `src` for `language`, falling back to escaped plain text. */
|
|
1856
|
-
function mkHighlight(src, language) {
|
|
1857
|
-
return language === 'json' ? mkHighlightJson(src) : mkEscapeHtml(src);
|
|
1858
|
-
}
|
|
1730
|
+
/** Delegates to `@mk-kit/core` — the exported names are unchanged. */
|
|
1859
1731
|
|
|
1860
1732
|
/**
|
|
1861
1733
|
* Re-validates the bound form control whenever a validator's inputs change.
|
|
@@ -2066,259 +1938,16 @@ function mkInjectFieldTouched() {
|
|
|
2066
1938
|
});
|
|
2067
1939
|
}
|
|
2068
1940
|
|
|
2069
|
-
/** Operators offered per field type, in menu order. */
|
|
2070
|
-
const MK_QUERY_OPERATORS = {
|
|
2071
|
-
string: ['contains', 'notContains', 'eq', 'neq', 'startsWith', 'endsWith', 'empty', 'notEmpty'],
|
|
2072
|
-
number: ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'between', 'empty', 'notEmpty'],
|
|
2073
|
-
boolean: ['eq'],
|
|
2074
|
-
date: ['eq', 'neq', 'before', 'after', 'between', 'empty', 'notEmpty'],
|
|
2075
|
-
select: ['eq', 'neq', 'in', 'notIn', 'empty', 'notEmpty'],
|
|
2076
|
-
};
|
|
2077
|
-
/** Operators that take no value. */
|
|
2078
|
-
const MK_QUERY_UNARY = new Set(['empty', 'notEmpty']);
|
|
2079
|
-
/** True for a group node (as opposed to a rule). */
|
|
2080
|
-
function mkIsQueryGroup(node) {
|
|
2081
|
-
return Array.isArray(node.rules);
|
|
2082
|
-
}
|
|
2083
|
-
/** A fresh empty group. */
|
|
2084
|
-
function mkCreateQueryGroup(init = {}) {
|
|
2085
|
-
return { id: mkUniqueId('mkq'), combinator: 'and', rules: [], ...init };
|
|
2086
|
-
}
|
|
2087
|
-
/** A fresh rule on `field`, using its first operator. */
|
|
2088
|
-
function mkCreateQueryRule(field) {
|
|
2089
|
-
const ops = mkQueryOperatorsFor(field);
|
|
2090
|
-
return { id: mkUniqueId('mkq'), field: field.key, operator: ops[0] ?? 'eq' };
|
|
2091
|
-
}
|
|
2092
|
-
/** Operators a field offers (its own list, else the defaults for its type). */
|
|
2093
|
-
function mkQueryOperatorsFor(field) {
|
|
2094
|
-
if (!field)
|
|
2095
|
-
return MK_QUERY_OPERATORS.string;
|
|
2096
|
-
return field.operators ?? MK_QUERY_OPERATORS[field.type ?? 'string'];
|
|
2097
|
-
}
|
|
2098
|
-
/** True when the tree holds no rule at all (empty groups only). */
|
|
2099
|
-
function mkQueryIsEmpty(group) {
|
|
2100
|
-
return group.rules.every((n) => (mkIsQueryGroup(n) ? mkQueryIsEmpty(n) : false));
|
|
2101
|
-
}
|
|
2102
|
-
/** Number of rules in the tree. */
|
|
2103
|
-
function mkQueryRuleCount(group) {
|
|
2104
|
-
return group.rules.reduce((n, r) => n + (mkIsQueryGroup(r) ? mkQueryRuleCount(r) : 1), 0);
|
|
2105
|
-
}
|
|
2106
|
-
/** Drop empty groups and rules that still need a value, so the API gets only complete conditions. */
|
|
2107
|
-
function mkQueryCompact(group) {
|
|
2108
|
-
const rules = group.rules
|
|
2109
|
-
.map((n) => (mkIsQueryGroup(n) ? mkQueryCompact(n) : n))
|
|
2110
|
-
.filter((n) => (mkIsQueryGroup(n) ? n.rules.length > 0 : mkQueryRuleIsComplete(n)));
|
|
2111
|
-
return { ...group, rules };
|
|
2112
|
-
}
|
|
2113
|
-
/** A rule is complete when its operator needs no value or has one. */
|
|
2114
|
-
function mkQueryRuleIsComplete(rule) {
|
|
2115
|
-
if (MK_QUERY_UNARY.has(rule.operator))
|
|
2116
|
-
return true;
|
|
2117
|
-
const v = rule.value;
|
|
2118
|
-
if (v == null || v === '')
|
|
2119
|
-
return false;
|
|
2120
|
-
if (rule.operator === 'between')
|
|
2121
|
-
return Array.isArray(v) && v.length === 2 && v.every((x) => x != null && x !== '');
|
|
2122
|
-
if (rule.operator === 'in' || rule.operator === 'notIn')
|
|
2123
|
-
return Array.isArray(v) && v.length > 0;
|
|
2124
|
-
return true;
|
|
2125
|
-
}
|
|
2126
|
-
/** Sortable / comparable primitive of a raw value for a field type. */
|
|
2127
|
-
function normalise(value, type) {
|
|
2128
|
-
if (value == null || value === '')
|
|
2129
|
-
return null;
|
|
2130
|
-
switch (type) {
|
|
2131
|
-
case 'number': {
|
|
2132
|
-
const n = typeof value === 'number' ? value : Number(value);
|
|
2133
|
-
return Number.isNaN(n) ? null : n;
|
|
2134
|
-
}
|
|
2135
|
-
case 'date': {
|
|
2136
|
-
const d = value instanceof Date ? value : new Date(value);
|
|
2137
|
-
return Number.isNaN(d.getTime()) ? null : d.getTime();
|
|
2138
|
-
}
|
|
2139
|
-
case 'boolean':
|
|
2140
|
-
return value === true || value === 'true';
|
|
2141
|
-
default:
|
|
2142
|
-
return typeof value === 'string' ? value : String(value);
|
|
2143
|
-
}
|
|
2144
|
-
}
|
|
2145
|
-
/** Day precision for date equality: two timestamps on the same local day are equal. */
|
|
2146
|
-
function sameDay(a, b) {
|
|
2147
|
-
const da = new Date(a);
|
|
2148
|
-
const db = new Date(b);
|
|
2149
|
-
return da.getFullYear() === db.getFullYear() && da.getMonth() === db.getMonth() && da.getDate() === db.getDate();
|
|
2150
|
-
}
|
|
2151
|
-
function inferType(rule) {
|
|
2152
|
-
const v = Array.isArray(rule.value) ? rule.value[0] : rule.value;
|
|
2153
|
-
if (typeof v === 'number')
|
|
2154
|
-
return 'number';
|
|
2155
|
-
if (typeof v === 'boolean')
|
|
2156
|
-
return 'boolean';
|
|
2157
|
-
if (v instanceof Date)
|
|
2158
|
-
return 'date';
|
|
2159
|
-
return 'string';
|
|
2160
|
-
}
|
|
2161
|
-
/** Evaluate one rule against a row. */
|
|
2162
|
-
function mkQueryRuleMatches(rule, row, field) {
|
|
2163
|
-
const type = field?.type ?? inferType(rule);
|
|
2164
|
-
const raw = row[rule.field];
|
|
2165
|
-
const isEmpty = raw == null || raw === '' || (Array.isArray(raw) && raw.length === 0);
|
|
2166
|
-
if (rule.operator === 'empty')
|
|
2167
|
-
return isEmpty;
|
|
2168
|
-
if (rule.operator === 'notEmpty')
|
|
2169
|
-
return !isEmpty;
|
|
2170
|
-
if (!mkQueryRuleIsComplete(rule))
|
|
2171
|
-
return true; // an unfinished rule filters nothing
|
|
2172
|
-
if (isEmpty)
|
|
2173
|
-
return false;
|
|
2174
|
-
const cmpType = type === 'select' ? 'string' : type;
|
|
2175
|
-
const left = normalise(raw, cmpType);
|
|
2176
|
-
const collator = new Intl.Collator(undefined, { sensitivity: 'base' });
|
|
2177
|
-
const eqText = (a, b) => collator.compare(String(a), String(b)) === 0;
|
|
2178
|
-
switch (rule.operator) {
|
|
2179
|
-
case 'eq':
|
|
2180
|
-
case 'neq': {
|
|
2181
|
-
const right = normalise(rule.value, cmpType);
|
|
2182
|
-
let equal;
|
|
2183
|
-
if (left == null || right == null)
|
|
2184
|
-
equal = left === right;
|
|
2185
|
-
else if (cmpType === 'date')
|
|
2186
|
-
equal = sameDay(left, right);
|
|
2187
|
-
else if (cmpType === 'string')
|
|
2188
|
-
equal = eqText(left, right);
|
|
2189
|
-
else
|
|
2190
|
-
equal = left === right;
|
|
2191
|
-
return rule.operator === 'eq' ? equal : !equal;
|
|
2192
|
-
}
|
|
2193
|
-
case 'contains':
|
|
2194
|
-
case 'notContains': {
|
|
2195
|
-
const has = String(raw).toLocaleLowerCase().includes(String(rule.value).toLocaleLowerCase());
|
|
2196
|
-
return rule.operator === 'contains' ? has : !has;
|
|
2197
|
-
}
|
|
2198
|
-
case 'startsWith':
|
|
2199
|
-
return String(raw).toLocaleLowerCase().startsWith(String(rule.value).toLocaleLowerCase());
|
|
2200
|
-
case 'endsWith':
|
|
2201
|
-
return String(raw).toLocaleLowerCase().endsWith(String(rule.value).toLocaleLowerCase());
|
|
2202
|
-
case 'gt':
|
|
2203
|
-
case 'gte':
|
|
2204
|
-
case 'lt':
|
|
2205
|
-
case 'lte':
|
|
2206
|
-
case 'before':
|
|
2207
|
-
case 'after': {
|
|
2208
|
-
const right = normalise(rule.value, cmpType);
|
|
2209
|
-
if (left == null || right == null || typeof left === 'boolean' || typeof right === 'boolean')
|
|
2210
|
-
return false;
|
|
2211
|
-
const c = typeof left === 'number' && typeof right === 'number' ? left - right : collator.compare(String(left), String(right));
|
|
2212
|
-
switch (rule.operator) {
|
|
2213
|
-
case 'gt':
|
|
2214
|
-
case 'after':
|
|
2215
|
-
return c > 0;
|
|
2216
|
-
case 'gte':
|
|
2217
|
-
return c >= 0;
|
|
2218
|
-
case 'lt':
|
|
2219
|
-
case 'before':
|
|
2220
|
-
return c < 0;
|
|
2221
|
-
default:
|
|
2222
|
-
return c <= 0;
|
|
2223
|
-
}
|
|
2224
|
-
}
|
|
2225
|
-
case 'between': {
|
|
2226
|
-
const [a, b] = rule.value;
|
|
2227
|
-
const lo = normalise(a, cmpType);
|
|
2228
|
-
const hi = normalise(b, cmpType);
|
|
2229
|
-
if (left == null || lo == null || hi == null)
|
|
2230
|
-
return false;
|
|
2231
|
-
if (typeof left === 'number' && typeof lo === 'number' && typeof hi === 'number')
|
|
2232
|
-
return left >= lo && left <= hi;
|
|
2233
|
-
return collator.compare(String(left), String(lo)) >= 0 && collator.compare(String(left), String(hi)) <= 0;
|
|
2234
|
-
}
|
|
2235
|
-
case 'in':
|
|
2236
|
-
case 'notIn': {
|
|
2237
|
-
const set = rule.value;
|
|
2238
|
-
const hit = Array.isArray(raw)
|
|
2239
|
-
? raw.some((r) => set.some((s) => eqText(r, s)))
|
|
2240
|
-
: set.some((s) => (cmpType === 'string' ? eqText(raw, s) : normalise(raw, cmpType) === normalise(s, cmpType)));
|
|
2241
|
-
return rule.operator === 'in' ? hit : !hit;
|
|
2242
|
-
}
|
|
2243
|
-
default:
|
|
2244
|
-
return true;
|
|
2245
|
-
}
|
|
2246
|
-
}
|
|
2247
|
-
/**
|
|
2248
|
-
* Compile a query into a row predicate for client-side filtering
|
|
2249
|
-
* (`rows.filter(mkQueryToPredicate(query, fields))`). Unfinished rules are
|
|
2250
|
-
* ignored, so a half-edited query never blanks the table.
|
|
2251
|
-
*/
|
|
2252
|
-
function mkQueryToPredicate(group, fields = []) {
|
|
2253
|
-
const byKey = new Map(fields.map((f) => [f.key, f]));
|
|
2254
|
-
const evalGroup = (g, row) => {
|
|
2255
|
-
const active = g.rules.filter((n) => (mkIsQueryGroup(n) ? !mkQueryIsEmpty(n) : mkQueryRuleIsComplete(n)));
|
|
2256
|
-
let result;
|
|
2257
|
-
if (!active.length)
|
|
2258
|
-
result = true;
|
|
2259
|
-
else if (g.combinator === 'or')
|
|
2260
|
-
result = active.some((n) => evalNode(n, row));
|
|
2261
|
-
else
|
|
2262
|
-
result = active.every((n) => evalNode(n, row));
|
|
2263
|
-
return g.not ? !result : result;
|
|
2264
|
-
};
|
|
2265
|
-
const evalNode = (n, row) => mkIsQueryGroup(n) ? evalGroup(n, row) : mkQueryRuleMatches(n, row, byKey.get(n.field));
|
|
2266
|
-
return (row) => evalGroup(group, row);
|
|
2267
|
-
}
|
|
2268
|
-
/** Localised label of an operator. */
|
|
2269
|
-
function mkQueryOperatorLabel(op, i18n = MK_DEFAULT_I18N) {
|
|
2270
|
-
const key = `queryOp${op[0].toUpperCase()}${op.slice(1)}`;
|
|
2271
|
-
return i18n[key] ?? op;
|
|
2272
|
-
}
|
|
2273
1941
|
/**
|
|
2274
|
-
*
|
|
2275
|
-
* `
|
|
1942
|
+
* Delegates to `@mk-kit/core` — the exported names and behaviour are
|
|
1943
|
+
* unchanged. `mkQueryOperatorLabel` / `mkQueryToText` accept the full
|
|
1944
|
+
* `MkI18nStrings` map as before: it is a structural superset of the
|
|
1945
|
+
* package's `MkQueryTextStrings`.
|
|
2276
1946
|
*/
|
|
2277
|
-
function mkQueryToText(group, fields = [], i18n = MK_DEFAULT_I18N) {
|
|
2278
|
-
const byKey = new Map(fields.map((f) => [f.key, f]));
|
|
2279
|
-
const fmt = (v, field) => {
|
|
2280
|
-
if (field?.type === 'select') {
|
|
2281
|
-
const opt = field.options?.find((o) => o.value === v);
|
|
2282
|
-
if (opt)
|
|
2283
|
-
return opt.label;
|
|
2284
|
-
}
|
|
2285
|
-
if (field?.type === 'boolean' || typeof v === 'boolean')
|
|
2286
|
-
return v === true || v === 'true' ? i18n.queryTrue : i18n.queryFalse;
|
|
2287
|
-
if (typeof v === 'number')
|
|
2288
|
-
return String(v);
|
|
2289
|
-
if (v instanceof Date)
|
|
2290
|
-
return v.toISOString().slice(0, 10);
|
|
2291
|
-
return `"${String(v)}"`;
|
|
2292
|
-
};
|
|
2293
|
-
const rule = (r) => {
|
|
2294
|
-
const f = byKey.get(r.field);
|
|
2295
|
-
const label = f?.label ?? r.field;
|
|
2296
|
-
const op = mkQueryOperatorLabel(r.operator, i18n);
|
|
2297
|
-
if (MK_QUERY_UNARY.has(r.operator))
|
|
2298
|
-
return `${label} ${op}`;
|
|
2299
|
-
if (r.operator === 'between' && Array.isArray(r.value))
|
|
2300
|
-
return `${label} ${op} ${fmt(r.value[0], f)} – ${fmt(r.value[1], f)}`;
|
|
2301
|
-
if (Array.isArray(r.value))
|
|
2302
|
-
return `${label} ${op} ${r.value.map((v) => fmt(v, f)).join(', ')}`;
|
|
2303
|
-
return `${label} ${op} ${fmt(r.value, f)}`;
|
|
2304
|
-
};
|
|
2305
|
-
const grp = (g, top) => {
|
|
2306
|
-
const parts = g.rules
|
|
2307
|
-
.filter((n) => (mkIsQueryGroup(n) ? !mkQueryIsEmpty(n) : mkQueryRuleIsComplete(n)))
|
|
2308
|
-
.map((n) => (mkIsQueryGroup(n) ? grp(n, false) : rule(n)));
|
|
2309
|
-
if (!parts.length)
|
|
2310
|
-
return '';
|
|
2311
|
-
const joiner = g.combinator === 'or' ? i18n.queryOr : i18n.queryAnd;
|
|
2312
|
-
const body = parts.join(` ${joiner.toLocaleLowerCase()} `);
|
|
2313
|
-
const wrapped = top && !g.not ? body : parts.length > 1 || g.not ? `(${body})` : body;
|
|
2314
|
-
return g.not ? `${i18n.queryNot.toLocaleLowerCase()} ${wrapped}` : wrapped;
|
|
2315
|
-
};
|
|
2316
|
-
return grp(group, true);
|
|
2317
|
-
}
|
|
2318
1947
|
|
|
2319
1948
|
/**
|
|
2320
1949
|
* Generated bundle index. Do not edit.
|
|
2321
1950
|
*/
|
|
2322
1951
|
|
|
2323
|
-
export { MK_BREAKPOINTS, MK_DEFAULT_BREAKPOINTS, MK_DEFAULT_DATE_NAMES, MK_DEFAULT_I18N, MK_DEFAULT_VALIDATION, MK_I18N, MK_OVERLAY_DATA, MK_OVERLAY_ROOT,
|
|
1952
|
+
export { MK_BREAKPOINTS, MK_DEFAULT_BREAKPOINTS, MK_DEFAULT_DATE_NAMES, MK_DEFAULT_I18N, MK_DEFAULT_VALIDATION, MK_I18N, MK_OVERLAY_DATA, MK_OVERLAY_ROOT, MkAnchoredPanel, MkBreakpointService, MkFieldContext, MkFocusTrap, MkLiveAnnouncer, MkOverlayRef, MkOverlayService, MkThemeService, mkBodyLevelAncestor, mkFirstErrorMessage, mkGetFocusable, mkInjectFieldTouched, mkIsResponsive, mkMergeI18n, mkSignalErrorMessage, mkSignalErrorsToValidationErrors, mkValidatorChange, provideMkI18n };
|
|
2324
1953
|
//# sourceMappingURL=mk-kit-ui-core.mjs.map
|