@fun-land/fun-web 0.4.0 → 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/README.md +104 -93
- package/dist/esm/src/dom.d.ts +26 -27
- package/dist/esm/src/dom.js +59 -44
- package/dist/esm/src/dom.js.map +1 -1
- package/dist/esm/src/index.d.ts +1 -1
- package/dist/esm/src/index.js +1 -1
- package/dist/esm/src/index.js.map +1 -1
- package/dist/esm/src/types.d.ts +1 -1
- package/dist/esm/tsconfig.publish.tsbuildinfo +1 -1
- package/dist/src/dom.d.ts +26 -27
- package/dist/src/dom.js +62 -49
- package/dist/src/dom.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +2 -4
- package/dist/src/index.js.map +1 -1
- package/dist/src/types.d.ts +1 -1
- package/dist/tsconfig.publish.tsbuildinfo +1 -1
- package/examples/README.md +1 -1
- package/examples/todo-app/AddTodoForm.ts +4 -4
- package/examples/todo-app/DraggableTodoList.ts +52 -47
- package/examples/todo-app/README.md +1 -1
- package/examples/todo-app/Todo.ts +7 -7
- package/examples/todo-app/TodoApp.js +94 -51
- package/examples/todo-app/TodoApp.ts +4 -9
- package/package.json +5 -5
- package/src/dom.test.ts +289 -290
- package/src/dom.ts +168 -155
- package/src/index.ts +1 -3
- package/src/types.ts +3 -4
|
@@ -28,18 +28,10 @@
|
|
|
28
28
|
function comp(...accs) {
|
|
29
29
|
return accs.reduce(_comp);
|
|
30
30
|
}
|
|
31
|
-
var viewed = (toView, fromView) => ({
|
|
32
|
-
query: (s) => [toView(s)],
|
|
33
|
-
mod: (f) => (s) => fromView(f(toView(s)))
|
|
34
|
-
});
|
|
35
31
|
var all = () => ({
|
|
36
32
|
query: (xs) => xs,
|
|
37
33
|
mod: (transform) => (xs) => xs.map(transform)
|
|
38
34
|
});
|
|
39
|
-
var filter = (pred) => ({
|
|
40
|
-
query: (xs) => xs.filter(pred),
|
|
41
|
-
mod: (transform) => (s) => s.map((x) => pred(x) ? transform(x) : x)
|
|
42
|
-
});
|
|
43
35
|
var set = (acc) => flow(K, acc.mod);
|
|
44
36
|
var get = (acc) => (s) => {
|
|
45
37
|
var _a;
|
|
@@ -187,7 +179,29 @@
|
|
|
187
179
|
};
|
|
188
180
|
var onTo = (type, handler, signal) => (el) => on(el, type, handler, signal);
|
|
189
181
|
var enhance = (x, ...fns) => fns.reduce((acc, fn) => fn(acc), x);
|
|
190
|
-
|
|
182
|
+
var keyOf = (keyAcc, item) => {
|
|
183
|
+
const k = keyAcc.query(item)[0];
|
|
184
|
+
if (k == null)
|
|
185
|
+
throw new Error("bindListChildren: key accessor returned no value");
|
|
186
|
+
return k;
|
|
187
|
+
};
|
|
188
|
+
var byKey = (keyAcc, key) => ({
|
|
189
|
+
query: (xs) => {
|
|
190
|
+
const hit = xs.find((t) => keyOf(keyAcc, t) === key);
|
|
191
|
+
return hit ? [hit] : [];
|
|
192
|
+
},
|
|
193
|
+
mod: (f) => (xs) => {
|
|
194
|
+
let found = false;
|
|
195
|
+
return xs.map((t) => {
|
|
196
|
+
if (keyOf(keyAcc, t) !== key) return t;
|
|
197
|
+
if (found) throw new Error(`bindListChildren: duplicate key "${key}"`);
|
|
198
|
+
found = true;
|
|
199
|
+
return f(t);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
var bindListChildren = (options) => (parent) => {
|
|
204
|
+
const { signal, state: list, key: keyAcc, row: renderRow } = options;
|
|
191
205
|
const rows = /* @__PURE__ */ new Map();
|
|
192
206
|
const dispose = () => {
|
|
193
207
|
for (const row of rows.values()) {
|
|
@@ -201,8 +215,9 @@
|
|
|
201
215
|
const nextKeys = [];
|
|
202
216
|
const seen = /* @__PURE__ */ new Set();
|
|
203
217
|
for (const it of items) {
|
|
204
|
-
const k = it
|
|
205
|
-
if (seen.has(k))
|
|
218
|
+
const k = keyOf(keyAcc, it);
|
|
219
|
+
if (seen.has(k))
|
|
220
|
+
throw new Error(`bindListChildren: duplicate key "${k}"`);
|
|
206
221
|
seen.add(k);
|
|
207
222
|
nextKeys.push(k);
|
|
208
223
|
}
|
|
@@ -216,13 +231,13 @@
|
|
|
216
231
|
for (const k of nextKeys) {
|
|
217
232
|
if (!rows.has(k)) {
|
|
218
233
|
const ctrl = new AbortController();
|
|
219
|
-
const itemState = list.focus(
|
|
234
|
+
const itemState = list.focus(byKey(keyAcc, k));
|
|
220
235
|
const el = renderRow({
|
|
221
236
|
signal: ctrl.signal,
|
|
222
237
|
state: itemState,
|
|
223
|
-
remove: () => list.mod((
|
|
238
|
+
remove: () => list.mod((xs) => xs.filter((t) => keyOf(keyAcc, t) !== k))
|
|
224
239
|
});
|
|
225
|
-
rows.set(k, {
|
|
240
|
+
rows.set(k, { el, ctrl });
|
|
226
241
|
}
|
|
227
242
|
}
|
|
228
243
|
const children = parent.children;
|
|
@@ -230,17 +245,51 @@
|
|
|
230
245
|
const k = nextKeys[i];
|
|
231
246
|
const row = rows.get(k);
|
|
232
247
|
const currentAtI = children[i];
|
|
233
|
-
if (currentAtI !== row.el)
|
|
248
|
+
if (currentAtI !== row.el)
|
|
234
249
|
parent.insertBefore(row.el, currentAtI ?? null);
|
|
235
|
-
}
|
|
236
250
|
}
|
|
237
251
|
};
|
|
238
252
|
list.watch(signal, reconcile);
|
|
239
253
|
signal.addEventListener("abort", dispose, { once: true });
|
|
240
254
|
reconcile();
|
|
241
|
-
return
|
|
255
|
+
return parent;
|
|
256
|
+
};
|
|
257
|
+
function renderWhen(options) {
|
|
258
|
+
const {
|
|
259
|
+
state,
|
|
260
|
+
predicate = (x) => x,
|
|
261
|
+
component,
|
|
262
|
+
props,
|
|
263
|
+
signal
|
|
264
|
+
} = options;
|
|
265
|
+
const container = document.createElement("span");
|
|
266
|
+
container.style.display = "contents";
|
|
267
|
+
let childCtrl = null;
|
|
268
|
+
let childEl = null;
|
|
269
|
+
const reconcile = () => {
|
|
270
|
+
const shouldRender = predicate(state.get());
|
|
271
|
+
if (shouldRender && !childEl) {
|
|
272
|
+
childCtrl = new AbortController();
|
|
273
|
+
childEl = component(childCtrl.signal, props);
|
|
274
|
+
container.appendChild(childEl);
|
|
275
|
+
} else if (!shouldRender && childEl) {
|
|
276
|
+
childCtrl?.abort();
|
|
277
|
+
childEl.remove();
|
|
278
|
+
childEl = null;
|
|
279
|
+
childCtrl = null;
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
state.watch(signal, reconcile);
|
|
283
|
+
signal.addEventListener(
|
|
284
|
+
"abort",
|
|
285
|
+
() => {
|
|
286
|
+
childCtrl?.abort();
|
|
287
|
+
},
|
|
288
|
+
{ once: true }
|
|
289
|
+
);
|
|
290
|
+
reconcile();
|
|
291
|
+
return container;
|
|
242
292
|
}
|
|
243
|
-
var $ = (selector) => document.querySelector(selector) ?? void 0;
|
|
244
293
|
|
|
245
294
|
// src/mount.ts
|
|
246
295
|
var mount = (component, props, container) => {
|
|
@@ -260,8 +309,8 @@
|
|
|
260
309
|
var init_TodoAppState = () => ({
|
|
261
310
|
value: "",
|
|
262
311
|
items: [
|
|
263
|
-
{ checked: false, label: "Learn fun-web",
|
|
264
|
-
{ checked: true, label: "Build something cool",
|
|
312
|
+
{ checked: false, label: "Learn fun-web", key: "asdf" },
|
|
313
|
+
{ checked: true, label: "Build something cool", key: "fdas" }
|
|
265
314
|
]
|
|
266
315
|
});
|
|
267
316
|
var stateAcc = Acc();
|
|
@@ -272,27 +321,13 @@
|
|
|
272
321
|
prepend({
|
|
273
322
|
checked: false,
|
|
274
323
|
label: state.value,
|
|
275
|
-
priority: 1,
|
|
276
324
|
key: crypto.randomUUID()
|
|
277
325
|
})
|
|
278
326
|
)(state);
|
|
279
327
|
|
|
280
|
-
// examples/todo-app/TodoState.ts
|
|
281
|
-
var stateAcc2 = Acc();
|
|
282
|
-
var priorityAsString = stateAcc2.prop("priority").focus(viewed(String, Number));
|
|
283
|
-
|
|
284
328
|
// examples/todo-app/Todo.ts
|
|
285
329
|
var Todo = (signal, { state, removeItem, onDragStart, onDragEnd, onDragOver }) => {
|
|
286
330
|
const todoData = state.get();
|
|
287
|
-
const priorityState = state.focus(priorityAsString);
|
|
288
|
-
const prioritySelect = enhance(
|
|
289
|
-
h("select", {}, [
|
|
290
|
-
h("option", { value: "0" }, "High"),
|
|
291
|
-
h("option", { value: "1" }, "Low")
|
|
292
|
-
]),
|
|
293
|
-
bindPropertyTo("value", priorityState, signal),
|
|
294
|
-
onTo("change", (e) => priorityState.set(e.currentTarget.value), signal)
|
|
295
|
-
);
|
|
296
331
|
const checkedState = state.prop("checked");
|
|
297
332
|
const checkbox = enhance(
|
|
298
333
|
h("input", { type: "checkbox" }),
|
|
@@ -319,7 +354,6 @@
|
|
|
319
354
|
const li = h("li", { className: "todo-item", "data-key": todoData.key }, [
|
|
320
355
|
dragHandle,
|
|
321
356
|
checkbox,
|
|
322
|
-
prioritySelect,
|
|
323
357
|
labelInput,
|
|
324
358
|
deleteBtn
|
|
325
359
|
]);
|
|
@@ -372,7 +406,7 @@
|
|
|
372
406
|
|
|
373
407
|
// examples/todo-app/DraggableTodoList.ts
|
|
374
408
|
var ANIMATION_DURATION = 300;
|
|
375
|
-
var getElementByKey = (key) =>
|
|
409
|
+
var getElementByKey = (key) => document.querySelector(`[data-key="${key}"]`);
|
|
376
410
|
var DraggableTodoList = (signal, { items }) => {
|
|
377
411
|
let draggedKey = null;
|
|
378
412
|
let lastTargetKey = null;
|
|
@@ -382,12 +416,12 @@
|
|
|
382
416
|
if (currentCount > previousItemCount) {
|
|
383
417
|
const positions = /* @__PURE__ */ new Map();
|
|
384
418
|
currentItems.forEach((item) => {
|
|
385
|
-
const el =
|
|
419
|
+
const el = getElementByKey(item.key);
|
|
386
420
|
if (el) positions.set(item.key, el.getBoundingClientRect());
|
|
387
421
|
});
|
|
388
422
|
requestAnimationFrame(() => {
|
|
389
423
|
positions.forEach((first, key) => {
|
|
390
|
-
const el =
|
|
424
|
+
const el = getElementByKey(key);
|
|
391
425
|
if (!el) return;
|
|
392
426
|
const last = el.getBoundingClientRect();
|
|
393
427
|
const deltaY = first.top - last.top;
|
|
@@ -459,19 +493,19 @@
|
|
|
459
493
|
lastTargetKey = null;
|
|
460
494
|
};
|
|
461
495
|
const todoList = h("ul", { className: "todo-list" });
|
|
462
|
-
|
|
463
|
-
|
|
496
|
+
bindListChildren({
|
|
497
|
+
key: prop()("key"),
|
|
464
498
|
signal,
|
|
465
|
-
items,
|
|
466
|
-
(row) => Todo(row.signal, {
|
|
499
|
+
state: items,
|
|
500
|
+
row: (row) => Todo(row.signal, {
|
|
467
501
|
removeItem: () => {
|
|
468
|
-
const element =
|
|
502
|
+
const element = getElementByKey(row.state.get().key);
|
|
469
503
|
if (element) {
|
|
470
504
|
element.classList.add("todo-item-exit");
|
|
471
505
|
setTimeout(() => {
|
|
472
506
|
const positions = /* @__PURE__ */ new Map();
|
|
473
507
|
items.get().forEach((item) => {
|
|
474
|
-
const el =
|
|
508
|
+
const el = getElementByKey(item.key);
|
|
475
509
|
if (el) positions.set(item.key, el.getBoundingClientRect());
|
|
476
510
|
});
|
|
477
511
|
row.remove();
|
|
@@ -505,7 +539,7 @@
|
|
|
505
539
|
onDragEnd: handleDragEnd,
|
|
506
540
|
onDragOver: handleDragOver
|
|
507
541
|
})
|
|
508
|
-
);
|
|
542
|
+
})(todoList);
|
|
509
543
|
return todoList;
|
|
510
544
|
};
|
|
511
545
|
|
|
@@ -564,17 +598,26 @@
|
|
|
564
598
|
signal
|
|
565
599
|
)
|
|
566
600
|
);
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
601
|
+
const AllDoneComponent = () => {
|
|
602
|
+
return h("span", {
|
|
603
|
+
textContent: "\u{1F389} All Done!",
|
|
604
|
+
className: "all-done-text"
|
|
605
|
+
});
|
|
606
|
+
};
|
|
607
|
+
const allDoneState = funState(false);
|
|
571
608
|
state.focus(allCheckedAcc).watchAll(signal, (checks) => {
|
|
572
|
-
|
|
609
|
+
allDoneState.set(checks.length > 0 && checks.every(Boolean));
|
|
610
|
+
});
|
|
611
|
+
const allDoneEl = renderWhen({
|
|
612
|
+
state: allDoneState,
|
|
613
|
+
component: AllDoneComponent,
|
|
614
|
+
props: {},
|
|
615
|
+
signal
|
|
573
616
|
});
|
|
574
617
|
return h("div", { className: "todo-app" }, [
|
|
575
618
|
h("h1", { textContent: "Todo Example" }),
|
|
576
619
|
AddTodoForm(signal, { state }),
|
|
577
|
-
h("div", { className: "controls" }, [markAllBtn,
|
|
620
|
+
h("div", { className: "controls" }, [markAllBtn, allDoneEl]),
|
|
578
621
|
DraggableTodoList(signal, { items: state.prop("items") })
|
|
579
622
|
]);
|
|
580
623
|
};
|
|
@@ -2,17 +2,12 @@ import {
|
|
|
2
2
|
h,
|
|
3
3
|
funState,
|
|
4
4
|
mount,
|
|
5
|
-
|
|
5
|
+
on,
|
|
6
6
|
type Component,
|
|
7
7
|
enhance,
|
|
8
8
|
renderWhen,
|
|
9
9
|
} from "../../src/index";
|
|
10
|
-
import {
|
|
11
|
-
type TodoAppState,
|
|
12
|
-
markAllDone,
|
|
13
|
-
allCheckedAcc,
|
|
14
|
-
init_TodoAppState,
|
|
15
|
-
} from "./TodoAppState";
|
|
10
|
+
import { markAllDone, allCheckedAcc, init_TodoAppState } from "./TodoAppState";
|
|
16
11
|
import { DraggableTodoList } from "./DraggableTodoList";
|
|
17
12
|
import { AddTodoForm } from "./AddTodoForm";
|
|
18
13
|
|
|
@@ -28,7 +23,7 @@ const TodoApp: Component = (signal) => {
|
|
|
28
23
|
textContent: "Mark All Done",
|
|
29
24
|
className: "mark-all-btn",
|
|
30
25
|
}),
|
|
31
|
-
|
|
26
|
+
on(
|
|
32
27
|
"click",
|
|
33
28
|
() => {
|
|
34
29
|
state.mod(markAllDone);
|
|
@@ -55,7 +50,7 @@ const TodoApp: Component = (signal) => {
|
|
|
55
50
|
state: allDoneState,
|
|
56
51
|
component: AllDoneComponent,
|
|
57
52
|
props: {},
|
|
58
|
-
signal
|
|
53
|
+
signal,
|
|
59
54
|
});
|
|
60
55
|
|
|
61
56
|
return h("div", { className: "todo-app" }, [
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fun-land/fun-web",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "A web library for component-based development using fun-land",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"module": "dist/esm/index.js",
|
|
7
|
-
"types": "src/index.ts",
|
|
5
|
+
"main": "dist/src/index.js",
|
|
6
|
+
"module": "dist/esm/src/index.js",
|
|
7
|
+
"types": "dist/src/index.d.ts",
|
|
8
8
|
"author": "jethro larson",
|
|
9
9
|
"maintainers": [
|
|
10
10
|
"jethrolarson <jethrolarson@gmail.com>"
|
|
@@ -49,5 +49,5 @@
|
|
|
49
49
|
"url": "https://github.com/fun-land/fun-land/issues"
|
|
50
50
|
},
|
|
51
51
|
"license": "MIT",
|
|
52
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "0a5f0f1e9c1aa37b37041f06b3fe7bd79280a529"
|
|
53
53
|
}
|