cx 26.8.0 → 26.9.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/build/charts/Legend.d.ts +8 -1
- package/build/charts/Legend.d.ts.map +1 -1
- package/build/charts/Legend.js +8 -4
- package/build/jsx-dev-runtime.d.ts +2 -1
- package/build/jsx-dev-runtime.d.ts.map +1 -1
- package/build/svg/Svg.d.ts +9 -0
- package/build/svg/Svg.d.ts.map +1 -1
- package/build/svg/Svg.js +9 -1
- package/build/ui/index.d.ts +3 -2
- package/build/ui/index.d.ts.map +1 -1
- package/build/ui/index.js +1 -1
- package/build/util/DOM.d.ts.map +1 -1
- package/build/util/DOM.js +16 -4
- package/build/util/getActiveElement.d.ts +1 -1
- package/build/util/getActiveElement.d.ts.map +1 -1
- package/build/util/getActiveElement.js +14 -2
- package/build/widgets/form/TextArea.d.ts +4 -0
- package/build/widgets/form/TextArea.d.ts.map +1 -1
- package/build/widgets/form/TextArea.js +7 -1
- package/build/widgets/grid/Grid.d.ts +3 -2
- package/build/widgets/grid/Grid.d.ts.map +1 -1
- package/build/widgets/nav/MenuItem.js +2 -2
- package/build/widgets/overlay/Dropdown.d.ts +4 -4
- package/build/widgets/overlay/Dropdown.d.ts.map +1 -1
- package/build/widgets/overlay/Dropdown.js +82 -38
- package/build/widgets/overlay/Overlay.d.ts.map +1 -1
- package/build/widgets/overlay/Overlay.js +7 -3
- package/build/widgets/overlay/Window.d.ts.map +1 -1
- package/build/widgets/overlay/Window.js +2 -1
- package/dist/charts.js +10 -3
- package/dist/manifest.js +789 -789
- package/dist/svg.js +13 -0
- package/dist/util.js +31 -9
- package/dist/widgets.js +139 -47
- package/package.json +1 -1
- package/src/charts/Legend.tsx +22 -2
- package/src/jsx-dev-runtime.ts +2 -1
- package/src/svg/Svg.tsx +24 -1
- package/src/ui/Cx.tsx +505 -505
- package/src/ui/DataProxy.ts +55 -55
- package/src/ui/Rescope.ts +50 -50
- package/src/ui/exprHelpers.ts +96 -96
- package/src/ui/index.ts +3 -6
- package/src/util/DOM.ts +15 -4
- package/src/util/getActiveElement.ts +16 -2
- package/src/widgets/Sandbox.ts +104 -104
- package/src/widgets/form/ColorField.scss +112 -112
- package/src/widgets/form/DateTimeField.scss +111 -111
- package/src/widgets/form/LookupField.maps.scss +26 -26
- package/src/widgets/form/MonthField.scss +113 -113
- package/src/widgets/form/Select.scss +104 -104
- package/src/widgets/form/TextArea.tsx +10 -1
- package/src/widgets/form/TextField.scss +66 -66
- package/src/widgets/grid/Grid.tsx +3 -2
- package/src/widgets/index.ts +41 -41
- package/src/widgets/nav/MenuItem.tsx +2 -2
- package/src/widgets/overlay/Dropdown.tsx +87 -27
- package/src/widgets/overlay/Overlay.tsx +5 -3
- package/src/widgets/overlay/Window.tsx +2 -1
package/src/ui/DataProxy.ts
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
import { AccessorChain } from "../data";
|
|
2
|
-
import { NestedDataView } from "../data/NestedDataView";
|
|
3
|
-
import { StructuredProp, WritableProp } from "./Prop";
|
|
4
|
-
import { PureContainerBase, PureContainerConfig } from "./PureContainer";
|
|
5
|
-
import { StructuredInstanceDataAccessor } from "./StructuredInstanceDataAccessor";
|
|
6
|
-
|
|
7
|
-
export interface DataProxyConfig extends PureContainerConfig {
|
|
8
|
-
/** Data object with computed values to be exposed in the local store. */
|
|
9
|
-
data?: StructuredProp;
|
|
10
|
-
|
|
11
|
-
/** Binding to a value to be exposed under the `alias` name. */
|
|
12
|
-
value?: WritableProp<any>;
|
|
13
|
-
|
|
14
|
-
/** Alias name under which `value` is exposed in the local store. */
|
|
15
|
-
alias?: string | AccessorChain<any>;
|
|
16
|
-
|
|
17
|
-
/** Indicate that parent store data should not be mutated. */
|
|
18
|
-
immutable?: boolean;
|
|
19
|
-
|
|
20
|
-
/** Indicate that local store data should not be mutated. */
|
|
21
|
-
sealed?: boolean;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export class DataProxy extends PureContainerBase<DataProxyConfig> {
|
|
25
|
-
declare data?: any;
|
|
26
|
-
declare alias?: string;
|
|
27
|
-
declare value?: any;
|
|
28
|
-
declare immutable: boolean;
|
|
29
|
-
declare sealed: boolean;
|
|
30
|
-
|
|
31
|
-
init() {
|
|
32
|
-
if (!this.data) this.data = {};
|
|
33
|
-
|
|
34
|
-
if (this.alias) this.data[this.alias] = this.value;
|
|
35
|
-
|
|
36
|
-
super.init();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
initInstance(context: any, instance: any) {
|
|
40
|
-
instance.store = new NestedDataView({
|
|
41
|
-
store: instance.parentStore,
|
|
42
|
-
nestedData: new StructuredInstanceDataAccessor({ instance, data: this.data, useParentStore: true }),
|
|
43
|
-
immutable: this.immutable,
|
|
44
|
-
sealed: this.sealed,
|
|
45
|
-
});
|
|
46
|
-
super.initInstance(context, instance);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
applyParentStore(instance: any) {
|
|
50
|
-
instance.store.setStore(instance.parentStore);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
DataProxy.prototype.immutable = false;
|
|
55
|
-
DataProxy.prototype.sealed = false;
|
|
1
|
+
import { AccessorChain } from "../data";
|
|
2
|
+
import { NestedDataView } from "../data/NestedDataView";
|
|
3
|
+
import { StructuredProp, WritableProp } from "./Prop";
|
|
4
|
+
import { PureContainerBase, PureContainerConfig } from "./PureContainer";
|
|
5
|
+
import { StructuredInstanceDataAccessor } from "./StructuredInstanceDataAccessor";
|
|
6
|
+
|
|
7
|
+
export interface DataProxyConfig extends PureContainerConfig {
|
|
8
|
+
/** Data object with computed values to be exposed in the local store. */
|
|
9
|
+
data?: StructuredProp;
|
|
10
|
+
|
|
11
|
+
/** Binding to a value to be exposed under the `alias` name. */
|
|
12
|
+
value?: WritableProp<any>;
|
|
13
|
+
|
|
14
|
+
/** Alias name under which `value` is exposed in the local store. */
|
|
15
|
+
alias?: string | AccessorChain<any>;
|
|
16
|
+
|
|
17
|
+
/** Indicate that parent store data should not be mutated. */
|
|
18
|
+
immutable?: boolean;
|
|
19
|
+
|
|
20
|
+
/** Indicate that local store data should not be mutated. */
|
|
21
|
+
sealed?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class DataProxy extends PureContainerBase<DataProxyConfig> {
|
|
25
|
+
declare data?: any;
|
|
26
|
+
declare alias?: string;
|
|
27
|
+
declare value?: any;
|
|
28
|
+
declare immutable: boolean;
|
|
29
|
+
declare sealed: boolean;
|
|
30
|
+
|
|
31
|
+
init() {
|
|
32
|
+
if (!this.data) this.data = {};
|
|
33
|
+
|
|
34
|
+
if (this.alias) this.data[this.alias] = this.value;
|
|
35
|
+
|
|
36
|
+
super.init();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
initInstance(context: any, instance: any) {
|
|
40
|
+
instance.store = new NestedDataView({
|
|
41
|
+
store: instance.parentStore,
|
|
42
|
+
nestedData: new StructuredInstanceDataAccessor({ instance, data: this.data, useParentStore: true }),
|
|
43
|
+
immutable: this.immutable,
|
|
44
|
+
sealed: this.sealed,
|
|
45
|
+
});
|
|
46
|
+
super.initInstance(context, instance);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
applyParentStore(instance: any) {
|
|
50
|
+
instance.store.setStore(instance.parentStore);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
DataProxy.prototype.immutable = false;
|
|
55
|
+
DataProxy.prototype.sealed = false;
|
package/src/ui/Rescope.ts
CHANGED
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
import { Widget } from "./Widget";
|
|
2
|
-
import { PureContainerBase, PureContainerConfig } from "./PureContainer";
|
|
3
|
-
import { Binding } from "../data/Binding";
|
|
4
|
-
import { ZoomIntoPropertyView } from "../data/ZoomIntoPropertyView";
|
|
5
|
-
import { StructuredInstanceDataAccessor } from "./StructuredInstanceDataAccessor";
|
|
6
|
-
import { isObject } from "../util/isObject";
|
|
7
|
-
import { StructuredProp } from "./Prop";
|
|
8
|
-
import { AccessorChain } from "../data/createAccessorModelProxy";
|
|
9
|
-
|
|
10
|
-
export interface RescopeConfig extends PureContainerConfig {
|
|
11
|
-
bind: string | AccessorChain<any>;
|
|
12
|
-
rootName?: string | AccessorChain<any>;
|
|
13
|
-
rootAlias?: string | AccessorChain<any>;
|
|
14
|
-
data?: StructuredProp;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export class Rescope extends PureContainerBase<RescopeConfig> {
|
|
18
|
-
declare bind: string;
|
|
19
|
-
declare binding: any;
|
|
20
|
-
declare rootAlias?: string;
|
|
21
|
-
declare rootName: string;
|
|
22
|
-
declare data?: any;
|
|
23
|
-
|
|
24
|
-
init() {
|
|
25
|
-
this.binding = Binding.get(this.bind);
|
|
26
|
-
if (this.rootAlias) this.rootName = this.rootAlias;
|
|
27
|
-
super.init();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
initInstance(context: any, instance: any) {
|
|
31
|
-
instance.store = new ZoomIntoPropertyView({
|
|
32
|
-
store: instance.parentStore,
|
|
33
|
-
binding: this.binding,
|
|
34
|
-
rootName: this.rootName,
|
|
35
|
-
nestedData: isObject(this.data)
|
|
36
|
-
? new StructuredInstanceDataAccessor({ instance, data: this.data, useParentStore: true })
|
|
37
|
-
: undefined,
|
|
38
|
-
});
|
|
39
|
-
super.initInstance(context, instance);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
applyParentStore(instance: any) {
|
|
43
|
-
instance.store.setStore(instance.parentStore);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
Rescope.prototype.bind = "$page";
|
|
48
|
-
Rescope.prototype.rootName = "$root";
|
|
49
|
-
|
|
50
|
-
Widget.alias("rescope", Rescope);
|
|
1
|
+
import { Widget } from "./Widget";
|
|
2
|
+
import { PureContainerBase, PureContainerConfig } from "./PureContainer";
|
|
3
|
+
import { Binding } from "../data/Binding";
|
|
4
|
+
import { ZoomIntoPropertyView } from "../data/ZoomIntoPropertyView";
|
|
5
|
+
import { StructuredInstanceDataAccessor } from "./StructuredInstanceDataAccessor";
|
|
6
|
+
import { isObject } from "../util/isObject";
|
|
7
|
+
import { StructuredProp } from "./Prop";
|
|
8
|
+
import { AccessorChain } from "../data/createAccessorModelProxy";
|
|
9
|
+
|
|
10
|
+
export interface RescopeConfig extends PureContainerConfig {
|
|
11
|
+
bind: string | AccessorChain<any>;
|
|
12
|
+
rootName?: string | AccessorChain<any>;
|
|
13
|
+
rootAlias?: string | AccessorChain<any>;
|
|
14
|
+
data?: StructuredProp;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class Rescope extends PureContainerBase<RescopeConfig> {
|
|
18
|
+
declare bind: string;
|
|
19
|
+
declare binding: any;
|
|
20
|
+
declare rootAlias?: string;
|
|
21
|
+
declare rootName: string;
|
|
22
|
+
declare data?: any;
|
|
23
|
+
|
|
24
|
+
init() {
|
|
25
|
+
this.binding = Binding.get(this.bind);
|
|
26
|
+
if (this.rootAlias) this.rootName = this.rootAlias;
|
|
27
|
+
super.init();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
initInstance(context: any, instance: any) {
|
|
31
|
+
instance.store = new ZoomIntoPropertyView({
|
|
32
|
+
store: instance.parentStore,
|
|
33
|
+
binding: this.binding,
|
|
34
|
+
rootName: this.rootName,
|
|
35
|
+
nestedData: isObject(this.data)
|
|
36
|
+
? new StructuredInstanceDataAccessor({ instance, data: this.data, useParentStore: true })
|
|
37
|
+
: undefined,
|
|
38
|
+
});
|
|
39
|
+
super.initInstance(context, instance);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
applyParentStore(instance: any) {
|
|
43
|
+
instance.store.setStore(instance.parentStore);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
Rescope.prototype.bind = "$page";
|
|
48
|
+
Rescope.prototype.rootName = "$root";
|
|
49
|
+
|
|
50
|
+
Widget.alias("rescope", Rescope);
|
package/src/ui/exprHelpers.ts
CHANGED
|
@@ -1,96 +1,96 @@
|
|
|
1
|
-
import { computable } from "../data/computable";
|
|
2
|
-
import { AccessorChain } from "../data/createAccessorModelProxy";
|
|
3
|
-
import { Selector } from "../data/Selector";
|
|
4
|
-
import { Format } from "../util/Format";
|
|
5
|
-
import { expr } from "./expr";
|
|
6
|
-
|
|
7
|
-
/** Returns a selector that converts the value to boolean using !! */
|
|
8
|
-
export function truthy<V>(arg: AccessorChain<V>): Selector<boolean> {
|
|
9
|
-
return expr(arg, (x) => !!x);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/** Returns a selector that checks if the value is falsy using ! */
|
|
13
|
-
export function falsy<V>(arg: AccessorChain<V>): Selector<boolean> {
|
|
14
|
-
return expr(arg, (x) => !x);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** Returns a selector that checks if the value is strictly true (=== true) */
|
|
18
|
-
export function isTrue(arg: AccessorChain<any>): Selector<boolean> {
|
|
19
|
-
return expr(arg, (x) => x === true);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** Returns a selector that checks if the value is strictly false (=== false) */
|
|
23
|
-
export function isFalse(arg: AccessorChain<any>): Selector<boolean> {
|
|
24
|
-
return expr(arg, (x) => x === false);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** Returns a selector that checks if the value is not null or undefined (x != null) */
|
|
28
|
-
export function hasValue<V>(arg: AccessorChain<V>): Selector<boolean> {
|
|
29
|
-
return expr(arg, (x) => x != null);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** Returns a selector that checks if a string or array is empty (null, undefined, or length === 0) */
|
|
33
|
-
export function isEmpty(arg: AccessorChain<string | any[] | null | undefined>): Selector<boolean> {
|
|
34
|
-
return expr(arg, (x) => x == null || x.length === 0);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** Returns a selector that checks if a string or array is non-empty (not null/undefined and length > 0) */
|
|
38
|
-
export function isNonEmpty(arg: AccessorChain<string | any[] | null | undefined>): Selector<boolean> {
|
|
39
|
-
return expr(arg, (x) => x != null && x.length > 0);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Returns a selector that checks if the value is less than the given value */
|
|
43
|
-
export function lessThan<V>(arg: AccessorChain<V>, value: V): Selector<boolean> {
|
|
44
|
-
return expr(arg, (x) => x < value);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** Returns a selector that checks if the value is less than or equal to the given value */
|
|
48
|
-
export function lessThanOrEqual<V>(arg: AccessorChain<V>, value: V): Selector<boolean> {
|
|
49
|
-
return expr(arg, (x) => x <= value);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** Returns a selector that checks if the value is greater than the given value */
|
|
53
|
-
export function greaterThan<V>(arg: AccessorChain<V>, value: V): Selector<boolean> {
|
|
54
|
-
return expr(arg, (x) => x > value);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** Returns a selector that checks if the value is greater than or equal to the given value */
|
|
58
|
-
export function greaterThanOrEqual<V>(arg: AccessorChain<V>, value: V): Selector<boolean> {
|
|
59
|
-
return expr(arg, (x) => x >= value);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** Returns a selector that checks if the value equals the given value using == */
|
|
63
|
-
export function equal<V>(arg: AccessorChain<V>, value: V): Selector<boolean> {
|
|
64
|
-
return expr(arg, (x) => x == value);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** Returns a selector that checks if the value does not equal the given value using != */
|
|
68
|
-
export function notEqual<V>(arg: AccessorChain<V>, value: V): Selector<boolean> {
|
|
69
|
-
return expr(arg, (x) => x != value);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** Returns a selector that checks if the value strictly equals the given value using === */
|
|
73
|
-
export function strictEqual<V>(arg: AccessorChain<V>, value: V): Selector<boolean> {
|
|
74
|
-
return expr(arg, (x) => x === value);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** Returns a selector that checks if the value strictly does not equal the given value using !== */
|
|
78
|
-
export function strictNotEqual<V>(arg: AccessorChain<V>, value: V): Selector<boolean> {
|
|
79
|
-
return expr(arg, (x) => x !== value);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** Returns a selector that formats the value using the specified format string.
|
|
83
|
-
* Format strings use semicolon-separated syntax: "formatType;param1;param2"
|
|
84
|
-
* @param arg - The accessor chain to the value
|
|
85
|
-
* @param fmt - The format string
|
|
86
|
-
* @param nullText - Optional text to display for null/undefined values
|
|
87
|
-
* @example
|
|
88
|
-
* format(m.price, "n;2") // formats as number with 2 decimal places
|
|
89
|
-
* format(m.date, "d") // formats as date
|
|
90
|
-
* format(m.value, "p;0;2") // formats as percentage with 0-2 decimal places
|
|
91
|
-
* format(m.value, "n;2", "N/A") // shows "N/A" for null values
|
|
92
|
-
*/
|
|
93
|
-
export function format<V>(arg: AccessorChain<V>, fmt: string, nullText?: string): Selector<string> {
|
|
94
|
-
let f = nullText != null ? `${fmt}|${nullText}` : fmt;
|
|
95
|
-
return computable(arg, (x) => Format.value(x, f));
|
|
96
|
-
}
|
|
1
|
+
import { computable } from "../data/computable";
|
|
2
|
+
import { AccessorChain } from "../data/createAccessorModelProxy";
|
|
3
|
+
import { Selector } from "../data/Selector";
|
|
4
|
+
import { Format } from "../util/Format";
|
|
5
|
+
import { expr } from "./expr";
|
|
6
|
+
|
|
7
|
+
/** Returns a selector that converts the value to boolean using !! */
|
|
8
|
+
export function truthy<V>(arg: AccessorChain<V>): Selector<boolean> {
|
|
9
|
+
return expr(arg, (x) => !!x);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Returns a selector that checks if the value is falsy using ! */
|
|
13
|
+
export function falsy<V>(arg: AccessorChain<V>): Selector<boolean> {
|
|
14
|
+
return expr(arg, (x) => !x);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Returns a selector that checks if the value is strictly true (=== true) */
|
|
18
|
+
export function isTrue(arg: AccessorChain<any>): Selector<boolean> {
|
|
19
|
+
return expr(arg, (x) => x === true);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Returns a selector that checks if the value is strictly false (=== false) */
|
|
23
|
+
export function isFalse(arg: AccessorChain<any>): Selector<boolean> {
|
|
24
|
+
return expr(arg, (x) => x === false);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Returns a selector that checks if the value is not null or undefined (x != null) */
|
|
28
|
+
export function hasValue<V>(arg: AccessorChain<V>): Selector<boolean> {
|
|
29
|
+
return expr(arg, (x) => x != null);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Returns a selector that checks if a string or array is empty (null, undefined, or length === 0) */
|
|
33
|
+
export function isEmpty(arg: AccessorChain<string | any[] | null | undefined>): Selector<boolean> {
|
|
34
|
+
return expr(arg, (x) => x == null || x.length === 0);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Returns a selector that checks if a string or array is non-empty (not null/undefined and length > 0) */
|
|
38
|
+
export function isNonEmpty(arg: AccessorChain<string | any[] | null | undefined>): Selector<boolean> {
|
|
39
|
+
return expr(arg, (x) => x != null && x.length > 0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Returns a selector that checks if the value is less than the given value */
|
|
43
|
+
export function lessThan<V>(arg: AccessorChain<V>, value: V): Selector<boolean> {
|
|
44
|
+
return expr(arg, (x) => x < value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Returns a selector that checks if the value is less than or equal to the given value */
|
|
48
|
+
export function lessThanOrEqual<V>(arg: AccessorChain<V>, value: V): Selector<boolean> {
|
|
49
|
+
return expr(arg, (x) => x <= value);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Returns a selector that checks if the value is greater than the given value */
|
|
53
|
+
export function greaterThan<V>(arg: AccessorChain<V>, value: V): Selector<boolean> {
|
|
54
|
+
return expr(arg, (x) => x > value);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Returns a selector that checks if the value is greater than or equal to the given value */
|
|
58
|
+
export function greaterThanOrEqual<V>(arg: AccessorChain<V>, value: V): Selector<boolean> {
|
|
59
|
+
return expr(arg, (x) => x >= value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Returns a selector that checks if the value equals the given value using == */
|
|
63
|
+
export function equal<V>(arg: AccessorChain<V>, value: V): Selector<boolean> {
|
|
64
|
+
return expr(arg, (x) => x == value);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Returns a selector that checks if the value does not equal the given value using != */
|
|
68
|
+
export function notEqual<V>(arg: AccessorChain<V>, value: V): Selector<boolean> {
|
|
69
|
+
return expr(arg, (x) => x != value);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Returns a selector that checks if the value strictly equals the given value using === */
|
|
73
|
+
export function strictEqual<V>(arg: AccessorChain<V>, value: V): Selector<boolean> {
|
|
74
|
+
return expr(arg, (x) => x === value);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Returns a selector that checks if the value strictly does not equal the given value using !== */
|
|
78
|
+
export function strictNotEqual<V>(arg: AccessorChain<V>, value: V): Selector<boolean> {
|
|
79
|
+
return expr(arg, (x) => x !== value);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Returns a selector that formats the value using the specified format string.
|
|
83
|
+
* Format strings use semicolon-separated syntax: "formatType;param1;param2"
|
|
84
|
+
* @param arg - The accessor chain to the value
|
|
85
|
+
* @param fmt - The format string
|
|
86
|
+
* @param nullText - Optional text to display for null/undefined values
|
|
87
|
+
* @example
|
|
88
|
+
* format(m.price, "n;2") // formats as number with 2 decimal places
|
|
89
|
+
* format(m.date, "d") // formats as date
|
|
90
|
+
* format(m.value, "p;0;2") // formats as percentage with 0-2 decimal places
|
|
91
|
+
* format(m.value, "n;2", "N/A") // shows "N/A" for null values
|
|
92
|
+
*/
|
|
93
|
+
export function format<V>(arg: AccessorChain<V>, fmt: string, nullText?: string): Selector<string> {
|
|
94
|
+
let f = nullText != null ? `${fmt}|${nullText}` : fmt;
|
|
95
|
+
return computable(arg, (x) => Format.value(x, f));
|
|
96
|
+
}
|
package/src/ui/index.ts
CHANGED
|
@@ -43,9 +43,6 @@ export * from "./exprHelpers";
|
|
|
43
43
|
|
|
44
44
|
//re-export computable here
|
|
45
45
|
import { computable } from "../data/computable";
|
|
46
|
-
import {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
AccessorChain,
|
|
50
|
-
} from "../data/createAccessorModelProxy";
|
|
51
|
-
export { computable, createAccessorModelProxy, createModel, AccessorChain };
|
|
46
|
+
import { createAccessorModelProxy, createModel } from "../data/createAccessorModelProxy";
|
|
47
|
+
export { computable, createAccessorModelProxy, createModel };
|
|
48
|
+
export type { AccessorChain } from "../data/createAccessorModelProxy";
|
package/src/util/DOM.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isNumber } from "../util/isNumber";
|
|
2
|
+
import { getActiveElement } from "./getActiveElement";
|
|
2
3
|
|
|
3
4
|
type ElementFilter = (el: Element, condition: (el: Element) => boolean) => Element | null;
|
|
4
5
|
|
|
@@ -51,11 +52,11 @@ export function closestParent(el: Element, condition: (el: any) => boolean): HTM
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
export function isFocused(el: Element): boolean {
|
|
54
|
-
return
|
|
55
|
+
return getActiveElement() == el;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
export function isFocusedDeep(el: Element): boolean {
|
|
58
|
-
return
|
|
59
|
+
return isSelfOrDescendant(el, getActiveElement());
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
const focusableWithoutTabIndex = ["INPUT", "SELECT", "TEXTAREA", "A", "BUTTON"];
|
|
@@ -76,13 +77,23 @@ export function isFocusable(el: Element): el is HTMLElement {
|
|
|
76
77
|
* @returns {Element}
|
|
77
78
|
*/
|
|
78
79
|
export function getFocusedElement(): Element | null {
|
|
79
|
-
return
|
|
80
|
+
return getActiveElement();
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
export function isDescendant(el: Element, descEl: Element): boolean {
|
|
83
84
|
return el.contains(descEl);
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
//`descEl` may live in a different document than `el` (e.g. focus is inside a same-origin
|
|
88
|
+
//iframe) - Node.contains never crosses document boundaries, so walk out through each
|
|
89
|
+
//frame's <iframe> element in its parent document until we either cross into el's document
|
|
90
|
+
//or run out of ancestor frames.
|
|
86
91
|
export function isSelfOrDescendant(el: Element, descEl: Element): boolean {
|
|
87
|
-
|
|
92
|
+
let node: Element | null | undefined = descEl;
|
|
93
|
+
while (node) {
|
|
94
|
+
if (el == node || el.contains(node)) return true;
|
|
95
|
+
let win: Window | null = node.ownerDocument?.defaultView;
|
|
96
|
+
node = win?.frameElement;
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
88
99
|
}
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
//IE sometimes returns null while other browsers always return document.body.
|
|
2
|
-
export function getActiveElement(): Element {
|
|
3
|
-
|
|
2
|
+
export function getActiveElement(doc: Document = document): Element {
|
|
3
|
+
let active = doc.activeElement ?? doc.body;
|
|
4
|
+
|
|
5
|
+
//when focus is inside a same-origin iframe, `doc.activeElement` only reports the
|
|
6
|
+
//<iframe> element itself - drill into its own document to find the element that's
|
|
7
|
+
//actually focused there (recursively, in case of nested iframes)
|
|
8
|
+
if (active && active.tagName === "IFRAME") {
|
|
9
|
+
const iframe = active as HTMLIFrameElement;
|
|
10
|
+
let frameDoc = iframe.contentDocument;
|
|
11
|
+
if (frameDoc) {
|
|
12
|
+
const inner = getActiveElement(frameDoc);
|
|
13
|
+
return inner ?? active;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return active;
|
|
4
18
|
}
|