@mapvx/website-component 0.9.0 → 0.11.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 +310 -46
- package/dist/browser/main.js +34 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -274,9 +274,10 @@ The web component accepts the following input properties to customize its behavi
|
|
|
274
274
|
|
|
275
275
|
The web component emits custom events that you can listen to for user interactions:
|
|
276
276
|
|
|
277
|
-
| Event Name | Type
|
|
278
|
-
| -------------- |
|
|
279
|
-
| `cardSelected` | `string`
|
|
277
|
+
| Event Name | Type | Description |
|
|
278
|
+
| -------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
279
|
+
| `cardSelected` | `string` | ⚠️ **Deprecated**: Use `userAction` with type `show-place` instead. Emitted when a location card is selected. Contains the card ID. |
|
|
280
|
+
| `userAction` | `UserAction` | Emitted when a user performs an action (filter selection, search, place navigation, etc.). Contains action type and associated data. |
|
|
280
281
|
|
|
281
282
|
### Output Usage Examples
|
|
282
283
|
|
|
@@ -284,30 +285,30 @@ The web component emits custom events that you can listen to for user interactio
|
|
|
284
285
|
|
|
285
286
|
```javascript
|
|
286
287
|
// Wait for the web component to be registered
|
|
287
|
-
function
|
|
288
|
+
function setupUserActionListener() {
|
|
288
289
|
const element = document.querySelector('mapvx-website')
|
|
289
290
|
if (element) {
|
|
290
|
-
element.addEventListener('
|
|
291
|
-
if (event instanceof CustomEvent) {
|
|
292
|
-
const
|
|
293
|
-
console.log('
|
|
294
|
-
// Handle
|
|
291
|
+
element.addEventListener('userAction', (event) => {
|
|
292
|
+
if (event instanceof CustomEvent && event.detail.type === 'show-place') {
|
|
293
|
+
const placeId = event.detail.data.placeId
|
|
294
|
+
console.log('Place shown:', placeId)
|
|
295
|
+
// Handle place display
|
|
295
296
|
}
|
|
296
297
|
})
|
|
297
298
|
} else {
|
|
298
299
|
// Retry if element is not yet available
|
|
299
|
-
setTimeout(
|
|
300
|
+
setTimeout(setupUserActionListener, 100)
|
|
300
301
|
}
|
|
301
302
|
}
|
|
302
303
|
|
|
303
304
|
// Check if web component is already registered
|
|
304
305
|
if (customElements.get('mapvx-website')) {
|
|
305
|
-
|
|
306
|
+
setupUserActionListener()
|
|
306
307
|
} else {
|
|
307
308
|
// Wait for registration
|
|
308
309
|
const checkInterval = setInterval(() => {
|
|
309
310
|
if (customElements.get('mapvx-website')) {
|
|
310
|
-
|
|
311
|
+
setupUserActionListener()
|
|
311
312
|
clearInterval(checkInterval)
|
|
312
313
|
}
|
|
313
314
|
}, 100)
|
|
@@ -316,7 +317,7 @@ if (customElements.get('mapvx-website')) {
|
|
|
316
317
|
|
|
317
318
|
#### Update Browser URL with `history.pushState`
|
|
318
319
|
|
|
319
|
-
You can
|
|
320
|
+
You can react to user actions to change the browser URL without a full page reload, which is especially useful in SSR apps looking to keep client-side navigation in sync with the selected place.
|
|
320
321
|
|
|
321
322
|
```html
|
|
322
323
|
<script>
|
|
@@ -328,19 +329,60 @@ You can also react to the `cardSelected` event to change the browser URL without
|
|
|
328
329
|
return
|
|
329
330
|
}
|
|
330
331
|
|
|
331
|
-
const
|
|
332
|
+
const handleUserAction = (event) => {
|
|
332
333
|
if (event instanceof CustomEvent) {
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
334
|
+
const action = event.detail
|
|
335
|
+
switch (action.type) {
|
|
336
|
+
case 'return-to-home':
|
|
337
|
+
case 'select-filter': {
|
|
338
|
+
const { filter } = action.data
|
|
339
|
+
history.pushState(
|
|
340
|
+
{ page: 'home', filter },
|
|
341
|
+
'',
|
|
342
|
+
`${location.pathname}?tab=${encodeURIComponent(filter)}`,
|
|
343
|
+
)
|
|
344
|
+
break
|
|
345
|
+
}
|
|
346
|
+
case 'show-place': {
|
|
347
|
+
const { placeId, alias } = action.data
|
|
348
|
+
if (alias) {
|
|
349
|
+
history.pushState(
|
|
350
|
+
{ page: 'profile', id: alias },
|
|
351
|
+
'',
|
|
352
|
+
`${location.pathname}?tenant=${alias}`,
|
|
353
|
+
)
|
|
354
|
+
} else {
|
|
355
|
+
history.pushState(
|
|
356
|
+
{ page: 'profile', id: placeId },
|
|
357
|
+
'',
|
|
358
|
+
`${location.pathname}?tenant=${placeId}`,
|
|
359
|
+
)
|
|
360
|
+
}
|
|
361
|
+
break
|
|
362
|
+
}
|
|
363
|
+
case 'search': {
|
|
364
|
+
const { searchTerm } = action.data
|
|
365
|
+
history.pushState(
|
|
366
|
+
{ page: 'search', term: searchTerm },
|
|
367
|
+
'',
|
|
368
|
+
`${location.pathname}?search=${encodeURIComponent(searchTerm)}`,
|
|
369
|
+
)
|
|
370
|
+
break
|
|
371
|
+
}
|
|
372
|
+
case 'select-destination': {
|
|
373
|
+
const { destinationId } = action.data
|
|
374
|
+
history.pushState(
|
|
375
|
+
{ page: 'route', id: destinationId },
|
|
376
|
+
'',
|
|
377
|
+
`${location.pathname}?destination=${destinationId}`,
|
|
378
|
+
)
|
|
379
|
+
break
|
|
380
|
+
}
|
|
381
|
+
}
|
|
340
382
|
}
|
|
341
383
|
}
|
|
342
384
|
|
|
343
|
-
element.addEventListener('
|
|
385
|
+
element.addEventListener('userAction', handleUserAction)
|
|
344
386
|
}
|
|
345
387
|
|
|
346
388
|
// Check if web component is already registered
|
|
@@ -370,18 +412,214 @@ function App() {
|
|
|
370
412
|
const element = webComponentRef.current
|
|
371
413
|
if (!element) return
|
|
372
414
|
|
|
373
|
-
const
|
|
415
|
+
const handleUserAction = (event) => {
|
|
416
|
+
if (event instanceof CustomEvent && event.detail.type === 'show-place') {
|
|
417
|
+
const placeId = event.detail.data.placeId
|
|
418
|
+
console.log('Place shown:', placeId)
|
|
419
|
+
// Handle place display
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
element.addEventListener('userAction', handleUserAction)
|
|
424
|
+
|
|
425
|
+
return () => {
|
|
426
|
+
element.removeEventListener('userAction', handleUserAction)
|
|
427
|
+
}
|
|
428
|
+
}, [])
|
|
429
|
+
|
|
430
|
+
return <mapvx-website ref={webComponentRef} api-key="your-api-key-here" />
|
|
431
|
+
}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
#### Vue
|
|
435
|
+
|
|
436
|
+
```vue
|
|
437
|
+
<template>
|
|
438
|
+
<mapvx-website ref="webComponent" api-key="your-api-key-here" />
|
|
439
|
+
</template>
|
|
440
|
+
|
|
441
|
+
<script setup>
|
|
442
|
+
import { ref, onMounted, onUnmounted } from 'vue'
|
|
443
|
+
|
|
444
|
+
const webComponent = ref(null)
|
|
445
|
+
|
|
446
|
+
const handleUserAction = (event) => {
|
|
447
|
+
if (event instanceof CustomEvent && event.detail.type === 'show-place') {
|
|
448
|
+
const placeId = event.detail.data.placeId
|
|
449
|
+
console.log('Place shown:', placeId)
|
|
450
|
+
// Handle place display
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
onMounted(() => {
|
|
455
|
+
if (webComponent.value) {
|
|
456
|
+
webComponent.value.addEventListener('userAction', handleUserAction)
|
|
457
|
+
}
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
onUnmounted(() => {
|
|
461
|
+
if (webComponent.value) {
|
|
462
|
+
webComponent.value.removeEventListener('userAction', handleUserAction)
|
|
463
|
+
}
|
|
464
|
+
})
|
|
465
|
+
</script>
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
#### Angular
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
import { Component, ElementRef, OnDestroy, ViewChild, AfterViewInit } from '@angular/core'
|
|
472
|
+
|
|
473
|
+
@Component({
|
|
474
|
+
selector: 'app-mapvx',
|
|
475
|
+
template: ` <mapvx-website #webComponent [attr.api-key]="apiKey" /> `,
|
|
476
|
+
})
|
|
477
|
+
export class MapvxComponent implements AfterViewInit, OnDestroy {
|
|
478
|
+
@ViewChild('webComponent', { static: false }) webComponentRef!: ElementRef<HTMLElement>
|
|
479
|
+
|
|
480
|
+
apiKey = 'your-api-key-here'
|
|
481
|
+
private userActionListener?: (event: Event) => void
|
|
482
|
+
|
|
483
|
+
ngAfterViewInit() {
|
|
484
|
+
this.setupUserActionListener()
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
private setupUserActionListener() {
|
|
488
|
+
const element = this.webComponentRef?.nativeElement
|
|
489
|
+
if (!element) {
|
|
490
|
+
// Retry if element is not yet available
|
|
491
|
+
setTimeout(() => this.setupUserActionListener(), 100)
|
|
492
|
+
return
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
this.userActionListener = (event: Event) => {
|
|
374
496
|
if (event instanceof CustomEvent) {
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
|
|
497
|
+
const action = event.detail as { type: string; data: any }
|
|
498
|
+
if (action.type === 'show-place') {
|
|
499
|
+
const placeId = action.data.placeId
|
|
500
|
+
console.log('Place shown:', placeId)
|
|
501
|
+
// Handle place display
|
|
502
|
+
}
|
|
378
503
|
}
|
|
379
504
|
}
|
|
380
505
|
|
|
381
|
-
element.addEventListener('
|
|
506
|
+
element.addEventListener('userAction', this.userActionListener)
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
ngOnDestroy() {
|
|
510
|
+
const element = this.webComponentRef?.nativeElement
|
|
511
|
+
if (element && this.userActionListener) {
|
|
512
|
+
element.removeEventListener('userAction', this.userActionListener)
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### User Action Event
|
|
519
|
+
|
|
520
|
+
The `userAction` event provides detailed information about user interactions within the component. The event detail contains an object with `type` and `data` properties.
|
|
521
|
+
|
|
522
|
+
#### UserAction Types
|
|
523
|
+
|
|
524
|
+
| Type | Description | Data Structure |
|
|
525
|
+
| -------------------- | ------------------------------------------ | --------------------------------------- | ------------ |
|
|
526
|
+
| `select-filter` | Emitted when a filter is selected | `{ filter: string }` |
|
|
527
|
+
| `show-place` | Emitted when a place is displayed | `{ placeId: string, alias: string | undefined }` |
|
|
528
|
+
| `select-destination` | Emitted when a destination is selected | `{ destinationId: string, alias: string | undefined }` |
|
|
529
|
+
| `search` | Emitted when a search is performed | `{ searchTerm: string }` |
|
|
530
|
+
| `return-to-home` | Emitted when user returns to the home view | `{ filter: string }` |
|
|
531
|
+
|
|
532
|
+
#### UserAction Usage Examples
|
|
533
|
+
|
|
534
|
+
#### Vanilla JavaScript
|
|
535
|
+
|
|
536
|
+
```javascript
|
|
537
|
+
// Wait for the web component to be registered
|
|
538
|
+
function setupUserActionListener() {
|
|
539
|
+
const element = document.querySelector('mapvx-website')
|
|
540
|
+
if (element) {
|
|
541
|
+
element.addEventListener('userAction', (event) => {
|
|
542
|
+
if (event instanceof CustomEvent) {
|
|
543
|
+
const action = event.detail
|
|
544
|
+
console.log('User action:', action.type, action.data)
|
|
545
|
+
|
|
546
|
+
// Handle different action types
|
|
547
|
+
switch (action.type) {
|
|
548
|
+
case 'select-filter':
|
|
549
|
+
console.log('Filter selected:', action.data.filter)
|
|
550
|
+
break
|
|
551
|
+
case 'show-place':
|
|
552
|
+
console.log('Place shown:', action.data.placeId)
|
|
553
|
+
break
|
|
554
|
+
case 'select-destination':
|
|
555
|
+
console.log('Destination selected:', action.data.destinationId)
|
|
556
|
+
break
|
|
557
|
+
case 'search':
|
|
558
|
+
console.log('Search performed:', action.data.searchTerm)
|
|
559
|
+
break
|
|
560
|
+
case 'return-to-home':
|
|
561
|
+
console.log('Returned to home with filter:', action.data.filter)
|
|
562
|
+
break
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
})
|
|
566
|
+
} else {
|
|
567
|
+
// Retry if element is not yet available
|
|
568
|
+
setTimeout(setupUserActionListener, 100)
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Check if web component is already registered
|
|
573
|
+
if (customElements.get('mapvx-website')) {
|
|
574
|
+
setupUserActionListener()
|
|
575
|
+
} else {
|
|
576
|
+
// Wait for registration
|
|
577
|
+
const checkInterval = setInterval(() => {
|
|
578
|
+
if (customElements.get('mapvx-website')) {
|
|
579
|
+
setupUserActionListener()
|
|
580
|
+
clearInterval(checkInterval)
|
|
581
|
+
}
|
|
582
|
+
}, 100)
|
|
583
|
+
}
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
#### React
|
|
587
|
+
|
|
588
|
+
```jsx
|
|
589
|
+
import { useEffect, useRef } from 'react'
|
|
590
|
+
|
|
591
|
+
function App() {
|
|
592
|
+
const webComponentRef = useRef(null)
|
|
593
|
+
|
|
594
|
+
useEffect(() => {
|
|
595
|
+
const element = webComponentRef.current
|
|
596
|
+
if (!element) return
|
|
597
|
+
|
|
598
|
+
const handleUserAction = (event) => {
|
|
599
|
+
if (event instanceof CustomEvent) {
|
|
600
|
+
const action = event.detail
|
|
601
|
+
console.log('User action:', action.type, action.data)
|
|
602
|
+
|
|
603
|
+
// Handle different action types
|
|
604
|
+
switch (action.type) {
|
|
605
|
+
case 'select-filter':
|
|
606
|
+
// Handle filter selection
|
|
607
|
+
break
|
|
608
|
+
case 'show-place':
|
|
609
|
+
// Handle place display
|
|
610
|
+
break
|
|
611
|
+
case 'search':
|
|
612
|
+
// Handle search
|
|
613
|
+
break
|
|
614
|
+
// ... other cases
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
element.addEventListener('userAction', handleUserAction)
|
|
382
620
|
|
|
383
621
|
return () => {
|
|
384
|
-
element.removeEventListener('
|
|
622
|
+
element.removeEventListener('userAction', handleUserAction)
|
|
385
623
|
}
|
|
386
624
|
}, [])
|
|
387
625
|
|
|
@@ -401,23 +639,36 @@ import { ref, onMounted, onUnmounted } from 'vue'
|
|
|
401
639
|
|
|
402
640
|
const webComponent = ref(null)
|
|
403
641
|
|
|
404
|
-
const
|
|
642
|
+
const handleUserAction = (event) => {
|
|
405
643
|
if (event instanceof CustomEvent) {
|
|
406
|
-
const
|
|
407
|
-
console.log('
|
|
408
|
-
|
|
644
|
+
const action = event.detail
|
|
645
|
+
console.log('User action:', action.type, action.data)
|
|
646
|
+
|
|
647
|
+
// Handle different action types
|
|
648
|
+
switch (action.type) {
|
|
649
|
+
case 'select-filter':
|
|
650
|
+
// Handle filter selection
|
|
651
|
+
break
|
|
652
|
+
case 'show-place':
|
|
653
|
+
// Handle place display
|
|
654
|
+
break
|
|
655
|
+
case 'search':
|
|
656
|
+
// Handle search
|
|
657
|
+
break
|
|
658
|
+
// ... other cases
|
|
659
|
+
}
|
|
409
660
|
}
|
|
410
661
|
}
|
|
411
662
|
|
|
412
663
|
onMounted(() => {
|
|
413
664
|
if (webComponent.value) {
|
|
414
|
-
webComponent.value.addEventListener('
|
|
665
|
+
webComponent.value.addEventListener('userAction', handleUserAction)
|
|
415
666
|
}
|
|
416
667
|
})
|
|
417
668
|
|
|
418
669
|
onUnmounted(() => {
|
|
419
670
|
if (webComponent.value) {
|
|
420
|
-
webComponent.value.removeEventListener('
|
|
671
|
+
webComponent.value.removeEventListener('userAction', handleUserAction)
|
|
421
672
|
}
|
|
422
673
|
})
|
|
423
674
|
</script>
|
|
@@ -426,7 +677,7 @@ onUnmounted(() => {
|
|
|
426
677
|
#### Angular
|
|
427
678
|
|
|
428
679
|
```typescript
|
|
429
|
-
import { Component, ElementRef,
|
|
680
|
+
import { Component, ElementRef, OnDestroy, ViewChild, AfterViewInit } from '@angular/core'
|
|
430
681
|
|
|
431
682
|
@Component({
|
|
432
683
|
selector: 'app-mapvx',
|
|
@@ -436,35 +687,48 @@ export class MapvxComponent implements AfterViewInit, OnDestroy {
|
|
|
436
687
|
@ViewChild('webComponent', { static: false }) webComponentRef!: ElementRef<HTMLElement>
|
|
437
688
|
|
|
438
689
|
apiKey = 'your-api-key-here'
|
|
439
|
-
private
|
|
690
|
+
private userActionListener?: (event: Event) => void
|
|
440
691
|
|
|
441
692
|
ngAfterViewInit() {
|
|
442
|
-
this.
|
|
693
|
+
this.setupUserActionListener()
|
|
443
694
|
}
|
|
444
695
|
|
|
445
|
-
private
|
|
696
|
+
private setupUserActionListener() {
|
|
446
697
|
const element = this.webComponentRef?.nativeElement
|
|
447
698
|
if (!element) {
|
|
448
699
|
// Retry if element is not yet available
|
|
449
|
-
setTimeout(() => this.
|
|
700
|
+
setTimeout(() => this.setupUserActionListener(), 100)
|
|
450
701
|
return
|
|
451
702
|
}
|
|
452
703
|
|
|
453
|
-
this.
|
|
704
|
+
this.userActionListener = (event: Event) => {
|
|
454
705
|
if (event instanceof CustomEvent) {
|
|
455
|
-
const
|
|
456
|
-
console.log('
|
|
457
|
-
|
|
706
|
+
const action = event.detail as { type: string; data: any }
|
|
707
|
+
console.log('User action:', action.type, action.data)
|
|
708
|
+
|
|
709
|
+
// Handle different action types
|
|
710
|
+
switch (action.type) {
|
|
711
|
+
case 'select-filter':
|
|
712
|
+
// Handle filter selection
|
|
713
|
+
break
|
|
714
|
+
case 'show-place':
|
|
715
|
+
// Handle place display
|
|
716
|
+
break
|
|
717
|
+
case 'search':
|
|
718
|
+
// Handle search
|
|
719
|
+
break
|
|
720
|
+
// ... other cases
|
|
721
|
+
}
|
|
458
722
|
}
|
|
459
723
|
}
|
|
460
724
|
|
|
461
|
-
element.addEventListener('
|
|
725
|
+
element.addEventListener('userAction', this.userActionListener)
|
|
462
726
|
}
|
|
463
727
|
|
|
464
728
|
ngOnDestroy() {
|
|
465
729
|
const element = this.webComponentRef?.nativeElement
|
|
466
|
-
if (element && this.
|
|
467
|
-
element.removeEventListener('
|
|
730
|
+
if (element && this.userActionListener) {
|
|
731
|
+
element.removeEventListener('userAction', this.userActionListener)
|
|
468
732
|
}
|
|
469
733
|
}
|
|
470
734
|
}
|