@davidsouther/jiffies 2026.24.0 → 2026.24.2

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 (226) hide show
  1. package/lib/esm/assert.d.ts +26 -0
  2. package/lib/esm/assert.js +38 -0
  3. package/lib/esm/awaitable.js +1 -0
  4. package/lib/esm/case.d.ts +1 -0
  5. package/lib/esm/case.js +5 -0
  6. package/lib/esm/components/accordion.d.ts +5 -0
  7. package/lib/esm/components/accordion.js +9 -0
  8. package/lib/esm/components/alert.d.ts +7 -0
  9. package/lib/esm/components/alert.js +31 -0
  10. package/lib/esm/components/button_bar.d.ts +8 -0
  11. package/lib/esm/components/button_bar.js +25 -0
  12. package/lib/esm/components/card.d.ts +8 -0
  13. package/lib/esm/components/card.js +31 -0
  14. package/lib/esm/components/children.d.ts +2 -0
  15. package/{src/components/children.ts → lib/esm/components/children.js} +2 -6
  16. package/lib/esm/components/form.d.ts +5 -0
  17. package/lib/esm/components/form.js +13 -0
  18. package/{src/components/index.ts → lib/esm/components/index.d.ts} +2 -15
  19. package/lib/esm/components/index.js +10 -0
  20. package/lib/esm/components/inline_edit.d.ts +12 -0
  21. package/lib/esm/components/inline_edit.js +48 -0
  22. package/lib/esm/components/link.d.ts +5 -0
  23. package/lib/esm/components/link.js +11 -0
  24. package/lib/esm/components/logger.d.ts +6 -0
  25. package/lib/esm/components/logger.js +22 -0
  26. package/lib/esm/components/modal.d.ts +2 -0
  27. package/{src/components/modal.ts → lib/esm/components/modal.js} +3 -8
  28. package/lib/esm/components/nav.d.ts +11 -0
  29. package/lib/esm/components/nav.js +27 -0
  30. package/lib/esm/components/property.d.ts +9 -0
  31. package/lib/esm/components/property.js +16 -0
  32. package/lib/esm/components/select.d.ts +10 -0
  33. package/lib/esm/components/select.js +3 -0
  34. package/lib/esm/components/tabs.d.ts +20 -0
  35. package/lib/esm/components/tabs.js +45 -0
  36. package/lib/esm/components/virtual_scroll.d.ts +42 -0
  37. package/lib/esm/components/virtual_scroll.js +94 -0
  38. package/lib/esm/debounce.d.ts +1 -0
  39. package/lib/esm/debounce.js +11 -0
  40. package/lib/esm/diff.d.ts +15 -0
  41. package/lib/esm/diff.js +50 -0
  42. package/lib/esm/display.d.ts +5 -0
  43. package/lib/esm/display.js +11 -0
  44. package/lib/esm/dom/css/border.d.ts +11 -0
  45. package/lib/esm/dom/css/border.js +27 -0
  46. package/lib/esm/dom/css/constants.d.ts +31 -0
  47. package/lib/esm/dom/css/constants.js +28 -0
  48. package/lib/esm/dom/css/core.d.ts +5 -0
  49. package/lib/esm/dom/css/core.js +24 -0
  50. package/lib/esm/dom/css/fstyle.d.ts +5 -0
  51. package/lib/esm/dom/css/fstyle.js +32 -0
  52. package/lib/esm/dom/css/sizing.d.ts +5 -0
  53. package/lib/esm/dom/css/sizing.js +10 -0
  54. package/lib/esm/dom/dom.d.ts +36 -0
  55. package/lib/esm/dom/dom.js +217 -0
  56. package/lib/esm/dom/fc.d.ts +10 -0
  57. package/lib/esm/dom/fc.js +32 -0
  58. package/lib/esm/dom/form/form.app.d.ts +1 -0
  59. package/lib/esm/dom/form/form.app.js +19 -0
  60. package/lib/esm/dom/form/form.d.ts +27 -0
  61. package/lib/esm/dom/form/form.js +65 -0
  62. package/lib/esm/dom/html.d.ts +112 -0
  63. package/{src/dom/html.ts → lib/esm/dom/html.js} +2 -14
  64. package/lib/esm/dom/hydrate.d.ts +39 -0
  65. package/lib/esm/dom/hydrate.js +187 -0
  66. package/lib/esm/dom/index.js +2 -0
  67. package/lib/esm/dom/navigation/index.d.ts +76 -0
  68. package/lib/esm/dom/navigation/index.js +292 -0
  69. package/lib/esm/dom/observable.d.ts +2 -0
  70. package/lib/esm/dom/observable.js +6 -0
  71. package/lib/esm/dom/provide.d.ts +3 -0
  72. package/lib/esm/dom/provide.js +7 -0
  73. package/lib/esm/dom/render.d.ts +8 -0
  74. package/lib/esm/dom/render.js +28 -0
  75. package/lib/esm/dom/router/link.d.ts +6 -0
  76. package/lib/esm/dom/router/link.js +3 -0
  77. package/lib/esm/dom/router/router.d.ts +13 -0
  78. package/lib/esm/dom/router/router.js +52 -0
  79. package/lib/esm/dom/svg.d.ts +64 -0
  80. package/{src/dom/svg.ts → lib/esm/dom/svg.js} +2 -19
  81. package/lib/esm/dom/types/css.d.ts +6590 -0
  82. package/lib/esm/dom/types/css.js +1 -0
  83. package/lib/esm/dom/types/dom.js +1 -0
  84. package/lib/esm/dom/types/html.d.ts +614 -0
  85. package/lib/esm/dom/types/html.js +1 -0
  86. package/lib/esm/dom/xml.d.ts +1 -0
  87. package/lib/esm/dom/xml.js +4 -0
  88. package/lib/esm/equal.d.ts +11 -0
  89. package/lib/esm/equal.js +43 -0
  90. package/lib/esm/fs.d.ts +72 -0
  91. package/lib/esm/fs.js +227 -0
  92. package/lib/esm/fs_node.d.ts +15 -0
  93. package/lib/esm/fs_node.js +45 -0
  94. package/lib/esm/generator.d.ts +1 -0
  95. package/lib/esm/generator.js +10 -0
  96. package/lib/esm/lock.d.ts +1 -0
  97. package/lib/esm/lock.js +23 -0
  98. package/lib/esm/log.d.ts +69 -0
  99. package/lib/esm/log.js +211 -0
  100. package/lib/esm/observable/event.d.ts +35 -0
  101. package/lib/esm/observable/event.js +46 -0
  102. package/lib/esm/observable/observable.d.ts +134 -0
  103. package/lib/esm/observable/observable.js +349 -0
  104. package/lib/esm/range.d.ts +1 -0
  105. package/lib/esm/range.js +7 -0
  106. package/lib/esm/result.d.ts +31 -0
  107. package/lib/esm/result.js +66 -0
  108. package/lib/esm/safe.d.ts +1 -0
  109. package/lib/esm/safe.js +10 -0
  110. package/lib/esm/server/http/apps.d.ts +5 -0
  111. package/lib/esm/server/http/apps.js +23 -0
  112. package/lib/esm/server/http/css.d.ts +5 -0
  113. package/lib/esm/server/http/css.js +43 -0
  114. package/lib/esm/server/http/index.d.ts +16 -0
  115. package/lib/esm/server/http/index.js +78 -0
  116. package/lib/esm/server/http/response.d.ts +4 -0
  117. package/lib/esm/server/http/response.js +43 -0
  118. package/lib/esm/server/http/sitemap.d.ts +2 -0
  119. package/lib/esm/server/http/sitemap.js +22 -0
  120. package/lib/esm/server/http/static.d.ts +2 -0
  121. package/lib/esm/server/http/static.js +22 -0
  122. package/lib/esm/server/http/typescript.d.ts +5 -0
  123. package/lib/esm/server/http/typescript.js +40 -0
  124. package/lib/esm/server/live-reload.d.ts +46 -0
  125. package/lib/esm/server/live-reload.js +161 -0
  126. package/lib/esm/server/main.d.ts +2 -0
  127. package/{src/server/main.ts → lib/esm/server/main.js} +8 -15
  128. package/lib/esm/server/ws/frame.d.ts +2 -0
  129. package/lib/esm/server/ws/frame.js +35 -0
  130. package/lib/esm/server/ws/handshake.d.ts +4 -0
  131. package/lib/esm/server/ws/handshake.js +32 -0
  132. package/lib/esm/server/ws/index.d.ts +14 -0
  133. package/lib/esm/server/ws/index.js +68 -0
  134. package/lib/esm/ssg/bundle.d.ts +14 -0
  135. package/lib/esm/ssg/bundle.js +73 -0
  136. package/lib/esm/ssg/copy-public.d.ts +6 -0
  137. package/lib/esm/ssg/copy-public.js +34 -0
  138. package/lib/esm/ssg/discover.d.ts +15 -0
  139. package/lib/esm/ssg/discover.js +117 -0
  140. package/lib/esm/ssg/main.d.ts +2 -0
  141. package/lib/esm/ssg/main.js +122 -0
  142. package/lib/esm/ssg/rewrite.d.ts +9 -0
  143. package/{src/ssg/rewrite.ts → lib/esm/ssg/rewrite.js} +6 -9
  144. package/lib/esm/ssg/ssg.d.ts +26 -0
  145. package/lib/esm/ssg/ssg.js +84 -0
  146. package/lib/esm/transpile.d.mts +3 -0
  147. package/lib/esm/transpile.mjs +12 -0
  148. package/package.json +11 -7
  149. package/src/404.html +0 -14
  150. package/src/assert.ts +0 -56
  151. package/src/case.ts +0 -5
  152. package/src/components/_notes +0 -33
  153. package/src/components/accordion.ts +0 -25
  154. package/src/components/alert.ts +0 -47
  155. package/src/components/button_bar.ts +0 -42
  156. package/src/components/card.ts +0 -54
  157. package/src/components/form.ts +0 -25
  158. package/src/components/inline_edit.ts +0 -78
  159. package/src/components/link.ts +0 -22
  160. package/src/components/logger.ts +0 -35
  161. package/src/components/nav.ts +0 -42
  162. package/src/components/property.ts +0 -32
  163. package/src/components/select.ts +0 -22
  164. package/src/components/tabs.ts +0 -82
  165. package/src/components/virtual_scroll.ts +0 -199
  166. package/src/debounce.ts +0 -14
  167. package/src/diff.ts +0 -82
  168. package/src/display.ts +0 -18
  169. package/src/dom/README.md +0 -107
  170. package/src/dom/SKILL.md +0 -201
  171. package/src/dom/css/border.ts +0 -47
  172. package/src/dom/css/constants.ts +0 -34
  173. package/src/dom/css/core.ts +0 -28
  174. package/src/dom/css/fstyle.ts +0 -42
  175. package/src/dom/css/sizing.ts +0 -11
  176. package/src/dom/dom.ts +0 -327
  177. package/src/dom/fc.ts +0 -81
  178. package/src/dom/form/form.app.ts +0 -44
  179. package/src/dom/form/form.ts +0 -151
  180. package/src/dom/form/index.html +0 -15
  181. package/src/dom/hydrate.ts +0 -206
  182. package/src/dom/navigation/index.ts +0 -349
  183. package/src/dom/observable.ts +0 -11
  184. package/src/dom/provide.ts +0 -11
  185. package/src/dom/render.ts +0 -41
  186. package/src/dom/router/link.ts +0 -14
  187. package/src/dom/router/router.ts +0 -72
  188. package/src/dom/types/css.ts +0 -10088
  189. package/src/dom/types/html.ts +0 -629
  190. package/src/dom/xml.ts +0 -11
  191. package/src/equal.ts +0 -66
  192. package/src/favicon.ico +0 -0
  193. package/src/fs.ts +0 -300
  194. package/src/fs_node.ts +0 -57
  195. package/src/generator.ts +0 -12
  196. package/src/hooks/_notes +0 -6
  197. package/src/lock.ts +0 -23
  198. package/src/log.ts +0 -307
  199. package/src/observable/_notes +0 -26
  200. package/src/observable/event.ts +0 -93
  201. package/src/observable/observable.ts +0 -484
  202. package/src/range.ts +0 -7
  203. package/src/result.ts +0 -107
  204. package/src/safe.ts +0 -12
  205. package/src/server/http/apps.ts +0 -26
  206. package/src/server/http/css.ts +0 -49
  207. package/src/server/http/index.ts +0 -127
  208. package/src/server/http/response.ts +0 -60
  209. package/src/server/http/sitemap.ts +0 -24
  210. package/src/server/http/static.ts +0 -28
  211. package/src/server/http/typescript.ts +0 -46
  212. package/src/server/live-reload.ts +0 -208
  213. package/src/server/ws/frame.ts +0 -36
  214. package/src/server/ws/handshake.ts +0 -42
  215. package/src/server/ws/index.ts +0 -100
  216. package/src/ssg/bundle.ts +0 -85
  217. package/src/ssg/copy-public.ts +0 -44
  218. package/src/ssg/discover.ts +0 -143
  219. package/src/ssg/main.ts +0 -168
  220. package/src/ssg/ssg.ts +0 -134
  221. package/src/transpile.mjs +0 -16
  222. package/src/zip/spec.txt +0 -3260
  223. package/tsconfig.json +0 -34
  224. /package/{src/awaitable.ts → lib/esm/awaitable.d.ts} +0 -0
  225. /package/{src/dom/index.ts → lib/esm/dom/index.d.ts} +0 -0
  226. /package/{src/dom/types/dom.ts → lib/esm/dom/types/dom.d.ts} +0 -0
@@ -1,199 +0,0 @@
1
- import { debounce } from "../debounce.ts";
2
- import { FC, State } from "../dom/fc.ts";
3
- import { div } from "../dom/html.ts";
4
-
5
- export interface VirtualScrollSettings {
6
- minIndex: number;
7
- maxIndex: number;
8
- startIndex: number;
9
- itemHeight: number; // In pixels
10
- count: number;
11
- tolerance: number;
12
- }
13
-
14
- export type VirtualScrollDataAdapter<T> = (
15
- offset: number,
16
- limit: number,
17
- ) => Iterable<T>;
18
-
19
- export function arrayAdapter<T>(data: T[]): VirtualScrollDataAdapter<T> {
20
- return (offset, limit) => data.slice(offset, offset + limit);
21
- }
22
-
23
- export interface VirtualScrollProps<T, U extends HTMLElement> {
24
- settings: Partial<VirtualScrollSettings>;
25
- get: VirtualScrollDataAdapter<T>;
26
- row: (t: T) => U;
27
- }
28
-
29
- export function fillVirtualScrollSettings(
30
- settings: Partial<VirtualScrollSettings>,
31
- ): VirtualScrollSettings {
32
- const {
33
- minIndex = 0,
34
- maxIndex = Number.MAX_SAFE_INTEGER,
35
- startIndex = 0,
36
- itemHeight = 20,
37
- count = maxIndex - minIndex + 1,
38
- tolerance = count,
39
- } = settings;
40
-
41
- return { minIndex, maxIndex, startIndex, itemHeight, count, tolerance };
42
- }
43
-
44
- export function initialState<T>(
45
- settings: VirtualScrollSettings,
46
- ): VirtualScrollState<T> {
47
- // From Denis Hilt, https://blog.logrocket.com/virtual-scrolling-core-principles-and-basic-implementation-in-react/
48
- const { minIndex, maxIndex, startIndex, itemHeight, count, tolerance } =
49
- settings;
50
- const bufferedItems = count + 2 * tolerance;
51
- const itemsAbove = Math.max(0, startIndex - tolerance - minIndex);
52
-
53
- const viewportHeight = count * itemHeight;
54
- const totalHeight = (maxIndex - minIndex + 1) * itemHeight;
55
- const toleranceHeight = tolerance * itemHeight;
56
- const bufferHeight = viewportHeight + 2 * toleranceHeight;
57
- const topPaddingHeight = itemsAbove * itemHeight;
58
- const bottomPaddingHeight = totalHeight - (topPaddingHeight + bufferHeight);
59
-
60
- return {
61
- scrollTop: 0,
62
- settings,
63
- viewportHeight,
64
- totalHeight,
65
- toleranceHeight,
66
- bufferedItems,
67
- topPaddingHeight,
68
- bottomPaddingHeight,
69
- data: [],
70
- rows: [],
71
- };
72
- }
73
-
74
- export function getData<T>(
75
- minIndex: number,
76
- maxIndex: number,
77
- offset: number,
78
- limit: number,
79
- get: VirtualScrollDataAdapter<T>,
80
- ): T[] {
81
- const start = Math.max(0, minIndex, offset);
82
- const end = Math.min(maxIndex, offset + limit - 1);
83
- const data = get(start, end - start);
84
- return [...data];
85
- }
86
-
87
- export function doScroll<T>(
88
- scrollTop: number,
89
- state: VirtualScrollState<T>,
90
- get: VirtualScrollDataAdapter<T>,
91
- ): {
92
- scrollTop: number;
93
- topPaddingHeight: number;
94
- bottomPaddingHeight: number;
95
- data: T[];
96
- } {
97
- const {
98
- totalHeight,
99
- toleranceHeight,
100
- bufferedItems,
101
- settings: { itemHeight, minIndex, maxIndex },
102
- } = state;
103
- const index =
104
- minIndex + Math.floor((scrollTop - toleranceHeight) / itemHeight);
105
- const data = getData(minIndex, maxIndex, index, bufferedItems, get);
106
- const topPaddingHeight = Math.max((index - minIndex) * itemHeight, 0);
107
- const bottomPaddingHeight = Math.max(
108
- totalHeight - (topPaddingHeight + data.length * itemHeight),
109
- 0,
110
- );
111
-
112
- return { scrollTop, topPaddingHeight, bottomPaddingHeight, data };
113
- }
114
-
115
- interface VirtualScrollState<T, U extends HTMLElement = HTMLElement> {
116
- settings: VirtualScrollSettings;
117
- scrollTop: number; // px
118
- bufferedItems: number; // count
119
- totalHeight: number; // px
120
- viewportHeight: number; // px
121
- topPaddingHeight: number; // px
122
- bottomPaddingHeight: number; // px
123
- toleranceHeight: number; // px
124
- data: T[];
125
- rows: U[];
126
- }
127
-
128
- export interface VirtualScroll<T, U extends HTMLElement> {
129
- state: VirtualScrollState<T>;
130
- rows: U[];
131
- }
132
-
133
- export const VirtualScroll = FC<
134
- VirtualScrollProps<unknown, HTMLElement>,
135
- VirtualScrollState<unknown, HTMLElement>
136
- >("virtual-scroll", (element, props) => {
137
- const settings = fillVirtualScrollSettings(props.settings);
138
- const state = {
139
- ...initialState(settings),
140
- ...element[State],
141
- };
142
- element[State] = state;
143
-
144
- const scrollTo = (
145
- { target }: { target?: { scrollTop: number } } = { target: state },
146
- ) => {
147
- const scrollTop = target?.scrollTop ?? state.topPaddingHeight;
148
- const updatedSate = {
149
- ...state,
150
- ...doScroll(scrollTop, state, props.get),
151
- };
152
- setState(updatedSate);
153
- };
154
-
155
- const viewportElement = div({
156
- style: { height: `${state.viewportHeight}px`, overflowY: "scroll" },
157
- events: {
158
- // @ts-expect-error
159
- scroll: debounce(scrollTo, 0),
160
- },
161
- });
162
- setTimeout(() => {
163
- viewportElement.scroll?.({ top: state.scrollTop });
164
- });
165
-
166
- const setState = (newState: VirtualScrollState<unknown>) => {
167
- state.scrollTop = newState.scrollTop;
168
- state.topPaddingHeight = newState.topPaddingHeight;
169
- state.bottomPaddingHeight = newState.bottomPaddingHeight;
170
- state.data = newState.data;
171
- state.rows = state.data.map(props.row);
172
-
173
- viewportElement.update(
174
- div({
175
- class: "VirtualScroll__topPadding",
176
- style: { height: `${state.topPaddingHeight}px` },
177
- }),
178
- ...(state.rows ?? []).map((row, i) =>
179
- div(
180
- {
181
- class: `VirtualScroll__item_${i}`,
182
- style: { height: `${settings.itemHeight}px` },
183
- },
184
- row,
185
- ),
186
- ),
187
- div({
188
- class: "VirtualScroll__bottomPadding",
189
- style: { height: `${state.bottomPaddingHeight}px` },
190
- }),
191
- );
192
- };
193
-
194
- scrollTo();
195
-
196
- return viewportElement;
197
- });
198
-
199
- export default VirtualScroll;
package/src/debounce.ts DELETED
@@ -1,14 +0,0 @@
1
- export function debounce<T extends unknown[]>(
2
- fn: (...args: T) => void,
3
- ms = 32,
4
- ): (...args: T) => void {
5
- let timer: ReturnType<typeof setTimeout>;
6
- return (...args: T) => {
7
- clearTimeout(timer);
8
- timer = setTimeout(() => {
9
- clearTimeout(timer);
10
- return fn(...args);
11
- }, ms);
12
- return timer;
13
- };
14
- }
package/src/diff.ts DELETED
@@ -1,82 +0,0 @@
1
- import { range } from "./range.ts";
2
- import { isSome, None, type Option, Some } from "./result.ts";
3
-
4
- export const DiffA = Symbol("A");
5
- export const DiffB = Symbol("B");
6
-
7
- export type DiffIndex = string | number;
8
- export type DiffPrimitive = string | number | boolean | null | undefined;
9
-
10
- interface DiffEntry {
11
- key: DiffIndex;
12
- left: DiffPrimitive;
13
- right: DiffPrimitive;
14
- }
15
-
16
- interface DiffList {
17
- key: DiffIndex;
18
- children: (DiffEntry | DiffList)[];
19
- }
20
-
21
- function doDiff<T>(va: T, vb: T, k: DiffIndex): Option<DiffList | DiffEntry> {
22
- if (Array.isArray(va)) {
23
- // @ts-expect-error
24
- return diffArray(va, vb, k);
25
- }
26
- if (typeof va === "object") {
27
- const d = diffObject(va ?? {}, vb ?? {}, k);
28
- if (d.children.length === 0) {
29
- return None();
30
- }
31
- return Some(d);
32
- }
33
- if (Object.is(va, vb)) {
34
- return None();
35
- }
36
- // @ts-expect-error
37
- return { key: k, left: va, right: vb };
38
- }
39
-
40
- function diffArray<T>(
41
- a: Partial<T>[],
42
- b: Partial<T>[],
43
- key: DiffIndex,
44
- ): Option<DiffList> {
45
- const indexes = Math.max(a.length, b.length);
46
- const children = range(0, indexes)
47
- .map((i) => doDiff(a[i], b[i], i))
48
- .filter(isSome);
49
- return children.length > 0 ? { key, children } : None();
50
- }
51
-
52
- function diffObject<T>(
53
- a: Partial<T>,
54
- b: Partial<T>,
55
- key: DiffIndex = "",
56
- ): DiffList {
57
- const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
58
- const children = [...keys]
59
- // @ts-expect-error
60
- .map((k) => doDiff(a[k], b[k], k))
61
- .filter(isSome);
62
- return {
63
- key,
64
- children,
65
- };
66
- }
67
-
68
- export function diff<T>(
69
- a: Partial<T>,
70
- b: Partial<T>,
71
- ): (DiffEntry | DiffList)[] {
72
- if (typeof a !== "object" && !Object.is(a, b)) {
73
- // @ts-expect-error
74
- return [{ key: "", left: a, right: b }];
75
- }
76
- return (
77
- Array.isArray(a)
78
- ? // @ts-ignore
79
- (diffArray(a, b, "") ?? { children: [] })
80
- : diffObject(a, b, "")
81
- ).children;
82
- }
package/src/display.ts DELETED
@@ -1,18 +0,0 @@
1
- export type Display =
2
- | string
3
- | {
4
- toString(): string;
5
- };
6
-
7
- export const isDisplay = (a: unknown): a is Display =>
8
- typeof (a as Display)?.toString === "function" ||
9
- typeof (a as Display) === "string";
10
-
11
- export const display = (a: unknown | Display): string => {
12
- if (isDisplay(a)) {
13
- const str = a.toString();
14
- if (str === "[object Object]") return JSON.stringify(a);
15
- return str;
16
- }
17
- return JSON.stringify(a);
18
- };
package/src/dom/README.md DELETED
@@ -1,107 +0,0 @@
1
- # Jiffies DOM
2
-
3
- Jiffies DOM is an HTML microframework, exposing access to the DOM in a functional-first way.
4
-
5
- ```js
6
- import {form, input, label} from 'jiffies/dom/html';
7
-
8
- export const Form({
9
- title,
10
- action="#",
11
- onSubmit = (event) => {},
12
- },
13
- ...children
14
- ) =>
15
- form({
16
- action,
17
- events: {
18
- submit: (event) => {
19
- event.preventDefault();
20
- onSubmit(event);
21
- }
22
- }
23
- },
24
- h3(title),
25
- ...children,
26
- button({type: "submit"}, "Submit")
27
- );
28
-
29
- export const Input(name, type="string") => {
30
- let id = name.replace(/\s+/g, '_').toLowerCase();
31
- return label(
32
- {for: id},
33
- name,
34
- input({name, id, type})
35
- )
36
- }
37
-
38
- window.document.body.append(
39
- Form(
40
- {
41
- title: "Details",
42
- onSubmit: (event) => {
43
- console.log(event.target);
44
- }
45
- },
46
- Input({name: "First Name"}),
47
- Input({name: "Last Name"}),
48
- Input({name: "Number of cats", type: "number"})
49
- )
50
- );
51
- ```
52
-
53
- Exposing HTML as a tree of function calls makes it very easy to compose units of HTML.
54
- Creating new functions which pass HTML fragments is natural and intuitive, and creating reusable chunks of common HTML patterns is easy.
55
-
56
- ## Functional Components
57
-
58
- Exposing HTML as functions makes it easy on the programmer to create and compose HTML, but those functions lose context when they return.
59
- To capture HTML chunks and update them, use the `FC` function.
60
- The `FC` function creates new WebComponent elements in the DOM, and exposes an interface that matches native HTML elements.
61
-
62
- ```js
63
- export const GameSquare = FC<{piece: Piece}>('game-square', (el, {piece}) => {
64
- el.textContent = piece;
65
- // `el` is retained between updates
66
- return el;
67
- });
68
-
69
- export const GameBoard = FC<{pieces: Piece[]}>('game-board', (el, {pieces} => {
70
- return el;
71
- }));
72
- ```
73
-
74
- ## Style Blocks
75
-
76
- The `compileFstyle` function streamlines creating nested style rules.
77
- When used with the `style` HTML tag, this can create complex layouts inline with components.
78
-
79
- ```js
80
- const MyPage = () => [
81
- style(
82
- compileFstyle({
83
- main: {
84
- display: "flex",
85
- flexDirection: "row",
86
- },
87
- "@media max-width(768px)": {
88
- main: {
89
- flexDirection: "column",
90
- },
91
- },
92
- })
93
- ),
94
- section("..."),
95
- section("..."),
96
- section("..."),
97
- ];
98
- ```
99
-
100
- ## jiffies-css
101
-
102
- Including [jiffies-css](https://www.npmjs.com/package/@davidsouther/jiffies-css)
103
- provides a semantic HTML base that styles by element type and ARIA role rather
104
- than class names. Load it with `jiffiesCssLink()` and build pages from the typed
105
- component functions in [`dom/components`](./components/index.ts) (`Card`, `Alert`,
106
- `Nav`, ...), which emit the exact structures jiffies-css targets — no manual class
107
- annotation.
package/src/dom/SKILL.md DELETED
@@ -1,201 +0,0 @@
1
- ---
2
- name: using-jiffies-dom
3
- description: Use when building or updating UI in a project that depends on @davidsouther/jiffies and you are writing DOM code with its html/svg/fc modules — creating elements, updating a node in place via its .update() method, wiring event handlers, setting class/style, or building stateful FC components. Covers the reentrant create-or-update model and the correct .ts import paths.
4
- ---
5
-
6
- # Using Jiffies DOM
7
-
8
- ## Overview
9
-
10
- Jiffies DOM is a tiny functional library that implements **reentrant DOM**, where every
11
- node is a function that when called again updates its contents. There are exactly two
12
- operations:
13
-
14
- - **Create:** calling a tag function (`div(...)`, `button(...)`, `circle(...)`) makes a
15
- **new** node every time.
16
- - **Update in place:** every node Jiffies creates carries an `.update(attrs?, ...children)`
17
- method. Calling it mutates **that same node** — same identity, no replacement.
18
-
19
- To update a node later, you must **keep a reference to it** and call `.update()` on it.
20
- Calling the tag function again does not update the old node; it builds a new one.
21
-
22
- > Ideally, the object returned from the tag function would itself be directly callable, so instead of `const el = div({'class': 'off'}); el.update({'class': 'on'});`, you could just do `const el = div({'class': 'off'}); el({'class': 'on})';`. This is possible by wrapping the returned `HTMLDivElement` in a `Proxy` that implements a call interceptor, but `Proxy` cannot be passed to DOM APIs like appendChildren or addEventListener.
23
-
24
- ## Import paths (get this right first)
25
-
26
- The package is `@davidsouther/jiffies` and its export map is `"./*.ts": "./src/*.ts"`.
27
- Imports use the real subpath **with the `.ts` extension**:
28
-
29
- ```ts
30
- import { div, button, span, ul, li, p } from "@davidsouther/jiffies/dom/html.ts";
31
- import { FC, State } from "@davidsouther/jiffies/dom/fc.ts";
32
- import { svg, circle } from "@davidsouther/jiffies/dom/svg.ts";
33
- ```
34
-
35
- The package READMEs show `jiffies/dom/html` — that path is **wrong**. Only `html` and `fc`
36
- are re-exported from `dom/index.ts`; reach `svg`, `observable`, `router`, `provide`, and
37
- `xml` by their own deep paths.
38
-
39
- ## Quick reference
40
-
41
- | You want to… | Do this |
42
- |---|---|
43
- | Create an element | `div(attrs?, ...children)` — first arg is attrs only if a plain object |
44
- | Update a node in place | hold a reference to the node, then call `node.update(attrs?, ...children)` |
45
- | Set children only | `node.update("new text")` or `node.update(child1, child2)` |
46
- | Empty children | `import { CLEAR } from ".../dom/dom.ts"; node.update(CLEAR)` |
47
- | Add an event | `button({ events: { click: (e) => {...} } })` |
48
- | Replace an event handler | `node.update({ events: { click: newHandler } })` — old listener is removed first |
49
- | Remove an event | `node.update({ events: { click: null } })` |
50
- | Set a class | `div({ class: "a b" })` or `{ class: ["a", "b"] }` |
51
- | Remove a class on update | `node.update({ class: "!hidden" })` (`!` prefix removes) |
52
- | Inline style | `{ style: { flexDirection: "column" } }` or `{ style: "color:red" }` |
53
- | Boolean attribute | `{ disabled: true }` (falsy removes the attribute) |
54
-
55
- ## The argument rule (common error source)
56
-
57
- The first argument is treated as an **attributes object** only if it is a plain object with
58
- no `nodeType`. A string, a Node, or `CLEAR` is treated as the **first child**.
59
-
60
- ```ts
61
- div({ class: "row" }, span("a"), span("b")); // attrs + 2 children
62
- div(span("a"), span("b")); // 0 attrs, 2 children
63
- div("hello"); // 0 attrs, 1 text child
64
- ```
65
-
66
- ## Example: in-place Counter
67
-
68
- The number node is created once and reused on every click. The tag function is the create;
69
- `display.update(...)` is the reentrant update.
70
-
71
- ```ts
72
- import { div, span, button } from "@davidsouther/jiffies/dom/html.ts";
73
-
74
- export function Counter(start = 0) {
75
- let count = start;
76
- const display = span(`${count}`); // created ONCE — keep the reference
77
-
78
- return div(
79
- display,
80
- button(
81
- { events: { click: () => { count += 1; display.update(`${count}`); } } },
82
- "+1",
83
- ),
84
- );
85
- }
86
- ```
87
-
88
- ## FC: stateful components
89
-
90
- Use `FC` when a component owns state and should re-render itself from props. `FC(name, render)`
91
- defines a custom element and returns a constructor. Calling it creates the element; calling
92
- `.update(props)` **merges** props, re-runs `render`, and reconciles the rendered output into the host.
93
-
94
- ```ts
95
- import { FC } from "@davidsouther/jiffies/dom/fc.ts";
96
- import { section, h2, ul, li } from "@davidsouther/jiffies/dom/html.ts";
97
-
98
- export const TodoList = FC<{ title: string; items: string[] }>(
99
- "todo-list",
100
- (_el, { title, items }) =>
101
- section(h2(title ?? "Todos"), ul(...items.map((i) => li(i)))),
102
- );
103
-
104
- const list = TodoList({ title: "Chores", items: ["wash", "fold"] });
105
- document.body.append(list);
106
- list.update({ items: ["wash", "fold", "iron"] }); // title is retained (props merge)
107
- ```
108
-
109
- The render function is `(el, props, children) => Element | Element[]`. `el` is the host
110
- custom element (also typed to carry `.update()` and the `State` symbol). It may return a
111
- single node or an array. Persist component state on the host via the `State` symbol:
112
-
113
- ```ts
114
- import { FC, State } from "@davidsouther/jiffies/dom/fc.ts";
115
-
116
- export const Toggle = FC<{ label: string }, { on: boolean }>(
117
- "app-toggle",
118
- (el, { label }) => {
119
- el[State] ??= { on: false }; // initialise once; retained across updates
120
- const s = el[State];
121
- return button(
122
- { events: { click: () => { s.on = !s.on; el.update(); } } }, // el.update() re-renders
123
- `${label}: ${s.on ? "on" : "off"}`,
124
- );
125
- },
126
- );
127
- ```
128
-
129
- ## Typing nodes
130
-
131
- You do not need a Jiffies-specific type to hold a node. Jiffies augments the global DOM
132
- `Element` interface with `.update()`, so standard lib types already carry it:
133
-
134
- ```ts
135
- const display: HTMLSpanElement = span("0");
136
- display.update("1"); // .update is in scope on every Element
137
- ```
138
-
139
- `dom/dom.ts` also exports `DOMElement` (`Element & ElementCSSInlineStyle`) for the general case.
140
-
141
- ## Updates reconcile children by identity
142
-
143
- `.update(...children)` reconciles the new child list against the mounted children **by node
144
- object identity**. A child you pass back by the **same reference** is left in place and never
145
- detached, so its focus, scroll position, text selection, event listeners, and any descendant
146
- state survive the update. A freshly built node (or a string) has no matching identity, so it
147
- is inserted; a mounted child you omit is removed; order follows the argument list.
148
-
149
- This means you can update a parent and keep a specific subtree alive by passing the same node
150
- reference back through it:
151
-
152
- ```ts
153
- const panel = div(span("Title"), input({ name: "q" })); // keep this reference
154
- const root = div(panel, p("status"));
155
- // ...later: replace the status line but keep the panel (and its focused input):
156
- root.update(panel, p("ready")); // panel is reused in place; only the <p> is rebuilt
157
- ```
158
-
159
- Strings always rebuild (they carry no identity). The same reference must not appear twice in
160
- one update — a DOM node can occupy only one position.
161
-
162
- For fine-grained leaf updates (e.g. a counter), you can still hold a reference to the specific
163
- child node and call `.update()` on **that** node directly. **FC is the exception:** its
164
- `render` builds fresh nodes each update, so identity is not preserved inside an FC's own
165
- output (see the common mistake below).
166
-
167
- ## Testing
168
-
169
- Tests use the `scope` microframework and run under Node (jsdom loads automatically when
170
- `window` is undefined). Run with `npm test` (`node ./src/test.mjs`).
171
-
172
- ```ts
173
- import { describe, it, expect } from "@davidsouther/jiffies/scope/index.ts";
174
- import { button } from "@davidsouther/jiffies/dom/html.ts";
175
-
176
- describe("counter", () => {
177
- it("reuses the node on update", () => {
178
- const b = button("0");
179
- b.update("1");
180
- expect(b.textContent).toBe("1");
181
- });
182
- });
183
- ```
184
-
185
- ## Common mistakes
186
-
187
- - **Calling the tag function again to "update."** That creates a new node. Hold the
188
- reference and call `.update()` on it.
189
- - **Expecting event handlers to stack.** Calling `node.update({ events: { click: newFn } })`
190
- removes the old listener before adding the new one. Each event key tracks exactly one
191
- listener; the last one set is the one that fires.
192
- - **Importing `jiffies/dom/html`.** Use `@davidsouther/jiffies/dom/html.ts` (full name, `.ts`).
193
- - **Passing a config object as the first child.** A plain object becomes attrs; wrap text/nodes
194
- as children explicitly.
195
- - **Expecting FC `.update()` to preserve child DOM identity.** It re-renders and replaces
196
- children. For stable child nodes, update them directly.
197
- - **Using namespace-qualified SVG attributes.** `update()` always calls `setAttribute`, not
198
- `setAttributeNS`. Attributes on SVG elements are plain unqualified names; read them back with
199
- `getAttribute`, not `getAttributeNS`.
200
- - **Copying README examples verbatim.** The package README snippets are illustrative and not
201
- all valid TypeScript; rely on this skill and the `*.test.ts` files for working code.
@@ -1,47 +0,0 @@
1
- import type { Properties } from "../types/css.ts";
2
- import type { Side, Size } from "./constants.ts";
3
- import { getSide, getSize, isSide } from "./core.ts";
4
-
5
- export function rounded(size: Size = "", side: Side = "") {
6
- if (isSide(size)) {
7
- side = size;
8
- size = "";
9
- }
10
- const sized = getSize(size);
11
- return getSide(side).reduce((prev, curr) => {
12
- if (curr === "") {
13
- prev.borderRadius = sized;
14
- } else {
15
- // @ts-expect-error
16
- prev[`border${curr}Radius`] = sized;
17
- }
18
- return prev;
19
- }, {} as Properties);
20
- }
21
-
22
- export function border({
23
- side: _side = "",
24
- style: _style = "solid",
25
- radius: _radius = "",
26
- width: _width = 1,
27
- color: _color = "black",
28
- }: {
29
- side?: Side;
30
- style?: "solid" | "dotted" | "dashed" | "double" | "none";
31
- radius?: Size;
32
- width?: 0 | 1 | 2 | 4 | 8;
33
- color?: string;
34
- }) {
35
- return {};
36
- }
37
-
38
- export function inset(
39
- width: 0 | 1 | 2 | 4 | 8,
40
- color1 = "gray",
41
- color2 = "lightgray",
42
- ) {
43
- return {
44
- ...border({ side: "tl", width, color: color1, radius: "none" }),
45
- ...border({ side: "br", width, color: color2, radius: "none" }),
46
- };
47
- }