@jay-framework/component 0.5.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/dist/index.d.ts +69 -0
- package/dist/index.js +322 -0
- package/docs/component.md +137 -0
- package/docs/create-derived-array.md +54 -0
- package/docs/create-effect.md +27 -0
- package/docs/create-event.md +78 -0
- package/docs/create-memo.md +32 -0
- package/docs/create-patchable-signal.md +55 -0
- package/docs/create-signal.md +44 -0
- package/docs/provide-context.md +39 -0
- package/docs/provide-reactive-context.md +68 -0
- package/docs/use-reactive.md +13 -0
- package/package.json +38 -0
- package/readme.md +33 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { JayElement, ContextMarker, PreRenderElement, JayComponent, EventEmitter } from '@jay-framework/runtime';
|
|
2
|
+
import { Getter, Reactive, ValueOrGetter, Setter } from '@jay-framework/reactive';
|
|
3
|
+
import { JSONPatch } from '@jay-framework/json-patch';
|
|
4
|
+
import { HTMLElement } from 'node-html-parser';
|
|
5
|
+
|
|
6
|
+
type Patcher<T> = (...patch: JSONPatch) => void;
|
|
7
|
+
type hasProps<PropsT> = {
|
|
8
|
+
props: Getter<PropsT>;
|
|
9
|
+
};
|
|
10
|
+
type Props<PropsT> = hasProps<PropsT> & {
|
|
11
|
+
[K in keyof PropsT]: K extends string ? Getter<PropsT[K]> : PropsT[K];
|
|
12
|
+
};
|
|
13
|
+
type ViewStateGetters<ViewState> = {
|
|
14
|
+
[K in keyof ViewState]: ViewState[K] | Getter<ViewState[K]>;
|
|
15
|
+
};
|
|
16
|
+
type UpdatableProps<PropsT> = Props<PropsT> & {
|
|
17
|
+
update(newProps: PropsT): any;
|
|
18
|
+
};
|
|
19
|
+
interface JayComponentCore<PropsT, ViewState> {
|
|
20
|
+
render: () => ViewStateGetters<ViewState>;
|
|
21
|
+
}
|
|
22
|
+
type ConcreteJayComponent1<PropsT extends object, ViewState, Refs, CompCore extends JayComponentCore<PropsT, ViewState>, JayElementT extends JayElement<ViewState, Refs>> = Omit<CompCore, 'render'> & JayComponent<PropsT, ViewState, JayElementT>;
|
|
23
|
+
type ConcreteJayComponent<PropsT extends object, ViewState, Refs, CompCore extends JayComponentCore<PropsT, ViewState>, JayElementT extends JayElement<ViewState, Refs>> = ConcreteJayComponent1<PropsT, ViewState, Refs, CompCore, JayElementT>;
|
|
24
|
+
declare function materializeViewState<ViewState extends object>(vsValueOrGetter: ViewStateGetters<ViewState>): ViewState;
|
|
25
|
+
type ComponentConstructor<PropsT extends object, Refs extends object, ViewState extends object, Contexts extends Array<any>, CompCore extends JayComponentCore<PropsT, ViewState>> = (props: Props<PropsT>, refs: Refs, ...contexts: Contexts) => CompCore;
|
|
26
|
+
type ContextMarkers<T extends any[]> = {
|
|
27
|
+
[K in keyof T]: ContextMarker<T[K]>;
|
|
28
|
+
};
|
|
29
|
+
declare function makeJayComponent<PropsT extends object, ViewState extends object, Refs extends object, JayElementT extends JayElement<ViewState, Refs>, Contexts extends Array<any>, CompCore extends JayComponentCore<PropsT, ViewState>>(preRender: PreRenderElement<ViewState, Refs, JayElementT>, comp: ComponentConstructor<PropsT, Refs, ViewState, Contexts, CompCore>, ...contextMarkers: ContextMarkers<Contexts>): (props: PropsT) => ConcreteJayComponent<PropsT, ViewState, Refs, CompCore, JayElementT>;
|
|
30
|
+
type JsxNode = HTMLElement;
|
|
31
|
+
type JayTsxComponentConstructor<PropsT extends object, CompT extends {
|
|
32
|
+
render: () => JsxNode;
|
|
33
|
+
}> = (props: Props<PropsT>) => CompT;
|
|
34
|
+
declare function makeJayTsxComponent<PropsT extends object, CompT extends {
|
|
35
|
+
render: () => JsxNode;
|
|
36
|
+
}>(comp: JayTsxComponentConstructor<PropsT, CompT>): JayComponent<PropsT, any, any>;
|
|
37
|
+
declare function makePropsProxy<PropsT extends object>(reactive: Reactive, props: PropsT): UpdatableProps<PropsT>;
|
|
38
|
+
declare const forTesting: {
|
|
39
|
+
makePropsProxy: typeof makePropsProxy;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
declare const CONTEXT_REACTIVE_SYMBOL_CONTEXT: unique symbol;
|
|
43
|
+
declare function createReactiveContext<T extends object>(mkContext: () => T): T;
|
|
44
|
+
|
|
45
|
+
type EffectCleanup = () => void;
|
|
46
|
+
declare function createEffect(effect: () => void | EffectCleanup): void;
|
|
47
|
+
declare function createSignal<T>(value: ValueOrGetter<T>): [get: Getter<T>, set: Setter<T>];
|
|
48
|
+
declare function createPatchableSignal<T>(value: ValueOrGetter<T>): [get: Getter<T>, set: Setter<T>, patchFunc: Patcher<T>];
|
|
49
|
+
declare function useReactive(): Reactive;
|
|
50
|
+
declare function createMemo<T>(computation: (prev: T) => T, initialValue?: T): Getter<T>;
|
|
51
|
+
declare function createDerivedArray<T extends object, U>(arrayGetter: Getter<T[]>, mapCallback: (item: Getter<T>, index: Getter<number>, length: Getter<number>) => U): Getter<U[]>;
|
|
52
|
+
declare function createEvent<EventType>(eventEffect?: (emitter: EventEmitter<EventType, any>) => void): EventEmitter<EventType, any>;
|
|
53
|
+
declare function provideContext<ContextType>(marker: ContextMarker<ContextType>, context: ContextType): void;
|
|
54
|
+
declare function provideReactiveContext<ContextType extends object>(marker: ContextMarker<ContextType>, mkContext: () => ContextType): ContextType;
|
|
55
|
+
|
|
56
|
+
interface HookContext {
|
|
57
|
+
reactive: Reactive;
|
|
58
|
+
mountedSignal: [Getter<boolean>, Setter<boolean>];
|
|
59
|
+
provideContexts: [ContextMarker<any>, any][];
|
|
60
|
+
}
|
|
61
|
+
interface ComponentContext extends HookContext {
|
|
62
|
+
reactive: Reactive;
|
|
63
|
+
getComponentInstance: () => JayComponent<any, any, any>;
|
|
64
|
+
mountedSignal: [Getter<boolean>, Setter<boolean>];
|
|
65
|
+
}
|
|
66
|
+
declare const COMPONENT_CONTEXT: ContextMarker<ComponentContext>;
|
|
67
|
+
declare const CONTEXT_CREATION_CONTEXT: ContextMarker<HookContext>;
|
|
68
|
+
|
|
69
|
+
export { COMPONENT_CONTEXT, CONTEXT_CREATION_CONTEXT, CONTEXT_REACTIVE_SYMBOL_CONTEXT, type ComponentConstructor, type ComponentContext, type ConcreteJayComponent, type ContextMarkers, type EffectCleanup, type HookContext, type JayComponentCore, type JayTsxComponentConstructor, type JsxNode, type Patcher, type Props, type UpdatableProps, type ViewStateGetters, createDerivedArray, createEffect, createEvent, createMemo, createPatchableSignal, createReactiveContext, createSignal, forTesting, type hasProps, makeJayComponent, makeJayTsxComponent, makePropsProxy, materializeViewState, provideContext, provideReactiveContext, useReactive };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { createJayContext, withContext, findContext, useContext } from "@jay-framework/runtime";
|
|
2
|
+
import { mkReactive, GetterMark, SetterMark, MeasureOfChange } from "@jay-framework/reactive";
|
|
3
|
+
import { patch } from "@jay-framework/json-patch";
|
|
4
|
+
const COMPONENT_CONTEXT = createJayContext();
|
|
5
|
+
const CONTEXT_CREATION_CONTEXT = createJayContext();
|
|
6
|
+
const CONTEXT_REACTIVE_SYMBOL_CONTEXT = Symbol("context-reactive");
|
|
7
|
+
function newContextProxy(reactive, context) {
|
|
8
|
+
const wrapped = /* @__PURE__ */ new Map();
|
|
9
|
+
const wrap = (target) => {
|
|
10
|
+
if (!wrapped.has(target))
|
|
11
|
+
wrapped.set(
|
|
12
|
+
target,
|
|
13
|
+
(...args) => reactive.batchReactions(() => target.apply(context, args))
|
|
14
|
+
);
|
|
15
|
+
return wrapped.get(target);
|
|
16
|
+
};
|
|
17
|
+
return new Proxy(context, {
|
|
18
|
+
get(target, p, receiver) {
|
|
19
|
+
if (p === CONTEXT_REACTIVE_SYMBOL_CONTEXT)
|
|
20
|
+
return reactive;
|
|
21
|
+
else if (!target[p][GetterMark] && !target[p][SetterMark] && typeof target[p] === "function")
|
|
22
|
+
return wrap(target[p]);
|
|
23
|
+
else
|
|
24
|
+
return target[p];
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function createReactiveContext(mkContext) {
|
|
29
|
+
const reactive = mkReactive("ctx", mkContext.name);
|
|
30
|
+
const context = withContext(
|
|
31
|
+
CONTEXT_CREATION_CONTEXT,
|
|
32
|
+
{ reactive, mountedSignal: reactive.createSignal(true), provideContexts: [] },
|
|
33
|
+
mkContext
|
|
34
|
+
);
|
|
35
|
+
return newContextProxy(reactive, context);
|
|
36
|
+
}
|
|
37
|
+
function currentHookContext() {
|
|
38
|
+
return findContext((_) => _ === COMPONENT_CONTEXT || _ === CONTEXT_CREATION_CONTEXT);
|
|
39
|
+
}
|
|
40
|
+
function createEffect(effect) {
|
|
41
|
+
let cleanup = void 0;
|
|
42
|
+
const clean = () => {
|
|
43
|
+
if (cleanup !== void 0) {
|
|
44
|
+
cleanup();
|
|
45
|
+
cleanup = void 0;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
let lastMounted = false;
|
|
49
|
+
const mounted = currentHookContext().mountedSignal[0];
|
|
50
|
+
currentHookContext().reactive.createReaction(() => {
|
|
51
|
+
if (lastMounted !== mounted()) {
|
|
52
|
+
if (mounted())
|
|
53
|
+
cleanup = effect();
|
|
54
|
+
else
|
|
55
|
+
clean();
|
|
56
|
+
lastMounted = mounted();
|
|
57
|
+
} else if (mounted()) {
|
|
58
|
+
clean();
|
|
59
|
+
cleanup = effect();
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function createSignal(value) {
|
|
64
|
+
return currentHookContext().reactive.createSignal(value);
|
|
65
|
+
}
|
|
66
|
+
function createPatchableSignal(value) {
|
|
67
|
+
const [get, set] = createSignal(value);
|
|
68
|
+
const patchFunc = (...jsonPatch) => set(patch(get(), jsonPatch));
|
|
69
|
+
return [get, set, patchFunc];
|
|
70
|
+
}
|
|
71
|
+
function useReactive() {
|
|
72
|
+
return currentHookContext().reactive;
|
|
73
|
+
}
|
|
74
|
+
function createMemo(computation, initialValue) {
|
|
75
|
+
let [value, setValue] = currentHookContext().reactive.createSignal(initialValue);
|
|
76
|
+
currentHookContext().reactive.createReaction(() => {
|
|
77
|
+
setValue((oldValue) => computation(oldValue));
|
|
78
|
+
});
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
function trackableGetter(value) {
|
|
82
|
+
let wasUsed = false;
|
|
83
|
+
return {
|
|
84
|
+
getter: () => {
|
|
85
|
+
wasUsed = true;
|
|
86
|
+
return value;
|
|
87
|
+
},
|
|
88
|
+
wasUsed: () => wasUsed
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function mapItem(item, index, length, force, cached, mapCallback) {
|
|
92
|
+
const itemGetter = trackableGetter(item);
|
|
93
|
+
const indexGetter = trackableGetter(index);
|
|
94
|
+
const lengthGetter = trackableGetter(length);
|
|
95
|
+
const needToMap = force || !cached || item !== cached.item || index !== cached.index && cached.usedIndex || length !== cached.length && cached.usedLength;
|
|
96
|
+
if (needToMap) {
|
|
97
|
+
const mappedItem = mapCallback(itemGetter.getter, indexGetter.getter, lengthGetter.getter);
|
|
98
|
+
return {
|
|
99
|
+
item,
|
|
100
|
+
mappedItem,
|
|
101
|
+
index,
|
|
102
|
+
length,
|
|
103
|
+
usedIndex: indexGetter.wasUsed(),
|
|
104
|
+
usedLength: lengthGetter.wasUsed()
|
|
105
|
+
};
|
|
106
|
+
} else
|
|
107
|
+
return cached;
|
|
108
|
+
}
|
|
109
|
+
function createDerivedArray(arrayGetter, mapCallback) {
|
|
110
|
+
let [sourceArray] = currentHookContext().reactive.createSignal(
|
|
111
|
+
arrayGetter,
|
|
112
|
+
MeasureOfChange.PARTIAL
|
|
113
|
+
);
|
|
114
|
+
let [mappedArray, setMappedArray] = createSignal([]);
|
|
115
|
+
let mappedItemsCache = /* @__PURE__ */ new WeakMap();
|
|
116
|
+
currentHookContext().reactive.createReaction((measureOfChange) => {
|
|
117
|
+
let newMappedItemsCache = /* @__PURE__ */ new WeakMap();
|
|
118
|
+
setMappedArray((oldValue) => {
|
|
119
|
+
let length = sourceArray().length;
|
|
120
|
+
let newMappedArray = sourceArray().map((item, index) => {
|
|
121
|
+
const force = measureOfChange == MeasureOfChange.FULL;
|
|
122
|
+
const mappedItemTracking = mapItem(
|
|
123
|
+
item,
|
|
124
|
+
index,
|
|
125
|
+
length,
|
|
126
|
+
force,
|
|
127
|
+
mappedItemsCache.get(item),
|
|
128
|
+
mapCallback
|
|
129
|
+
);
|
|
130
|
+
newMappedItemsCache.set(item, mappedItemTracking);
|
|
131
|
+
return mappedItemTracking.mappedItem;
|
|
132
|
+
});
|
|
133
|
+
mappedItemsCache = newMappedItemsCache;
|
|
134
|
+
return newMappedArray;
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
return mappedArray;
|
|
138
|
+
}
|
|
139
|
+
function createEvent(eventEffect) {
|
|
140
|
+
let handler;
|
|
141
|
+
let emitter = (h) => handler = h;
|
|
142
|
+
emitter.emit = (event) => handler && handler({ event });
|
|
143
|
+
if (eventEffect)
|
|
144
|
+
createEffect(() => eventEffect(emitter));
|
|
145
|
+
return emitter;
|
|
146
|
+
}
|
|
147
|
+
function provideContext(marker, context) {
|
|
148
|
+
currentHookContext().provideContexts.push([marker, context]);
|
|
149
|
+
}
|
|
150
|
+
function provideReactiveContext(marker, mkContext) {
|
|
151
|
+
const context = createReactiveContext(mkContext);
|
|
152
|
+
currentHookContext().provideContexts.push([marker, context]);
|
|
153
|
+
return context;
|
|
154
|
+
}
|
|
155
|
+
function materializeViewState(vsValueOrGetter) {
|
|
156
|
+
let vs = {};
|
|
157
|
+
for (let key in vsValueOrGetter) {
|
|
158
|
+
let value = vsValueOrGetter[key];
|
|
159
|
+
if (typeof value === "function")
|
|
160
|
+
value = value();
|
|
161
|
+
vs[key] = value;
|
|
162
|
+
}
|
|
163
|
+
return vs;
|
|
164
|
+
}
|
|
165
|
+
function renderWithContexts(provideContexts, render, viewState, index = 0) {
|
|
166
|
+
if (provideContexts.length > index) {
|
|
167
|
+
let [marker, context] = provideContexts[0];
|
|
168
|
+
return withContext(
|
|
169
|
+
marker,
|
|
170
|
+
context,
|
|
171
|
+
() => renderWithContexts(provideContexts, render, viewState, index + 1)
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
return render(viewState);
|
|
175
|
+
}
|
|
176
|
+
function mkMounts(componentContext, element) {
|
|
177
|
+
const [mounted, setMounted] = componentContext.mountedSignal;
|
|
178
|
+
componentContext.reactive.createReaction(() => {
|
|
179
|
+
if (mounted)
|
|
180
|
+
element.mount();
|
|
181
|
+
else
|
|
182
|
+
element.unmount();
|
|
183
|
+
});
|
|
184
|
+
const mount = () => {
|
|
185
|
+
componentContext.reactive.enable();
|
|
186
|
+
componentContext.reactive.batchReactions(() => setMounted(true));
|
|
187
|
+
};
|
|
188
|
+
const unmount = () => {
|
|
189
|
+
componentContext.reactive.batchReactions(() => setMounted(false));
|
|
190
|
+
componentContext.reactive.disable();
|
|
191
|
+
};
|
|
192
|
+
return [mount, unmount];
|
|
193
|
+
}
|
|
194
|
+
function makeJayComponent(preRender, comp, ...contextMarkers) {
|
|
195
|
+
return (props) => {
|
|
196
|
+
let componentInstance = null;
|
|
197
|
+
const getComponentInstance = () => {
|
|
198
|
+
return componentInstance;
|
|
199
|
+
};
|
|
200
|
+
const reactive = mkReactive();
|
|
201
|
+
const componentContext = {
|
|
202
|
+
mountedSignal: reactive.createSignal(true),
|
|
203
|
+
reactive,
|
|
204
|
+
provideContexts: [],
|
|
205
|
+
getComponentInstance
|
|
206
|
+
};
|
|
207
|
+
return withContext(COMPONENT_CONTEXT, componentContext, () => {
|
|
208
|
+
let propsProxy = makePropsProxy(componentContext.reactive, props);
|
|
209
|
+
let eventWrapper = (orig, event) => {
|
|
210
|
+
return componentContext.reactive.batchReactions(() => orig(event));
|
|
211
|
+
};
|
|
212
|
+
let [refs, render] = preRender({ eventWrapper });
|
|
213
|
+
let contexts = contextMarkers.map((marker) => {
|
|
214
|
+
const context = useContext(marker);
|
|
215
|
+
reactive.enablePairing(context[CONTEXT_REACTIVE_SYMBOL_CONTEXT]);
|
|
216
|
+
return context;
|
|
217
|
+
});
|
|
218
|
+
let coreComp = comp(propsProxy, refs, ...contexts);
|
|
219
|
+
let { render: renderViewState, ...api } = coreComp;
|
|
220
|
+
let element;
|
|
221
|
+
componentContext.provideContexts.forEach(
|
|
222
|
+
([marker, context]) => context[CONTEXT_REACTIVE_SYMBOL_CONTEXT] && reactive.enablePairing(context[CONTEXT_REACTIVE_SYMBOL_CONTEXT])
|
|
223
|
+
);
|
|
224
|
+
componentContext.reactive.createReaction(() => {
|
|
225
|
+
let viewStateValueOrGetters = renderViewState();
|
|
226
|
+
let viewState = materializeViewState(viewStateValueOrGetters);
|
|
227
|
+
if (!element)
|
|
228
|
+
element = renderWithContexts(
|
|
229
|
+
componentContext.provideContexts,
|
|
230
|
+
render,
|
|
231
|
+
viewState
|
|
232
|
+
);
|
|
233
|
+
else
|
|
234
|
+
element.update(viewState);
|
|
235
|
+
});
|
|
236
|
+
const [mount, unmount] = mkMounts(componentContext, element);
|
|
237
|
+
let update = (updateProps) => {
|
|
238
|
+
propsProxy.update(updateProps);
|
|
239
|
+
};
|
|
240
|
+
let events = {};
|
|
241
|
+
let component = {
|
|
242
|
+
element,
|
|
243
|
+
update,
|
|
244
|
+
mount,
|
|
245
|
+
unmount,
|
|
246
|
+
addEventListener: (eventType, handler) => events[eventType](handler),
|
|
247
|
+
removeEventListener: (eventType) => events[eventType](void 0)
|
|
248
|
+
};
|
|
249
|
+
for (let key in api) {
|
|
250
|
+
if (typeof api[key] === "function") {
|
|
251
|
+
if (api[key].emit) {
|
|
252
|
+
component[key] = api[key];
|
|
253
|
+
if (key.indexOf("on") === 0) {
|
|
254
|
+
let [, , ...rest] = key;
|
|
255
|
+
events[rest.join("")] = api[key];
|
|
256
|
+
}
|
|
257
|
+
} else
|
|
258
|
+
component[key] = (...args) => componentContext.reactive.batchReactions(() => api[key](...args));
|
|
259
|
+
} else {
|
|
260
|
+
component[key] = api[key];
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return componentInstance = component;
|
|
264
|
+
});
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function makeJayTsxComponent(comp) {
|
|
268
|
+
return {};
|
|
269
|
+
}
|
|
270
|
+
function makePropsProxy(reactive, props) {
|
|
271
|
+
const stateMap = {};
|
|
272
|
+
const [_props, _setProps] = createSignal(props);
|
|
273
|
+
const update = (newProps) => {
|
|
274
|
+
reactive.batchReactions(() => {
|
|
275
|
+
_setProps(newProps);
|
|
276
|
+
for (const prop in newProps) {
|
|
277
|
+
if (!stateMap.hasOwnProperty(prop))
|
|
278
|
+
stateMap[prop] = reactive.createSignal(newProps[prop]);
|
|
279
|
+
else
|
|
280
|
+
stateMap[prop][1](newProps[prop]);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
};
|
|
284
|
+
const getter = (obj, prop) => {
|
|
285
|
+
if (!stateMap.hasOwnProperty(prop))
|
|
286
|
+
stateMap[prop] = reactive.createSignal(obj[prop]);
|
|
287
|
+
return stateMap[prop][0];
|
|
288
|
+
};
|
|
289
|
+
return new Proxy(props, {
|
|
290
|
+
get: function(obj, prop) {
|
|
291
|
+
if (prop === "update")
|
|
292
|
+
return update;
|
|
293
|
+
else if (prop === "props")
|
|
294
|
+
return _props;
|
|
295
|
+
else
|
|
296
|
+
return getter(obj, prop);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
const forTesting = {
|
|
301
|
+
makePropsProxy
|
|
302
|
+
};
|
|
303
|
+
export {
|
|
304
|
+
COMPONENT_CONTEXT,
|
|
305
|
+
CONTEXT_CREATION_CONTEXT,
|
|
306
|
+
CONTEXT_REACTIVE_SYMBOL_CONTEXT,
|
|
307
|
+
createDerivedArray,
|
|
308
|
+
createEffect,
|
|
309
|
+
createEvent,
|
|
310
|
+
createMemo,
|
|
311
|
+
createPatchableSignal,
|
|
312
|
+
createReactiveContext,
|
|
313
|
+
createSignal,
|
|
314
|
+
forTesting,
|
|
315
|
+
makeJayComponent,
|
|
316
|
+
makeJayTsxComponent,
|
|
317
|
+
makePropsProxy,
|
|
318
|
+
materializeViewState,
|
|
319
|
+
provideContext,
|
|
320
|
+
provideReactiveContext,
|
|
321
|
+
useReactive
|
|
322
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Creating Jay Components
|
|
2
|
+
|
|
3
|
+
Jay Component is defined in the `@jay-framework/runtime` library as anything that implements the interface
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
interface JayComponent<Props, ViewState, jayElement extends BaseJayElement<ViewState>> {
|
|
7
|
+
element: jayElement;
|
|
8
|
+
update: updateFunc<Props>;
|
|
9
|
+
mount: MountFunc;
|
|
10
|
+
unmount: MountFunc;
|
|
11
|
+
addEventListener: (type: string, handler: JayEventHandler<any, ViewState, void>) => void;
|
|
12
|
+
removeEventListener: (type: string, handler: JayEventHandler<any, ViewState, void>) => void;
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
the `@jay-framework/component` library defines a reactive and elegant way to create headless Jay-Components.
|
|
17
|
+
|
|
18
|
+
## Simple Example
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { render, CounterElementRefs } from './counter.jay-html';
|
|
22
|
+
import { createSignal, makeJayComponent, Props } from '@jay-framework/component';
|
|
23
|
+
|
|
24
|
+
export interface CounterProps {
|
|
25
|
+
initialValue: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function CounterConstructor({ initialValue }: Props<CounterProps>, refs: CounterElementRefs) {
|
|
29
|
+
const [count, setCount] = createSignal(initialValue);
|
|
30
|
+
|
|
31
|
+
refs.subtracter.onclick(() => setCount(count() - 1));
|
|
32
|
+
refs.adderButton.onclick(() => setCount(count() + 1));
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
render: () => ({ count }),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const Counter = makeJayComponent(render, CounterConstructor);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The full example can be found at [counter.ts](../../../../examples/jay/counter/src/counter.ts)
|
|
43
|
+
|
|
44
|
+
## The Component Constructor
|
|
45
|
+
|
|
46
|
+
The `@jay-framework/component` library offers constructing jay components using a component constructor function of the form
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
interface JayComponentCore<PropsT, ViewState> {
|
|
50
|
+
render: (props: Props<PropsT>) => ViewStateGetters<ViewState>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
type ComponentConstructor<
|
|
54
|
+
PropsT extends object,
|
|
55
|
+
Refs extends object,
|
|
56
|
+
ViewState extends object,
|
|
57
|
+
Contexts extends Array<any>,
|
|
58
|
+
CompCore extends JayComponentCore<PropsT, ViewState>,
|
|
59
|
+
> = (props: Props<PropsT>, refs: Refs, ...contexts: Contexts) => CompCore;
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Parameters:
|
|
63
|
+
|
|
64
|
+
- `props: Props<PropsT>`: an object holding signals for each of the component properties.
|
|
65
|
+
The `Props<T>` generic type turns an object of values into an object of signal getters.
|
|
66
|
+
- `refs: Refs`: the refs type from the associated Jay Element of the components
|
|
67
|
+
- `...contexts: Contexts`: The context instances requested by the `makeJayComponent` function
|
|
68
|
+
|
|
69
|
+
### Returns
|
|
70
|
+
|
|
71
|
+
The function has to return an object implementing `JayComponentCore` which has to have a render function.
|
|
72
|
+
In addition, the returned object may have exported API functions and exported events of the component.
|
|
73
|
+
|
|
74
|
+
#### returned object render function
|
|
75
|
+
|
|
76
|
+
The returned `JayComponentCore` has to have a render function that returns `ViewStateGetters<ViewState>`.
|
|
77
|
+
The `ViewState` is the view state of the jay-element the component is using, and the `ViewStateGetters` allows
|
|
78
|
+
to provide the view state as values or getter functions.
|
|
79
|
+
|
|
80
|
+
both of the below are valid usages of render:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// as a signal
|
|
84
|
+
const [count, setCount] = createSignal(initialValue);
|
|
85
|
+
return {
|
|
86
|
+
render: () => ({ count }),
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// as a value
|
|
90
|
+
return {
|
|
91
|
+
render: () => ({ count: count() }),
|
|
92
|
+
};
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The render function itself is reactive, allowing to perform calculations as part of the render function.
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// using a computation
|
|
99
|
+
const [count, setCount] = createSignal(initialValue);
|
|
100
|
+
return {
|
|
101
|
+
render: () => ({ count, description: `the count is ${count()}` }),
|
|
102
|
+
};
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## The `makeJayComponent` function
|
|
106
|
+
|
|
107
|
+
The `makeJayComponent` function returns a function that when called with `props`, will create a component instance.
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
declare function makeJayComponent<
|
|
111
|
+
PropsT extends object,
|
|
112
|
+
ViewState extends object,
|
|
113
|
+
Refs extends object,
|
|
114
|
+
JayElementT extends JayElement<ViewState, Refs>,
|
|
115
|
+
Contexts extends Array<any>,
|
|
116
|
+
CompCore extends JayComponentCore<PropsT, ViewState>,
|
|
117
|
+
>(
|
|
118
|
+
preRender: PreRenderElement<ViewState, Refs, JayElementT>,
|
|
119
|
+
comp: ComponentConstructor<PropsT, Refs, ViewState, Contexts, CompCore>,
|
|
120
|
+
...contextMarkers: ContextMarkers<Contexts>
|
|
121
|
+
): (props: PropsT) => ConcreteJayComponent<PropsT, ViewState, Refs, CompCore, JayElementT>;
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Parameters:
|
|
125
|
+
|
|
126
|
+
- `preRender: PreRenderElement<ViewState, Refs, JayElementT>`: The render function from a `JayElement` constructor.
|
|
127
|
+
- `comp: ComponentConstructor<PropsT, Refs, ViewState, Contexts, CompCore>`: the component constructor function.
|
|
128
|
+
- `...contextMarkers: ContextMarkers<Contexts>`: zero or more context markers created using `createJayContext`.
|
|
129
|
+
read more about providing contexts in [provide-context.md](./provide-context.md) and [provide-reactive-context.md](./provide-reactive-context.md).
|
|
130
|
+
|
|
131
|
+
### Returns
|
|
132
|
+
|
|
133
|
+
A function that when called with props creates a component instance.
|
|
134
|
+
|
|
135
|
+
#### returned function parameters:
|
|
136
|
+
|
|
137
|
+
- `props: PropsT`: an object holding the properties for the component.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# createDerivedArray
|
|
2
|
+
|
|
3
|
+
Creates a derived mapped array that updates whenever the source array or its elements change.
|
|
4
|
+
|
|
5
|
+
When using immutable state, the `Array.map` function always creates a new array with new items.
|
|
6
|
+
`createDerivedArray` only recreates new items if the original item has changed, or a dependency of the mapping function
|
|
7
|
+
has changed. `createDerivedArray` is the reactive equivalent of `Array.map`.
|
|
8
|
+
|
|
9
|
+
The mapping function is recalculated if
|
|
10
|
+
|
|
11
|
+
- the original item has changed
|
|
12
|
+
- the item index has changed, and the mapping function is using the index signal
|
|
13
|
+
- the original array length has changed, and the mapping function is using the length signal
|
|
14
|
+
- any other signal the mapping function depends on has changed
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
declare function createDerivedArray<T extends object, U>(
|
|
18
|
+
arrayGetter: Getter<T[]>,
|
|
19
|
+
mapCallback: (item: Getter<T>, index: Getter<number>, length: Getter<number>) => U,
|
|
20
|
+
): Getter<U[]>;
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Parameters:
|
|
24
|
+
|
|
25
|
+
- `arrayGetter: Getter<T[]>`: A getter function for the source array.
|
|
26
|
+
- `mapCallback`: A function that maps each item in the source array to a new value.
|
|
27
|
+
- `item: Getter<T>`: the original array item
|
|
28
|
+
- `index: Getter<number>`: the index of the original array item
|
|
29
|
+
- `length: Getter<number>`: the length of the original array
|
|
30
|
+
|
|
31
|
+
## Returns:
|
|
32
|
+
|
|
33
|
+
A getter function for the derived array.
|
|
34
|
+
|
|
35
|
+
## Example
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
const taskData = createDerivedArray(pillarTasks, (item, index, length) => {
|
|
39
|
+
let { id, title, description } = item();
|
|
40
|
+
return {
|
|
41
|
+
id,
|
|
42
|
+
taskProps: {
|
|
43
|
+
title,
|
|
44
|
+
description,
|
|
45
|
+
hasNext: hasNext(),
|
|
46
|
+
hasPrev: hasPrev(),
|
|
47
|
+
isBottom: index() === length() - 1,
|
|
48
|
+
isTop: index() === 0,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
See the full example in [Scrum Board/lib/pillar.ts](../../../../examples/jay/scrum-board/lib/pillar.ts).
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# createEffect
|
|
2
|
+
|
|
3
|
+
Creates a side effect that runs after the component renders and cleans up when the component unmounts.
|
|
4
|
+
|
|
5
|
+
The effect function is called initially on hook creation, as well as if any of the signals it depends on change.
|
|
6
|
+
|
|
7
|
+
If the `effect` function returns a cleanup function, the cleanup function is called on component unmount or if
|
|
8
|
+
any of the signal the effect function depends on change, before recalling the effect function.
|
|
9
|
+
|
|
10
|
+
```typescript
|
|
11
|
+
type EffectCleanup = () => void;
|
|
12
|
+
declare function createEffect(effect: () => void | EffectCleanup);
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Parameters:
|
|
16
|
+
|
|
17
|
+
- `effect`: A function that returns a cleanup function or undefined.
|
|
18
|
+
|
|
19
|
+
## example:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
createEffect(() => {
|
|
23
|
+
console.log('board', 'pillars:', pillars());
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
See the full example in [Scrum Board/lib/board.ts](../../../../examples/jay/scrum-board/lib/board.ts).
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# createEvent
|
|
2
|
+
|
|
3
|
+
Creates a Jay component event emitter.
|
|
4
|
+
|
|
5
|
+
Jay, unlike React or Solid.js, does not accept callbacks as props. Instead, the component constructor function has to
|
|
6
|
+
return as part of the returned object event emitters. The `createEvent` function is used to create those event emitters.
|
|
7
|
+
|
|
8
|
+
The event emitter itself is a function to register `JayEventHandler`s and has a member to `emit` events.
|
|
9
|
+
|
|
10
|
+
`createEvent` can optionally accept an `eventEffect` function which is used to create an effect given the event emitter.
|
|
11
|
+
It makes it simple to create an event that is triggered reactively based on the `eventEffect` effect function.
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
interface EventEmitter<EventType, ViewState> {
|
|
15
|
+
(handler: JayEventHandler<EventType, ViewState, void>): void;
|
|
16
|
+
emit: (event?: EventType) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
declare function createEvent<EventType>(
|
|
20
|
+
eventEffect?: (emitter: EventEmitter<EventType, any>) => void,
|
|
21
|
+
): EventEmitter<EventType, any>;
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Parameters:
|
|
25
|
+
|
|
26
|
+
- `eventEffect`: An optional effect to run when the event is emitted.
|
|
27
|
+
|
|
28
|
+
## Returns:
|
|
29
|
+
|
|
30
|
+
An event emitter object.
|
|
31
|
+
|
|
32
|
+
## Examples:
|
|
33
|
+
|
|
34
|
+
A simple event emitter:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
function TaskConstructor(props: Props<TaskProps>, refs: TaskElementRefs) {
|
|
38
|
+
let onNext = createEvent();
|
|
39
|
+
let onPrev = createEvent();
|
|
40
|
+
let onUp = createEvent();
|
|
41
|
+
let onDown = createEvent();
|
|
42
|
+
|
|
43
|
+
refs.next.onclick(() => onNext.emit());
|
|
44
|
+
refs.up.onclick(() => onUp.emit());
|
|
45
|
+
refs.down.onclick(() => onDown.emit());
|
|
46
|
+
refs.prev.onclick(() => onPrev.emit());
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
render: () => props,
|
|
50
|
+
onNext,
|
|
51
|
+
onDown,
|
|
52
|
+
onUp,
|
|
53
|
+
onPrev,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
See the full example in [Scrum Board/lib/task.ts](../../../../examples/jay/scrum-board/lib/task.ts).
|
|
59
|
+
|
|
60
|
+
An event emitter with `eventEffect`:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
function CounterComponent({}: Props<CounterProps>, refs: CounterRefs) {
|
|
64
|
+
let [value, setValue] = createSignal(0);
|
|
65
|
+
refs.inc.onclick(() => setValue(value() + 1));
|
|
66
|
+
refs.dec.onclick(() => setValue(value() - 1));
|
|
67
|
+
let onChange = createEvent<CounterChangeEvent>((emitter) => emitter.emit({ value: value() }));
|
|
68
|
+
return {
|
|
69
|
+
render: () => ({ value }),
|
|
70
|
+
onChange,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
In the above example, the `createEffect` parameter of `createEvent` is a function that reads the signal `value`, and
|
|
76
|
+
those the effect will run anytime the `value` signal changes, emitting an event.
|
|
77
|
+
|
|
78
|
+
See the full example in [component.test.ts](../test/component.test.ts).
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# createMemo
|
|
2
|
+
|
|
3
|
+
Creates a memoized value that updates only when its dependencies change.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
declare function createMemo<T>(computation: (prev: T) => T, initialValue?: T): Getter<T>;
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Parameters:
|
|
10
|
+
|
|
11
|
+
- `computation`: A function that calculates the new value based on the previous value.
|
|
12
|
+
- `initialValue`: The initial value of the memo.
|
|
13
|
+
|
|
14
|
+
## Returns:
|
|
15
|
+
|
|
16
|
+
A getter function for the memoized value.
|
|
17
|
+
|
|
18
|
+
## Example:
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
const activeTodoCount = createMemo(() =>
|
|
22
|
+
todos().reduce(function (accum: number, todo: ShownTodo) {
|
|
23
|
+
return todo.isCompleted ? accum : accum + 1;
|
|
24
|
+
}, 0),
|
|
25
|
+
);
|
|
26
|
+
const noActiveItems = createMemo(() => activeTodoCount() === 0);
|
|
27
|
+
const activeTodoWord = createMemo(() => (activeTodoCount() > 1 ? 'todos' : 'todo'));
|
|
28
|
+
const hasItems = createMemo(() => todos().length > 0);
|
|
29
|
+
const showClearCompleted = createMemo(() => !!todos().find((_) => _.isCompleted));
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
See the full example in [Todo/lib/todo.ts](../../../../examples/jay/todo/lib/todo.ts).
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# createPatchableSignal
|
|
2
|
+
|
|
3
|
+
Creates a reactive signal that also supports update using `JSONPatch`.
|
|
4
|
+
|
|
5
|
+
As Jay is using immutable state objects, when we need to update a deep structure of objects, we have to replace
|
|
6
|
+
the updated object and all parent objects. `createPatchableSignal` is a utility that is using the same `patch` from
|
|
7
|
+
`@jay-framework/json-patch` to simplify this update.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
declare function createPatchableSignal<T>(
|
|
11
|
+
value: ValueOrGetter<T>,
|
|
12
|
+
): [get: Getter<T>, set: Setter<T>, patchFunc: Patcher<T>];
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Parameters:
|
|
16
|
+
|
|
17
|
+
- `value`: The initial value of the signal.
|
|
18
|
+
|
|
19
|
+
## Returns:
|
|
20
|
+
|
|
21
|
+
A tuple containing a `getter`, a `setter`, and a `patch` function.
|
|
22
|
+
|
|
23
|
+
- `getter: Getter<T>` function to get the signal value.
|
|
24
|
+
- `setter: Setter<T>` function to set the signal value. The setter function can receive a value or a `Next` function that
|
|
25
|
+
given the old signal value will calculate the next signal value.
|
|
26
|
+
- `patch: Patcher<T>` function to patch the signal value using `...JSONPatch` (spread json patch).
|
|
27
|
+
|
|
28
|
+
## Examples:
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
const [todos, setTodos, patchTodos] = createPatchableSignal(
|
|
32
|
+
initialTodos().map((_) => ({ ..._, isEditing: false, editText: '' })),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// in some event handler
|
|
36
|
+
patchTodos(
|
|
37
|
+
{ op: REPLACE, path: [itemIndex, 'title'], value: val },
|
|
38
|
+
{ op: REPLACE, path: [itemIndex, 'isEditing'], value: false },
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// in another event handler
|
|
42
|
+
patchTodos({
|
|
43
|
+
op: ADD,
|
|
44
|
+
path: [todos().length],
|
|
45
|
+
value: {
|
|
46
|
+
id: uuid(),
|
|
47
|
+
title: val,
|
|
48
|
+
isEditing: false,
|
|
49
|
+
editText: '',
|
|
50
|
+
isCompleted: false,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
See the full example in [todo-one-flat-component/lib/todo.ts](../../../../examples/jay/todo-one-flat-component/lib/todo.ts).
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# createSignal
|
|
2
|
+
|
|
3
|
+
Creates a reactive signal. The signal can be initialized using a value or a `Getter`.
|
|
4
|
+
|
|
5
|
+
If initialized with a Getter, the signal will track the Getter's signal value, useful for signals tracking prop values
|
|
6
|
+
while allowing to update the signal value directly.
|
|
7
|
+
|
|
8
|
+
```typescript
|
|
9
|
+
declare function createSignal<T>(value: ValueOrGetter<T>): [get: Getter<T>, set: Setter<T>];
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Parameters:
|
|
13
|
+
|
|
14
|
+
- `value`: The initial value of the signal, or a function that returns a value track.
|
|
15
|
+
|
|
16
|
+
If the value is a function, createSignal automatically wraps it with Reactive's createReaction
|
|
17
|
+
creating a dependency between any signals read within the function and the created signal.
|
|
18
|
+
|
|
19
|
+
## Returns:
|
|
20
|
+
|
|
21
|
+
A tuple containing a `getter` and a `setter` for the signal's value.
|
|
22
|
+
|
|
23
|
+
- `getter: Getter<T>` function to get the signal value.
|
|
24
|
+
- `setter: Setter<T>` function to set the signal value. The setter function can receive a value or a `Next` function that
|
|
25
|
+
given the old signal value will calculate the next signal value.
|
|
26
|
+
|
|
27
|
+
## Examples:
|
|
28
|
+
|
|
29
|
+
Creating a signal with an initial value
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
const [count, setCount] = createSignal(initialValue);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Creating a signal that follows two other signal values
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
const [a, setA] = createSignal(10);
|
|
39
|
+
const [b, setB] = createSignal(20);
|
|
40
|
+
const [c, setC] = createSignal(() => a() + b());
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
In the above example, the signal `c` can be updated using the `setC` setter, or it is updated automatically
|
|
44
|
+
whenever the signals `a` or `b` are updated.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# provideContext
|
|
2
|
+
|
|
3
|
+
Provides a context value to child components.
|
|
4
|
+
|
|
5
|
+
A Context created using `provideContext` is injected into child components who declare dependency on it using the same
|
|
6
|
+
context `marker`.
|
|
7
|
+
|
|
8
|
+
This context is not reactive, and any update to the context values will not trigger updates in any components.
|
|
9
|
+
|
|
10
|
+
- To trigger changes in components as a context changes, use the `provideReactiveContext` hook instead.
|
|
11
|
+
- This hook is great for dependency injection into child components.
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
declare function provideContext<ContextType>(
|
|
15
|
+
marker: ContextMarker<ContextType>,
|
|
16
|
+
context: ContextType,
|
|
17
|
+
);
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Parameters:
|
|
21
|
+
|
|
22
|
+
- `marker`: A unique symbol identifying the context, created using `createJayContext`.
|
|
23
|
+
- `context`: The context value to provide.
|
|
24
|
+
|
|
25
|
+
## Example
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
const COUNT_CONTEXT = createJayContext<CountContext>();
|
|
29
|
+
|
|
30
|
+
// in a component constructor
|
|
31
|
+
provideContext(COUNT_CONTEXT, context);
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## design log
|
|
35
|
+
|
|
36
|
+
For additional information on the design decisions of Jay Context API, read
|
|
37
|
+
[16 - context api.md](../../../../design-log/16%20-%20context%20api.md),
|
|
38
|
+
[21 - alternative to context API.md](../../../../design-log/21%20-%20alternative%20to%20context%20API.md),
|
|
39
|
+
[30 - Jay Context API.md](../../../../design-log/30%20-%20Jay%20Context%20API.md)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# provideReactiveContext
|
|
2
|
+
|
|
3
|
+
Provides a reactive context value to child components.
|
|
4
|
+
|
|
5
|
+
The reactive context is similar to a Jay Component by having it's own Reactive instance, allowing it to use any
|
|
6
|
+
of Jay Hooks (`createSignal`, `createEffect`, etc.) as well as export API functions.
|
|
7
|
+
|
|
8
|
+
- Child Components who read reactive context signal getters are said to be paired, and changes to such context signals
|
|
9
|
+
will trigger updates of components reading those signal values.
|
|
10
|
+
- API functions of reactive context are automatically wrapped by `reactive.batchReactions`.
|
|
11
|
+
- Best practice is to update a context using exported API functions as it ensures optimal computation of dependent effects.
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
declare function provideReactiveContext<ContextType extends object>(
|
|
15
|
+
marker: ContextMarker<ContextType>,
|
|
16
|
+
mkContext: () => ContextType,
|
|
17
|
+
): ContextType;
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Parameters:
|
|
21
|
+
|
|
22
|
+
- `marker`: A unique symbol identifying the context, created using `createJayContext`.
|
|
23
|
+
- `mkContext`: A function that creates the initial context value.
|
|
24
|
+
The function may use any of the Jay Hooks and should return an object which represents the context.
|
|
25
|
+
|
|
26
|
+
## Returns:
|
|
27
|
+
|
|
28
|
+
The created reactive context value.
|
|
29
|
+
|
|
30
|
+
## Examples:
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
export const SCRUM_CONTEXT = createJayContext<ScrumContext>();
|
|
34
|
+
export const provideScrumContext = () =>
|
|
35
|
+
provideReactiveContext(SCRUM_CONTEXT, () => {
|
|
36
|
+
let [pillars, setPillars] = createSignal(DEFAULT_PILLARS);
|
|
37
|
+
|
|
38
|
+
const moveTaskToNext = (pillarId: string, taskId: string) => {
|
|
39
|
+
setPillars(moveTask(pillars(), pillarId, taskId, +1, 0));
|
|
40
|
+
};
|
|
41
|
+
const moveTaskToPrev = (pillarId: string, taskId: string) => {
|
|
42
|
+
setPillars(moveTask(pillars(), pillarId, taskId, -1, 0));
|
|
43
|
+
};
|
|
44
|
+
const moveTaskUp = (pillarId: string, taskId: string) => {
|
|
45
|
+
setPillars(moveTask(pillars(), pillarId, taskId, 0, +1));
|
|
46
|
+
};
|
|
47
|
+
const moveTaskDown = (pillarId: string, taskId: string) => {
|
|
48
|
+
setPillars(moveTask(pillars(), pillarId, taskId, 0, -1));
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
pillars,
|
|
53
|
+
moveTaskToNext,
|
|
54
|
+
moveTaskToPrev,
|
|
55
|
+
moveTaskDown,
|
|
56
|
+
moveTaskUp,
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
See the full example in [scrum-context.ts](../../../../examples/jay-context/scrum-board-with-context/lib/scrum-context.ts)
|
|
62
|
+
|
|
63
|
+
## design log
|
|
64
|
+
|
|
65
|
+
For additional information on the design decisions of Jay Context API, read
|
|
66
|
+
[16 - context api.md](../../../../design-log/16%20-%20context%20api.md),
|
|
67
|
+
[21 - alternative to context API.md](../../../../design-log/21%20-%20alternative%20to%20context%20API.md),
|
|
68
|
+
[30 - Jay Context API.md](../../../../design-log/30%20-%20Jay%20Context%20API.md)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# useReactive
|
|
2
|
+
|
|
3
|
+
Gets the current reactive context of the current component or reactive context.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
declare function useReactive(): Reactive;
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Returns:
|
|
10
|
+
|
|
11
|
+
The current reactive context object.
|
|
12
|
+
|
|
13
|
+
Read more about Jay Reactive in [reactive](..%2F..%2Freactive)
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jay-framework/component",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"docs",
|
|
10
|
+
"readme.md"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "npm run build:js && npm run build:types",
|
|
14
|
+
"build:watch": "npm run build:js -- --watch & npm run build:types -- --watch",
|
|
15
|
+
"build:js": "vite build",
|
|
16
|
+
"build:types": "tsup lib/index.ts --dts-only --format esm",
|
|
17
|
+
"build:check-types": "tsc",
|
|
18
|
+
"clean": "rimraf dist",
|
|
19
|
+
"confirm": "npm run clean && npm run build && npm run build:check-types && npm run test",
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"test:watch": "vitest"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@jay-framework/json-patch": "workspace:^",
|
|
25
|
+
"@jay-framework/reactive": "workspace:^",
|
|
26
|
+
"@jay-framework/runtime": "workspace:^"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@jay-framework/dev-environment": "workspace:^",
|
|
30
|
+
"@testing-library/jest-dom": "^6.2.0",
|
|
31
|
+
"@types/node": "^20.11.5",
|
|
32
|
+
"rimraf": "^5.0.5",
|
|
33
|
+
"tsup": "^8.0.1",
|
|
34
|
+
"typescript": "^5.3.3",
|
|
35
|
+
"vite": "^5.0.11",
|
|
36
|
+
"vitest": "^1.2.1"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Jay Component
|
|
2
|
+
|
|
3
|
+
The Jay Component library defines the methods of constructing a Jay Component.
|
|
4
|
+
|
|
5
|
+
# Creating Components
|
|
6
|
+
|
|
7
|
+
- [Creating Jay Components](./docs/component.md)
|
|
8
|
+
|
|
9
|
+
# Jay Component Hooks
|
|
10
|
+
|
|
11
|
+
hooks to create signals:
|
|
12
|
+
|
|
13
|
+
- [createSignal](./docs/create-signal.md)
|
|
14
|
+
- [createPatchableSignal](./docs/create-patchable-signal.md)
|
|
15
|
+
|
|
16
|
+
hooks to create computed values:
|
|
17
|
+
|
|
18
|
+
- [createMemo](./docs/create-memo.md)
|
|
19
|
+
- [createDerivedArray](./docs/create-derived-array.md)
|
|
20
|
+
|
|
21
|
+
hooks to create reactions & events:
|
|
22
|
+
|
|
23
|
+
- [createEffect](./docs/create-effect.md)
|
|
24
|
+
- [createEvent](./docs/create-event.md)
|
|
25
|
+
|
|
26
|
+
Hooks for providing context:
|
|
27
|
+
|
|
28
|
+
- [provideContext](./docs/provide-context.md)
|
|
29
|
+
- [provideReactiveContext](./docs/provide-reactive-context.md)
|
|
30
|
+
|
|
31
|
+
Hooks to get Component Reactive Instance:
|
|
32
|
+
|
|
33
|
+
- [use-reactive](./docs/use-reactive.md)
|