cx 26.0.2 → 26.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/build/data/ArrayElementView.spec.js +1 -1
  2. package/build/hooks/store.spec.js +1 -1
  3. package/build/hooks/useTrigger.spec.js +1 -1
  4. package/build/ui/Controller.spec.js +2 -6
  5. package/build/ui/PureContainer.spec.d.ts +1 -0
  6. package/build/ui/PureContainer.spec.js +149 -0
  7. package/build/ui/layout/ContentPlaceholder.spec.js +12 -12
  8. package/build.js +129 -129
  9. package/dist/manifest.d.ts +1443 -0
  10. package/dist/manifest.js +826 -826
  11. package/package.json +4 -3
  12. package/src/charts/Chart.ts +108 -108
  13. package/src/data/ArrayElementView.ts +90 -90
  14. package/src/data/AugmentedViewBase.ts +88 -88
  15. package/src/data/Binding.ts +104 -104
  16. package/src/data/ExposedRecordView.ts +95 -95
  17. package/src/data/ExposedValueView.ts +89 -89
  18. package/src/data/Expression.spec.ts +229 -229
  19. package/src/data/Expression.ts +233 -233
  20. package/src/data/Grouper.spec.ts +57 -57
  21. package/src/data/Grouper.ts +158 -158
  22. package/src/data/NestedDataView.ts +43 -43
  23. package/src/data/ReadOnlyDataView.ts +39 -39
  24. package/src/data/Ref.ts +104 -104
  25. package/src/data/Selector.ts +10 -10
  26. package/src/data/Store.ts +52 -52
  27. package/src/data/StoreProxy.ts +19 -19
  28. package/src/data/StoreRef.ts +66 -66
  29. package/src/data/StringTemplate.spec.ts +132 -132
  30. package/src/data/StringTemplate.ts +93 -93
  31. package/src/data/StructuredSelector.spec.ts +113 -113
  32. package/src/data/StructuredSelector.ts +146 -146
  33. package/src/data/SubscribableView.ts +63 -63
  34. package/src/data/ZoomIntoPropertyView.spec.ts +64 -64
  35. package/src/data/ZoomIntoPropertyView.ts +45 -45
  36. package/src/data/computable.spec.ts +62 -62
  37. package/src/data/createAccessorModelProxy.ts +60 -60
  38. package/src/data/createStructuredSelector.ts +62 -62
  39. package/src/data/getAccessor.spec.ts +11 -11
  40. package/src/data/getAccessor.ts +74 -74
  41. package/src/data/getSelector.spec.ts +43 -43
  42. package/src/data/getSelector.ts +66 -66
  43. package/src/data/ops/filter.spec.ts +35 -35
  44. package/src/data/ops/filter.ts +9 -9
  45. package/src/data/ops/merge.ts +13 -13
  46. package/src/data/ops/removeTreeNodes.spec.ts +37 -37
  47. package/src/data/ops/removeTreeNodes.ts +15 -15
  48. package/src/data/ops/updateArray.spec.ts +69 -69
  49. package/src/data/ops/updateArray.ts +31 -31
  50. package/src/data/ops/updateTree.ts +23 -23
  51. package/src/data/test-types.ts +7 -7
  52. package/src/hooks/useTrigger.ts +26 -26
  53. package/src/index.scss +6 -6
  54. package/src/jsx-runtime.ts +72 -72
  55. package/src/svg/BoundedObject.ts +101 -101
  56. package/src/ui/CSSHelper.ts +17 -17
  57. package/src/ui/ContentResolver.ts +124 -124
  58. package/src/ui/Controller.ts +189 -189
  59. package/src/ui/Culture.ts +159 -159
  60. package/src/ui/DataProxy.ts +55 -55
  61. package/src/ui/FocusManager.ts +171 -171
  62. package/src/ui/Instance.ts +868 -868
  63. package/src/ui/RenderingContext.ts +99 -99
  64. package/src/ui/Rescope.ts +49 -49
  65. package/src/ui/StructuredInstanceDataAccessor.ts +32 -32
  66. package/src/ui/VDOM.ts +34 -34
  67. package/src/ui/adapter/ArrayAdapter.spec.ts +55 -55
  68. package/src/ui/adapter/ArrayAdapter.ts +226 -226
  69. package/src/ui/adapter/TreeAdapter.spec.ts +76 -76
  70. package/src/ui/adapter/TreeAdapter.ts +185 -185
  71. package/src/ui/app/History.ts +133 -133
  72. package/src/ui/app/Url.spec.ts +50 -50
  73. package/src/ui/app/startHotAppLoop.ts +40 -40
  74. package/src/ui/createFunctionalComponent.ts +65 -65
  75. package/src/ui/index.ts +45 -45
  76. package/src/ui/layout/Content.ts +30 -30
  77. package/src/ui/layout/FirstVisibleChildLayout.ts +60 -60
  78. package/src/ui/selection/KeySelection.ts +165 -165
  79. package/src/ui/selection/PropertySelection.ts +87 -87
  80. package/src/ui/selection/Selection.ts +118 -118
  81. package/src/util/Format.ts +267 -267
  82. package/src/util/browserSupportsPassiveEventHandlers.ts +20 -20
  83. package/src/util/color/rgbToHsl.ts +35 -35
  84. package/src/util/getActiveElement.ts +4 -4
  85. package/src/util/hasKey.ts +18 -18
  86. package/src/util/index.ts +55 -55
  87. package/src/util/innerTextTrim.ts +10 -10
  88. package/src/util/isArray.ts +3 -3
  89. package/src/util/isDataRecord.ts +5 -5
  90. package/src/util/isDefined.ts +3 -3
  91. package/src/util/isString.ts +3 -3
  92. package/src/widgets/Sandbox.ts +103 -103
  93. package/src/widgets/autoFocus.ts +9 -9
  94. package/src/widgets/cx.ts +63 -63
  95. package/src/widgets/grid/GridCell.ts +143 -143
  96. package/src/widgets/icons/calendar.tsx +17 -17
  97. package/src/widgets/icons/check.tsx +13 -13
  98. package/src/widgets/icons/clear.tsx +15 -15
  99. package/src/widgets/icons/close.tsx +20 -20
  100. package/src/widgets/icons/cx.tsx +38 -38
  101. package/src/widgets/icons/drop-down.tsx +15 -15
  102. package/src/widgets/icons/file.tsx +13 -13
  103. package/src/widgets/icons/folder-open.tsx +15 -15
  104. package/src/widgets/icons/folder.tsx +13 -13
  105. package/src/widgets/icons/forward.tsx +22 -22
  106. package/src/widgets/icons/loading.tsx +24 -24
  107. package/src/widgets/icons/menu.tsx +17 -17
  108. package/src/widgets/icons/pixel-picker.tsx +18 -18
  109. package/src/widgets/icons/search.tsx +13 -13
  110. package/src/widgets/icons/sort-asc.tsx +14 -14
  111. package/src/widgets/icons/square.tsx +18 -18
  112. package/src/widgets/nav/Route.ts +142 -142
  113. package/src/widgets/overlay/ContextMenu.ts +42 -42
  114. package/src/widgets/overlay/Dropdown.tsx +762 -762
  115. package/src/widgets/overlay/MsgBox.tsx +141 -141
  116. package/src/widgets/overlay/Toast.ts +111 -111
  117. package/src/widgets/overlay/Window.tsx +299 -299
  118. package/src/widgets/overlay/alerts.ts +46 -46
  119. package/src/widgets/overlay/captureMouse.ts +195 -195
  120. package/src/widgets/overlay/createHotPromiseWindowFactory.ts +72 -72
  121. package/src/widgets/overlay/index.d.ts +11 -11
  122. package/src/widgets/overlay/index.ts +11 -11
  123. package/src/widgets/overlay/tooltip-ops.ts +173 -173
@@ -1,124 +1,124 @@
1
- import { PureContainerBase, PureContainerConfig } from "./PureContainer";
2
- import { isPromise } from "../util/isPromise";
3
- import { RenderingContext } from "./RenderingContext";
4
- import { BooleanProp, StructuredProp, ResolveStructuredProp } from "./Prop";
5
- import { Instance } from "./Instance";
6
-
7
- /**
8
- * Configuration for ContentResolver widget.
9
- *
10
- * The params type parameter enables type inference for the onResolve callback:
11
- * - Literal values (numbers, strings, booleans) preserve their types
12
- * - AccessorChain<T> resolves to T
13
- * - Bind/Tpl/Expr resolve to any (type cannot be determined at compile time)
14
- *
15
- * @example
16
- * ```typescript
17
- * <ContentResolver
18
- * params={{
19
- * count: 42, // number
20
- * name: model.user.name // AccessorChain<string> -> string
21
- * }}
22
- * onResolve={(params) => {
23
- * // params.count is number, params.name is string
24
- * }}
25
- * />
26
- * ```
27
- */
28
- export interface ContentResolverConfig<P extends StructuredProp = StructuredProp> extends PureContainerConfig {
29
- /** Parameters that trigger content resolution when changed. */
30
- params?: P;
31
-
32
- /**
33
- * Callback function that resolves content based on params. Can return content directly or a Promise.
34
- * The params type is inferred from the params property - literal values and AccessorChain<T>
35
- * preserve their types, while bindings (bind/tpl/expr) resolve to `any`.
36
- */
37
- onResolve?: string | ((params: ResolveStructuredProp<P>, instance: Instance) => any);
38
-
39
- /** How to combine resolved content with initial content. Default is 'replace'. */
40
- mode?: "replace" | "prepend" | "append";
41
-
42
- /** Indicates if content is being loaded. */
43
- loading?: BooleanProp;
44
- }
45
-
46
- export class ContentResolver<P extends StructuredProp = StructuredProp> extends PureContainerBase<
47
- ContentResolverConfig<P>
48
- > {
49
- constructor(config?: ContentResolverConfig<P>) {
50
- super(config);
51
- }
52
-
53
- declare mode: "replace" | "prepend" | "append";
54
- declare onResolve?: string | ((params: ResolveStructuredProp<P>, instance: Instance) => any);
55
- declare initialItems: any;
56
-
57
- declareData(...args: any[]): void {
58
- super.declareData(...args, {
59
- params: { structured: true },
60
- loading: undefined,
61
- });
62
- }
63
-
64
- init(): void {
65
- super.init();
66
- this.initialItems = this.layout ? this.layout.items : this.items;
67
- this.clear();
68
- }
69
-
70
- initInstance(context: RenderingContext, instance: any): void {
71
- instance.content = this.initialItems;
72
- instance.cachedParams = {}; //unique value which will never pass the equality check
73
- }
74
-
75
- prepareData(context: RenderingContext, instance: any): void {
76
- let { data } = instance;
77
-
78
- if (data.params !== instance.cachedParams && this.onResolve) {
79
- instance.cachedParams = data.params;
80
- let content = instance.invoke("onResolve", data.params, instance);
81
- if (isPromise(content)) {
82
- instance.set("loading", true);
83
- this.setContent(instance, null);
84
- content.then((cnt: any) => {
85
- this.setContent(instance, cnt);
86
- instance.setState({ cacheBuster: {} });
87
- instance.set("loading", false);
88
- });
89
- } else this.setContent(instance, content);
90
- }
91
- }
92
-
93
- setContent(instance: any, content: any): void {
94
- if (content) {
95
- this.clear();
96
- switch (this.mode) {
97
- case "prepend":
98
- this.add(content);
99
- this.add(this.initialItems);
100
- break;
101
-
102
- case "append":
103
- this.add(this.initialItems);
104
- this.add(content);
105
- break;
106
-
107
- case "replace":
108
- this.add(content);
109
- break;
110
- }
111
- instance.content = this.layout ? this.layout.items : this.items;
112
- this.clear();
113
- } else instance.content = this.initialItems;
114
- }
115
-
116
- explore(context: RenderingContext, instance: any): void {
117
- //a little bit hacky
118
- if (this.layout) this.layout.items = instance.content;
119
- else this.items = instance.content;
120
- super.explore(context, instance);
121
- }
122
- }
123
-
124
- ContentResolver.prototype.mode = "replace";
1
+ import { PureContainerBase, PureContainerConfig } from "./PureContainer";
2
+ import { isPromise } from "../util/isPromise";
3
+ import { RenderingContext } from "./RenderingContext";
4
+ import { BooleanProp, StructuredProp, ResolveStructuredProp } from "./Prop";
5
+ import { Instance } from "./Instance";
6
+
7
+ /**
8
+ * Configuration for ContentResolver widget.
9
+ *
10
+ * The params type parameter enables type inference for the onResolve callback:
11
+ * - Literal values (numbers, strings, booleans) preserve their types
12
+ * - AccessorChain<T> resolves to T
13
+ * - Bind/Tpl/Expr resolve to any (type cannot be determined at compile time)
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * <ContentResolver
18
+ * params={{
19
+ * count: 42, // number
20
+ * name: model.user.name // AccessorChain<string> -> string
21
+ * }}
22
+ * onResolve={(params) => {
23
+ * // params.count is number, params.name is string
24
+ * }}
25
+ * />
26
+ * ```
27
+ */
28
+ export interface ContentResolverConfig<P extends StructuredProp = StructuredProp> extends PureContainerConfig {
29
+ /** Parameters that trigger content resolution when changed. */
30
+ params?: P;
31
+
32
+ /**
33
+ * Callback function that resolves content based on params. Can return content directly or a Promise.
34
+ * The params type is inferred from the params property - literal values and AccessorChain<T>
35
+ * preserve their types, while bindings (bind/tpl/expr) resolve to `any`.
36
+ */
37
+ onResolve?: string | ((params: ResolveStructuredProp<P>, instance: Instance) => any);
38
+
39
+ /** How to combine resolved content with initial content. Default is 'replace'. */
40
+ mode?: "replace" | "prepend" | "append";
41
+
42
+ /** Indicates if content is being loaded. */
43
+ loading?: BooleanProp;
44
+ }
45
+
46
+ export class ContentResolver<P extends StructuredProp = StructuredProp> extends PureContainerBase<
47
+ ContentResolverConfig<P>
48
+ > {
49
+ constructor(config?: ContentResolverConfig<P>) {
50
+ super(config);
51
+ }
52
+
53
+ declare mode: "replace" | "prepend" | "append";
54
+ declare onResolve?: string | ((params: ResolveStructuredProp<P>, instance: Instance) => any);
55
+ declare initialItems: any;
56
+
57
+ declareData(...args: any[]): void {
58
+ super.declareData(...args, {
59
+ params: { structured: true },
60
+ loading: undefined,
61
+ });
62
+ }
63
+
64
+ init(): void {
65
+ super.init();
66
+ this.initialItems = this.layout ? this.layout.items : this.items;
67
+ this.clear();
68
+ }
69
+
70
+ initInstance(context: RenderingContext, instance: any): void {
71
+ instance.content = this.initialItems;
72
+ instance.cachedParams = {}; //unique value which will never pass the equality check
73
+ }
74
+
75
+ prepareData(context: RenderingContext, instance: any): void {
76
+ let { data } = instance;
77
+
78
+ if (data.params !== instance.cachedParams && this.onResolve) {
79
+ instance.cachedParams = data.params;
80
+ let content = instance.invoke("onResolve", data.params, instance);
81
+ if (isPromise(content)) {
82
+ instance.set("loading", true);
83
+ this.setContent(instance, null);
84
+ content.then((cnt: any) => {
85
+ this.setContent(instance, cnt);
86
+ instance.setState({ cacheBuster: {} });
87
+ instance.set("loading", false);
88
+ });
89
+ } else this.setContent(instance, content);
90
+ }
91
+ }
92
+
93
+ setContent(instance: any, content: any): void {
94
+ if (content) {
95
+ this.clear();
96
+ switch (this.mode) {
97
+ case "prepend":
98
+ this.add(content);
99
+ this.add(this.initialItems);
100
+ break;
101
+
102
+ case "append":
103
+ this.add(this.initialItems);
104
+ this.add(content);
105
+ break;
106
+
107
+ case "replace":
108
+ this.add(content);
109
+ break;
110
+ }
111
+ instance.content = this.layout ? this.layout.items : this.items;
112
+ this.clear();
113
+ } else instance.content = this.initialItems;
114
+ }
115
+
116
+ explore(context: RenderingContext, instance: any): void {
117
+ //a little bit hacky
118
+ if (this.layout) this.layout.items = instance.content;
119
+ else this.items = instance.content;
120
+ super.explore(context, instance);
121
+ }
122
+ }
123
+
124
+ ContentResolver.prototype.mode = "replace";
@@ -1,189 +1,189 @@
1
- import { computable, ComputableSelector, InferSelectorValue } from "../data/computable";
2
- import { Component, ComponentConstructor, ComponentConfigType } from "../util/Component";
3
- import { isArray } from "../util/isArray";
4
- import { isFunction } from "../util/isFunction";
5
- import { StoreProxy } from "../data/StoreProxy";
6
- import { RenderingContext } from "./RenderingContext";
7
- import { View, ViewMethods } from "../data/View";
8
- import { Widget } from "./Widget";
9
- import { Instance } from "./Instance";
10
-
11
- const computablePrefix = "computable-";
12
- const triggerPrefix = "trigger-";
13
-
14
- interface ComputableEntry {
15
- name: string;
16
- selector: any;
17
- type: "computable" | "trigger";
18
- }
19
-
20
- // Controller methods interface for ThisType in config objects
21
- export interface ControllerMethods {
22
- store: View;
23
- instance: Instance;
24
- widget?: Widget;
25
- invokeParentMethod(method: string, ...args: any[]): any;
26
- addTrigger<Selectors extends readonly ComputableSelector[]>(
27
- name: string,
28
- args: [...Selectors],
29
- callback: (...values: { [K in keyof Selectors]: InferSelectorValue<Selectors[K]> }) => any,
30
- autoRun?: boolean,
31
- ): void;
32
- addComputable<Selectors extends readonly ComputableSelector[]>(
33
- name: string,
34
- args: [...Selectors],
35
- callback: (...values: { [K in keyof Selectors]: InferSelectorValue<Selectors[K]> }) => any,
36
- ): void;
37
- }
38
-
39
- export interface BaseControllerConfig {
40
- store?: View;
41
- instance?: Instance;
42
- widget?: Widget;
43
-
44
- onInit?: (context: RenderingContext) => void;
45
- onExplore?: (context: RenderingContext) => void;
46
- onPrepare?: (context: RenderingContext) => void;
47
- onCleanup?: (context: RenderingContext) => void;
48
- onDestroy?: () => void;
49
- }
50
-
51
- // Controller config object type with Controller methods AND its own methods available on 'this'
52
- export type ControllerConfig<T extends BaseControllerConfig = BaseControllerConfig> = T &
53
- ThisType<ControllerMethods & T>;
54
-
55
- /** Controller factory function type */
56
- export type ControllerFactory<T extends BaseControllerConfig = BaseControllerConfig> = (
57
- config: ViewMethods,
58
- ) => ControllerConfig<T>;
59
-
60
- export class Controller extends Component implements ControllerMethods {
61
- initialized?: boolean;
62
- onInit?(context: RenderingContext): void;
63
- onExplore?(context: RenderingContext): void;
64
- onPrepare?(context: RenderingContext): void;
65
- onCleanup?(context: RenderingContext): void;
66
- onDestroy?(): void;
67
- declare instance: Instance;
68
- declare store: View;
69
- declare widget?: Widget;
70
- computables?: Record<string, ComputableEntry>;
71
-
72
- constructor(config?: BaseControllerConfig) {
73
- super(config);
74
- }
75
-
76
- init(context?: RenderingContext): void {
77
- if (!this.initialized) {
78
- this.initialized = true;
79
- if (this.onInit && context) this.onInit(context);
80
- }
81
- }
82
-
83
- explore(context: RenderingContext): void {
84
- let { store } = this.instance;
85
- this.store = store; //in rare cases instance may change its store
86
-
87
- if (!this.initialized) {
88
- this.init(context);
89
- //forgive if the developer forgets to call super.init()
90
- this.initialized = true;
91
- }
92
-
93
- if (this.computables) {
94
- for (let key in this.computables) {
95
- let x = this.computables[key];
96
- let v = x.selector(store.getData());
97
- if (x.type == "computable") store.set(x.name, v);
98
- }
99
- }
100
-
101
- if (this.onExplore) {
102
- this.onExplore(context);
103
- }
104
- }
105
-
106
- prepare(context: RenderingContext): void {
107
- if (this.onPrepare) {
108
- this.onPrepare(context);
109
- }
110
- }
111
-
112
- cleanup(context: RenderingContext): void {
113
- if (this.onCleanup) {
114
- this.onCleanup(context);
115
- }
116
- }
117
-
118
- addComputable<Selectors extends readonly ComputableSelector[]>(
119
- name: string,
120
- args: [...Selectors],
121
- callback: (...values: { [K in keyof Selectors]: InferSelectorValue<Selectors[K]> }) => any,
122
- ): void {
123
- if (!isArray(args)) throw new Error("Second argument to the addComputable method should be an array.");
124
- let selector = computable(...args, callback).memoize();
125
- if (!this.computables) this.computables = {};
126
- this.computables[computablePrefix + name] = { name, selector, type: "computable" };
127
- }
128
-
129
- addTrigger<Selectors extends readonly ComputableSelector[]>(
130
- name: string,
131
- args: [...Selectors],
132
- callback: (...values: { [K in keyof Selectors]: InferSelectorValue<Selectors[K]> }) => any,
133
- autoRun?: boolean,
134
- ): void {
135
- if (!isArray(args)) throw new Error("Second argument to the addTrigger method should be an array.");
136
- let selector = computable(...args, callback).memoize(!autoRun && this.store.getData());
137
- if (!this.computables) this.computables = {};
138
- this.computables[triggerPrefix + name] = { name, selector, type: "trigger" };
139
- }
140
-
141
- removeTrigger(name: string): void {
142
- if (this.computables) delete this.computables[triggerPrefix + name];
143
- }
144
-
145
- removeComputable(name: string): void {
146
- if (this.computables) delete this.computables[computablePrefix + name];
147
- }
148
-
149
- invokeParentMethod(methodName: string, ...args: any[]): any {
150
- let parent = this.instance.parent;
151
- if (!parent) throw new Error("Cannot invoke a parent controller method as the instance does not have a parent.");
152
- return parent.invokeControllerMethod(methodName, ...args);
153
- }
154
-
155
- invokeMethod(methodName: string, ...args: any[]): any {
156
- return this.instance.invokeControllerMethod(methodName, ...args);
157
- }
158
- }
159
-
160
- Controller.namespace = "ui.controller.";
161
-
162
- Controller.factory = function (alias: any, config?: any, more?: any) {
163
- if (isFunction(alias)) {
164
- let cfg = {
165
- ...config,
166
- ...more,
167
- };
168
-
169
- if (cfg.instance) {
170
- //in rare cases instance.store may change, so we cannot rely on the store passed through configuration
171
- cfg.store = new StoreProxy(() => cfg.instance.store);
172
- Object.assign(cfg, cfg.store.getMethods());
173
- }
174
-
175
- let result = alias(cfg);
176
- if (result instanceof Controller) return result;
177
-
178
- return Controller.create({
179
- ...config,
180
- ...more,
181
- ...result,
182
- });
183
- }
184
-
185
- return Controller.create({
186
- ...config,
187
- ...more,
188
- });
189
- };
1
+ import { computable, ComputableSelector, InferSelectorValue } from "../data/computable";
2
+ import { Component, ComponentConstructor, ComponentConfigType } from "../util/Component";
3
+ import { isArray } from "../util/isArray";
4
+ import { isFunction } from "../util/isFunction";
5
+ import { StoreProxy } from "../data/StoreProxy";
6
+ import { RenderingContext } from "./RenderingContext";
7
+ import { View, ViewMethods } from "../data/View";
8
+ import { Widget } from "./Widget";
9
+ import { Instance } from "./Instance";
10
+
11
+ const computablePrefix = "computable-";
12
+ const triggerPrefix = "trigger-";
13
+
14
+ interface ComputableEntry {
15
+ name: string;
16
+ selector: any;
17
+ type: "computable" | "trigger";
18
+ }
19
+
20
+ // Controller methods interface for ThisType in config objects
21
+ export interface ControllerMethods {
22
+ store: View;
23
+ instance: Instance;
24
+ widget?: Widget;
25
+ invokeParentMethod(method: string, ...args: any[]): any;
26
+ addTrigger<Selectors extends readonly ComputableSelector[]>(
27
+ name: string,
28
+ args: [...Selectors],
29
+ callback: (...values: { [K in keyof Selectors]: InferSelectorValue<Selectors[K]> }) => any,
30
+ autoRun?: boolean,
31
+ ): void;
32
+ addComputable<Selectors extends readonly ComputableSelector[]>(
33
+ name: string,
34
+ args: [...Selectors],
35
+ callback: (...values: { [K in keyof Selectors]: InferSelectorValue<Selectors[K]> }) => any,
36
+ ): void;
37
+ }
38
+
39
+ export interface BaseControllerConfig {
40
+ store?: View;
41
+ instance?: Instance;
42
+ widget?: Widget;
43
+
44
+ onInit?: (context: RenderingContext) => void;
45
+ onExplore?: (context: RenderingContext) => void;
46
+ onPrepare?: (context: RenderingContext) => void;
47
+ onCleanup?: (context: RenderingContext) => void;
48
+ onDestroy?: () => void;
49
+ }
50
+
51
+ // Controller config object type with Controller methods AND its own methods available on 'this'
52
+ export type ControllerConfig<T extends BaseControllerConfig = BaseControllerConfig> = T &
53
+ ThisType<ControllerMethods & T>;
54
+
55
+ /** Controller factory function type */
56
+ export type ControllerFactory<T extends BaseControllerConfig = BaseControllerConfig> = (
57
+ config: ViewMethods,
58
+ ) => ControllerConfig<T>;
59
+
60
+ export class Controller extends Component implements ControllerMethods {
61
+ initialized?: boolean;
62
+ onInit?(context: RenderingContext): void;
63
+ onExplore?(context: RenderingContext): void;
64
+ onPrepare?(context: RenderingContext): void;
65
+ onCleanup?(context: RenderingContext): void;
66
+ onDestroy?(): void;
67
+ declare instance: Instance;
68
+ declare store: View;
69
+ declare widget?: Widget;
70
+ computables?: Record<string, ComputableEntry>;
71
+
72
+ constructor(config?: BaseControllerConfig) {
73
+ super(config);
74
+ }
75
+
76
+ init(context?: RenderingContext): void {
77
+ if (!this.initialized) {
78
+ this.initialized = true;
79
+ if (this.onInit && context) this.onInit(context);
80
+ }
81
+ }
82
+
83
+ explore(context: RenderingContext): void {
84
+ let { store } = this.instance;
85
+ this.store = store; //in rare cases instance may change its store
86
+
87
+ if (!this.initialized) {
88
+ this.init(context);
89
+ //forgive if the developer forgets to call super.init()
90
+ this.initialized = true;
91
+ }
92
+
93
+ if (this.computables) {
94
+ for (let key in this.computables) {
95
+ let x = this.computables[key];
96
+ let v = x.selector(store.getData());
97
+ if (x.type == "computable") store.set(x.name, v);
98
+ }
99
+ }
100
+
101
+ if (this.onExplore) {
102
+ this.onExplore(context);
103
+ }
104
+ }
105
+
106
+ prepare(context: RenderingContext): void {
107
+ if (this.onPrepare) {
108
+ this.onPrepare(context);
109
+ }
110
+ }
111
+
112
+ cleanup(context: RenderingContext): void {
113
+ if (this.onCleanup) {
114
+ this.onCleanup(context);
115
+ }
116
+ }
117
+
118
+ addComputable<Selectors extends readonly ComputableSelector[]>(
119
+ name: string,
120
+ args: [...Selectors],
121
+ callback: (...values: { [K in keyof Selectors]: InferSelectorValue<Selectors[K]> }) => any,
122
+ ): void {
123
+ if (!isArray(args)) throw new Error("Second argument to the addComputable method should be an array.");
124
+ let selector = computable(...args, callback).memoize();
125
+ if (!this.computables) this.computables = {};
126
+ this.computables[computablePrefix + name] = { name, selector, type: "computable" };
127
+ }
128
+
129
+ addTrigger<Selectors extends readonly ComputableSelector[]>(
130
+ name: string,
131
+ args: [...Selectors],
132
+ callback: (...values: { [K in keyof Selectors]: InferSelectorValue<Selectors[K]> }) => any,
133
+ autoRun?: boolean,
134
+ ): void {
135
+ if (!isArray(args)) throw new Error("Second argument to the addTrigger method should be an array.");
136
+ let selector = computable(...args, callback).memoize(!autoRun && this.store.getData());
137
+ if (!this.computables) this.computables = {};
138
+ this.computables[triggerPrefix + name] = { name, selector, type: "trigger" };
139
+ }
140
+
141
+ removeTrigger(name: string): void {
142
+ if (this.computables) delete this.computables[triggerPrefix + name];
143
+ }
144
+
145
+ removeComputable(name: string): void {
146
+ if (this.computables) delete this.computables[computablePrefix + name];
147
+ }
148
+
149
+ invokeParentMethod(methodName: string, ...args: any[]): any {
150
+ let parent = this.instance.parent;
151
+ if (!parent) throw new Error("Cannot invoke a parent controller method as the instance does not have a parent.");
152
+ return parent.invokeControllerMethod(methodName, ...args);
153
+ }
154
+
155
+ invokeMethod(methodName: string, ...args: any[]): any {
156
+ return this.instance.invokeControllerMethod(methodName, ...args);
157
+ }
158
+ }
159
+
160
+ Controller.namespace = "ui.controller.";
161
+
162
+ Controller.factory = function (alias: any, config?: any, more?: any) {
163
+ if (isFunction(alias)) {
164
+ let cfg = {
165
+ ...config,
166
+ ...more,
167
+ };
168
+
169
+ if (cfg.instance) {
170
+ //in rare cases instance.store may change, so we cannot rely on the store passed through configuration
171
+ cfg.store = new StoreProxy(() => cfg.instance.store);
172
+ Object.assign(cfg, cfg.store.getMethods());
173
+ }
174
+
175
+ let result = alias(cfg);
176
+ if (result instanceof Controller) return result;
177
+
178
+ return Controller.create({
179
+ ...config,
180
+ ...more,
181
+ ...result,
182
+ });
183
+ }
184
+
185
+ return Controller.create({
186
+ ...config,
187
+ ...more,
188
+ });
189
+ };