@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.
@@ -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
- function keyedChildren(parent, signal, list, renderRow) {
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.key;
205
- if (seen.has(k)) throw new Error(`keyedChildren: duplicate key "${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(filter((t) => t.key === k));
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((list2) => list2.filter((t) => t.key !== k))
238
+ remove: () => list.mod((xs) => xs.filter((t) => keyOf(keyAcc, t) !== k))
224
239
  });
225
- rows.set(k, { key: k, el, ctrl });
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 { reconcile, dispose };
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", priority: 0, key: "asdf" },
264
- { checked: true, label: "Build something cool", priority: 1, key: "fdas" }
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) => $(`[data-key="${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 = $(`[data-key="${item.key}"]`);
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 = $(`[data-key="${key}"]`);
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
- keyedChildren(
463
- todoList,
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 = $(`[data-key="${row.state.get().key}"]`);
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 = $(`[data-key="${item.key}"]`);
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 allDoneText = h("span", {
568
- textContent: "",
569
- className: "all-done-text"
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
- allDoneText.textContent = checks.length > 0 && checks.every(Boolean) ? "\u{1F389} All Done!" : "";
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, allDoneText]),
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
- onTo,
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
- onTo(
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.4.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": "e280f8e416926eb9ef6b644f62f123c74e6408c4"
52
+ "gitHead": "0a5f0f1e9c1aa37b37041f06b3fe7bd79280a529"
53
53
  }