talkdom 0.3.1 → 0.4.1

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.
@@ -1 +1,7 @@
1
- {"version":3,"names":["WS","parseMessage","str","trimmed","trim","tokens","split","receiver","body","substring","length","rest","slice","keywords","args","currentArg","i","token","endsWith","push","join","selector","receiverName","el","attr","getAttribute","sp","indexOf","receiverCache","Object","create","cacheValid","findReceivers","name","result","document","querySelectorAll","apply","op","content","accepts","innerHTML","textContent","insertAdjacentHTML","outerHTML","hasAttribute","key","localStorage","setItem","JSON","stringify","persist","console","error","MutationObserver","observe","childList","subtree","attributes","attributeFilter","csrfMeta","request","method","url","headers","location","href","isConnected","querySelector","warn","fetch","then","r","ok","status","Promise","reject","trigger","get","text","dispatchRaw","err","recName","methods","message","confirm","t","pushing","replayState","state","sender","resolveTarget","next","parent","previousElementSibling","lastElementChild","send","msg","piped","els","undefined","concat","forEach","detail","parentNode","nextElementSibling","target","dispatchEvent","CustomEvent","bubbles","run","raw","resolve","chains","map","chain","step","steps","s","filter","Boolean","reduce","prev","all","catch","dispatch","senderEl","first","colonIdx","pathname","search","history","pushState","pushUrl","window","addEventListener","e","activePollers","maxPollers","closest","preventDefault","getItem","parse","removeItem","interval","match","n","parseInt","parseInterval","cachedTargets","id","setInterval","clearInterval","talkDOM"],"sources":["index.js"],"mappings":"CAAC,WAEC,IAAIA,EAAK,MAIT,SAASC,EAAaC,GAUpB,IATA,IAAIC,EAAUD,EAAIE,OACdC,EAASF,EAAQG,MAAMN,GACvBO,EAAWF,EAAO,GAClBG,EAAOL,EAAQM,UAAUF,EAASG,QAAQN,OAC1CO,EAAON,EAAOO,MAAM,GACpBC,EAAW,GACXC,EAAO,GACPC,EAAa,GAERC,EAAI,EAAGA,EAAIL,EAAKD,OAAQM,IAAK,CACpC,IAAIC,EAAQN,EAAKK,GACbC,EAAMC,SAAS,MACbL,EAASH,OAAS,GAAKK,EAAWL,OAAS,GAC7CI,EAAKK,KAAKJ,EAAWK,KAAK,MAC1BL,EAAa,IACJF,EAASH,OAAS,GAC3BI,EAAKK,KAAK,IAEZN,EAASM,KAAKF,IAEdF,EAAWI,KAAKF,EAEpB,CAKA,OAJIJ,EAASH,OAAS,GACpBI,EAAKK,KAAKJ,EAAWK,KAAK,MAGrB,CAAEb,SAAUA,EAAUc,SAAUR,EAASO,KAAK,IAAKP,SAAUA,EAAUC,KAAMA,EAAMN,KAAMA,EAClG,CAGA,SAASc,EAAaC,GACpB,IAAIC,EAAOD,EAAGE,aAAa,YAAYrB,OACnCsB,EAAKF,EAAKG,QAAQ,KACtB,OAAe,IAARD,EAAYF,EAAOA,EAAKf,UAAU,EAAGiB,EAC9C,CAGA,IAAIE,EAAgBC,OAAOC,OAAO,MAC9BC,GAAa,EAMjB,SAASC,EAAcC,GAErB,GADKF,IAAcH,EAAgBC,OAAOC,OAAO,MAAOC,GAAa,GACjEH,EAAcK,GAAO,OAAOL,EAAcK,GAC9C,IAAIC,EAASC,SAASC,iBAAiB,eAAiBH,EAAO,MAE/D,OADAL,EAAcK,GAAQC,EACfA,CACT,CAwCA,SAASG,EAAMd,EAAIe,EAAIC,GACrB,GArCF,SAAiBhB,EAAIe,GACnB,IAAId,EAAOD,EAAGE,aAAa,WAC3B,OAAKD,IACkD,KAA/C,IAAMA,EAAO,KAAKG,QAAQ,IAAMW,EAAK,IAC/C,CAiCOE,CAAQjB,EAAIe,GAAjB,CAIA,OAAQA,GACN,IAAK,QAASf,EAAGkB,UAAYF,EAAS,MACtC,IAAK,OAAQhB,EAAGmB,YAAcH,EAAS,MACvC,IAAK,SAAUhB,EAAGoB,mBAAmB,YAAaJ,GAAU,MAC5D,IAAK,QAAShB,EAAGqB,UAAYL,EAG/B,OAzCF,SAAiBhB,EAAIe,GACnB,GAAKf,EAAGsB,aAAa,aAAgBtB,EAAGsB,aAAa,WAArD,CACA,IACIC,EAAM,WADCxB,EAAaC,GAEb,UAAPe,EACFS,aAAaC,QAAQF,EAAKG,KAAKC,UAAU,CAAEZ,GAAIA,EAAIC,QAAShB,EAAGqB,aAE/DG,aAAaC,QAAQF,EAAKG,KAAKC,UAAU,CAAEZ,GAAIA,EAAIC,QAAShB,EAAGkB,YANM,CAQzE,CA+BEU,CAAQ5B,EAAIe,GACLC,CARP,CAFEa,QAAQC,MAAM/B,EAAaC,GAAM,oBAAsBe,EAW3D,CA/DA,IAAIgB,iBAAiB,WAAcvB,GAAa,CAAO,GACpDwB,QAAQpB,SAAU,CAAEqB,WAAW,EAAMC,SAAS,EAAMC,YAAY,EAAMC,gBAAiB,CAAC,cAgE3F,IAAIC,EAAW,KAYf,SAASC,EAAQC,EAAQC,EAAKxD,GAC5B,IAAIyD,EAAU,CACZ,oBAAqB,OACrB,wBAAyBC,SAASC,MAKpC,GAHI3D,IACFyD,EAAQ,sBAAwBzD,GAEnB,QAAXuD,EAAkB,CACpB,IAAI7C,GAjBD2C,GAAaA,EAASO,cACzBP,EAAWzB,SAASiC,cAAc,4BAE7BR,EAAWA,EAASnC,aAAa,WAAa,IAe/CR,EAAO+C,EAAQ,gBAAkB/C,EAChCmC,QAAQiB,KAAK,oCAAsCP,EAAS,IAAMC,EACzE,CACA,OAAOO,MAAMP,EAAK,CAAED,OAAQA,EAAQE,QAASA,IAAWO,KAAK,SAAUC,GACrE,IAAKA,EAAEC,GAEL,OADArB,QAAQC,MAAM,YAAcS,EAAS,IAAMC,EAAM,IAAMS,EAAEE,QAClDC,QAAQC,OAAOJ,EAAEE,QAE1B,IAAIG,EAAUL,EAAER,QAAQc,IAAI,qBAC5B,OAAON,EAAEO,OAAOR,KAAK,SAAUQ,GAE7B,OADIF,GAASG,EAAYH,GAClBE,CACT,EACF,EAAG,SAAUE,GAEX,OADA7B,QAAQC,MAAM,YAAcS,EAAS,IAAMC,EAAM,UAAWkB,GACrDN,QAAQC,OAAOK,EACxB,EACF,CAEA,SAASC,EAAQ3D,GACf,OAAOA,EAAGsB,aAAa,YAAcvB,EAAaC,GAAM,EAC1D,CAIA,MAAM4D,EAAU,CACd,OAAQ,SAAU5D,EAAIwC,GAAO,OAAOF,EAAQ,MAAOE,EAAKmB,EAAQ3D,GAAM,EACtE,QAAS,SAAUA,EAAIwC,GAAO,OAAOF,EAAQ,OAAQE,EAAKmB,EAAQ3D,GAAM,EACxE,OAAQ,SAAUA,EAAIwC,GAAO,OAAOF,EAAQ,MAAOE,EAAKmB,EAAQ3D,GAAM,EACtE,UAAW,SAAUA,EAAIwC,GAAO,OAAOF,EAAQ,SAAUE,EAAKmB,EAAQ3D,GAAM,EAC5E,WAAY,SAAUA,EAAI6D,GAAW,IAAKC,QAAQD,GAAU,OAAOT,QAAQC,OAAO,YAAc,EAChG,SAAU,SAAUrD,EAAIgB,EAASD,GAAM,OAAOD,EAAMd,EAAIe,EAAIC,EAAU,EACtE,aAAc,SAAUhB,EAAIwC,EAAKzB,GAAM,OAAOuB,EAAQ,MAAOE,EAAKmB,EAAQ3D,IAAKgD,KAAK,SAAUe,GAAK,OAAOjD,EAAMd,EAAIe,EAAIgD,EAAI,EAAI,EAChI,cAAe,SAAU/D,EAAIwC,EAAKzB,GAAM,OAAOuB,EAAQ,OAAQE,EAAKmB,EAAQ3D,IAAKgD,KAAK,SAAUe,GAAK,OAAOjD,EAAMd,EAAIe,EAAIgD,EAAI,EAAI,EAClI,aAAc,SAAU/D,EAAIwC,EAAKzB,GAAM,OAAOuB,EAAQ,MAAOE,EAAKmB,EAAQ3D,IAAKgD,KAAK,SAAUe,GAAK,OAAOjD,EAAMd,EAAIe,EAAIgD,EAAI,EAAI,EAChI,gBAAiB,SAAU/D,EAAIwC,EAAKzB,GAAM,OAAOuB,EAAQ,SAAUE,EAAKmB,EAAQ3D,IAAKgD,KAAK,SAAUe,GAAK,OAAOjD,EAAMd,EAAIe,EAAIgD,EAAI,EAAI,GAGxI,IAAIC,GAAU,EAsBd,SAASC,EAAYC,GACdA,GAAUA,EAAMC,SACrBH,GAAU,EACVP,EAAYS,EAAMC,QAClBH,GAAU,EACZ,CASA,SAASI,EAAcpE,EAAIqE,EAAMC,EAAQ5D,GACvC,OAAIV,EAAG4C,YAAoB5C,GACXqE,GAAQA,EAAKzB,YAAcyB,EAAKE,uBAC5CD,GAAUA,EAAO1B,YAAc0B,EAAOE,iBAAmB,OACzC/D,EAAcC,GAAM,EAC1C,CAIA,SAAS+D,EAAKC,EAAKC,GACjB,IAAIC,EAAMnE,EAAciE,EAAI1F,UAC5B,GAAmB,IAAf4F,EAAIzF,OAAR,CAIA,IAAIoD,EAASqB,EAAQc,EAAI5E,UACzB,GAAKyC,EAAL,CAIA,IACI5B,EADApB,OAAiBsF,IAAVF,EAAsB,CAACA,GAAOG,OAAOJ,EAAInF,MAAQmF,EAAInF,KAwBhE,OAtBAqF,EAAIG,QAAQ,SAAU/E,GACpB,IAAIgF,EAAS,CAAEhG,SAAU0F,EAAI1F,SAAUc,SAAU4E,EAAI5E,SAAUP,KAAMmF,EAAInF,MAIrE+E,EAAStE,EAAGiF,WACZZ,EAAOrE,EAAGkF,mBAEd,IADAvE,EAAS4B,EAAOvC,KAAOT,KACc,mBAAhBoB,EAAOqC,KAC1BrC,EAAOqC,KAAK,WACV,IAAImC,EAASf,EAAcpE,EAAIqE,EAAMC,EAAQI,EAAI1F,UAC7CmG,GAAQA,EAAOC,cAAc,IAAIC,YAAY,eAAgB,CAAEC,SAAS,EAAMN,OAAQA,IAC5F,EAAG,SAAUtB,GACXsB,EAAOlD,MAAQ4B,EACf,IAAIyB,EAASf,EAAcpE,EAAIqE,EAAMC,EAAQI,EAAI1F,UAC7CmG,GAAQA,EAAOC,cAAc,IAAIC,YAAY,gBAAiB,CAAEC,SAAS,EAAMN,OAAQA,IAC7F,OACK,CACL,IAAIG,EAASf,EAAcpE,EAAIqE,EAAMC,EAAQI,EAAI1F,UAC7CmG,GAAQA,EAAOC,cAAc,IAAIC,YAAY,eAAgB,CAAEC,SAAS,EAAMN,OAAQA,IAC5F,CACF,GACOrE,CAzBP,CAFEkB,QAAQC,MAAM4C,EAAI1F,SAAW,wBAA0B0F,EAAI5E,SAH7D,MAFE+B,QAAQC,MAAM4C,EAAI1F,SAAW,aAiCjC,CAIA,SAASuG,EAAIC,GACX,IAAI5G,EAAU4G,EAAI3G,OAElB,IAA8B,IAA1BD,EAAQwB,QAAQ,OAAyC,IAA1BxB,EAAQwB,QAAQ,KACjD,OAAOgD,QAAQqC,QAAQhB,EAAK/F,EAAaE,KAAWoE,KAAK,SAAUC,GAAK,MAAO,CAACA,EAAI,GAGtF,IAAIyC,EAAS9G,EAAQG,MAAM,KAAK4G,IAAI,SAAUC,GAC5C,IAAIC,EAAOD,EAAM/G,OACjB,IAAKgH,EAAM,OAAOzC,QAAQqC,UAG1B,IAAIK,EAAQD,EAAK9G,MAAM,KAAK4G,IAAI,SAAUI,GAAK,OAAOA,EAAElH,MAAQ,GAAGmH,OAAOC,SAC1E,OAAqB,IAAjBH,EAAM3G,OACDiE,QAAQqC,QAAQhB,EAAK/F,EAAaoH,EAAM,MAI1CA,EAAMI,OAAO,SAAUC,EAAMN,GAClC,IAAInB,EAAMhG,EAAamH,GACvB,OAAOzC,QAAQqC,QAAQU,GAAMnD,KAAK,SAAU2B,GAC1C,OAAOF,EAAKC,EAAKC,EACnB,EACF,OAAGE,EACL,GAEA,OAAOzB,QAAQgD,IAAIV,EACrB,CAGA,SAASjC,EAAY+B,GACnBD,EAAIC,GAAKa,MAAM,SAAU3C,GAAO7B,QAAQiB,KAAK,WAAYY,EAAM,EACjE,CAGA,SAAS4C,EAASC,GAChB,IAAIf,EAAMe,EAASrG,aAAa,UAChCuD,EAAY+B,GACPxB,GAxHP,SAAiBuC,EAAUf,GACzB,GAAKe,EAASjF,aAAa,YAA3B,CACA,IAAIkB,EAAM+D,EAASrG,aAAa,YAChC,IAAKsC,EAAK,CAGR,IAAIgE,EAAQhB,EAAIzG,MAAM,KAAK,GAAGA,MAAM,KAAK,GAAGF,OACxC4H,EAAWD,EAAMpG,QAAQ,MACX,IAAdqG,IAEFjE,EADiBgE,EAAMtH,UAAUuH,EAAW,GAAG5H,OAC9BE,MAAM,MAAM,IAAM,GAEvC,CACIyD,GAAQE,SAASgE,SAAWhE,SAASiE,SAAYnE,GACnDoE,QAAQC,UAAU,CAAE1C,OAAQqB,GAAO,GAAIhD,EAbK,CAehD,CAwGgBsE,CAAQP,EAAUf,EAClC,CA/FAuB,OAAOC,iBAAiB,WAAY,SAAUC,GAC5ChD,EAAYgD,EAAE/C,MAChB,GAwGA,IAAIgD,EAAgB,EAChBC,EAAa,GAqCjBvG,SAASoG,iBAAiB,QAAS,SAAUC,GAC3C,MAAM9C,EAAS8C,EAAE9B,OAAOiC,QAAQ,YAC5BjD,IACF8C,EAAEI,iBACFf,EAASnC,GAEb,GA9QEvD,SAASC,iBAAiB,aAAakE,QAAQ,SAAU/E,GACvD,GAAKA,EAAGsB,aAAa,YAArB,CACA,IAAIZ,EAAOX,EAAaC,GACpBwF,EAAMhE,aAAa8F,QAAQ,WAAa5G,GAC5C,GAAK8E,EAAL,CACA,IAAItB,EACJ,IAAMA,EAAQxC,KAAK6F,MAAM/B,EAAM,CAAE,MAAOyB,GAAyD,YAA5CzF,aAAagG,WAAW,WAAa9G,EAAe,CACxF,UAAbwD,EAAMnD,GACRf,EAAGqB,UAAY6C,EAAMlD,QAErBhB,EAAGkB,UAAYgD,EAAMlD,OANP,CAHwB,CAW1C,GAqQFiD,EAAY2C,QAAQ1C,OACpBtD,SAASC,iBAAiB,cAAckE,QA7CxC,SAAsB/E,GACpB,IACI0E,EAAMhG,EADCsB,EAAGE,aAAa,aAE3B,GAA8C,UAA1CwE,EAAIpF,SAASoF,EAAIpF,SAASH,OAAS,GACvC,GAAI+H,GAAiBC,EACnBtF,QAAQiB,KAAK,yBAA2BqE,EAAa,uBAAyBzC,EAAI1F,cADpF,CAIA,IAAIyI,EApBN,SAAuB9I,GACrB,IAAI+I,EAAQ/I,EAAI+I,MAAM,iBACtB,IAAKA,EAAO,OAAO,KACnB,IAAIC,EAAIC,SAASF,EAAM,GAAI,IAC3B,MAAoB,MAAbA,EAAM,GAAiB,IAAJC,EAAWA,CACvC,CAeiBE,CAAcnD,EAAInF,KAAKmF,EAAInF,KAAKJ,OAAS,IACxD,GAAKsI,EAAL,CAIA,IAAI3H,EAAW4E,EAAIpF,SAASD,MAAM,GAAI,GAAGQ,KAAK,IAC1CN,EAAOmF,EAAInF,KAAKF,MAAM,GAAI,GAC1BqB,EAAOgE,EAAI1F,SACX8I,EAAgBrH,EAAcC,GAC9B6B,EAASqB,EAAQ9D,GACrBoH,IACA,IAAIa,EAAKC,YAAY,WACnB,IAAKhI,EAAG4C,YAAmD,OAApCqF,cAAcF,QAAKb,IACb,IAAzBY,EAAc3I,QAAiB2I,EAAc,GAAGlF,cAClDkF,EAAgBrH,EAAcC,IAEH,IAAzBoH,EAAc3I,SACboD,IAAQA,EAASqB,EAAQ9D,IACzByC,EAILuF,EAAc/C,QAAQ,SAAUI,GAAU5C,EAAO4C,KAAW5F,EAAO,GAHjEsC,QAAQC,MAAMpB,EAAO,wBAA0BZ,GAInD,EAAG2H,EAnBH,MAFE5F,QAAQC,MAAM,8BAAgC4C,EAAI1F,SAHpD,CAyBF,GAeA+H,OAAOmB,QAAU,CACftE,QAASA,EACTa,KAAMc,EACN,cAAI4B,GAAe,OAAOA,CAAY,EACtC,cAAIA,CAAWQ,GAAKR,EAAaQ,CAAG,EAGxC,CA7WA","ignoreList":[]}
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.js"],
4
+ "sourcesContent": ["const WS = /\\s+/;\nconst STORAGE_PREFIX = \"talkDOM:\";\n\n// Parse \"receiver keyword: arg keyword: arg\" into structured message object.\n// Tokens ending with \":\" are keywords, everything else fills args.\nfunction parseMessage(str) {\n const trimmed = str.trim();\n const tokens = trimmed.split(WS);\n const receiver = tokens[0];\n const body = trimmed.substring(receiver.length).trim();\n const rest = tokens.slice(1);\n const keywords = [];\n const args = [];\n let currentArg = [];\n\n for (let i = 0; i < rest.length; i++) {\n const token = rest[i];\n if (token.endsWith(\":\")) {\n if (keywords.length > 0 && currentArg.length > 0) {\n args.push(currentArg.join(\" \"));\n currentArg = [];\n } else if (keywords.length > 0) {\n args.push(\"\");\n }\n keywords.push(token);\n } else {\n currentArg.push(token);\n }\n }\n if (keywords.length > 0) {\n args.push(currentArg.join(\" \"));\n }\n\n return { receiver, selector: keywords.join(\"\"), keywords, args, body };\n}\n\n// Extract the first word from the receiver attribute (the name).\nfunction receiverName(el) {\n const attr = el.getAttribute(\"receiver\").trim();\n const sp = attr.indexOf(\" \");\n return sp === -1 ? attr : attr.substring(0, sp);\n}\n\n// Receiver cache: maps name -> NodeList, invalidated by DOM mutations.\nconst receiverCache = Object.create(null);\nlet cacheValid = false;\n\nnew MutationObserver(() => { cacheValid = false; })\n .observe(document, { childList: true, subtree: true, attributes: true, attributeFilter: [\"receiver\"] });\n\n// Find all elements whose receiver attribute contains the given name.\nfunction findReceivers(name) {\n if (!cacheValid) {\n Object.keys(receiverCache).forEach(key => delete receiverCache[key]);\n cacheValid = true;\n }\n if (receiverCache[name]) return receiverCache[name];\n const result = document.querySelectorAll('[receiver~=\"' + name + '\"]');\n receiverCache[name] = result;\n return result;\n}\n\n// Check if a receiver allows a given apply operation (inner, text, append, outer).\n// No \"accepts\" attribute means everything is allowed.\nfunction accepts(el, op) {\n const attr = el.getAttribute(\"accepts\");\n if (!attr) return true;\n return (\" \" + attr + \" \").includes(\" \" + op + \" \");\n}\n\n// Save receiver content to localStorage after apply, keyed by receiver name.\nfunction persist(el, op) {\n if (!el.hasAttribute(\"receiver\") || !el.hasAttribute(\"persist\")) return;\n const name = receiverName(el);\n const key = STORAGE_PREFIX + name;\n const content = op === \"outer\" ? el.outerHTML : el.innerHTML;\n localStorage.setItem(key, JSON.stringify({ op, content }));\n}\n\n// On page load, restore persisted receiver content from localStorage.\nfunction restore() {\n document.querySelectorAll(\"[persist]\").forEach((el) => {\n if (!el.hasAttribute(\"receiver\")) return;\n const name = receiverName(el);\n const raw = localStorage.getItem(STORAGE_PREFIX + name);\n if (!raw) return;\n let state;\n try { state = JSON.parse(raw); } catch {\n localStorage.removeItem(STORAGE_PREFIX + name);\n return;\n }\n if (state.op === \"outer\") {\n el.outerHTML = state.content;\n } else {\n el.innerHTML = state.content;\n }\n });\n}\n\n// Apply content to an element using the specified operation (inner, text, append, outer).\nfunction apply(el, op, content) {\n if (!accepts(el, op)) {\n console.error(receiverName(el) + \" does not accept \" + op);\n return;\n }\n switch (op) {\n case \"inner\": el.innerHTML = content; break;\n case \"text\": el.textContent = content; break;\n case \"append\": el.insertAdjacentHTML(\"beforeend\", content); break;\n case \"outer\": el.outerHTML = content; break;\n }\n persist(el, op);\n return content;\n}\n\nlet csrfMeta = null;\n\nfunction csrfToken() {\n if (!csrfMeta || !csrfMeta.isConnected) {\n csrfMeta = document.querySelector('meta[name=\"csrf-token\"]');\n }\n return csrfMeta ? csrfMeta.getAttribute(\"content\") : \"\";\n}\n\n// Perform a fetch with talkDOM headers. Returns a promise resolving to response text.\n// Fires server-triggered messages from X-TalkDOM-Trigger header if present.\nfunction request(method, url, receiver, body) {\n const headers = {\n \"X-TalkDOM-Request\": \"true\",\n \"X-TalkDOM-Current-URL\": location.href,\n };\n if (receiver) {\n headers[\"X-TalkDOM-Receiver\"] = receiver;\n }\n if (method !== \"GET\") {\n const token = csrfToken();\n if (token) headers[\"X-CSRF-Token\"] = token;\n else console.warn(\"talkDOM: no CSRF token found for \" + method + \" \" + url);\n }\n return fetch(url, { method, headers, body }).then((r) => {\n if (!r.ok) {\n console.error(\"talkDOM: \" + method + \" \" + url + \" \" + r.status);\n return Promise.reject(r.status);\n }\n const trigger = r.headers.get(\"X-TalkDOM-Trigger\");\n return r.text().then((text) => {\n if (trigger) dispatchRaw(trigger);\n return text;\n });\n });\n}\n\nfunction serializeForm(form) {\n return new URLSearchParams(new FormData(form)).toString();\n}\n\nfunction recName(el) {\n return el.hasAttribute(\"receiver\") ? receiverName(el) : \"\";\n}\n\n// Built-in method table. Each method receives (el, ...args) from the parsed message.\n// Extensible via talkDOM.methods at runtime.\nconst methods = {\n \"get:\": function (el, url) { return request(\"GET\", url, recName(el)); },\n \"post:\": function (el, url) { return request(\"POST\", url, recName(el)); },\n \"put:\": function (el, url) { return request(\"PUT\", url, recName(el)); },\n \"delete:\": function (el, url) { return request(\"DELETE\", url, recName(el)); },\n \"confirm:\": function (el, message) { if (!confirm(message)) return Promise.reject(\"cancelled\"); },\n \"apply:\": function (el, content, op) { return apply(el, op, content); },\n \"get:apply:\": function (el, url, op) { return request(\"GET\", url, recName(el)).then(function (t) { return apply(el, op, t); }); },\n \"post:apply:\": function (el, url, op) { return request(\"POST\", url, recName(el)).then(function (t) { return apply(el, op, t); }); },\n \"put:apply:\": function (el, url, op) { return request(\"PUT\", url, recName(el)).then(function (t) { return apply(el, op, t); }); },\n \"delete:apply:\": function (el, url, op) { return request(\"DELETE\", url, recName(el)).then(function (t) { return apply(el, op, t); }); },\n \"post-form:apply:form:\": function (el, url, op, formSelector) {\n const form = document.querySelector(formSelector);\n if (!form) { console.error(\"post-form: form not found: \" + formSelector); return; }\n const body = serializeForm(form);\n return request(\"POST\", url, recName(el), body).then(function (t) { return apply(el, op, t); });\n },\n \"put-form:apply:form:\": function (el, url, op, formSelector) {\n const form = document.querySelector(formSelector);\n if (!form) { console.error(\"put-form: form not found: \" + formSelector); return; }\n const body = serializeForm(form);\n return request(\"PUT\", url, recName(el), body).then(function (t) { return apply(el, op, t); });\n },\n \"delete-form:apply:form:\": function (el, url, op, formSelector) {\n const form = document.querySelector(formSelector);\n if (!form) { console.error(\"delete-form: form not found: \" + formSelector); return; }\n const body = serializeForm(form);\n return request(\"DELETE\", url, recName(el), body).then(function (t) { return apply(el, op, t); });\n },\n \"post-json:apply:json:\": function (el, url, op, jsonStr) {\n let data;\n try { data = JSON.parse(jsonStr); } catch { data = {}; }\n return request(\"POST\", url, recName(el), JSON.stringify(data)).then(function (t) { return apply(el, op, t); });\n },\n \"put-json:apply:json:\": function (el, url, op, jsonStr) {\n let data;\n try { data = JSON.parse(jsonStr); } catch { data = {}; }\n return request(\"PUT\", url, recName(el), JSON.stringify(data)).then(function (t) { return apply(el, op, t); });\n },\n};\n\nlet pushing = false;\n\n// Push URL to browser history. Uses push-url attr value, or falls back to first message arg.\nfunction pushUrl(senderEl, raw) {\n if (!senderEl.hasAttribute(\"push-url\")) return;\n let url = senderEl.getAttribute(\"push-url\");\n if (!url) {\n const msg = parseMessage(raw.split(\";\")[0].split(\"|\")[0].trim());\n if (msg.args.length > 0) {\n url = msg.args[0].split(/\\s/)[0] || \"\";\n }\n }\n if (url && (location.pathname + location.search) !== url) {\n history.pushState({ sender: raw }, \"\", url);\n }\n}\n\n// Re-dispatch a sender message from history state (back/forward navigation).\nfunction replayState(state) {\n if (!state || !state.sender) return;\n pushing = true;\n dispatchRaw(state.sender);\n pushing = false;\n}\n\nwindow.addEventListener(\"popstate\", function (e) {\n replayState(e.state);\n});\n\n// After an outer swap `el` is gone. Walk from the snapshotted sibling or\n// parent to find the element that took its place; fall back to a fresh\n// receiver query if the DOM was restructured.\nfunction resolveTarget(el, next, parent, name) {\n if (el.isConnected) return el;\n const candidate = next && next.isConnected ? next.previousElementSibling\n : parent && parent.isConnected ? parent.lastElementChild : null;\n return candidate || findReceivers(name)[0];\n}\n\n// Deliver a parsed message to all matching receivers. Fires talkdom:done or talkdom:error\n// lifecycle events on the receiver element (or its replacement if outer-swapped).\nfunction send(msg, piped) {\n const els = findReceivers(msg.receiver);\n if (els.length === 0) {\n console.error(msg.receiver + \" not found\");\n return;\n }\n let loadingClass = null;\n let keywords = msg.keywords;\n let args = msg.args;\n const loadingIdx = keywords.indexOf(\"loading:\");\n if (loadingIdx !== -1) {\n loadingClass = args[loadingIdx];\n keywords = keywords.filter(function (_, i) { return i !== loadingIdx; });\n args = args.filter(function (_, i) { return i !== loadingIdx; });\n }\n const selector = keywords.join(\"\");\n const method = methods[selector];\n if (!method) {\n console.error(msg.receiver + \" does not understand \" + selector);\n return;\n }\n const finalArgs = piped !== undefined ? [piped].concat(args) : args;\n let result;\n els.forEach(function (el) {\n const detail = { receiver: msg.receiver, selector: selector, args: msg.args };\n const parent = el.parentNode;\n const next = el.nextElementSibling;\n if (loadingClass) el.classList.add(loadingClass);\n result = method(el, ...finalArgs);\n if (result && typeof result.then === \"function\") {\n result.then(function () {\n if (loadingClass) el.classList.remove(loadingClass);\n const target = resolveTarget(el, next, parent, msg.receiver);\n if (target) target.dispatchEvent(new CustomEvent(\"talkdom:done\", { bubbles: true, detail }));\n }, function (err) {\n if (loadingClass) el.classList.remove(loadingClass);\n detail.error = err;\n const target = resolveTarget(el, next, parent, msg.receiver);\n if (target) target.dispatchEvent(new CustomEvent(\"talkdom:error\", { bubbles: true, detail }));\n });\n } else {\n if (loadingClass) el.classList.remove(loadingClass);\n const target = resolveTarget(el, next, parent, msg.receiver);\n if (target) target.dispatchEvent(new CustomEvent(\"talkdom:done\", { bubbles: true, detail }));\n }\n });\n return result;\n}\n\n// Programmatic API: parse and execute a raw message string (supports pipes and semicolons).\n// Returns a promise that resolves when all chains complete.\nfunction run(raw) {\n const trimmed = raw.trim();\n if (trimmed.indexOf(\";\") === -1 && trimmed.indexOf(\"|\") === -1) {\n return Promise.resolve(send(parseMessage(trimmed))).then(function (r) { return [r]; });\n }\n const chains = trimmed.split(\";\").map(function (chain) {\n const step = chain.trim();\n if (!step) return Promise.resolve();\n const steps = step.split(\"|\").map(function (s) { return s.trim(); }).filter(Boolean);\n if (steps.length === 1) {\n return Promise.resolve(send(parseMessage(steps[0])));\n }\n return steps.reduce(function (prev, step) {\n const msg = parseMessage(step);\n return Promise.resolve(prev).then(function (piped) {\n return send(msg, piped);\n });\n }, undefined);\n });\n return Promise.all(chains);\n}\n\n// Fire-and-forget dispatch used by declarative senders and server triggers.\nfunction dispatchRaw(raw) {\n run(raw).catch(function (err) { console.warn(\"talkDOM:\", err); });\n}\n\n// Entry point for a sender click: dispatch its message and optionally push URL.\nfunction dispatch(senderEl) {\n const raw = senderEl.getAttribute(\"sender\");\n dispatchRaw(raw);\n if (!pushing) pushUrl(senderEl, raw);\n}\n\nfunction parseInterval(str) {\n const match = str.match(/^(\\d+)(s|ms)$/);\n if (!match) return null;\n const n = parseInt(match[1], 10);\n return match[2] === \"s\" ? n * 1000 : n;\n}\n\n// Set up a repeating interval for receivers with a poll: keyword.\nlet activePollers = 0;\nlet maxPollers = 64;\n\nfunction startPolling(el) {\n const attr = el.getAttribute(\"receiver\");\n const msg = parseMessage(attr);\n if (msg.keywords[msg.keywords.length - 1] !== \"poll:\") return;\n if (activePollers >= maxPollers) {\n console.warn(\"talkDOM: max pollers (\" + maxPollers + \") reached, ignoring \" + msg.receiver);\n return;\n }\n const interval = parseInterval(msg.args[msg.args.length - 1]);\n if (!interval) {\n console.error(\"poll: invalid interval for \" + msg.receiver);\n return;\n }\n const selector = msg.keywords.slice(0, -1).join(\"\");\n const args = msg.args.slice(0, -1);\n const name = msg.receiver;\n let cachedTargets = findReceivers(name);\n let method = methods[selector];\n activePollers++;\n const id = setInterval(function () {\n if (!el.isConnected) { clearInterval(id); activePollers--; return; }\n if (cachedTargets.length === 0 || !cachedTargets[0].isConnected) {\n cachedTargets = findReceivers(name);\n }\n if (cachedTargets.length === 0) return;\n if (!method) method = methods[selector];\n if (!method) {\n console.error(name + \" does not understand \" + selector);\n return;\n }\n cachedTargets.forEach(function (target) { method(target, ...args); });\n }, interval);\n}\n\n// Global click handler: delegate to any element with a sender attribute.\ndocument.addEventListener(\"click\", function (e) {\n const sender = e.target.closest(\"[sender]\");\n if (sender) {\n e.preventDefault();\n dispatch(sender);\n }\n});\n\nrestore();\nreplayState(history.state);\ndocument.querySelectorAll(\"[receiver]\").forEach(startPolling);\n\nnew MutationObserver(function (mutations) {\n for (let i = 0; i < mutations.length; i++) {\n const added = mutations[i].addedNodes;\n for (let j = 0; j < added.length; j++) {\n const node = added[j];\n if (node.nodeType === Node.ELEMENT_NODE) {\n if (node.hasAttribute && node.hasAttribute(\"receiver\")) {\n startPolling(node);\n }\n const children = node.querySelectorAll ? node.querySelectorAll(\"[receiver]\") : [];\n for (let k = 0; k < children.length; k++) {\n startPolling(children[k]);\n }\n }\n }\n };\n}).observe(document, { childList: true, subtree: true });\n\nconst talkDOM = {\n methods,\n send: run,\n get maxPollers() { return maxPollers; },\n set maxPollers(n) { maxPollers = n; },\n};\n\nwindow.talkDOM = talkDOM;\n\nexport { parseMessage, receiverName, findReceivers, accepts, persist, restore, apply, request, methods, run, dispatchRaw, talkDOM };\n"],
5
+ "mappings": "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",
6
+ "names": ["WS", "STORAGE_PREFIX", "parseMessage", "str", "trimmed", "tokens", "receiver", "body", "rest", "keywords", "args", "currentArg", "i", "token", "receiverName", "el", "attr", "sp", "receiverCache", "cacheValid", "findReceivers", "name", "key", "result", "accepts", "op", "persist", "content", "restore", "raw", "state", "apply", "csrfMeta", "csrfToken", "request", "method", "url", "headers", "r", "trigger", "text", "dispatchRaw", "serializeForm", "form", "recName", "methods", "message", "t", "formSelector", "jsonStr", "data", "pushing", "pushUrl", "senderEl", "msg", "replayState", "resolveTarget", "next", "parent", "send", "piped", "els", "loadingClass", "loadingIdx", "_", "selector", "finalArgs", "detail", "target", "err", "run", "chains", "chain", "step", "steps", "s", "prev", "dispatch", "parseInterval", "match", "activePollers", "maxPollers", "startPolling", "interval", "cachedTargets", "id", "sender", "mutations", "added", "j", "node", "children", "k", "talkDOM", "n"]
7
+ }
package/package.json CHANGED
@@ -1,18 +1,30 @@
1
1
  {
2
2
  "name": "talkdom",
3
- "version": "0.3.1",
3
+ "version": "0.4.1",
4
4
  "description": "Smalltalk-inspired message passing for the DOM",
5
- "main": "index.js",
5
+ "type": "module",
6
+ "main": "dist/talkdom.min.js",
7
+ "module": "dist/talkdom.esm.js",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/talkdom.esm.js",
11
+ "require": "./dist/talkdom.min.js"
12
+ },
13
+ "./ws": {
14
+ "import": "./dist/talkdom-ws.esm.js",
15
+ "require": "./dist/talkdom-ws.min.js"
16
+ }
17
+ },
6
18
  "jsdelivr": "dist/talkdom.min.js",
7
19
  "unpkg": "dist/talkdom.min.js",
8
20
  "files": [
9
- "index.js",
21
+ "src/",
10
22
  "dist/"
11
23
  ],
12
24
  "scripts": {
13
- "build": "node -e \"require('fs').mkdirSync('dist',{recursive:true})\" && terser index.js -o dist/talkdom.min.js -c -m --source-map",
25
+ "build": "node build.js",
14
26
  "test": "node test-runner.js",
15
- "lint": "eslint index.js",
27
+ "lint": "eslint src/",
16
28
  "prepublishOnly": "npm run build"
17
29
  },
18
30
  "homepage": "https://talkdom.org",
@@ -29,8 +41,8 @@
29
41
  ],
30
42
  "license": "MIT",
31
43
  "devDependencies": {
44
+ "esbuild": "^0.28.0",
32
45
  "eslint": "^10.1.0",
33
- "jsdom": "^29.0.1",
34
- "terser": "^5.46.1"
46
+ "jsdom": "^29.0.1"
35
47
  }
36
48
  }
package/src/index.js ADDED
@@ -0,0 +1,415 @@
1
+ const WS = /\s+/;
2
+ const STORAGE_PREFIX = "talkDOM:";
3
+
4
+ // Parse "receiver keyword: arg keyword: arg" into structured message object.
5
+ // Tokens ending with ":" are keywords, everything else fills args.
6
+ function parseMessage(str) {
7
+ const trimmed = str.trim();
8
+ const tokens = trimmed.split(WS);
9
+ const receiver = tokens[0];
10
+ const body = trimmed.substring(receiver.length).trim();
11
+ const rest = tokens.slice(1);
12
+ const keywords = [];
13
+ const args = [];
14
+ let currentArg = [];
15
+
16
+ for (let i = 0; i < rest.length; i++) {
17
+ const token = rest[i];
18
+ if (token.endsWith(":")) {
19
+ if (keywords.length > 0 && currentArg.length > 0) {
20
+ args.push(currentArg.join(" "));
21
+ currentArg = [];
22
+ } else if (keywords.length > 0) {
23
+ args.push("");
24
+ }
25
+ keywords.push(token);
26
+ } else {
27
+ currentArg.push(token);
28
+ }
29
+ }
30
+ if (keywords.length > 0) {
31
+ args.push(currentArg.join(" "));
32
+ }
33
+
34
+ return { receiver, selector: keywords.join(""), keywords, args, body };
35
+ }
36
+
37
+ // Extract the first word from the receiver attribute (the name).
38
+ function receiverName(el) {
39
+ const attr = el.getAttribute("receiver").trim();
40
+ const sp = attr.indexOf(" ");
41
+ return sp === -1 ? attr : attr.substring(0, sp);
42
+ }
43
+
44
+ // Receiver cache: maps name -> NodeList, invalidated by DOM mutations.
45
+ const receiverCache = Object.create(null);
46
+ let cacheValid = false;
47
+
48
+ new MutationObserver(() => { cacheValid = false; })
49
+ .observe(document, { childList: true, subtree: true, attributes: true, attributeFilter: ["receiver"] });
50
+
51
+ // Find all elements whose receiver attribute contains the given name.
52
+ function findReceivers(name) {
53
+ if (!cacheValid) {
54
+ Object.keys(receiverCache).forEach(key => delete receiverCache[key]);
55
+ cacheValid = true;
56
+ }
57
+ if (receiverCache[name]) return receiverCache[name];
58
+ const result = document.querySelectorAll('[receiver~="' + name + '"]');
59
+ receiverCache[name] = result;
60
+ return result;
61
+ }
62
+
63
+ // Check if a receiver allows a given apply operation (inner, text, append, outer).
64
+ // No "accepts" attribute means everything is allowed.
65
+ function accepts(el, op) {
66
+ const attr = el.getAttribute("accepts");
67
+ if (!attr) return true;
68
+ return (" " + attr + " ").includes(" " + op + " ");
69
+ }
70
+
71
+ // Save receiver content to localStorage after apply, keyed by receiver name.
72
+ function persist(el, op) {
73
+ if (!el.hasAttribute("receiver") || !el.hasAttribute("persist")) return;
74
+ const name = receiverName(el);
75
+ const key = STORAGE_PREFIX + name;
76
+ const content = op === "outer" ? el.outerHTML : el.innerHTML;
77
+ localStorage.setItem(key, JSON.stringify({ op, content }));
78
+ }
79
+
80
+ // On page load, restore persisted receiver content from localStorage.
81
+ function restore() {
82
+ document.querySelectorAll("[persist]").forEach((el) => {
83
+ if (!el.hasAttribute("receiver")) return;
84
+ const name = receiverName(el);
85
+ const raw = localStorage.getItem(STORAGE_PREFIX + name);
86
+ if (!raw) return;
87
+ let state;
88
+ try { state = JSON.parse(raw); } catch {
89
+ localStorage.removeItem(STORAGE_PREFIX + name);
90
+ return;
91
+ }
92
+ if (state.op === "outer") {
93
+ el.outerHTML = state.content;
94
+ } else {
95
+ el.innerHTML = state.content;
96
+ }
97
+ });
98
+ }
99
+
100
+ // Apply content to an element using the specified operation (inner, text, append, outer).
101
+ function apply(el, op, content) {
102
+ if (!accepts(el, op)) {
103
+ console.error(receiverName(el) + " does not accept " + op);
104
+ return;
105
+ }
106
+ switch (op) {
107
+ case "inner": el.innerHTML = content; break;
108
+ case "text": el.textContent = content; break;
109
+ case "append": el.insertAdjacentHTML("beforeend", content); break;
110
+ case "outer": el.outerHTML = content; break;
111
+ }
112
+ persist(el, op);
113
+ return content;
114
+ }
115
+
116
+ let csrfMeta = null;
117
+
118
+ function csrfToken() {
119
+ if (!csrfMeta || !csrfMeta.isConnected) {
120
+ csrfMeta = document.querySelector('meta[name="csrf-token"]');
121
+ }
122
+ return csrfMeta ? csrfMeta.getAttribute("content") : "";
123
+ }
124
+
125
+ // Perform a fetch with talkDOM headers. Returns a promise resolving to response text.
126
+ // Fires server-triggered messages from X-TalkDOM-Trigger header if present.
127
+ function request(method, url, receiver, body) {
128
+ const headers = {
129
+ "X-TalkDOM-Request": "true",
130
+ "X-TalkDOM-Current-URL": location.href,
131
+ };
132
+ if (receiver) {
133
+ headers["X-TalkDOM-Receiver"] = receiver;
134
+ }
135
+ if (method !== "GET") {
136
+ const token = csrfToken();
137
+ if (token) headers["X-CSRF-Token"] = token;
138
+ else console.warn("talkDOM: no CSRF token found for " + method + " " + url);
139
+ }
140
+ return fetch(url, { method, headers, body }).then((r) => {
141
+ if (!r.ok) {
142
+ console.error("talkDOM: " + method + " " + url + " " + r.status);
143
+ return Promise.reject(r.status);
144
+ }
145
+ const trigger = r.headers.get("X-TalkDOM-Trigger");
146
+ return r.text().then((text) => {
147
+ if (trigger) dispatchRaw(trigger);
148
+ return text;
149
+ });
150
+ });
151
+ }
152
+
153
+ function serializeForm(form) {
154
+ return new URLSearchParams(new FormData(form)).toString();
155
+ }
156
+
157
+ function recName(el) {
158
+ return el.hasAttribute("receiver") ? receiverName(el) : "";
159
+ }
160
+
161
+ // Built-in method table. Each method receives (el, ...args) from the parsed message.
162
+ // Extensible via talkDOM.methods at runtime.
163
+ const methods = {
164
+ "get:": function (el, url) { return request("GET", url, recName(el)); },
165
+ "post:": function (el, url) { return request("POST", url, recName(el)); },
166
+ "put:": function (el, url) { return request("PUT", url, recName(el)); },
167
+ "delete:": function (el, url) { return request("DELETE", url, recName(el)); },
168
+ "confirm:": function (el, message) { if (!confirm(message)) return Promise.reject("cancelled"); },
169
+ "apply:": function (el, content, op) { return apply(el, op, content); },
170
+ "get:apply:": function (el, url, op) { return request("GET", url, recName(el)).then(function (t) { return apply(el, op, t); }); },
171
+ "post:apply:": function (el, url, op) { return request("POST", url, recName(el)).then(function (t) { return apply(el, op, t); }); },
172
+ "put:apply:": function (el, url, op) { return request("PUT", url, recName(el)).then(function (t) { return apply(el, op, t); }); },
173
+ "delete:apply:": function (el, url, op) { return request("DELETE", url, recName(el)).then(function (t) { return apply(el, op, t); }); },
174
+ "post-form:apply:form:": function (el, url, op, formSelector) {
175
+ const form = document.querySelector(formSelector);
176
+ if (!form) { console.error("post-form: form not found: " + formSelector); return; }
177
+ const body = serializeForm(form);
178
+ return request("POST", url, recName(el), body).then(function (t) { return apply(el, op, t); });
179
+ },
180
+ "put-form:apply:form:": function (el, url, op, formSelector) {
181
+ const form = document.querySelector(formSelector);
182
+ if (!form) { console.error("put-form: form not found: " + formSelector); return; }
183
+ const body = serializeForm(form);
184
+ return request("PUT", url, recName(el), body).then(function (t) { return apply(el, op, t); });
185
+ },
186
+ "delete-form:apply:form:": function (el, url, op, formSelector) {
187
+ const form = document.querySelector(formSelector);
188
+ if (!form) { console.error("delete-form: form not found: " + formSelector); return; }
189
+ const body = serializeForm(form);
190
+ return request("DELETE", url, recName(el), body).then(function (t) { return apply(el, op, t); });
191
+ },
192
+ "post-json:apply:json:": function (el, url, op, jsonStr) {
193
+ let data;
194
+ try { data = JSON.parse(jsonStr); } catch { data = {}; }
195
+ return request("POST", url, recName(el), JSON.stringify(data)).then(function (t) { return apply(el, op, t); });
196
+ },
197
+ "put-json:apply:json:": function (el, url, op, jsonStr) {
198
+ let data;
199
+ try { data = JSON.parse(jsonStr); } catch { data = {}; }
200
+ return request("PUT", url, recName(el), JSON.stringify(data)).then(function (t) { return apply(el, op, t); });
201
+ },
202
+ };
203
+
204
+ let pushing = false;
205
+
206
+ // Push URL to browser history. Uses push-url attr value, or falls back to first message arg.
207
+ function pushUrl(senderEl, raw) {
208
+ if (!senderEl.hasAttribute("push-url")) return;
209
+ let url = senderEl.getAttribute("push-url");
210
+ if (!url) {
211
+ const msg = parseMessage(raw.split(";")[0].split("|")[0].trim());
212
+ if (msg.args.length > 0) {
213
+ url = msg.args[0].split(/\s/)[0] || "";
214
+ }
215
+ }
216
+ if (url && (location.pathname + location.search) !== url) {
217
+ history.pushState({ sender: raw }, "", url);
218
+ }
219
+ }
220
+
221
+ // Re-dispatch a sender message from history state (back/forward navigation).
222
+ function replayState(state) {
223
+ if (!state || !state.sender) return;
224
+ pushing = true;
225
+ dispatchRaw(state.sender);
226
+ pushing = false;
227
+ }
228
+
229
+ window.addEventListener("popstate", function (e) {
230
+ replayState(e.state);
231
+ });
232
+
233
+ // After an outer swap `el` is gone. Walk from the snapshotted sibling or
234
+ // parent to find the element that took its place; fall back to a fresh
235
+ // receiver query if the DOM was restructured.
236
+ function resolveTarget(el, next, parent, name) {
237
+ if (el.isConnected) return el;
238
+ const candidate = next && next.isConnected ? next.previousElementSibling
239
+ : parent && parent.isConnected ? parent.lastElementChild : null;
240
+ return candidate || findReceivers(name)[0];
241
+ }
242
+
243
+ // Deliver a parsed message to all matching receivers. Fires talkdom:done or talkdom:error
244
+ // lifecycle events on the receiver element (or its replacement if outer-swapped).
245
+ function send(msg, piped) {
246
+ const els = findReceivers(msg.receiver);
247
+ if (els.length === 0) {
248
+ console.error(msg.receiver + " not found");
249
+ return;
250
+ }
251
+ let loadingClass = null;
252
+ let keywords = msg.keywords;
253
+ let args = msg.args;
254
+ const loadingIdx = keywords.indexOf("loading:");
255
+ if (loadingIdx !== -1) {
256
+ loadingClass = args[loadingIdx];
257
+ keywords = keywords.filter(function (_, i) { return i !== loadingIdx; });
258
+ args = args.filter(function (_, i) { return i !== loadingIdx; });
259
+ }
260
+ const selector = keywords.join("");
261
+ const method = methods[selector];
262
+ if (!method) {
263
+ console.error(msg.receiver + " does not understand " + selector);
264
+ return;
265
+ }
266
+ const finalArgs = piped !== undefined ? [piped].concat(args) : args;
267
+ let result;
268
+ els.forEach(function (el) {
269
+ const detail = { receiver: msg.receiver, selector: selector, args: msg.args };
270
+ const parent = el.parentNode;
271
+ const next = el.nextElementSibling;
272
+ if (loadingClass) el.classList.add(loadingClass);
273
+ result = method(el, ...finalArgs);
274
+ if (result && typeof result.then === "function") {
275
+ result.then(function () {
276
+ if (loadingClass) el.classList.remove(loadingClass);
277
+ const target = resolveTarget(el, next, parent, msg.receiver);
278
+ if (target) target.dispatchEvent(new CustomEvent("talkdom:done", { bubbles: true, detail }));
279
+ }, function (err) {
280
+ if (loadingClass) el.classList.remove(loadingClass);
281
+ detail.error = err;
282
+ const target = resolveTarget(el, next, parent, msg.receiver);
283
+ if (target) target.dispatchEvent(new CustomEvent("talkdom:error", { bubbles: true, detail }));
284
+ });
285
+ } else {
286
+ if (loadingClass) el.classList.remove(loadingClass);
287
+ const target = resolveTarget(el, next, parent, msg.receiver);
288
+ if (target) target.dispatchEvent(new CustomEvent("talkdom:done", { bubbles: true, detail }));
289
+ }
290
+ });
291
+ return result;
292
+ }
293
+
294
+ // Programmatic API: parse and execute a raw message string (supports pipes and semicolons).
295
+ // Returns a promise that resolves when all chains complete.
296
+ function run(raw) {
297
+ const trimmed = raw.trim();
298
+ if (trimmed.indexOf(";") === -1 && trimmed.indexOf("|") === -1) {
299
+ return Promise.resolve(send(parseMessage(trimmed))).then(function (r) { return [r]; });
300
+ }
301
+ const chains = trimmed.split(";").map(function (chain) {
302
+ const step = chain.trim();
303
+ if (!step) return Promise.resolve();
304
+ const steps = step.split("|").map(function (s) { return s.trim(); }).filter(Boolean);
305
+ if (steps.length === 1) {
306
+ return Promise.resolve(send(parseMessage(steps[0])));
307
+ }
308
+ return steps.reduce(function (prev, step) {
309
+ const msg = parseMessage(step);
310
+ return Promise.resolve(prev).then(function (piped) {
311
+ return send(msg, piped);
312
+ });
313
+ }, undefined);
314
+ });
315
+ return Promise.all(chains);
316
+ }
317
+
318
+ // Fire-and-forget dispatch used by declarative senders and server triggers.
319
+ function dispatchRaw(raw) {
320
+ run(raw).catch(function (err) { console.warn("talkDOM:", err); });
321
+ }
322
+
323
+ // Entry point for a sender click: dispatch its message and optionally push URL.
324
+ function dispatch(senderEl) {
325
+ const raw = senderEl.getAttribute("sender");
326
+ dispatchRaw(raw);
327
+ if (!pushing) pushUrl(senderEl, raw);
328
+ }
329
+
330
+ function parseInterval(str) {
331
+ const match = str.match(/^(\d+)(s|ms)$/);
332
+ if (!match) return null;
333
+ const n = parseInt(match[1], 10);
334
+ return match[2] === "s" ? n * 1000 : n;
335
+ }
336
+
337
+ // Set up a repeating interval for receivers with a poll: keyword.
338
+ let activePollers = 0;
339
+ let maxPollers = 64;
340
+
341
+ function startPolling(el) {
342
+ const attr = el.getAttribute("receiver");
343
+ const msg = parseMessage(attr);
344
+ if (msg.keywords[msg.keywords.length - 1] !== "poll:") return;
345
+ if (activePollers >= maxPollers) {
346
+ console.warn("talkDOM: max pollers (" + maxPollers + ") reached, ignoring " + msg.receiver);
347
+ return;
348
+ }
349
+ const interval = parseInterval(msg.args[msg.args.length - 1]);
350
+ if (!interval) {
351
+ console.error("poll: invalid interval for " + msg.receiver);
352
+ return;
353
+ }
354
+ const selector = msg.keywords.slice(0, -1).join("");
355
+ const args = msg.args.slice(0, -1);
356
+ const name = msg.receiver;
357
+ let cachedTargets = findReceivers(name);
358
+ let method = methods[selector];
359
+ activePollers++;
360
+ const id = setInterval(function () {
361
+ if (!el.isConnected) { clearInterval(id); activePollers--; return; }
362
+ if (cachedTargets.length === 0 || !cachedTargets[0].isConnected) {
363
+ cachedTargets = findReceivers(name);
364
+ }
365
+ if (cachedTargets.length === 0) return;
366
+ if (!method) method = methods[selector];
367
+ if (!method) {
368
+ console.error(name + " does not understand " + selector);
369
+ return;
370
+ }
371
+ cachedTargets.forEach(function (target) { method(target, ...args); });
372
+ }, interval);
373
+ }
374
+
375
+ // Global click handler: delegate to any element with a sender attribute.
376
+ document.addEventListener("click", function (e) {
377
+ const sender = e.target.closest("[sender]");
378
+ if (sender) {
379
+ e.preventDefault();
380
+ dispatch(sender);
381
+ }
382
+ });
383
+
384
+ restore();
385
+ replayState(history.state);
386
+ document.querySelectorAll("[receiver]").forEach(startPolling);
387
+
388
+ new MutationObserver(function (mutations) {
389
+ for (let i = 0; i < mutations.length; i++) {
390
+ const added = mutations[i].addedNodes;
391
+ for (let j = 0; j < added.length; j++) {
392
+ const node = added[j];
393
+ if (node.nodeType === Node.ELEMENT_NODE) {
394
+ if (node.hasAttribute && node.hasAttribute("receiver")) {
395
+ startPolling(node);
396
+ }
397
+ const children = node.querySelectorAll ? node.querySelectorAll("[receiver]") : [];
398
+ for (let k = 0; k < children.length; k++) {
399
+ startPolling(children[k]);
400
+ }
401
+ }
402
+ }
403
+ };
404
+ }).observe(document, { childList: true, subtree: true });
405
+
406
+ const talkDOM = {
407
+ methods,
408
+ send: run,
409
+ get maxPollers() { return maxPollers; },
410
+ set maxPollers(n) { maxPollers = n; },
411
+ };
412
+
413
+ window.talkDOM = talkDOM;
414
+
415
+ export { parseMessage, receiverName, findReceivers, accepts, persist, restore, apply, request, methods, run, dispatchRaw, talkDOM };