@webqit/webflo 0.11.61 → 0.20.2-next.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.
Files changed (235) hide show
  1. package/.github/FUNDING.yml +12 -0
  2. package/.github/workflows/publish.yml +48 -0
  3. package/.gitignore +2 -0
  4. package/LICENSE +2 -2
  5. package/README.md +71 -2050
  6. package/package.json +28 -13
  7. package/site/-/_.md +139 -0
  8. package/site/-/docs.old.md +2010 -0
  9. package/site/.vitepress/cache/deps/@braintree_sanitize-url 2.js +93 -0
  10. package/site/.vitepress/cache/deps/@braintree_sanitize-url.js +93 -0
  11. package/site/.vitepress/cache/deps/@braintree_sanitize-url.js 2.map +7 -0
  12. package/site/.vitepress/cache/deps/@braintree_sanitize-url.js.map +7 -0
  13. package/site/.vitepress/cache/deps/_metadata 2.json +85 -0
  14. package/site/.vitepress/cache/deps/_metadata.json +85 -0
  15. package/site/.vitepress/cache/deps/chunk-BUSYA2B4 2.js +9 -0
  16. package/site/.vitepress/cache/deps/chunk-BUSYA2B4.js +9 -0
  17. package/site/.vitepress/cache/deps/chunk-BUSYA2B4.js 2.map +7 -0
  18. package/site/.vitepress/cache/deps/chunk-BUSYA2B4.js.map +7 -0
  19. package/site/.vitepress/cache/deps/chunk-Q2AYPHVK 2.js +9719 -0
  20. package/site/.vitepress/cache/deps/chunk-Q2AYPHVK.js +9719 -0
  21. package/site/.vitepress/cache/deps/chunk-Q2AYPHVK.js 2.map +7 -0
  22. package/site/.vitepress/cache/deps/chunk-Q2AYPHVK.js.map +7 -0
  23. package/site/.vitepress/cache/deps/chunk-QAXAIFA7 2.js +12705 -0
  24. package/site/.vitepress/cache/deps/chunk-QAXAIFA7.js +12705 -0
  25. package/site/.vitepress/cache/deps/chunk-QAXAIFA7.js 2.map +7 -0
  26. package/site/.vitepress/cache/deps/chunk-QAXAIFA7.js.map +7 -0
  27. package/site/.vitepress/cache/deps/cytoscape 2.js +30278 -0
  28. package/site/.vitepress/cache/deps/cytoscape-cose-bilkent 2.js +4710 -0
  29. package/site/.vitepress/cache/deps/cytoscape-cose-bilkent.js +4710 -0
  30. package/site/.vitepress/cache/deps/cytoscape-cose-bilkent.js 2.map +7 -0
  31. package/site/.vitepress/cache/deps/cytoscape-cose-bilkent.js.map +7 -0
  32. package/site/.vitepress/cache/deps/cytoscape.js +30278 -0
  33. package/site/.vitepress/cache/deps/cytoscape.js 2.map +7 -0
  34. package/site/.vitepress/cache/deps/cytoscape.js.map +7 -0
  35. package/site/.vitepress/cache/deps/dayjs 2.js +285 -0
  36. package/site/.vitepress/cache/deps/dayjs.js +285 -0
  37. package/site/.vitepress/cache/deps/dayjs.js 2.map +7 -0
  38. package/site/.vitepress/cache/deps/dayjs.js.map +7 -0
  39. package/site/.vitepress/cache/deps/debug 2.js +453 -0
  40. package/site/.vitepress/cache/deps/debug.js +453 -0
  41. package/site/.vitepress/cache/deps/debug.js 2.map +7 -0
  42. package/site/.vitepress/cache/deps/debug.js.map +7 -0
  43. package/site/.vitepress/cache/deps/package 2.json +3 -0
  44. package/site/.vitepress/cache/deps/package.json +3 -0
  45. package/site/.vitepress/cache/deps/vitepress___@vue_devtools-api 2.js +4507 -0
  46. package/site/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +4507 -0
  47. package/site/.vitepress/cache/deps/vitepress___@vue_devtools-api.js 2.map +7 -0
  48. package/site/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +7 -0
  49. package/site/.vitepress/cache/deps/vitepress___@vueuse_core 2.js +584 -0
  50. package/site/.vitepress/cache/deps/vitepress___@vueuse_core.js +584 -0
  51. package/site/.vitepress/cache/deps/vitepress___@vueuse_core.js 2.map +7 -0
  52. package/site/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +7 -0
  53. package/site/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap 2.js +1166 -0
  54. package/site/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js +1166 -0
  55. package/site/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js 2.map +7 -0
  56. package/site/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js.map +7 -0
  57. package/site/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js 2.js +1667 -0
  58. package/site/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js +1667 -0
  59. package/site/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js 2.map +7 -0
  60. package/site/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js.map +7 -0
  61. package/site/.vitepress/cache/deps/vitepress___minisearch 2.js +1815 -0
  62. package/site/.vitepress/cache/deps/vitepress___minisearch.js +1815 -0
  63. package/site/.vitepress/cache/deps/vitepress___minisearch.js 2.map +7 -0
  64. package/site/.vitepress/cache/deps/vitepress___minisearch.js.map +7 -0
  65. package/site/.vitepress/cache/deps/vue 2.js +344 -0
  66. package/site/.vitepress/cache/deps/vue.js +344 -0
  67. package/site/.vitepress/cache/deps/vue.js 2.map +7 -0
  68. package/site/.vitepress/cache/deps/vue.js.map +7 -0
  69. package/site/.vitepress/config.ts +147 -0
  70. package/site/.vitepress/theme/custom.css +50 -0
  71. package/site/.vitepress/theme/index.ts +6 -0
  72. package/site/api/webflo-fetch/FormData.md +0 -0
  73. package/site/api/webflo-fetch/Headers.md +0 -0
  74. package/site/api/webflo-fetch/LiveResponse.md +0 -0
  75. package/site/api/webflo-fetch/Request.md +0 -0
  76. package/site/api/webflo-fetch/Response.md +0 -0
  77. package/site/api/webflo-fetch/fetch.md +0 -0
  78. package/site/api/webflo-routing/HttpCookies.md +0 -0
  79. package/site/api/webflo-routing/HttpEvent/respondWith.md +1 -0
  80. package/site/api/webflo-routing/HttpEvent/waitUntil.md +1 -0
  81. package/site/api/webflo-routing/HttpEvent/waitUntilNavigate.md +1 -0
  82. package/site/api/webflo-routing/HttpEvent.md +30 -0
  83. package/site/api/webflo-routing/HttpSession.md +0 -0
  84. package/site/api/webflo-routing/HttpState.md +0 -0
  85. package/site/api/webflo-routing/HttpUser.md +0 -0
  86. package/site/api/webflo-routing/handler/fetch.md +42 -0
  87. package/site/api/webflo-routing/handler/next.md +54 -0
  88. package/site/api/webflo-routing/handler.md +119 -0
  89. package/site/api.md +26 -0
  90. package/site/contributing.md +16 -0
  91. package/site/docs/advanced/lifecycles.md +20 -0
  92. package/site/docs/advanced/redirects.md +0 -0
  93. package/site/docs/advanced/routing.md +1 -0
  94. package/site/docs/advanced.md +9 -0
  95. package/site/docs/concepts/realtime.md +637 -0
  96. package/site/docs/concepts/rendering.md +60 -0
  97. package/site/docs/concepts/request-response.md +47 -0
  98. package/site/docs/concepts/routing.md +656 -0
  99. package/site/docs/concepts/state.md +44 -0
  100. package/site/docs/concepts/templates.md +48 -0
  101. package/site/docs/concepts.md +97 -0
  102. package/site/docs/getting-started.md +378 -0
  103. package/site/docs/tech-stack.md +56 -0
  104. package/site/docs.md +100 -0
  105. package/site/examples/pwa.md +10 -0
  106. package/site/examples/web.md +11 -0
  107. package/site/examples.md +10 -0
  108. package/site/faq.md +13 -0
  109. package/site/guides/guide-auth.md +13 -0
  110. package/site/guides/guide-file-upload.md +11 -0
  111. package/site/guides/guide-service-worker.md +10 -0
  112. package/site/guides/tutorial-1-todo.md +24 -0
  113. package/site/guides.md +15 -0
  114. package/site/index.md +39 -0
  115. package/site/public/img/brand/logo-670x670.png +0 -0
  116. package/site/recipes/realtime.md +11 -0
  117. package/site/recipes/streaming.md +15 -0
  118. package/site/reference/cli.md +11 -0
  119. package/site/reference/config.md +13 -0
  120. package/site/reference/tools.md +9 -0
  121. package/src/Context.js +3 -11
  122. package/src/config-pi/deployment/Env.js +6 -19
  123. package/src/config-pi/deployment/Layout.js +11 -3
  124. package/src/config-pi/runtime/Client.js +40 -48
  125. package/src/config-pi/runtime/Server.js +52 -20
  126. package/src/config-pi/runtime/client/Worker.js +22 -20
  127. package/src/config-pi/static/Init.js +57 -0
  128. package/src/config-pi/static/index.js +2 -0
  129. package/src/deployment-pi/origins/index.js +1 -1
  130. package/src/deployment-pi/util.js +161 -0
  131. package/src/index.js +3 -9
  132. package/src/init-pi/index.js +117 -0
  133. package/src/init-pi/templates/pwa/app/handler.server.js +8 -0
  134. package/src/init-pi/templates/pwa/app/page.html +7 -0
  135. package/src/init-pi/templates/pwa/package.json +19 -0
  136. package/src/init-pi/templates/pwa/public/assets/app.css +16 -0
  137. package/src/init-pi/templates/pwa/public/index.html +39 -0
  138. package/src/init-pi/templates/pwa/public/manifest.json +29 -0
  139. package/src/init-pi/templates/web/app/handler.server.js +8 -0
  140. package/src/init-pi/templates/web/app/page.html +7 -0
  141. package/src/init-pi/templates/web/package.json +19 -0
  142. package/src/init-pi/templates/web/public/assets/app.css +16 -0
  143. package/src/init-pi/templates/web/public/index.html +39 -0
  144. package/src/runtime-pi/WebfloRuntime.js +350 -0
  145. package/src/runtime-pi/index.js +3 -10
  146. package/src/runtime-pi/webflo-client/ClientSideCookies.js +17 -0
  147. package/src/runtime-pi/webflo-client/ClientSideWorkport.js +63 -0
  148. package/src/runtime-pi/webflo-client/DeviceCapabilities.js +213 -0
  149. package/src/runtime-pi/webflo-client/WebfloClient.js +500 -0
  150. package/src/runtime-pi/webflo-client/WebfloRootClient1.js +206 -0
  151. package/src/runtime-pi/webflo-client/WebfloRootClient2.js +113 -0
  152. package/src/runtime-pi/webflo-client/WebfloSubClient.js +118 -0
  153. package/src/runtime-pi/webflo-client/index.js +17 -0
  154. package/src/runtime-pi/webflo-client/webflo-codegen.js +469 -0
  155. package/src/runtime-pi/webflo-client/webflo-devmode.js +243 -0
  156. package/src/runtime-pi/webflo-client/webflo-embedded.js +50 -0
  157. package/src/runtime-pi/webflo-fetch/LiveResponse.js +437 -0
  158. package/src/runtime-pi/webflo-fetch/cookies.js +10 -0
  159. package/src/runtime-pi/webflo-fetch/fetch.js +16 -0
  160. package/src/runtime-pi/webflo-fetch/formdata.js +54 -0
  161. package/src/runtime-pi/webflo-fetch/headers.js +151 -0
  162. package/src/runtime-pi/webflo-fetch/index.js +5 -0
  163. package/src/runtime-pi/webflo-fetch/message.js +49 -0
  164. package/src/runtime-pi/webflo-fetch/request.js +62 -0
  165. package/src/runtime-pi/webflo-fetch/response.js +110 -0
  166. package/src/runtime-pi/webflo-fetch/util.js +28 -0
  167. package/src/runtime-pi/webflo-messaging/WQBroadcastChannel.js +10 -0
  168. package/src/runtime-pi/webflo-messaging/WQMessageChannel.js +26 -0
  169. package/src/runtime-pi/webflo-messaging/WQMessageEvent.js +87 -0
  170. package/src/runtime-pi/webflo-messaging/WQMessagePort.js +38 -0
  171. package/src/runtime-pi/webflo-messaging/WQRelayPort.js +47 -0
  172. package/src/runtime-pi/webflo-messaging/WQSockPort.js +113 -0
  173. package/src/runtime-pi/webflo-messaging/WQStarPort.js +104 -0
  174. package/src/runtime-pi/webflo-messaging/wq-message-port.js +404 -0
  175. package/src/runtime-pi/webflo-routing/HttpCookies.js +42 -0
  176. package/src/runtime-pi/webflo-routing/HttpEvent.js +112 -0
  177. package/src/runtime-pi/webflo-routing/HttpSession.js +11 -0
  178. package/src/runtime-pi/webflo-routing/HttpState.js +153 -0
  179. package/src/runtime-pi/webflo-routing/HttpUser.js +54 -0
  180. package/src/runtime-pi/webflo-routing/WebfloRouter.js +245 -0
  181. package/src/runtime-pi/webflo-server/ServerSideCookies.js +19 -0
  182. package/src/runtime-pi/webflo-server/ServerSideSession.js +38 -0
  183. package/src/runtime-pi/webflo-server/WebfloServer.js +937 -0
  184. package/src/runtime-pi/webflo-server/index.js +11 -0
  185. package/src/runtime-pi/webflo-server/messaging/Client.js +27 -0
  186. package/src/runtime-pi/webflo-server/messaging/ClientRequestRealtime.js +50 -0
  187. package/src/runtime-pi/webflo-server/messaging/Clients.js +25 -0
  188. package/src/runtime-pi/webflo-server/webflo-devmode.js +326 -0
  189. package/src/runtime-pi/{client → webflo-url}/Url.js +27 -76
  190. package/src/runtime-pi/webflo-url/index.js +1 -0
  191. package/src/runtime-pi/webflo-url/urlpattern.js +38 -0
  192. package/src/runtime-pi/{util-url.js → webflo-url/util.js} +5 -43
  193. package/src/runtime-pi/webflo-url/xURL.js +94 -0
  194. package/src/runtime-pi/webflo-worker/WebfloWorker.js +234 -0
  195. package/src/runtime-pi/webflo-worker/WorkerSideCookies.js +19 -0
  196. package/src/runtime-pi/webflo-worker/WorkerSideWorkport.js +18 -0
  197. package/src/runtime-pi/webflo-worker/index.js +11 -0
  198. package/src/services-pi/index.js +2 -0
  199. package/src/services-pi/push/index.js +23 -0
  200. package/src/util.js +10 -0
  201. package/src/{webflo.js → webflo-cli.js} +4 -4
  202. package/src/runtime-pi/Application.js +0 -29
  203. package/src/runtime-pi/Cookies.js +0 -82
  204. package/src/runtime-pi/HttpEvent.js +0 -107
  205. package/src/runtime-pi/Router.js +0 -130
  206. package/src/runtime-pi/Runtime.js +0 -21
  207. package/src/runtime-pi/client/Application.js +0 -76
  208. package/src/runtime-pi/client/Context.js +0 -7
  209. package/src/runtime-pi/client/Router.js +0 -48
  210. package/src/runtime-pi/client/Runtime.js +0 -525
  211. package/src/runtime-pi/client/Workport.js +0 -190
  212. package/src/runtime-pi/client/createStorage.js +0 -58
  213. package/src/runtime-pi/client/generate.js +0 -481
  214. package/src/runtime-pi/client/index.js +0 -21
  215. package/src/runtime-pi/client/worker/Application.js +0 -44
  216. package/src/runtime-pi/client/worker/Context.js +0 -7
  217. package/src/runtime-pi/client/worker/Runtime.js +0 -275
  218. package/src/runtime-pi/client/worker/Workport.js +0 -78
  219. package/src/runtime-pi/client/worker/index.js +0 -21
  220. package/src/runtime-pi/server/Application.js +0 -101
  221. package/src/runtime-pi/server/Context.js +0 -16
  222. package/src/runtime-pi/server/Router.js +0 -159
  223. package/src/runtime-pi/server/Runtime.js +0 -558
  224. package/src/runtime-pi/server/index.js +0 -21
  225. package/src/runtime-pi/util-http.js +0 -86
  226. package/src/runtime-pi/xFormData.js +0 -24
  227. package/src/runtime-pi/xHeaders.js +0 -146
  228. package/src/runtime-pi/xRequest.js +0 -46
  229. package/src/runtime-pi/xRequestHeaders.js +0 -109
  230. package/src/runtime-pi/xResponse.js +0 -33
  231. package/src/runtime-pi/xResponseHeaders.js +0 -117
  232. package/src/runtime-pi/xURL.js +0 -105
  233. package/src/runtime-pi/xfetch.js +0 -23
  234. package/src/runtime-pi/xxHttpMessage.js +0 -102
  235. package/src/static-pi/index.js +0 -11
@@ -0,0 +1,500 @@
1
+ import { _before, _toTitle } from '@webqit/util/str/index.js';
2
+ import { _isObject } from '@webqit/util/js/index.js';
3
+ import { Observer } from '@webqit/quantum-js';
4
+ import { WebfloRuntime } from '../WebfloRuntime.js';
5
+ import { WQMessageChannel } from '../webflo-messaging/WQMessageChannel.js';
6
+ import { WQStarPort } from '../webflo-messaging/WQStarPort.js';
7
+ import { ClientSideCookies } from './ClientSideCookies.js';
8
+ import { HttpSession } from '../webflo-routing/HttpSession.js';
9
+ import { HttpEvent } from '../webflo-routing/HttpEvent.js';
10
+ import { HttpUser } from '../webflo-routing/HttpUser.js';
11
+ import { Url } from '../webflo-url/Url.js';
12
+ import { _wq } from '../../util.js';
13
+ import '../webflo-fetch/index.js';
14
+ import '../webflo-url/index.js';
15
+
16
+ export class WebfloClient extends WebfloRuntime {
17
+
18
+ static get HttpEvent() { return HttpEvent; }
19
+
20
+ static get HttpCookies() { return ClientSideCookies; }
21
+
22
+ static get HttpSession() { return HttpSession; }
23
+
24
+ static get HttpUser() { return HttpUser; }
25
+
26
+ #host;
27
+ get host() { return this.#host; }
28
+
29
+ #location;
30
+ get location() { return this.#location; }
31
+
32
+ #navigator;
33
+ get navigator() { return this.#navigator; }
34
+
35
+ #transition;
36
+ get transition() { return this.#transition; }
37
+
38
+ #background;
39
+ get background() { return this.#background; }
40
+
41
+ #sdk = {};
42
+ get sdk() { return this.#sdk; }
43
+
44
+ get isClientSide() { return true; }
45
+
46
+ constructor(cx, host) {
47
+ super(cx);
48
+ this.#host = host;
49
+ Object.defineProperty(this.host, 'webfloRuntime', { get: () => this });
50
+ this.#location = new Url/*NOT URL*/(this.host.location);
51
+ this.#navigator = {
52
+ requesting: null,
53
+ redirecting: null,
54
+ remotely: false,
55
+ origins: [],
56
+ error: null,
57
+ };
58
+ this.#transition = {
59
+ from: new Url/*NOT URL*/({}),
60
+ to: new Url/*NOT URL*/(this.host.location),
61
+ rel: 'unrelated',
62
+ phase: 0
63
+ };
64
+ this.#background = new WQStarPort;
65
+ }
66
+
67
+ async initialize() {
68
+ const instanceController = await super.initialize();
69
+ // Bind prompt handlers
70
+ const promptsHandler = (e) => {
71
+ const message = e.data?.message
72
+ ? e.data.message + (e.data.details ? `\r\n${e.data.details}` : '')
73
+ : e.data;
74
+ const execPromp = () => {
75
+ if (e.type === 'confirm') {
76
+ e.wqRespondWith(confirm(message));
77
+ } else if (e.type === 'prompt') {
78
+ e.wqRespondWith(prompt(message));
79
+ }
80
+ };
81
+ window.queueMicrotask(execPromp);
82
+ };
83
+ this.background.addEventListener('confirm', promptsHandler, { signal: instanceController.signal });
84
+ this.background.addEventListener('prompt', promptsHandler, { signal: instanceController.signal });
85
+ await this.setupCapabilities();
86
+ this.control();
87
+ await this.hydrate();
88
+ return instanceController;
89
+ }
90
+
91
+ async setupCapabilities() {
92
+ const instanceController = await super.setupCapabilities();
93
+ this.#sdk.Observer = Observer;
94
+ return instanceController;
95
+ }
96
+
97
+ controlSuper() {
98
+ return super.control();
99
+ }
100
+
101
+ controlClassic(locationCallback) {
102
+ const instanceController = super.control();
103
+ const setStates = (url, detail, method = 'GET') => {
104
+ Observer.set(this.navigator, {
105
+ requesting: new Url/*NOT URL*/(url),
106
+ origins: detail.navigationOrigins || [],
107
+ method,
108
+ error: null
109
+ });
110
+ };
111
+ const resetStates = () => {
112
+ Observer.set(this.navigator, {
113
+ requesting: null,
114
+ remotely: false,
115
+ origins: [],
116
+ method: null
117
+ });
118
+ };
119
+ // -----------------------
120
+ // Capture all link-clicks
121
+ const clickHandler = (e) => {
122
+ if (!this._canIntercept(e) || e.defaultPrevented) return;
123
+ var anchorEl = e.target.closest('a');
124
+ if (!anchorEl || !anchorEl.href || (anchorEl.target && !anchorEl.target.startsWith('_webflo:')) || anchorEl.download || !this.isSpaRoute(anchorEl)) return;
125
+ const resolvedUrl = new URL(anchorEl.hasAttribute('href') ? anchorEl.getAttribute('href') : '', this.location.href);
126
+ if (this.isHashChange(resolvedUrl)) {
127
+ Observer.set(this.location, 'href', resolvedUrl.href);
128
+ return;
129
+ }
130
+ // ---------------
131
+ // Handle now
132
+ e.preventDefault();
133
+ // Note the order of calls below
134
+ const detail = {
135
+ navigationType: 'push',
136
+ navigationOrigins: [anchorEl],
137
+ destination: this._asEntry(null),
138
+ source: this.currentEntry(), // this
139
+ userInitiated: true,
140
+ };
141
+ if (anchorEl.target === '_webflo:_parent' && this.superRuntime) {
142
+ setStates(resolvedUrl, detail);
143
+ this.superRuntime.navigate(resolvedUrl, {}, {
144
+ ...detail,
145
+ isHoisted: true,
146
+ }).then(resetStates);
147
+ return;
148
+ }
149
+ locationCallback(resolvedUrl); // this
150
+ this.navigate(resolvedUrl, {}, detail); // this
151
+ };
152
+ // -----------------------
153
+ // Capture all form-submits
154
+ const submitHandler = (e) => {
155
+ if (!this._canIntercept(e) || e.defaultPrevented) return;
156
+ // ---------------
157
+ // Declare form submission modifyers
158
+ const form = e.target.closest('form');
159
+ const submitter = e.submitter;
160
+ const _attr = (name) => {
161
+ let value = submitter && submitter.hasAttribute(`form${name.toLowerCase()}`) ? submitter[`form${_toTitle(name)}`] : (form.getAttribute(name) || form[name]);
162
+ if (value && [RadioNodeList, HTMLElement].some((x) => value instanceof x)) {
163
+ value = null;
164
+ }
165
+ return value;
166
+ };
167
+ const submitParams = Object.fromEntries(['method', 'action', 'enctype', 'noValidate', 'target'].map((name) => [name, _attr(name)]));
168
+ submitParams.method = submitParams.method || submitter.dataset.formmethod || 'GET';
169
+ submitParams.action = new URL(form.hasAttribute('action') ? form.getAttribute('action') : (
170
+ submitter?.hasAttribute('formaction') ? submitter.getAttribute('formaction') : ''),
171
+ this.location.href);
172
+ if ((submitParams.target && !submitParams.target.startsWith('_webflo:')) || !this.isSpaRoute(submitParams.action)) return;
173
+ // ---------------
174
+ // Handle now
175
+ let formData = new FormData(form);
176
+ if ((submitter || {}).name) {
177
+ formData.set(submitter.name, submitter.value);
178
+ }
179
+ if (submitParams.method.toUpperCase() === 'GET') {
180
+ Array.from(formData.entries()).forEach((_entry) => {
181
+ submitParams.action.searchParams.set(_entry[0], _entry[1]);
182
+ });
183
+ formData = null;
184
+ }
185
+ if (this.isHashChange(submitParams.action) && submitParams.method.toUpperCase() !== 'POST') {
186
+ Observer.set(this.location, 'href', submitParams.action.href);
187
+ return;
188
+ }
189
+ e.preventDefault();
190
+ // Note the order of calls below
191
+ const detail = {
192
+ navigationType: 'push',
193
+ navigationOrigins: [submitter, form],
194
+ destination: this._asEntry(null),
195
+ source: this.currentEntry(), // this
196
+ userInitiated: true,
197
+ };
198
+ if (submitParams.target === '_webflo:_parent' && this.superRuntime) {
199
+ setStates(submitParams.action, detail, submitParams.method);
200
+ this.superRuntime.navigate(
201
+ submitParams.action,
202
+ {
203
+ method: submitParams.method,
204
+ body: formData,
205
+ },
206
+ {
207
+ ...detail,
208
+ isHoisted: true,
209
+ }
210
+ ).then(resetStates);
211
+ return;
212
+ }
213
+ locationCallback(submitParams.action); // this
214
+ this.navigate(
215
+ submitParams.action,
216
+ {
217
+ method: submitParams.method,
218
+ body: formData,
219
+ },
220
+ detail
221
+ ); // this
222
+ };
223
+ this.host.addEventListener('click', clickHandler, { signal: instanceController.signal });
224
+ this.host.addEventListener('submit', submitHandler, { signal: instanceController.signal });
225
+ return instanceController;
226
+ }
227
+
228
+ _asEntry(state) { return { getState() { return state; } }; }
229
+
230
+ _canIntercept(e) { return !(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey); }
231
+
232
+ #xRedirectCode = 200;
233
+
234
+ isHashChange(urlObj) { return _before(this.location.href, '#') === _before(urlObj.href, '#') && (this.location.href.includes('#') || urlObj.href.includes('#')); }
235
+
236
+ isSpaRoute(urlObj) {
237
+ urlObj = typeof urlObj === 'string' ? new URL(urlObj, this.location.origin) : urlObj;
238
+ if (urlObj.origin && urlObj.origin !== this.location.origin) return false;
239
+ let b = urlObj.pathname.split('/').filter(s => s);
240
+ const match = a => {
241
+ a = a.split('/').filter(s => s);
242
+ return a.reduce((prev, s, i) => prev && (s === b[i] || [s, b[i]].includes('-')), true);
243
+ };
244
+ return match(this.routes.$root) && this.routes.$sparoots.reduce((prev, subroot) => {
245
+ return prev && !match(subroot);
246
+ }, true);
247
+ }
248
+
249
+ #prevEvent;
250
+ createHttpEvent(init, singleton = true) {
251
+ if (singleton && this.#prevEvent) {
252
+ this.#prevEvent.abort();
253
+ }
254
+ const httpEvent = super.createHttpEvent(init);
255
+ this.$instanceController.signal.addEventListener('abort', () => httpEvent.abort(), { once: true });
256
+ return this.#prevEvent = httpEvent;
257
+ }
258
+
259
+ createRequest(href, init = {}) {
260
+ const request = super.createRequest(href, init);
261
+ request.headers.set('Accept', 'application/json');
262
+ request.headers.set('X-Redirect-Policy', 'manual-when-cross-spa');
263
+ request.headers.set('X-Redirect-Code', this.#xRedirectCode);
264
+ request.headers.set('X-Powered-By', '@webqit/webflo');
265
+ return request;
266
+ }
267
+
268
+ async navigate(url, init = {}, detail = {}) {
269
+ // Resolve inputs
270
+ const scopeObj = { url, init, detail };
271
+ if (typeof scopeObj.url === 'string') {
272
+ scopeObj.url = new URL(scopeObj.url, self.location.origin);
273
+ }
274
+ // Create and route request
275
+ scopeObj.request = this.createRequest(scopeObj.url, scopeObj.init);
276
+ scopeObj.cookies = this.createHttpCookies({
277
+ request: scopeObj.request
278
+ });
279
+ scopeObj.session = this.createHttpSession({
280
+ store: this.#sdk.storage?.('session'),
281
+ request: scopeObj.request
282
+ });
283
+ const wqMessageChannel = new WQMessageChannel;
284
+ scopeObj.clientRequestRealtime = wqMessageChannel.port1;
285
+ scopeObj.user = this.createHttpUser({
286
+ store: this.#sdk.storage?.('user'),
287
+ request: scopeObj.request,
288
+ realtime: scopeObj.clientRequestRealtime,
289
+ session: scopeObj.session,
290
+ });
291
+ if (window.webqit?.oohtml?.configs) {
292
+ const { BINDINGS_API: { api: bindingsConfig } = {}, } = window.webqit.oohtml.configs;
293
+ scopeObj.UIState = (this.host[bindingsConfig.bindings] || {}).state;
294
+ }
295
+ scopeObj.httpEvent = this.createHttpEvent({
296
+ request: scopeObj.request,
297
+ realtime: scopeObj.clientRequestRealtime,
298
+ cookies: scopeObj.cookies,
299
+ session: scopeObj.session,
300
+ user: scopeObj.user,
301
+ sdk: this.#sdk,
302
+ detail: scopeObj.detail,
303
+ signal: init.signal,
304
+ state: scopeObj.UIState,
305
+ }, true);
306
+ // Set pre-request states
307
+ Observer.set(this.navigator, {
308
+ requesting: new Url/*NOT URL*/(scopeObj.url),
309
+ origins: scopeObj.detail.navigationOrigins || [],
310
+ method: scopeObj.request.method,
311
+ error: null
312
+ });
313
+ scopeObj.resetStates = () => {
314
+ Observer.set(this.navigator, {
315
+ requesting: null,
316
+ remotely: false,
317
+ origins: [],
318
+ method: null
319
+ });
320
+ };
321
+ // Ping existing background processes
322
+ // !IMPORTANT: Posting to the group when empty will keep the event until next addition
323
+ // and we don't want that
324
+ if (this.#background.length) {
325
+ const url = { ...Url.copy(scopeObj.url), method: scopeObj.request.method };
326
+ this.#background.postMessage(url, { wqEventOptions: { type: 'navigate' } });
327
+ }
328
+ // Dispatch for response
329
+ scopeObj.response = await this.dispatchNavigationEvent({
330
+ httpEvent: scopeObj.httpEvent,
331
+ crossLayerFetch: async (event) => {
332
+ // Was this nexted()? Tell the next layer we're in JSON mode by default
333
+ if (event !== scopeObj.httpEvent && !event.request.headers.has('Accept')) {
334
+ event.request.headers.set('Accept', 'application/json');
335
+ }
336
+ return await this.remoteFetch(event.request);
337
+ },
338
+ responseRealtime: wqMessageChannel.port2,
339
+ originalRequestInit: scopeObj.init
340
+ });
341
+ // Decode response
342
+ scopeObj.finalUrl = scopeObj.response.url || scopeObj.request.url;
343
+ if (scopeObj.response.redirected || scopeObj.detail.navigationType === 'rdr' || scopeObj.detail.isHoisted) {
344
+ const stateData = { ...(this.currentEntry()?.getState() || {}), redirected: true, };
345
+ await this.updateCurrentEntry({ state: stateData }, scopeObj.finalUrl);
346
+ }
347
+ // Transition UI
348
+ Observer.set(this.transition.from, Url.copy(this.location));
349
+ Observer.set(this.transition.to, 'href', scopeObj.finalUrl);
350
+ Observer.set(this.transition, 'rel', this.transition.from.pathname === this.transition.to.pathname ? 'unchanged' : (
351
+ `${this.transition.from.pathname}/`.startsWith(`${this.transition.to.pathname}/`) ? 'parent' : (
352
+ `${this.transition.to.pathname}/`.startsWith(`${this.transition.from.pathname}/`) ? 'child' : 'unrelated'
353
+ )
354
+ ));
355
+ await this.transitionUI(async () => {
356
+ // Set post-request states
357
+ Observer.set(this.location, 'href', scopeObj.finalUrl);
358
+ scopeObj.resetStates();
359
+ // Error?
360
+ if ([404, 500].includes(scopeObj.response.status)) {
361
+ const error = new Error(scopeObj.response.statusText, { code: scopeObj.response.status });
362
+ Object.defineProperty(error, 'retry', { value: async () => await this.navigate(scopeObj.url, scopeObj.init, scopeObj.detail) });
363
+ Observer.set(this.navigator, 'error', error);
364
+ }
365
+ // Render response
366
+ await this.render(
367
+ scopeObj.httpEvent,
368
+ scopeObj.response,
369
+ !(['GET'].includes(scopeObj.request.method) || scopeObj.response.redirected || scopeObj.detail.navigationType === 'rdr')
370
+ );
371
+ await this.applyPostRenderState(scopeObj.httpEvent);
372
+ });
373
+ }
374
+
375
+ async dispatchNavigationEvent({ httpEvent, crossLayerFetch, responseRealtime, originalRequestInit, processObj = {} }) {
376
+ const response = await super.dispatchNavigationEvent({ httpEvent, crossLayerFetch, responseRealtime });
377
+ // Obtain and connect responseRealtime as first thing
378
+ if (response.isLive()) {
379
+ this.background.addPort(response.wqRealtime);
380
+ }
381
+ // Await a response with an "Accepted" or redirect status
382
+ if (response.status === 202 || (response.headers.get('Location') && this.processRedirect(response))) {
383
+ return new Promise(async (resolve) => {
384
+ if (response.isLive()) {
385
+ const liveResponse = await LiveResponse.from(response);
386
+ liveResponse.addEventListener('replace', () => resolve(liveResponse), { once: true, signal: httpEvent.signal });
387
+ } // Never resolves otherwise
388
+ });
389
+ }
390
+ // Handle "retry" directives
391
+ if (response.headers.has('Retry-After')) {
392
+ if (!processObj.recurseController) {
393
+ // This is start of the process
394
+ processObj.recurseController = new AbortController;
395
+ httpEvent.signal.addEventListener('abort', () => processObj.recurseController.abort(), { once: true });
396
+ }
397
+ // Ensure a previous recursion hasn't aborted the process
398
+ if (!processObj.recurseController.signal.aborted) {
399
+ await new Promise((res) => setTimeout(res, parseInt(response.headers.get('Retry-After')) * 1000));
400
+ const eventClone = httpEvent.cloneWith({ request: this.createRequest(httpEvent.url, originalRequestInit) });
401
+ return await this.dispatchNavigationEvent({ httpEvent: eventClone, crossLayerFetch, responseRealtime, originalRequestInit, processObj });
402
+ }
403
+ } else if (processObj.recurseController) {
404
+ // Abort the signal. This is the end of the loop
405
+ processObj.recurseController.abort();
406
+ }
407
+ return response;
408
+ }
409
+
410
+ processRedirect(response) {
411
+ // Normalize redirect
412
+ const xActualRedirectCode = parseInt(response.headers.get('X-Redirect-Code'));
413
+ if (xActualRedirectCode && response.status === this.#xRedirectCode) {
414
+ const responseMeta = _wq(response, 'meta');
415
+ responseMeta.set('status', xActualRedirectCode); // @NOTE 1
416
+ }
417
+ // Trigger redirect
418
+ if ([302, 301].includes(response.status)) {
419
+ const location = new URL(response.headers.get('Location'), this.location.origin);
420
+ if (this.isSpaRoute(location)) {
421
+ this.navigate(location, {}, { navigationType: 'rdr' });
422
+ } else {
423
+ this.redirect(location, response.wqRealtime);
424
+ }
425
+ return true;
426
+ }
427
+ }
428
+
429
+ redirect(location, responseRealtime) {
430
+ if (responseRealtime) {
431
+ // Redundant as this is a window reload anyways
432
+ responseRealtime.close();
433
+ }
434
+ window.location = location;
435
+ }
436
+
437
+ async transitionUI(updateCallback) {
438
+ if (document.startViewTransition && this.withViewTransitions) {
439
+ const synthesizeWhile = window.webqit?.realdom?.synthesizeWhile || ((callback) => callback());
440
+ await synthesizeWhile(async () => {
441
+ Observer.set(this.transition, 'phase', 1);
442
+ const viewTransition = document.startViewTransition(updateCallback);
443
+ try { await viewTransition.updateCallbackDone; } catch (e) { console.log(e); }
444
+ Observer.set(this.transition, 'phase', 2);
445
+ try { await viewTransition.ready; } catch (e) { console.log(e); }
446
+ Observer.set(this.transition, 'phase', 3);
447
+ try { await viewTransition.finished; } catch (e) { console.log(e); }
448
+ Observer.set(this.transition, 'phase', 0);
449
+ });
450
+ } else await updateCallback();
451
+ }
452
+
453
+ async render(httpEvent, response, merge = false) {
454
+ const router = new this.constructor.Router(this, this.location.pathname);
455
+ await router.route('render', httpEvent, async (httpEvent) => {
456
+ if (!window.webqit?.oohtml?.configs) return;
457
+ if (window.webqit?.dom) {
458
+ await new Promise(res => window.webqit.dom.ready(res));
459
+ }
460
+ const {
461
+ BINDINGS_API: { api: bindingsConfig } = {},
462
+ HTML_IMPORTS: { attr: modulesContextAttrs } = {},
463
+ } = window.webqit.oohtml.configs;
464
+ if (bindingsConfig) {
465
+ const $response = await LiveResponse.from(response);
466
+ this.host[bindingsConfig.bind]({
467
+ state: {},
468
+ data: $response.body,
469
+ env: 'client',
470
+ navigator: this.navigator,
471
+ location: this.location,
472
+ network: this.network, // request, redirect, error, status, remote
473
+ capabilities: this.deviceCapabilities,
474
+ transition: this.transition,
475
+ }, { diff: true, merge });
476
+ $response.addEventListener('replace', (e) => {
477
+ if ($response.headers.get('Location') && this.processRedirect($response)) return;
478
+ this.host[bindingsConfig.bindings].data = $response.body;
479
+ });
480
+ }
481
+ if (modulesContextAttrs) {
482
+ const newRoute = '/' + `app/${this.location.pathname}`.split('/').map(a => (a => a.startsWith('$') ? '-' : a)(a.trim())).filter(a => a).join('/');
483
+ (this.host === window.document ? window.document.body : this.host).setAttribute(modulesContextAttrs.importscontext, newRoute);
484
+ }
485
+ });
486
+ }
487
+
488
+ async applyPostRenderState(httpEvent) {
489
+ if (!httpEvent.url.hash && httpEvent.detail.navigationType !== 'traverse' && httpEvent.request.method === 'GET') {
490
+ (this.host === document ? window : this.host).scrollTo(0, 0);
491
+ }
492
+ }
493
+
494
+ async remoteFetch(request, ...args) {
495
+ Observer.set(this.#navigator, 'remotely', true);
496
+ const response = await fetch(request, ...args);
497
+ Observer.set(this.#navigator, 'remotely', false);
498
+ return response;
499
+ }
500
+ }