@shaper.org/vite-react-plugin 1.0.6 → 1.0.7

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 (2) hide show
  1. package/package.json +2 -2
  2. package/templates/main.ts +217 -0
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@shaper.org/vite-react-plugin",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "private": false,
7
7
  "files": [
8
8
  "dist",
9
- "template"
9
+ "templates"
10
10
  ],
11
11
  "exports": {
12
12
  ".": "./dist/index.mjs",
@@ -0,0 +1,217 @@
1
+ /// <reference lib="dom" />
2
+ import {
3
+ iframePostMessageClient,
4
+ IframePostMessageClient,
5
+ } from "@shaper.org/core";
6
+
7
+ export const eventMap = {
8
+ MouseUp: 0,
9
+ MouseDown: 1,
10
+ Click: 2,
11
+ ContextMenu: 3,
12
+ DblClick: 4,
13
+ Focus: 5,
14
+ Blur: 6,
15
+ TouchStart: 7,
16
+ TouchMove: 8,
17
+ TouchEnd: 9,
18
+ TouchCancel: 10,
19
+ };
20
+ export type EventName = keyof typeof eventMap;
21
+
22
+ export const settings = {
23
+ MouseUp: true,
24
+ MouseDown: true,
25
+ Click: true,
26
+ ContextMenu: true,
27
+ DblClick: true,
28
+ Focus: true,
29
+ Blur: true,
30
+ TouchStart: true,
31
+ TouchMove: true,
32
+ TouchEnd: true,
33
+ TouchCancel: true,
34
+ };
35
+
36
+ export interface EventsSetting {
37
+ MouseUp?: boolean;
38
+ MouseDown?: boolean;
39
+ Click?: boolean;
40
+ ContextMenu?: boolean;
41
+ DblClick?: boolean;
42
+ Focus?: boolean;
43
+ Blur?: boolean;
44
+ TouchStart?: boolean;
45
+ TouchMove?: boolean;
46
+ TouchEnd?: boolean;
47
+ TouchCancel?: boolean;
48
+ }
49
+
50
+ export interface Router {
51
+ routes: Array<{ path: string; name?: string }>;
52
+ state: {
53
+ matches: Array<{
54
+ route: { path: string; name?: string };
55
+ }>;
56
+ };
57
+ }
58
+
59
+ export interface ReactRouteMonitorOptions {
60
+ router: Router;
61
+ eventSettings: EventsSetting;
62
+ }
63
+
64
+ export class ReactRouteMonitor {
65
+ private iframeClient: IframePostMessageClient;
66
+ private eventSettings: EventsSetting;
67
+ private router: Router;
68
+
69
+ private origPush: History["pushState"];
70
+ private origReplace: History["replaceState"];
71
+
72
+ private listeners: Array<() => void>;
73
+
74
+ private selectedNode: HTMLElement | null = null;
75
+
76
+ constructor({ router, eventSettings }: ReactRouteMonitorOptions) {
77
+ this.router = router;
78
+ this.iframeClient = iframePostMessageClient;
79
+
80
+ // store originals
81
+ this.origPush = history.pushState;
82
+ this.origReplace = history.replaceState;
83
+
84
+ this.listeners = [];
85
+
86
+ this.eventSettings = this._buildEventSettings(eventSettings);
87
+ }
88
+
89
+ _buildEventSettings(userSettings: EventsSetting) {
90
+ const settings: EventsSetting = {};
91
+
92
+ (Object.keys(eventMap) as EventName[]).forEach((key) => {
93
+ settings[key] =
94
+ typeof userSettings[key] === "boolean"
95
+ ? userSettings[key]!
96
+ : false;
97
+ });
98
+
99
+ return settings;
100
+ }
101
+
102
+ start() {
103
+ this.patchHistory();
104
+ this.attachGlobalListeners();
105
+ window.addEventListener("locationchange", this.onLocationChange);
106
+ window.addEventListener("popstate", this.triggerRouteChange);
107
+ }
108
+
109
+ stop() {
110
+ // Restore history
111
+ history.pushState = this.origPush;
112
+ history.replaceState = this.origReplace;
113
+
114
+ // Remove listeners
115
+ this.listeners.forEach((remove: () => void) => remove());
116
+ this.listeners = [];
117
+
118
+ window.removeEventListener("locationchange", this.onLocationChange);
119
+ window.removeEventListener("popstate", this.triggerRouteChange);
120
+ }
121
+
122
+ patchHistory() {
123
+ history.pushState = (...args) => {
124
+ this.origPush.apply(history, args);
125
+ this.triggerRouteChange();
126
+ };
127
+
128
+ history.replaceState = (...args) => {
129
+ this.origReplace.apply(history, args);
130
+ this.triggerRouteChange();
131
+ };
132
+ }
133
+
134
+ triggerRouteChange = () => {
135
+ window.dispatchEvent(
136
+ new CustomEvent("locationchange", {
137
+ detail: { node: this.selectedNode },
138
+ }),
139
+ );
140
+ };
141
+
142
+ onLocationChange = (_event: CustomEvent<{ node?: HTMLElement }>) => {
143
+ const url = new URL(window.location.href);
144
+ const toPathname = url.pathname;
145
+
146
+ const matches = this.router.routes.filter((r) => r.path === toPathname);
147
+ if (matches.length === 0) return;
148
+
149
+ const to = matches[0];
150
+
151
+ const state = this.router.state;
152
+ if (state.matches.length === 0) return;
153
+
154
+ const from = state.matches[0].route;
155
+ const fromPathname = this.router.state.location.pathname;
156
+
157
+ if (toPathname === fromPathname) {
158
+ this.iframeClient.sendRouteRefresh({
159
+ name: toPathname,
160
+ path: toPathname,
161
+ file: to.element.type().props["data-loc"],
162
+ });
163
+ } else {
164
+ this.iframeClient.sendRouteChange({
165
+ name: toPathname,
166
+ path: toPathname,
167
+ file: to.element.type().props["data-loc"],
168
+ });
169
+ }
170
+ };
171
+
172
+ attachGlobalListeners() {
173
+ (Object.keys(eventMap) as EventName[])
174
+ .filter((key) => this.eventSettings[key] !== false)
175
+ .forEach((key) => {
176
+ const handler = this._createInteractionHandler(key);
177
+ const remove = this._addEventListenerToDocument(
178
+ key.toLowerCase(),
179
+ handler,
180
+ );
181
+ this.listeners.push(remove);
182
+ });
183
+ }
184
+
185
+ _addEventListenerToDocument(eventName: string, handler: EventListener) {
186
+ document.addEventListener(eventName, handler, true);
187
+ return () => document.removeEventListener(eventName, handler, true);
188
+ }
189
+
190
+ _createInteractionHandler(eventType: EventName) {
191
+ return (event: Event) => {
192
+ if (eventMap[eventType] === eventMap.Click) {
193
+ const target = event.target as HTMLElement | null;
194
+
195
+ if (!target) return;
196
+
197
+ this.selectedNode = target;
198
+
199
+ const a = target.closest("a");
200
+
201
+ if (!a || a.target === "_blank" || a.href.startsWith("mailto:"))
202
+ return;
203
+
204
+ this.triggerRouteChange();
205
+ }
206
+ };
207
+ }
208
+ }
209
+
210
+ const monitor = new ReactRouteMonitor({
211
+ router,
212
+ eventSettings: {
213
+ Click: true,
214
+ },
215
+ });
216
+
217
+ monitor.start();