betal-fe 4.2.0 → 4.4.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.
- package/README.md +41 -6
- package/dist/betal-fe.js +41 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ npm install betal-fe
|
|
|
15
15
|
- **Reactive State** - Automatic re-rendering on state changes
|
|
16
16
|
- **Lifecycle Hooks** - `onMounted`, `onUnmounted`, `onPropsChange`, `onStateChange`
|
|
17
17
|
- **Slots** - Content projection for flexible component composition (Vue-style)
|
|
18
|
-
- **Hash Router** - Built-in SPA routing with route guards and
|
|
18
|
+
- **Hash Router** - Built-in SPA routing with route guards, params, and scroll behavior
|
|
19
19
|
- **Event System** - Parent-child communication via emit/subscribe
|
|
20
20
|
- **Scheduler** - Async lifecycle execution with microtask batching
|
|
21
21
|
|
|
@@ -65,7 +65,9 @@ const router = new HashRouter([
|
|
|
65
65
|
{ path: '/', component: HomePage },
|
|
66
66
|
{ path: '/about', component: AboutPage },
|
|
67
67
|
{ path: '/user/:id', component: UserPage },
|
|
68
|
-
]
|
|
68
|
+
], {
|
|
69
|
+
scrollBehavior: 'top' // 'top', false, or custom function
|
|
70
|
+
});
|
|
69
71
|
|
|
70
72
|
const App = defineComponent({
|
|
71
73
|
render() {
|
|
@@ -197,10 +199,36 @@ Creates a hash-based router.
|
|
|
197
199
|
const router = new HashRouter([
|
|
198
200
|
{ path: '/', component: HomePage },
|
|
199
201
|
{ path: '/user/:id', component: UserPage },
|
|
202
|
+
{
|
|
203
|
+
path: '/admin',
|
|
204
|
+
component: AdminPage,
|
|
205
|
+
beforeEnter: (from, to) => {
|
|
206
|
+
// Route guard - return false to cancel, string to redirect
|
|
207
|
+
return isLoggedIn() ? true : '/login';
|
|
208
|
+
}
|
|
209
|
+
}
|
|
200
210
|
], {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
211
|
+
scrollBehavior: 'top' // 'top' (default), false, or custom function
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
#### Scroll Behavior Options
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
// Default: scroll to top
|
|
219
|
+
new HashRouter(routes);
|
|
220
|
+
|
|
221
|
+
// Disable scroll
|
|
222
|
+
new HashRouter(routes, { scrollBehavior: false });
|
|
223
|
+
|
|
224
|
+
// Custom function for route-specific behavior
|
|
225
|
+
new HashRouter(routes, {
|
|
226
|
+
scrollBehavior: (from, to) => {
|
|
227
|
+
// Don't scroll when navigating within the same section
|
|
228
|
+
if (from?.path?.startsWith('/docs') && to?.path?.startsWith('/docs')) {
|
|
229
|
+
return null; // Prevent scrolling
|
|
230
|
+
}
|
|
231
|
+
return { x: 0, y: 0, behavior: 'smooth' };
|
|
204
232
|
}
|
|
205
233
|
});
|
|
206
234
|
```
|
|
@@ -216,7 +244,14 @@ this.appContext.router.navigateTo('/path')
|
|
|
216
244
|
#### `RouterLink` Component
|
|
217
245
|
|
|
218
246
|
```javascript
|
|
219
|
-
h(RouterLink, { to: '/about'
|
|
247
|
+
h(RouterLink, { to: '/about' }, ['About'])
|
|
248
|
+
|
|
249
|
+
// With anchor fragments
|
|
250
|
+
h(RouterLink, { to: '/about#team' }, ['Meet the Team'])
|
|
251
|
+
h(RouterLink, { to: '#features' }, ['Jump to Features'])
|
|
252
|
+
|
|
253
|
+
// With custom classes and attributes
|
|
254
|
+
h(RouterLink, { to: '/about', class: 'nav-link active' }, ['About'])
|
|
220
255
|
```
|
|
221
256
|
|
|
222
257
|
#### `RouterOutlet` Component
|
package/dist/betal-fe.js
CHANGED
|
@@ -530,6 +530,7 @@ class HashRouter {
|
|
|
530
530
|
#dispatcher = new Dispatcher();
|
|
531
531
|
#subscriptions = new WeakMap();
|
|
532
532
|
#subscriberFns = new Set();
|
|
533
|
+
#scrollBehavior = 'top';
|
|
533
534
|
get matchedRoute() {
|
|
534
535
|
return this.#matchedRoute;
|
|
535
536
|
}
|
|
@@ -549,9 +550,12 @@ class HashRouter {
|
|
|
549
550
|
return hash.slice(1);
|
|
550
551
|
}
|
|
551
552
|
#onPopState = () => this.#matchCurrentRoute();
|
|
552
|
-
constructor(routes = []) {
|
|
553
|
+
constructor(routes = [], options = {}) {
|
|
553
554
|
assert(Array.isArray(routes), "Routes must be an array");
|
|
554
555
|
this.#matchers = routes.map(makeRouteMatcher);
|
|
556
|
+
if (options.scrollBehavior !== undefined) {
|
|
557
|
+
this.#scrollBehavior = options.scrollBehavior;
|
|
558
|
+
}
|
|
555
559
|
}
|
|
556
560
|
async init() {
|
|
557
561
|
if (this.#isInitialized) {
|
|
@@ -573,9 +577,12 @@ class HashRouter {
|
|
|
573
577
|
this.#isInitialized = false;
|
|
574
578
|
}
|
|
575
579
|
async navigateTo(path) {
|
|
576
|
-
const
|
|
580
|
+
const hashIndex = path.indexOf('#', 1);
|
|
581
|
+
const pathWithoutHash = hashIndex !== -1 ? path.slice(0, hashIndex) : path;
|
|
582
|
+
const hash = hashIndex !== -1 ? path.slice(hashIndex + 1) : null;
|
|
583
|
+
const matcher = this.#matchers.find((matcher) => matcher.checkMatch(pathWithoutHash));
|
|
577
584
|
if (matcher == null) {
|
|
578
|
-
console.warn(`[Router] No route matches path "${
|
|
585
|
+
console.warn(`[Router] No route matches path "${pathWithoutHash}"`);
|
|
579
586
|
this.#matchedRoute = null;
|
|
580
587
|
this.#params = {};
|
|
581
588
|
this.#query = {};
|
|
@@ -593,12 +600,39 @@ class HashRouter {
|
|
|
593
600
|
}
|
|
594
601
|
if (shouldNavigate) {
|
|
595
602
|
this.#matchedRoute = matcher.route;
|
|
596
|
-
this.#params = matcher.extractParams(
|
|
597
|
-
this.#query = matcher.extractQuery(
|
|
603
|
+
this.#params = matcher.extractParams(pathWithoutHash);
|
|
604
|
+
this.#query = matcher.extractQuery(pathWithoutHash);
|
|
598
605
|
this.#pushState(path);
|
|
606
|
+
this.#handleScrollBehavior(from, to, hash);
|
|
599
607
|
this.#dispatcher.dispatch(ROUTER_EVENT, { from, to, router: this });
|
|
600
608
|
}
|
|
601
609
|
}
|
|
610
|
+
#handleScrollBehavior(from, to, hash) {
|
|
611
|
+
if (this.#scrollBehavior === false) {
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
setTimeout(() => {
|
|
615
|
+
if (typeof this.#scrollBehavior === 'function') {
|
|
616
|
+
const position = this.#scrollBehavior(from, to, { hash });
|
|
617
|
+
if (position) {
|
|
618
|
+
window.scrollTo({
|
|
619
|
+
left: position.x || 0,
|
|
620
|
+
top: position.y || 0,
|
|
621
|
+
behavior: position.behavior || 'auto'
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
} else if (this.#scrollBehavior === 'top') {
|
|
625
|
+
if (hash) {
|
|
626
|
+
const element = document.getElementById(hash);
|
|
627
|
+
if (element) {
|
|
628
|
+
element.scrollIntoView({ behavior: 'auto' });
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
window.scrollTo({ top: 0, left: 0, behavior: 'auto' });
|
|
633
|
+
}
|
|
634
|
+
}, 0);
|
|
635
|
+
}
|
|
602
636
|
back() {
|
|
603
637
|
window.history.back();
|
|
604
638
|
}
|
|
@@ -1121,42 +1155,18 @@ const RouterLink = defineComponent({
|
|
|
1121
1155
|
return h(
|
|
1122
1156
|
"a",
|
|
1123
1157
|
{
|
|
1124
|
-
href: to
|
|
1158
|
+
href: `#${to}`,
|
|
1125
1159
|
...rest,
|
|
1126
1160
|
on: {
|
|
1127
1161
|
click: (e) => {
|
|
1128
1162
|
e.preventDefault();
|
|
1129
|
-
this.
|
|
1163
|
+
this.appContext.router.navigateTo(to);
|
|
1130
1164
|
},
|
|
1131
1165
|
},
|
|
1132
1166
|
},
|
|
1133
1167
|
[hSlot()]
|
|
1134
1168
|
);
|
|
1135
1169
|
},
|
|
1136
|
-
handleNavigation(to) {
|
|
1137
|
-
const anchorIndex = to.indexOf('#');
|
|
1138
|
-
if (anchorIndex !== -1 && anchorIndex > 0) {
|
|
1139
|
-
const path = to.substring(0, anchorIndex);
|
|
1140
|
-
const anchor = to.substring(anchorIndex + 1);
|
|
1141
|
-
this.appContext.router.navigateTo(path);
|
|
1142
|
-
setTimeout(() => {
|
|
1143
|
-
this.scrollToAnchor(anchor);
|
|
1144
|
-
}, 0);
|
|
1145
|
-
} else if (anchorIndex === 0) {
|
|
1146
|
-
const anchor = to.substring(1);
|
|
1147
|
-
this.scrollToAnchor(anchor);
|
|
1148
|
-
} else {
|
|
1149
|
-
this.appContext.router.navigateTo(to);
|
|
1150
|
-
}
|
|
1151
|
-
},
|
|
1152
|
-
scrollToAnchor(anchorId) {
|
|
1153
|
-
const element = document.getElementById(anchorId);
|
|
1154
|
-
if (element) {
|
|
1155
|
-
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
1156
|
-
} else {
|
|
1157
|
-
console.warn(`[RouterLink] Element with id "${anchorId}" not found`);
|
|
1158
|
-
}
|
|
1159
|
-
},
|
|
1160
1170
|
});
|
|
1161
1171
|
const RouterOutlet = defineComponent({
|
|
1162
1172
|
state() {
|