@domql/router 3.2.3 → 3.2.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.
package/README.md ADDED
@@ -0,0 +1,198 @@
1
+ # @domql/router
2
+
3
+ Client-side router plugin for DOMQL. Handles route matching, navigation, scroll management, and state updates within DOMQL elements.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @domql/router
9
+ ```
10
+
11
+ ## Basic Usage
12
+
13
+ ```js
14
+ import { router } from '@domql/router'
15
+
16
+ // Navigate to a path
17
+ router('/about', element)
18
+
19
+ // With state and options
20
+ router('/dashboard', element, { userId: 1 }, { scrollToTop: true })
21
+ ```
22
+
23
+ Define routes on your DOMQL element:
24
+
25
+ ```js
26
+ const App = {
27
+ routes: {
28
+ '/': HomePage,
29
+ '/about': AboutPage,
30
+ '/contact': ContactPage,
31
+ '/*': NotFoundPage
32
+ }
33
+ }
34
+ ```
35
+
36
+ ## Dynamic Route Params
37
+
38
+ Match routes with `:param` segments. Enable with `useParamsMatching: true`.
39
+
40
+ ```js
41
+ const App = {
42
+ routes: {
43
+ '/': HomePage,
44
+ '/:id': UserPage,
45
+ '/:category/:slug': ArticlePage,
46
+ '/*': NotFoundPage
47
+ }
48
+ }
49
+
50
+ router('/users/42', element, {}, { useParamsMatching: true })
51
+ // state.params = { id: '42' }
52
+
53
+ router('/tech/my-article', element, {}, { useParamsMatching: true })
54
+ // state.params = { category: 'tech', slug: 'my-article' }
55
+ ```
56
+
57
+ Exact segments score higher than params, so `/about` will match a literal `/about` route before `/:id`.
58
+
59
+ ## Query String Parsing
60
+
61
+ Query parameters are automatically parsed and stored in state.
62
+
63
+ ```js
64
+ router('/search?q=hello&tag=a&tag=b', element)
65
+ // state.query = { q: 'hello', tag: ['a', 'b'] }
66
+ ```
67
+
68
+ Duplicate keys are collected into arrays.
69
+
70
+ ## Guards / Middleware
71
+
72
+ Run async guard functions before navigation. Return `true` to allow, `false` to block, or a string to redirect.
73
+
74
+ ```js
75
+ const authGuard = ({ element }) => {
76
+ if (!element.state.root.isLoggedIn) return '/login'
77
+ return true
78
+ }
79
+
80
+ const roleGuard = ({ params }) => {
81
+ if (params.section === 'admin') return false
82
+ return true
83
+ }
84
+
85
+ router('/dashboard', element, {}, {
86
+ guards: [authGuard, roleGuard]
87
+ })
88
+ ```
89
+
90
+ Guard functions receive a context object:
91
+
92
+ ```js
93
+ {
94
+ pathname, // full pathname
95
+ route, // matched route key
96
+ params, // dynamic route params
97
+ query, // parsed query string
98
+ hash, // URL hash
99
+ element, // DOMQL element
100
+ state // navigation state
101
+ }
102
+ ```
103
+
104
+ ## 404 Handling
105
+
106
+ Provide an `onNotFound` callback for unmatched routes:
107
+
108
+ ```js
109
+ router('/unknown', element, {}, {
110
+ onNotFound: ({ pathname, route, element }) => {
111
+ console.warn(`No route found for ${pathname}`)
112
+ }
113
+ })
114
+ ```
115
+
116
+ You can also define a `/*` wildcard route as a catch-all fallback.
117
+
118
+ ## Options
119
+
120
+ | Option | Type | Default | Description |
121
+ |---|---|---|---|
122
+ | `level` | `number` | `0` | Route nesting level (which path segment to match) |
123
+ | `pushState` | `boolean` | `true` | Push to browser history |
124
+ | `initialRender` | `boolean` | `false` | Whether this is the initial page render |
125
+ | `scrollToTop` | `boolean` | `true` | Scroll to top after navigation |
126
+ | `scrollToNode` | `boolean` | `false` | Scroll within the element node |
127
+ | `scrollNode` | `Element` | `document.documentElement` | Node to scroll |
128
+ | `scrollToOffset` | `number` | `0` | Offset when scrolling to hash anchors |
129
+ | `scrollToOptions` | `object` | `{ behavior: 'smooth' }` | Options passed to `scrollTo()` |
130
+ | `useFragment` | `boolean` | `false` | Use fragment tag for content |
131
+ | `updateState` | `boolean` | `true` | Update element state on navigation |
132
+ | `contentElementKey` | `string` | `'content'` | Key for the content element slot |
133
+ | `removeOldElement` | `boolean` | `false` | Remove old content element before setting new |
134
+ | `useParamsMatching` | `boolean` | `false` | Enable dynamic `:param` route matching |
135
+ | `guards` | `function[]` | `undefined` | Array of guard/middleware functions |
136
+ | `onNotFound` | `function` | `undefined` | Callback when no route matches |
137
+
138
+ ## Exported Utilities
139
+
140
+ ### `getActiveRoute(level, route)`
141
+
142
+ Returns the active route segment at the given nesting level.
143
+
144
+ ```js
145
+ import { getActiveRoute } from '@domql/router'
146
+
147
+ getActiveRoute(0, '/users/42') // '/users'
148
+ getActiveRoute(1, '/users/42') // '/42'
149
+ ```
150
+
151
+ ### `parseQuery(search)`
152
+
153
+ Parses a query string into an object.
154
+
155
+ ```js
156
+ import { parseQuery } from '@domql/router'
157
+
158
+ parseQuery('?page=1&sort=name') // { page: '1', sort: 'name' }
159
+ ```
160
+
161
+ ### `matchRoute(pathname, routes, level)`
162
+
163
+ Matches a pathname against a routes object. Returns `{ key, content, params }`.
164
+
165
+ ```js
166
+ import { matchRoute } from '@domql/router'
167
+
168
+ const routes = { '/': Home, '/:id': Detail, '/*': NotFound }
169
+ const result = matchRoute('/42', routes)
170
+ // { key: '/:id', content: Detail, params: { id: '42' } }
171
+ ```
172
+
173
+ ### `parseRoutePattern(pattern)`
174
+
175
+ Parses a route pattern string into segments, param definitions, and wildcard flag. Results are cached.
176
+
177
+ ### `runGuards(guards, context)`
178
+
179
+ Runs an array of async guard functions sequentially. Returns `true`, `false`, or a redirect path string.
180
+
181
+ ## Events
182
+
183
+ The router triggers an `on.routeChanged` event on the element after navigation completes. Listen for it in your element definition:
184
+
185
+ ```js
186
+ const App = {
187
+ routes: { ... },
188
+ on: {
189
+ routeChanged: (element, options) => {
190
+ console.log('Route changed:', element.state.route)
191
+ }
192
+ }
193
+ }
194
+ ```
195
+
196
+ ## License
197
+
198
+ MIT
package/dist/cjs/index.js CHANGED
@@ -22,12 +22,115 @@ __export(index_exports, {
22
22
  getActiveRoute: () => getActiveRoute,
23
23
  lastLevel: () => lastLevel,
24
24
  lastPathname: () => lastPathname,
25
- router: () => router
25
+ matchRoute: () => matchRoute,
26
+ parseQuery: () => parseQuery,
27
+ parseRoutePattern: () => parseRoutePattern,
28
+ router: () => router,
29
+ runGuards: () => runGuards
26
30
  });
27
31
  module.exports = __toCommonJS(index_exports);
28
- var import_event = require("@domql/event");
29
32
  var import_utils = require("@domql/utils");
30
33
  var import_set = require("@domql/element/set");
34
+ const paramPattern = /^:(.+)/;
35
+ const wildcardPattern = /^\*$/;
36
+ const routeCache = /* @__PURE__ */ new Map();
37
+ const parseRoutePattern = (pattern) => {
38
+ const cached = routeCache.get(pattern);
39
+ if (cached) return cached;
40
+ const segments = pattern.replace(/^\//, "").split("/");
41
+ const params = [];
42
+ let hasWildcard = false;
43
+ for (let i = 0; i < segments.length; i++) {
44
+ const match = segments[i].match(paramPattern);
45
+ if (match) {
46
+ params.push({ index: i, name: match[1] });
47
+ } else if (wildcardPattern.test(segments[i])) {
48
+ hasWildcard = true;
49
+ }
50
+ }
51
+ const result = { segments, params, hasWildcard, pattern };
52
+ routeCache.set(pattern, result);
53
+ return result;
54
+ };
55
+ const matchRoute = (pathname, routes, level = 0) => {
56
+ const pathSegments = pathname.replace(/^\//, "").split("/").filter(Boolean);
57
+ const relevantSegments = pathSegments.slice(level);
58
+ const routePath = "/" + (relevantSegments[0] || "");
59
+ let bestMatch = null;
60
+ let bestScore = -1;
61
+ let matchedParams = {};
62
+ for (const key in routes) {
63
+ if (key === "/*") continue;
64
+ const parsed = parseRoutePattern(key);
65
+ const score = scoreMatch(relevantSegments, parsed);
66
+ if (score > bestScore) {
67
+ bestScore = score;
68
+ bestMatch = key;
69
+ matchedParams = extractParams(relevantSegments, parsed);
70
+ }
71
+ }
72
+ if (!bestMatch && routes["/*"]) {
73
+ bestMatch = "/*";
74
+ }
75
+ return {
76
+ key: bestMatch,
77
+ content: bestMatch ? routes[bestMatch] : null,
78
+ params: matchedParams,
79
+ routePath
80
+ };
81
+ };
82
+ const scoreMatch = (pathSegments, parsed) => {
83
+ const { segments, hasWildcard } = parsed;
84
+ if (!hasWildcard && segments.length !== pathSegments.length && segments.length !== 1) {
85
+ if (segments.length > pathSegments.length) return -1;
86
+ }
87
+ let score = 0;
88
+ const len = Math.min(segments.length, pathSegments.length);
89
+ for (let i = 0; i < len; i++) {
90
+ if (segments[i] === pathSegments[i]) {
91
+ score += 3;
92
+ } else if (paramPattern.test(segments[i])) {
93
+ score += 1;
94
+ } else if (wildcardPattern.test(segments[i])) {
95
+ score += 0.5;
96
+ } else {
97
+ return -1;
98
+ }
99
+ }
100
+ return score;
101
+ };
102
+ const extractParams = (pathSegments, parsed) => {
103
+ const params = {};
104
+ for (const { index, name } of parsed.params) {
105
+ if (pathSegments[index]) {
106
+ params[name] = decodeURIComponent(pathSegments[index]);
107
+ }
108
+ }
109
+ return params;
110
+ };
111
+ const parseQuery = (search) => {
112
+ if (!search || search === "?") return {};
113
+ const params = {};
114
+ const searchParams = new URLSearchParams(search);
115
+ searchParams.forEach((value, key) => {
116
+ if (params[key] !== void 0) {
117
+ if (!Array.isArray(params[key])) params[key] = [params[key]];
118
+ params[key].push(value);
119
+ } else {
120
+ params[key] = value;
121
+ }
122
+ });
123
+ return params;
124
+ };
125
+ const runGuards = async (guards, context) => {
126
+ if (!guards || !guards.length) return true;
127
+ for (const guard of guards) {
128
+ const result = await guard(context);
129
+ if (result === false) return false;
130
+ if (typeof result === "string") return result;
131
+ }
132
+ return true;
133
+ };
31
134
  const getActiveRoute = (level = 0, route = import_utils.window.location.pathname) => {
32
135
  const routeArray = route.split("/");
33
136
  const activeRoute = routeArray[level + 1];
@@ -47,7 +150,8 @@ const defaultOptions = {
47
150
  updateState: true,
48
151
  scrollToOffset: 0,
49
152
  contentElementKey: "content",
50
- scrollToOptions: { behavior: "smooth" }
153
+ scrollToOptions: { behavior: "smooth" },
154
+ useParamsMatching: false
51
155
  };
52
156
  const router = (path, el, state = {}, options = {}) => {
53
157
  const element = el || void 0;
@@ -66,24 +170,48 @@ const router = (path, el, state = {}, options = {}) => {
66
170
  const contentElementKey = (0, import_set.setContentKey)(element, opts);
67
171
  const urlObj = new win.URL(win.location.origin + path);
68
172
  const { pathname, search, hash } = urlObj;
173
+ const query = parseQuery(search);
69
174
  const rootNode = element.node;
70
- const route = getActiveRoute(opts.level, pathname);
71
- const content = element.routes[route || "/"] || element.routes["/*"];
72
- const scrollNode = opts.scrollToNode ? rootNode : opts.scrollNode;
73
175
  const hashChanged = hash && hash !== win.location.hash.slice(1);
74
176
  const pathChanged = pathname !== lastPathname;
75
177
  lastPathname = pathname;
178
+ let route, content, params;
179
+ if (opts.useParamsMatching) {
180
+ const match = matchRoute(pathname, element.routes, opts.level);
181
+ route = match.routePath;
182
+ content = match.content;
183
+ params = match.params;
184
+ } else {
185
+ route = getActiveRoute(opts.level, pathname);
186
+ content = element.routes[route || "/"] || element.routes["/*"];
187
+ params = {};
188
+ }
189
+ const scrollNode = opts.scrollToNode ? rootNode : opts.scrollNode;
76
190
  if (!content || element.state.root.debugging) {
77
191
  element.state.root.debugging = false;
192
+ if (opts.onNotFound) {
193
+ opts.onNotFound({ pathname, route, element });
194
+ }
78
195
  return;
79
196
  }
197
+ if (opts.guards && opts.guards.length) {
198
+ const guardContext = { pathname, route, params, query, hash, element, state };
199
+ const guardResult = runGuards(opts.guards, guardContext);
200
+ if (guardResult === false) return;
201
+ if (typeof guardResult === "string") {
202
+ return router(guardResult, el, state, { ...options, guards: [] });
203
+ }
204
+ }
80
205
  if (opts.pushState) {
81
206
  win.history.pushState(state, null, pathname + (search || "") + (hash || ""));
82
207
  }
83
208
  if (pathChanged || !hashChanged) {
209
+ const stateUpdate = { route, hash, debugging: false };
210
+ if (Object.keys(params).length) stateUpdate.params = params;
211
+ if (Object.keys(query).length) stateUpdate.query = query;
84
212
  if (opts.updateState) {
85
213
  element.state.update(
86
- { route, hash, debugging: false },
214
+ stateUpdate,
87
215
  { preventContentUpdate: true }
88
216
  );
89
217
  }
@@ -115,7 +243,7 @@ const router = (path, el, state = {}, options = {}) => {
115
243
  if (hash) {
116
244
  const activeNode = doc.getElementById(hash);
117
245
  if (activeNode) {
118
- const top = activeNode.getBoundingClientRect().top + rootNode.scrollTop - opts.scrollToOffset || 0;
246
+ const top = activeNode.getBoundingClientRect().top + rootNode.scrollTop - (opts.scrollToOffset || 0);
119
247
  scrollNode.scrollTo({
120
248
  ...opts.scrollToOptions || {},
121
249
  top,
@@ -123,6 +251,6 @@ const router = (path, el, state = {}, options = {}) => {
123
251
  });
124
252
  }
125
253
  }
126
- (0, import_event.triggerEventOn)("routeChanged", element, opts);
254
+ (0, import_utils.triggerEventOn)("routeChanged", element, opts);
127
255
  };
128
256
  var index_default = router;
package/dist/esm/index.js CHANGED
@@ -1,25 +1,105 @@
1
- var __defProp = Object.defineProperty;
2
- var __defProps = Object.defineProperties;
3
- var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
- var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
- var __spreadValues = (a, b) => {
9
- for (var prop in b || (b = {}))
10
- if (__hasOwnProp.call(b, prop))
11
- __defNormalProp(a, prop, b[prop]);
12
- if (__getOwnPropSymbols)
13
- for (var prop of __getOwnPropSymbols(b)) {
14
- if (__propIsEnum.call(b, prop))
15
- __defNormalProp(a, prop, b[prop]);
1
+ import { document, window, triggerEventOn } from "@domql/utils";
2
+ import { setContentKey } from "@domql/element/set";
3
+ const paramPattern = /^:(.+)/;
4
+ const wildcardPattern = /^\*$/;
5
+ const routeCache = /* @__PURE__ */ new Map();
6
+ const parseRoutePattern = (pattern) => {
7
+ const cached = routeCache.get(pattern);
8
+ if (cached) return cached;
9
+ const segments = pattern.replace(/^\//, "").split("/");
10
+ const params = [];
11
+ let hasWildcard = false;
12
+ for (let i = 0; i < segments.length; i++) {
13
+ const match = segments[i].match(paramPattern);
14
+ if (match) {
15
+ params.push({ index: i, name: match[1] });
16
+ } else if (wildcardPattern.test(segments[i])) {
17
+ hasWildcard = true;
16
18
  }
17
- return a;
19
+ }
20
+ const result = { segments, params, hasWildcard, pattern };
21
+ routeCache.set(pattern, result);
22
+ return result;
23
+ };
24
+ const matchRoute = (pathname, routes, level = 0) => {
25
+ const pathSegments = pathname.replace(/^\//, "").split("/").filter(Boolean);
26
+ const relevantSegments = pathSegments.slice(level);
27
+ const routePath = "/" + (relevantSegments[0] || "");
28
+ let bestMatch = null;
29
+ let bestScore = -1;
30
+ let matchedParams = {};
31
+ for (const key in routes) {
32
+ if (key === "/*") continue;
33
+ const parsed = parseRoutePattern(key);
34
+ const score = scoreMatch(relevantSegments, parsed);
35
+ if (score > bestScore) {
36
+ bestScore = score;
37
+ bestMatch = key;
38
+ matchedParams = extractParams(relevantSegments, parsed);
39
+ }
40
+ }
41
+ if (!bestMatch && routes["/*"]) {
42
+ bestMatch = "/*";
43
+ }
44
+ return {
45
+ key: bestMatch,
46
+ content: bestMatch ? routes[bestMatch] : null,
47
+ params: matchedParams,
48
+ routePath
49
+ };
50
+ };
51
+ const scoreMatch = (pathSegments, parsed) => {
52
+ const { segments, hasWildcard } = parsed;
53
+ if (!hasWildcard && segments.length !== pathSegments.length && segments.length !== 1) {
54
+ if (segments.length > pathSegments.length) return -1;
55
+ }
56
+ let score = 0;
57
+ const len = Math.min(segments.length, pathSegments.length);
58
+ for (let i = 0; i < len; i++) {
59
+ if (segments[i] === pathSegments[i]) {
60
+ score += 3;
61
+ } else if (paramPattern.test(segments[i])) {
62
+ score += 1;
63
+ } else if (wildcardPattern.test(segments[i])) {
64
+ score += 0.5;
65
+ } else {
66
+ return -1;
67
+ }
68
+ }
69
+ return score;
70
+ };
71
+ const extractParams = (pathSegments, parsed) => {
72
+ const params = {};
73
+ for (const { index, name } of parsed.params) {
74
+ if (pathSegments[index]) {
75
+ params[name] = decodeURIComponent(pathSegments[index]);
76
+ }
77
+ }
78
+ return params;
79
+ };
80
+ const parseQuery = (search) => {
81
+ if (!search || search === "?") return {};
82
+ const params = {};
83
+ const searchParams = new URLSearchParams(search);
84
+ searchParams.forEach((value, key) => {
85
+ if (params[key] !== void 0) {
86
+ if (!Array.isArray(params[key])) params[key] = [params[key]];
87
+ params[key].push(value);
88
+ } else {
89
+ params[key] = value;
90
+ }
91
+ });
92
+ return params;
93
+ };
94
+ const runGuards = async (guards, context) => {
95
+ if (!guards || !guards.length) return true;
96
+ for (const guard of guards) {
97
+ const result = await guard(context);
98
+ if (result === false) return false;
99
+ if (typeof result === "string") return result;
100
+ }
101
+ return true;
18
102
  };
19
- var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
- import { triggerEventOn } from "@domql/event";
21
- import { document, window } from "@domql/utils";
22
- import { setContentKey } from "@domql/element/set";
23
103
  const getActiveRoute = (level = 0, route = window.location.pathname) => {
24
104
  const routeArray = route.split("/");
25
105
  const activeRoute = routeArray[level + 1];
@@ -39,13 +119,18 @@ const defaultOptions = {
39
119
  updateState: true,
40
120
  scrollToOffset: 0,
41
121
  contentElementKey: "content",
42
- scrollToOptions: { behavior: "smooth" }
122
+ scrollToOptions: { behavior: "smooth" },
123
+ useParamsMatching: false
43
124
  };
44
125
  const router = (path, el, state = {}, options = {}) => {
45
126
  const element = el || void 0;
46
127
  const win = element.context.window || window;
47
128
  const doc = element.context.document || document;
48
- const opts = __spreadValues(__spreadValues(__spreadValues({}, defaultOptions), element.context.routerOptions), options);
129
+ const opts = {
130
+ ...defaultOptions,
131
+ ...element.context.routerOptions,
132
+ ...options
133
+ };
49
134
  lastLevel = opts.lastLevel;
50
135
  const ref = element.__ref;
51
136
  if (opts.contentElementKey !== "content" && opts.contentElementKey !== ref.contentElementKey || !ref.contentElementKey) {
@@ -54,24 +139,48 @@ const router = (path, el, state = {}, options = {}) => {
54
139
  const contentElementKey = setContentKey(element, opts);
55
140
  const urlObj = new win.URL(win.location.origin + path);
56
141
  const { pathname, search, hash } = urlObj;
142
+ const query = parseQuery(search);
57
143
  const rootNode = element.node;
58
- const route = getActiveRoute(opts.level, pathname);
59
- const content = element.routes[route || "/"] || element.routes["/*"];
60
- const scrollNode = opts.scrollToNode ? rootNode : opts.scrollNode;
61
144
  const hashChanged = hash && hash !== win.location.hash.slice(1);
62
145
  const pathChanged = pathname !== lastPathname;
63
146
  lastPathname = pathname;
147
+ let route, content, params;
148
+ if (opts.useParamsMatching) {
149
+ const match = matchRoute(pathname, element.routes, opts.level);
150
+ route = match.routePath;
151
+ content = match.content;
152
+ params = match.params;
153
+ } else {
154
+ route = getActiveRoute(opts.level, pathname);
155
+ content = element.routes[route || "/"] || element.routes["/*"];
156
+ params = {};
157
+ }
158
+ const scrollNode = opts.scrollToNode ? rootNode : opts.scrollNode;
64
159
  if (!content || element.state.root.debugging) {
65
160
  element.state.root.debugging = false;
161
+ if (opts.onNotFound) {
162
+ opts.onNotFound({ pathname, route, element });
163
+ }
66
164
  return;
67
165
  }
166
+ if (opts.guards && opts.guards.length) {
167
+ const guardContext = { pathname, route, params, query, hash, element, state };
168
+ const guardResult = runGuards(opts.guards, guardContext);
169
+ if (guardResult === false) return;
170
+ if (typeof guardResult === "string") {
171
+ return router(guardResult, el, state, { ...options, guards: [] });
172
+ }
173
+ }
68
174
  if (opts.pushState) {
69
175
  win.history.pushState(state, null, pathname + (search || "") + (hash || ""));
70
176
  }
71
177
  if (pathChanged || !hashChanged) {
178
+ const stateUpdate = { route, hash, debugging: false };
179
+ if (Object.keys(params).length) stateUpdate.params = params;
180
+ if (Object.keys(query).length) stateUpdate.query = query;
72
181
  if (opts.updateState) {
73
182
  element.state.update(
74
- { route, hash, debugging: false },
183
+ stateUpdate,
75
184
  { preventContentUpdate: true }
76
185
  );
77
186
  }
@@ -87,25 +196,28 @@ const router = (path, el, state = {}, options = {}) => {
87
196
  );
88
197
  }
89
198
  if (opts.scrollToTop) {
90
- scrollNode.scrollTo(__spreadProps(__spreadValues({}, opts.scrollToOptions || {}), {
199
+ scrollNode.scrollTo({
200
+ ...opts.scrollToOptions || {},
91
201
  top: 0,
92
202
  left: 0
93
- }));
203
+ });
94
204
  }
95
205
  if (opts.scrollToNode) {
96
- content[contentElementKey].node.scrollTo(__spreadProps(__spreadValues({}, opts.scrollToOptions || {}), {
206
+ content[contentElementKey].node.scrollTo({
207
+ ...opts.scrollToOptions || {},
97
208
  top: 0,
98
209
  left: 0
99
- }));
210
+ });
100
211
  }
101
212
  if (hash) {
102
213
  const activeNode = doc.getElementById(hash);
103
214
  if (activeNode) {
104
- const top = activeNode.getBoundingClientRect().top + rootNode.scrollTop - opts.scrollToOffset || 0;
105
- scrollNode.scrollTo(__spreadProps(__spreadValues({}, opts.scrollToOptions || {}), {
215
+ const top = activeNode.getBoundingClientRect().top + rootNode.scrollTop - (opts.scrollToOffset || 0);
216
+ scrollNode.scrollTo({
217
+ ...opts.scrollToOptions || {},
106
218
  top,
107
219
  left: 0
108
- }));
220
+ });
109
221
  }
110
222
  }
111
223
  triggerEventOn("routeChanged", element, opts);
@@ -116,5 +228,9 @@ export {
116
228
  getActiveRoute,
117
229
  lastLevel,
118
230
  lastPathname,
119
- router
231
+ matchRoute,
232
+ parseQuery,
233
+ parseRoutePattern,
234
+ router,
235
+ runGuards
120
236
  };
@@ -0,0 +1,312 @@
1
+ "use strict";
2
+ var DomqlRouter = (() => {
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // index.js
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ default: () => index_default,
25
+ getActiveRoute: () => getActiveRoute,
26
+ lastLevel: () => lastLevel,
27
+ lastPathname: () => lastPathname,
28
+ matchRoute: () => matchRoute,
29
+ parseQuery: () => parseQuery,
30
+ parseRoutePattern: () => parseRoutePattern,
31
+ router: () => router,
32
+ runGuards: () => runGuards
33
+ });
34
+
35
+ // ../../packages/utils/dist/esm/globals.js
36
+ var window2 = globalThis;
37
+ var document = window2.document;
38
+
39
+ // ../../packages/utils/dist/esm/types.js
40
+ var isFunction = (arg) => typeof arg === "function";
41
+
42
+ // ../../packages/utils/dist/esm/triggerEvent.js
43
+ var getOnOrPropsEvent = (param, element) => {
44
+ const onEvent = element.on?.[param];
45
+ if (onEvent) return onEvent;
46
+ const props = element.props;
47
+ if (!props) return;
48
+ const propKey = "on" + param.charAt(0).toUpperCase() + param.slice(1);
49
+ return props[propKey];
50
+ };
51
+ var applyEvent = (param, element, state, context, options) => {
52
+ if (!isFunction(param)) return;
53
+ const result = param.call(
54
+ element,
55
+ element,
56
+ state || element.state,
57
+ context || element.context,
58
+ options
59
+ );
60
+ if (result && typeof result.then === "function") {
61
+ result.catch(() => {
62
+ });
63
+ }
64
+ return result;
65
+ };
66
+ var triggerEventOn = (param, element, options) => {
67
+ if (!element) {
68
+ throw new Error("Element is required");
69
+ }
70
+ const appliedFunction = getOnOrPropsEvent(param, element);
71
+ if (appliedFunction) {
72
+ const { state, context } = element;
73
+ return applyEvent(appliedFunction, element, state, context, options);
74
+ }
75
+ };
76
+
77
+ // ../../packages/element/dist/esm/set.js
78
+ var setContentKey = (element, opts = {}) => {
79
+ const { __ref: ref } = element;
80
+ const contentElementKey = opts.contentElementKey;
81
+ if (!ref.contentElementKey || contentElementKey !== ref.contentElementKey) {
82
+ ref.contentElementKey = contentElementKey || "content";
83
+ }
84
+ return ref.contentElementKey;
85
+ };
86
+
87
+ // index.js
88
+ var paramPattern = /^:(.+)/;
89
+ var wildcardPattern = /^\*$/;
90
+ var routeCache = /* @__PURE__ */ new Map();
91
+ var parseRoutePattern = (pattern) => {
92
+ const cached = routeCache.get(pattern);
93
+ if (cached) return cached;
94
+ const segments = pattern.replace(/^\//, "").split("/");
95
+ const params = [];
96
+ let hasWildcard = false;
97
+ for (let i = 0; i < segments.length; i++) {
98
+ const match = segments[i].match(paramPattern);
99
+ if (match) {
100
+ params.push({ index: i, name: match[1] });
101
+ } else if (wildcardPattern.test(segments[i])) {
102
+ hasWildcard = true;
103
+ }
104
+ }
105
+ const result = { segments, params, hasWildcard, pattern };
106
+ routeCache.set(pattern, result);
107
+ return result;
108
+ };
109
+ var matchRoute = (pathname, routes, level = 0) => {
110
+ const pathSegments = pathname.replace(/^\//, "").split("/").filter(Boolean);
111
+ const relevantSegments = pathSegments.slice(level);
112
+ const routePath = "/" + (relevantSegments[0] || "");
113
+ let bestMatch = null;
114
+ let bestScore = -1;
115
+ let matchedParams = {};
116
+ for (const key in routes) {
117
+ if (key === "/*") continue;
118
+ const parsed = parseRoutePattern(key);
119
+ const score = scoreMatch(relevantSegments, parsed);
120
+ if (score > bestScore) {
121
+ bestScore = score;
122
+ bestMatch = key;
123
+ matchedParams = extractParams(relevantSegments, parsed);
124
+ }
125
+ }
126
+ if (!bestMatch && routes["/*"]) {
127
+ bestMatch = "/*";
128
+ }
129
+ return {
130
+ key: bestMatch,
131
+ content: bestMatch ? routes[bestMatch] : null,
132
+ params: matchedParams,
133
+ routePath
134
+ };
135
+ };
136
+ var scoreMatch = (pathSegments, parsed) => {
137
+ const { segments, hasWildcard } = parsed;
138
+ if (!hasWildcard && segments.length !== pathSegments.length && segments.length !== 1) {
139
+ if (segments.length > pathSegments.length) return -1;
140
+ }
141
+ let score = 0;
142
+ const len = Math.min(segments.length, pathSegments.length);
143
+ for (let i = 0; i < len; i++) {
144
+ if (segments[i] === pathSegments[i]) {
145
+ score += 3;
146
+ } else if (paramPattern.test(segments[i])) {
147
+ score += 1;
148
+ } else if (wildcardPattern.test(segments[i])) {
149
+ score += 0.5;
150
+ } else {
151
+ return -1;
152
+ }
153
+ }
154
+ return score;
155
+ };
156
+ var extractParams = (pathSegments, parsed) => {
157
+ const params = {};
158
+ for (const { index, name } of parsed.params) {
159
+ if (pathSegments[index]) {
160
+ params[name] = decodeURIComponent(pathSegments[index]);
161
+ }
162
+ }
163
+ return params;
164
+ };
165
+ var parseQuery = (search) => {
166
+ if (!search || search === "?") return {};
167
+ const params = {};
168
+ const searchParams = new URLSearchParams(search);
169
+ searchParams.forEach((value, key) => {
170
+ if (params[key] !== void 0) {
171
+ if (!Array.isArray(params[key])) params[key] = [params[key]];
172
+ params[key].push(value);
173
+ } else {
174
+ params[key] = value;
175
+ }
176
+ });
177
+ return params;
178
+ };
179
+ var runGuards = async (guards, context) => {
180
+ if (!guards || !guards.length) return true;
181
+ for (const guard of guards) {
182
+ const result = await guard(context);
183
+ if (result === false) return false;
184
+ if (typeof result === "string") return result;
185
+ }
186
+ return true;
187
+ };
188
+ var getActiveRoute = (level = 0, route = window2.location.pathname) => {
189
+ const routeArray = route.split("/");
190
+ const activeRoute = routeArray[level + 1];
191
+ if (activeRoute) return `/${activeRoute}`;
192
+ };
193
+ var lastPathname;
194
+ var lastLevel = 0;
195
+ var defaultOptions = {
196
+ level: lastLevel,
197
+ pushState: true,
198
+ initialRender: false,
199
+ scrollToTop: true,
200
+ scrollToNode: false,
201
+ scrollNode: document && document.documentElement,
202
+ scrollBody: false,
203
+ useFragment: false,
204
+ updateState: true,
205
+ scrollToOffset: 0,
206
+ contentElementKey: "content",
207
+ scrollToOptions: { behavior: "smooth" },
208
+ useParamsMatching: false
209
+ };
210
+ var router = (path, el, state = {}, options = {}) => {
211
+ const element = el || void 0;
212
+ const win = element.context.window || window2;
213
+ const doc = element.context.document || document;
214
+ const opts = {
215
+ ...defaultOptions,
216
+ ...element.context.routerOptions,
217
+ ...options
218
+ };
219
+ lastLevel = opts.lastLevel;
220
+ const ref = element.__ref;
221
+ if (opts.contentElementKey !== "content" && opts.contentElementKey !== ref.contentElementKey || !ref.contentElementKey) {
222
+ ref.contentElementKey = opts.contentElementKey || "content";
223
+ }
224
+ const contentElementKey = setContentKey(element, opts);
225
+ const urlObj = new win.URL(win.location.origin + path);
226
+ const { pathname, search, hash } = urlObj;
227
+ const query = parseQuery(search);
228
+ const rootNode = element.node;
229
+ const hashChanged = hash && hash !== win.location.hash.slice(1);
230
+ const pathChanged = pathname !== lastPathname;
231
+ lastPathname = pathname;
232
+ let route, content, params;
233
+ if (opts.useParamsMatching) {
234
+ const match = matchRoute(pathname, element.routes, opts.level);
235
+ route = match.routePath;
236
+ content = match.content;
237
+ params = match.params;
238
+ } else {
239
+ route = getActiveRoute(opts.level, pathname);
240
+ content = element.routes[route || "/"] || element.routes["/*"];
241
+ params = {};
242
+ }
243
+ const scrollNode = opts.scrollToNode ? rootNode : opts.scrollNode;
244
+ if (!content || element.state.root.debugging) {
245
+ element.state.root.debugging = false;
246
+ if (opts.onNotFound) {
247
+ opts.onNotFound({ pathname, route, element });
248
+ }
249
+ return;
250
+ }
251
+ if (opts.guards && opts.guards.length) {
252
+ const guardContext = { pathname, route, params, query, hash, element, state };
253
+ const guardResult = runGuards(opts.guards, guardContext);
254
+ if (guardResult === false) return;
255
+ if (typeof guardResult === "string") {
256
+ return router(guardResult, el, state, { ...options, guards: [] });
257
+ }
258
+ }
259
+ if (opts.pushState) {
260
+ win.history.pushState(state, null, pathname + (search || "") + (hash || ""));
261
+ }
262
+ if (pathChanged || !hashChanged) {
263
+ const stateUpdate = { route, hash, debugging: false };
264
+ if (Object.keys(params).length) stateUpdate.params = params;
265
+ if (Object.keys(query).length) stateUpdate.query = query;
266
+ if (opts.updateState) {
267
+ element.state.update(
268
+ stateUpdate,
269
+ { preventContentUpdate: true }
270
+ );
271
+ }
272
+ if (contentElementKey && opts.removeOldElement) {
273
+ element[contentElementKey].remove();
274
+ }
275
+ element.set(
276
+ {
277
+ tag: opts.useFragment && "fragment",
278
+ extends: content
279
+ },
280
+ { contentElementKey }
281
+ );
282
+ }
283
+ if (opts.scrollToTop) {
284
+ scrollNode.scrollTo({
285
+ ...opts.scrollToOptions || {},
286
+ top: 0,
287
+ left: 0
288
+ });
289
+ }
290
+ if (opts.scrollToNode) {
291
+ content[contentElementKey].node.scrollTo({
292
+ ...opts.scrollToOptions || {},
293
+ top: 0,
294
+ left: 0
295
+ });
296
+ }
297
+ if (hash) {
298
+ const activeNode = doc.getElementById(hash);
299
+ if (activeNode) {
300
+ const top = activeNode.getBoundingClientRect().top + rootNode.scrollTop - (opts.scrollToOffset || 0);
301
+ scrollNode.scrollTo({
302
+ ...opts.scrollToOptions || {},
303
+ top,
304
+ left: 0
305
+ });
306
+ }
307
+ }
308
+ triggerEventOn("routeChanged", element, opts);
309
+ };
310
+ var index_default = router;
311
+ return __toCommonJS(index_exports);
312
+ })();
package/index.js CHANGED
@@ -1,9 +1,137 @@
1
1
  'use strict'
2
2
 
3
- import { triggerEventOn } from '@domql/event'
4
- import { document, window } from '@domql/utils'
3
+ import { document, window, triggerEventOn } from '@domql/utils'
5
4
  import { setContentKey } from '@domql/element/set'
6
5
 
6
+ // --- Route matching utilities ---
7
+
8
+ const paramPattern = /^:(.+)/
9
+ const wildcardPattern = /^\*$/
10
+
11
+ const routeCache = new Map()
12
+
13
+ export const parseRoutePattern = (pattern) => {
14
+ const cached = routeCache.get(pattern)
15
+ if (cached) return cached
16
+
17
+ const segments = pattern.replace(/^\//, '').split('/')
18
+ const params = []
19
+ let hasWildcard = false
20
+
21
+ for (let i = 0; i < segments.length; i++) {
22
+ const match = segments[i].match(paramPattern)
23
+ if (match) {
24
+ params.push({ index: i, name: match[1] })
25
+ } else if (wildcardPattern.test(segments[i])) {
26
+ hasWildcard = true
27
+ }
28
+ }
29
+
30
+ const result = { segments, params, hasWildcard, pattern }
31
+ routeCache.set(pattern, result)
32
+ return result
33
+ }
34
+
35
+ export const matchRoute = (pathname, routes, level = 0) => {
36
+ const pathSegments = pathname.replace(/^\//, '').split('/').filter(Boolean)
37
+ const relevantSegments = pathSegments.slice(level)
38
+ const routePath = '/' + (relevantSegments[0] || '')
39
+
40
+ let bestMatch = null
41
+ let bestScore = -1
42
+ let matchedParams = {}
43
+
44
+ for (const key in routes) {
45
+ if (key === '/*') continue
46
+
47
+ const parsed = parseRoutePattern(key)
48
+ const score = scoreMatch(relevantSegments, parsed)
49
+
50
+ if (score > bestScore) {
51
+ bestScore = score
52
+ bestMatch = key
53
+ matchedParams = extractParams(relevantSegments, parsed)
54
+ }
55
+ }
56
+
57
+ if (!bestMatch && routes['/*']) {
58
+ bestMatch = '/*'
59
+ }
60
+
61
+ return {
62
+ key: bestMatch,
63
+ content: bestMatch ? routes[bestMatch] : null,
64
+ params: matchedParams,
65
+ routePath
66
+ }
67
+ }
68
+
69
+ const scoreMatch = (pathSegments, parsed) => {
70
+ const { segments, hasWildcard } = parsed
71
+
72
+ if (!hasWildcard && segments.length !== pathSegments.length &&
73
+ segments.length !== 1) {
74
+ // For single-segment patterns, match just the first segment
75
+ if (segments.length > pathSegments.length) return -1
76
+ }
77
+
78
+ let score = 0
79
+ const len = Math.min(segments.length, pathSegments.length)
80
+
81
+ for (let i = 0; i < len; i++) {
82
+ if (segments[i] === pathSegments[i]) {
83
+ score += 3 // exact match
84
+ } else if (paramPattern.test(segments[i])) {
85
+ score += 1 // param match
86
+ } else if (wildcardPattern.test(segments[i])) {
87
+ score += 0.5
88
+ } else {
89
+ return -1 // no match
90
+ }
91
+ }
92
+
93
+ return score
94
+ }
95
+
96
+ const extractParams = (pathSegments, parsed) => {
97
+ const params = {}
98
+ for (const { index, name } of parsed.params) {
99
+ if (pathSegments[index]) {
100
+ params[name] = decodeURIComponent(pathSegments[index])
101
+ }
102
+ }
103
+ return params
104
+ }
105
+
106
+ export const parseQuery = (search) => {
107
+ if (!search || search === '?') return {}
108
+ const params = {}
109
+ const searchParams = new URLSearchParams(search)
110
+ searchParams.forEach((value, key) => {
111
+ if (params[key] !== undefined) {
112
+ if (!Array.isArray(params[key])) params[key] = [params[key]]
113
+ params[key].push(value)
114
+ } else {
115
+ params[key] = value
116
+ }
117
+ })
118
+ return params
119
+ }
120
+
121
+ // --- Route guards / middleware ---
122
+
123
+ export const runGuards = async (guards, context) => {
124
+ if (!guards || !guards.length) return true
125
+ for (const guard of guards) {
126
+ const result = await guard(context)
127
+ if (result === false) return false
128
+ if (typeof result === 'string') return result // redirect path
129
+ }
130
+ return true
131
+ }
132
+
133
+ // --- Core router ---
134
+
7
135
  export const getActiveRoute = (level = 0, route = window.location.pathname) => {
8
136
  const routeArray = route.split('/')
9
137
  const activeRoute = routeArray[level + 1]
@@ -25,7 +153,8 @@ const defaultOptions = {
25
153
  updateState: true,
26
154
  scrollToOffset: 0,
27
155
  contentElementKey: 'content',
28
- scrollToOptions: { behavior: 'smooth' }
156
+ scrollToOptions: { behavior: 'smooth' },
157
+ useParamsMatching: false
29
158
  }
30
159
 
31
160
  export const router = (path, el, state = {}, options = {}) => {
@@ -38,6 +167,7 @@ export const router = (path, el, state = {}, options = {}) => {
38
167
  ...options
39
168
  }
40
169
  lastLevel = opts.lastLevel
170
+
41
171
  const ref = element.__ref
42
172
 
43
173
  if (
@@ -53,27 +183,60 @@ export const router = (path, el, state = {}, options = {}) => {
53
183
  const urlObj = new win.URL(win.location.origin + path)
54
184
  const { pathname, search, hash } = urlObj
55
185
 
186
+ const query = parseQuery(search)
187
+
56
188
  const rootNode = element.node
57
- const route = getActiveRoute(opts.level, pathname)
58
- const content = element.routes[route || '/'] || element.routes['/*']
59
- const scrollNode = opts.scrollToNode ? rootNode : opts.scrollNode
60
189
  const hashChanged = hash && hash !== win.location.hash.slice(1)
61
190
  const pathChanged = pathname !== lastPathname
62
191
  lastPathname = pathname
63
192
 
193
+ // Route matching - support both simple and param-based
194
+ let route, content, params
195
+ if (opts.useParamsMatching) {
196
+ const match = matchRoute(pathname, element.routes, opts.level)
197
+ route = match.routePath
198
+ content = match.content
199
+ params = match.params
200
+ } else {
201
+ route = getActiveRoute(opts.level, pathname)
202
+ content = element.routes[route || '/'] || element.routes['/*']
203
+ params = {}
204
+ }
205
+
206
+ const scrollNode = opts.scrollToNode ? rootNode : opts.scrollNode
207
+
64
208
  if (!content || element.state.root.debugging) {
65
209
  element.state.root.debugging = false
210
+
211
+ if (opts.onNotFound) {
212
+ opts.onNotFound({ pathname, route, element })
213
+ }
66
214
  return
67
215
  }
68
216
 
217
+ // Run guards
218
+ if (opts.guards && opts.guards.length) {
219
+ const guardContext = { pathname, route, params, query, hash, element, state }
220
+ const guardResult = runGuards(opts.guards, guardContext)
221
+ if (guardResult === false) return
222
+ if (typeof guardResult === 'string') {
223
+ // Redirect
224
+ return router(guardResult, el, state, { ...options, guards: [] })
225
+ }
226
+ }
227
+
69
228
  if (opts.pushState) {
70
229
  win.history.pushState(state, null, pathname + (search || '') + (hash || ''))
71
230
  }
72
231
 
73
232
  if (pathChanged || !hashChanged) {
233
+ const stateUpdate = { route, hash, debugging: false }
234
+ if (Object.keys(params).length) stateUpdate.params = params
235
+ if (Object.keys(query).length) stateUpdate.query = query
236
+
74
237
  if (opts.updateState) {
75
238
  element.state.update(
76
- { route, hash, debugging: false },
239
+ stateUpdate,
77
240
  { preventContentUpdate: true }
78
241
  )
79
242
  }
@@ -112,7 +275,7 @@ export const router = (path, el, state = {}, options = {}) => {
112
275
  const top =
113
276
  activeNode.getBoundingClientRect().top +
114
277
  rootNode.scrollTop -
115
- opts.scrollToOffset || 0
278
+ (opts.scrollToOffset || 0)
116
279
  scrollNode.scrollTo({
117
280
  ...(opts.scrollToOptions || {}),
118
281
  top,
package/package.json CHANGED
@@ -1,32 +1,41 @@
1
1
  {
2
2
  "name": "@domql/router",
3
- "version": "3.2.3",
3
+ "version": "3.2.7",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
- "module": "dist/esm/index.js",
7
- "unpkg": "dist/iife/index.js",
8
- "jsdelivr": "dist/iife/index.js",
9
- "main": "dist/esm/index.js",
10
- "exports": "./dist/cjs/index.js",
6
+ "module": "./dist/esm/index.js",
7
+ "unpkg": "./dist/iife/index.js",
8
+ "jsdelivr": "./dist/iife/index.js",
9
+ "main": "./dist/cjs/index.js",
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/esm/index.js",
13
+ "require": "./dist/cjs/index.js",
14
+ "browser": "./dist/iife/index.js",
15
+ "default": "./dist/esm/index.js"
16
+ }
17
+ },
11
18
  "source": "index.js",
12
19
  "files": [
13
- "*.js",
14
- "dist"
20
+ "dist",
21
+ "*.js"
15
22
  ],
16
23
  "scripts": {
17
24
  "copy:package:cjs": "cp ../../build/package-cjs.json dist/cjs/package.json",
18
- "build:esm": "npx cross-env NODE_ENV=$NODE_ENV npx esbuild *.js --target=es2017 --format=esm --outdir=dist/esm",
19
- "build:cjs": "npx cross-env NODE_ENV=$NODE_ENV npx esbuild *.js --target=node16 --format=cjs --outdir=dist/cjs",
20
- "build:iife": "npx cross-env NODE_ENV=$NODE_ENV npx esbuild *.js --target=node16 --format=iife --outdir=dist/iife",
21
- "build": "npx rimraf -I dist; npm run build:cjs; npm run build:esm",
22
- "prepublish": "npx rimraf -I dist && npm run build && npm run copy:package:cjs"
25
+ "build:esm": "cross-env NODE_ENV=$NODE_ENV esbuild *.js --target=es2020 --format=esm --outdir=dist/esm --define:process.env.NODE_ENV=process.env.NODE_ENV",
26
+ "build:cjs": "cross-env NODE_ENV=$NODE_ENV esbuild *.js --target=node18 --format=cjs --outdir=dist/cjs --define:process.env.NODE_ENV=process.env.NODE_ENV",
27
+ "build:iife": "cross-env NODE_ENV=$NODE_ENV esbuild index.js --bundle --target=es2020 --format=iife --global-name=DomqlRouter --outfile=dist/iife/index.js --define:process.env.NODE_ENV=process.env.NODE_ENV",
28
+ "build": "node ../../build/build.js",
29
+ "prepublish": "npm run build && npm run copy:package:cjs"
23
30
  },
24
31
  "dependencies": {
25
- "@domql/event": "^3.2.3",
32
+ "@domql/element": "^3.2.3",
26
33
  "@domql/utils": "^3.2.3"
27
34
  },
28
35
  "gitHead": "9fc1b79b41cdc725ca6b24aec64920a599634681",
29
36
  "devDependencies": {
30
37
  "@babel/core": "^7.26.0"
31
- }
38
+ },
39
+ "browser": "./dist/iife/index.js",
40
+ "sideEffects": false
32
41
  }
@@ -1,4 +0,0 @@
1
- {
2
- "type": "commonjs",
3
- "main": "index.js"
4
- }