1 | 'use strict';
|
2 |
|
3 | Object.defineProperty(exports, '__esModule', { value: true });
|
4 |
|
5 | var constants = require('./constants.js');
|
6 | var formControl = require('./formControl.js');
|
7 | var helpers = require('./helpers.js');
|
8 | var list = require('./list.js');
|
9 | var token = require('./token.js');
|
10 | var attribute = require('./attribute.js');
|
11 | var context = require('./context.js');
|
12 |
|
13 | const HYDRATE_ATTR = "mosaic-hydrate";
|
14 |
|
15 | const render = (
|
16 | target,
|
17 | { getState, dispatch },
|
18 | template,
|
19 | updatedCallback,
|
20 | beforeMountCallback
|
21 | ) => {
|
22 | let observer = () => {
|
23 | let subscribers = new Set();
|
24 | return {
|
25 | publish: (cb) => {
|
26 | for (let fn of subscribers) {
|
27 | fn();
|
28 | }
|
29 | cb?.();
|
30 | },
|
31 | subscribe(fn) {
|
32 | subscribers.add(fn);
|
33 | },
|
34 | }
|
35 | };
|
36 |
|
37 | const createSubscription = {
|
38 | [constants.TEXT]: ({ value, node, context }, { getState }) => {
|
39 | return {
|
40 | handler: () => {
|
41 | let state = context ? context.wrap(getState()) : getState();
|
42 | let a = node.textContent;
|
43 | let b = token.getValueFromParts(state, token.getParts(value));
|
44 | if (a !== b) node.textContent = b;
|
45 | },
|
46 | }
|
47 | },
|
48 | [constants.ATTRIBUTE]: ({ value, node, name, context }, { getState }) => {
|
49 | return {
|
50 | handler: () => {
|
51 | let state = context ? context.wrap(getState()) : getState();
|
52 | let b = token.getValueFromParts(state, token.getParts(value));
|
53 |
|
54 | attribute.applyAttribute(node, name, b);
|
55 |
|
56 | if (node.nodeName === "OPTION") {
|
57 | let path = node.parentNode.getAttribute("name");
|
58 | let selected = helpers.getValueAtPath(path, state);
|
59 | node.selected = selected === b;
|
60 | }
|
61 | },
|
62 | }
|
63 | },
|
64 | [constants.INPUT]: ({ node, path, context }, { getState, dispatch }) => {
|
65 | node.addEventListener("input", () => {
|
66 | let value =
|
67 | node.getAttribute("type") === "checkbox" ? node.checked : node.value;
|
68 |
|
69 | if (value.trim?.().length && !isNaN(value)) value = +value;
|
70 |
|
71 | if (context) {
|
72 | let state = context.wrap(getState());
|
73 | state[path] = value;
|
74 | dispatch({
|
75 | type: "MERGE",
|
76 | payload: state,
|
77 | });
|
78 | } else {
|
79 | dispatch({
|
80 | type: "SET",
|
81 | payload: {
|
82 | name: path,
|
83 | value,
|
84 | context,
|
85 | },
|
86 | });
|
87 | }
|
88 | });
|
89 |
|
90 | return {
|
91 | handler: () => {
|
92 | let state = context ? context.wrap(getState()) : getState();
|
93 | formControl.updateFormControl(node, helpers.getValueAtPath(path, state));
|
94 | },
|
95 | }
|
96 | },
|
97 | [constants.EVENT]: (
|
98 | { node, eventType, actionType, context },
|
99 | { dispatch, getState }
|
100 | ) => {
|
101 | |
102 |
|
103 |
|
104 |
|
105 |
|
106 |
|
107 | node.addEventListener(eventType, (event) => {
|
108 | let isGlobal = actionType.startsWith("$");
|
109 |
|
110 | let action = {
|
111 | type: actionType,
|
112 | event,
|
113 | };
|
114 |
|
115 | if (!isGlobal)
|
116 | action.context = context ? context.wrap(getState()) : getState();
|
117 |
|
118 | dispatch(action);
|
119 |
|
120 | if (isGlobal) {
|
121 | node.dispatchEvent(
|
122 | new CustomEvent(actionType, {
|
123 | detail: action,
|
124 | bubbles: true,
|
125 | })
|
126 | );
|
127 | }
|
128 | });
|
129 | return {
|
130 | handler: () => {},
|
131 | }
|
132 | },
|
133 | [constants.REPEAT]: (
|
134 | {
|
135 | node,
|
136 | context: context$1,
|
137 | map,
|
138 | path,
|
139 | identifier,
|
140 | index,
|
141 | key,
|
142 | blockIndex,
|
143 | hydrate,
|
144 | pickupNode,
|
145 | },
|
146 | { getState }
|
147 | ) => {
|
148 | let oldValue;
|
149 | node.$t = blockIndex - 1;
|
150 |
|
151 | const initialiseBlock = (rootNode, i, k, exitNode) => {
|
152 | helpers.walk(
|
153 | rootNode,
|
154 | multi(
|
155 | (node) => {
|
156 | if (node === exitNode) return false
|
157 | },
|
158 | bindAll(
|
159 | map,
|
160 | hydrate,
|
161 | context.createContext(
|
162 | (context$1?.get() || []).concat({
|
163 | path,
|
164 | identifier,
|
165 | key,
|
166 | index,
|
167 | i,
|
168 | k,
|
169 | })
|
170 | )
|
171 | ),
|
172 | (child) => (child.$t = blockIndex)
|
173 | )
|
174 | );
|
175 | };
|
176 |
|
177 | function firstChild(v) {
|
178 | return (v.nodeType === v.DOCUMENT_FRAGMENT_NODE && v.firstChild) || v
|
179 | }
|
180 |
|
181 | const createListItem = (datum, i) => {
|
182 | let k = datum[key];
|
183 | let frag = helpers.fragmentFromTemplate(node);
|
184 | initialiseBlock(firstChild(frag), i, k);
|
185 | return frag
|
186 | };
|
187 |
|
188 | if (hydrate) {
|
189 | let x = helpers.getValueAtPath(path, getState());
|
190 | let blocks = list.getBlocks(node);
|
191 |
|
192 | blocks.forEach((block, i) => {
|
193 | let datum = x[i];
|
194 | let k = datum?.[key];
|
195 | initialiseBlock(block[0], i, k, helpers.last(block).nextSibling);
|
196 | });
|
197 |
|
198 | pickupNode = helpers.last(helpers.last(blocks)).nextSibling;
|
199 | }
|
200 |
|
201 | return {
|
202 | handler: () => {
|
203 | let state = context$1 ? context$1.wrap(getState()) : getState();
|
204 |
|
205 | const newValue = Object.entries(helpers.getValueAtPath(path, state) || []);
|
206 | const delta = list.compareKeyedLists(key, oldValue, newValue);
|
207 |
|
208 | if (delta) {
|
209 | list.updateList(node, delta, newValue, createListItem);
|
210 | }
|
211 | oldValue = newValue.slice(0);
|
212 | },
|
213 | pickupNode,
|
214 | }
|
215 | },
|
216 | };
|
217 |
|
218 | const mediator = () => {
|
219 | const o = observer();
|
220 | return {
|
221 | bind(v) {
|
222 | let s = createSubscription[v.type](v, { getState, dispatch });
|
223 | o.subscribe(s.handler);
|
224 | return s
|
225 | },
|
226 |
|
227 |
|
228 |
|
229 | update(cb) {
|
230 | return o.publish(cb)
|
231 | },
|
232 | }
|
233 | };
|
234 |
|
235 | const { bind, update } = mediator();
|
236 |
|
237 | let blockCount = 0;
|
238 |
|
239 | const parse = (frag) => {
|
240 | let index = 0;
|
241 | let map = {};
|
242 |
|
243 | helpers.walk(frag, (node) => {
|
244 | let x = [];
|
245 | let pickupNode;
|
246 | switch (node.nodeType) {
|
247 | case node.TEXT_NODE: {
|
248 | let value = node.textContent;
|
249 | if (token.hasMustache(value)) {
|
250 | x.push({
|
251 | type: constants.TEXT,
|
252 | value,
|
253 | });
|
254 | }
|
255 | break
|
256 | }
|
257 | case node.ELEMENT_NODE: {
|
258 | let each = list.parseEach(node);
|
259 |
|
260 | if (each) {
|
261 | let ns = node.namespaceURI;
|
262 | let m;
|
263 |
|
264 | if (ns.endsWith("/svg")) {
|
265 | node.removeAttribute("each");
|
266 | let tpl = document.createElementNS(ns, "defs");
|
267 | tpl.innerHTML = node.outerHTML;
|
268 | node.parentNode.replaceChild(tpl, node);
|
269 | node = tpl;
|
270 | m = parse(node.firstChild);
|
271 | } else {
|
272 | if (node.nodeName !== "TEMPLATE") {
|
273 | node.removeAttribute("each");
|
274 | let tpl = document.createElement("template");
|
275 |
|
276 | tpl.innerHTML = node.outerHTML;
|
277 | node.parentNode.replaceChild(tpl, node);
|
278 | node = tpl;
|
279 | }
|
280 | m = parse(node.content.firstChild);
|
281 | }
|
282 |
|
283 | pickupNode = node.nextSibling;
|
284 |
|
285 | x.push({
|
286 | type: constants.REPEAT,
|
287 | map: m,
|
288 | blockIndex: blockCount++,
|
289 | ...each,
|
290 | pickupNode,
|
291 | });
|
292 |
|
293 | break
|
294 | }
|
295 |
|
296 | let attrs = node.attributes;
|
297 | let i = attrs.length;
|
298 | while (i--) {
|
299 | let { name, value } = attrs[i];
|
300 |
|
301 | if (
|
302 | name === ":name" &&
|
303 | value &&
|
304 | (node.nodeName === "INPUT" ||
|
305 | node.nodeName === "SELECT" ||
|
306 | node.nodeName === "TEXTAREA")
|
307 | ) {
|
308 | x.push({
|
309 | type: constants.INPUT,
|
310 | path: value,
|
311 | });
|
312 |
|
313 | node.removeAttribute(name);
|
314 | node.setAttribute("name", value);
|
315 | } else if (name.startsWith(":on")) {
|
316 | node.removeAttribute(name);
|
317 | let eventType = name.split(":on")[1];
|
318 | x.push({
|
319 | type: constants.EVENT,
|
320 | eventType,
|
321 | actionType: value,
|
322 | });
|
323 | } else if (name.startsWith(":")) {
|
324 | let prop = name.slice(1);
|
325 |
|
326 | let v = value || prop;
|
327 |
|
328 | if (!v.includes("{{")) v = `{{${v}}}`;
|
329 |
|
330 | x.push({
|
331 | type: constants.ATTRIBUTE,
|
332 | name: prop,
|
333 | value: v,
|
334 | });
|
335 | node.removeAttribute(name);
|
336 | }
|
337 | }
|
338 | }
|
339 | }
|
340 | if (x.length) map[index] = x;
|
341 | index++;
|
342 | return pickupNode
|
343 | });
|
344 |
|
345 | return map
|
346 | };
|
347 |
|
348 | const multi =
|
349 | (...fns) =>
|
350 | (...args) => {
|
351 | for (let fn of fns) {
|
352 | let v = fn(...args);
|
353 | if (v === false) return false
|
354 | }
|
355 | };
|
356 |
|
357 | const bindAll = (bMap, hydrate = 0, context) => {
|
358 | let index = 0;
|
359 | return (node) => {
|
360 | let k = index;
|
361 | let p;
|
362 | if (k in bMap) {
|
363 | bMap[k].forEach((v) => {
|
364 | let x = bind({
|
365 | ...v,
|
366 | node,
|
367 | context,
|
368 | hydrate,
|
369 | });
|
370 | p = x.pickupNode;
|
371 | });
|
372 | node.$i = index;
|
373 | }
|
374 | index++;
|
375 | return p
|
376 | }
|
377 | };
|
378 |
|
379 | let frag = helpers.fragmentFromTemplate(template);
|
380 | let map = parse(frag);
|
381 | let hydrate = target.hasAttribute?.(HYDRATE_ATTR);
|
382 | if (hydrate) {
|
383 | helpers.walk(target, bindAll(map, 1));
|
384 | } else {
|
385 | helpers.walk(frag, bindAll(map));
|
386 | beforeMountCallback?.(frag);
|
387 | target.prepend(frag);
|
388 | update();
|
389 | target.setAttribute?.(HYDRATE_ATTR, 1);
|
390 | }
|
391 |
|
392 | return helpers.debounce(() => update(updatedCallback))
|
393 | };
|
394 |
|
395 | exports.render = render;
|