cx 26.0.4 → 26.0.6

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 (127) 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 +6 -2
  5. package/build/ui/app/startAppLoop.d.ts +3 -2
  6. package/build/ui/app/startHotAppLoop.d.ts +3 -2
  7. package/build/ui/layout/ContentPlaceholder.spec.js +12 -12
  8. package/build.js +129 -129
  9. package/dist/manifest.js +844 -844
  10. package/package.json +3 -2
  11. package/src/charts/Chart.ts +108 -108
  12. package/src/data/ArrayElementView.ts +90 -90
  13. package/src/data/AugmentedViewBase.ts +88 -88
  14. package/src/data/Binding.ts +104 -104
  15. package/src/data/ExposedRecordView.ts +95 -95
  16. package/src/data/ExposedValueView.ts +89 -89
  17. package/src/data/Expression.spec.ts +229 -229
  18. package/src/data/Expression.ts +233 -233
  19. package/src/data/Grouper.spec.ts +57 -57
  20. package/src/data/Grouper.ts +158 -158
  21. package/src/data/NestedDataView.ts +43 -43
  22. package/src/data/ReadOnlyDataView.ts +39 -39
  23. package/src/data/Ref.ts +104 -104
  24. package/src/data/Selector.ts +10 -10
  25. package/src/data/Store.ts +52 -52
  26. package/src/data/StoreProxy.ts +19 -19
  27. package/src/data/StoreRef.ts +66 -66
  28. package/src/data/StringTemplate.spec.ts +132 -132
  29. package/src/data/StringTemplate.ts +93 -93
  30. package/src/data/StructuredSelector.spec.ts +113 -113
  31. package/src/data/StructuredSelector.ts +146 -146
  32. package/src/data/SubscribableView.ts +63 -63
  33. package/src/data/ZoomIntoPropertyView.spec.ts +64 -64
  34. package/src/data/ZoomIntoPropertyView.ts +45 -45
  35. package/src/data/computable.spec.ts +62 -62
  36. package/src/data/createAccessorModelProxy.ts +60 -60
  37. package/src/data/createStructuredSelector.ts +62 -62
  38. package/src/data/getAccessor.spec.ts +11 -11
  39. package/src/data/getAccessor.ts +74 -74
  40. package/src/data/getSelector.spec.ts +43 -43
  41. package/src/data/getSelector.ts +66 -66
  42. package/src/data/ops/filter.spec.ts +35 -35
  43. package/src/data/ops/filter.ts +9 -9
  44. package/src/data/ops/merge.ts +13 -13
  45. package/src/data/ops/removeTreeNodes.spec.ts +37 -37
  46. package/src/data/ops/removeTreeNodes.ts +15 -15
  47. package/src/data/ops/updateArray.spec.ts +69 -69
  48. package/src/data/ops/updateArray.ts +31 -31
  49. package/src/data/ops/updateTree.ts +23 -23
  50. package/src/data/test-types.ts +7 -7
  51. package/src/hooks/useTrigger.ts +26 -26
  52. package/src/index.scss +6 -6
  53. package/src/jsx-runtime.ts +72 -72
  54. package/src/svg/BoundedObject.ts +101 -101
  55. package/src/ui/CSSHelper.ts +17 -17
  56. package/src/ui/ContentResolver.ts +124 -124
  57. package/src/ui/Controller.ts +189 -189
  58. package/src/ui/Culture.ts +159 -159
  59. package/src/ui/DataProxy.ts +55 -55
  60. package/src/ui/FocusManager.ts +171 -171
  61. package/src/ui/Instance.ts +868 -868
  62. package/src/ui/RenderingContext.ts +99 -99
  63. package/src/ui/Rescope.ts +49 -49
  64. package/src/ui/StructuredInstanceDataAccessor.ts +32 -32
  65. package/src/ui/VDOM.ts +34 -34
  66. package/src/ui/adapter/ArrayAdapter.spec.ts +55 -55
  67. package/src/ui/adapter/ArrayAdapter.ts +226 -226
  68. package/src/ui/adapter/TreeAdapter.spec.ts +76 -76
  69. package/src/ui/adapter/TreeAdapter.ts +185 -185
  70. package/src/ui/app/History.ts +133 -133
  71. package/src/ui/app/Url.spec.ts +50 -50
  72. package/src/ui/app/startAppLoop.tsx +2 -1
  73. package/src/ui/app/startHotAppLoop.ts +2 -1
  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/drag-drop/ops.tsx +1 -1
  96. package/src/widgets/grid/GridCell.ts +143 -143
  97. package/src/widgets/icons/calendar.tsx +17 -17
  98. package/src/widgets/icons/check.tsx +13 -13
  99. package/src/widgets/icons/clear.tsx +15 -15
  100. package/src/widgets/icons/close.tsx +20 -20
  101. package/src/widgets/icons/cx.tsx +38 -38
  102. package/src/widgets/icons/drop-down.tsx +15 -15
  103. package/src/widgets/icons/file.tsx +13 -13
  104. package/src/widgets/icons/folder-open.tsx +15 -15
  105. package/src/widgets/icons/folder.tsx +13 -13
  106. package/src/widgets/icons/forward.tsx +22 -22
  107. package/src/widgets/icons/loading.tsx +24 -24
  108. package/src/widgets/icons/menu.tsx +17 -17
  109. package/src/widgets/icons/pixel-picker.tsx +18 -18
  110. package/src/widgets/icons/search.tsx +13 -13
  111. package/src/widgets/icons/sort-asc.tsx +14 -14
  112. package/src/widgets/icons/square.tsx +18 -18
  113. package/src/widgets/nav/Route.ts +142 -142
  114. package/src/widgets/overlay/ContextMenu.ts +42 -42
  115. package/src/widgets/overlay/Dropdown.tsx +762 -762
  116. package/src/widgets/overlay/MsgBox.tsx +141 -141
  117. package/src/widgets/overlay/Toast.ts +111 -111
  118. package/src/widgets/overlay/Window.tsx +299 -299
  119. package/src/widgets/overlay/alerts.ts +46 -46
  120. package/src/widgets/overlay/captureMouse.ts +195 -195
  121. package/src/widgets/overlay/createHotPromiseWindowFactory.ts +72 -72
  122. package/src/widgets/overlay/index.d.ts +11 -11
  123. package/src/widgets/overlay/index.ts +11 -11
  124. package/src/widgets/overlay/tooltip-ops.ts +173 -173
  125. package/build/ui/PureContainer.spec.d.ts +0 -1
  126. package/build/ui/PureContainer.spec.js +0 -149
  127. package/dist/manifest.d.ts +0 -1443
@@ -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
+ };