@mapvx/website-component 0.9.0 → 0.10.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 +302 -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,52 @@ 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 } = action.data
|
|
348
|
+
history.pushState(
|
|
349
|
+
{ page: 'profile', id: placeId },
|
|
350
|
+
'',
|
|
351
|
+
`${location.pathname}?tenant=${placeId}`,
|
|
352
|
+
)
|
|
353
|
+
break
|
|
354
|
+
}
|
|
355
|
+
case 'search': {
|
|
356
|
+
const { searchTerm } = action.data
|
|
357
|
+
history.pushState(
|
|
358
|
+
{ page: 'search', term: searchTerm },
|
|
359
|
+
'',
|
|
360
|
+
`${location.pathname}?search=${encodeURIComponent(searchTerm)}`,
|
|
361
|
+
)
|
|
362
|
+
break
|
|
363
|
+
}
|
|
364
|
+
case 'select-destination': {
|
|
365
|
+
const { destinationId } = action.data
|
|
366
|
+
history.pushState(
|
|
367
|
+
{ page: 'route', id: destinationId },
|
|
368
|
+
'',
|
|
369
|
+
`${location.pathname}?destination=${destinationId}`,
|
|
370
|
+
)
|
|
371
|
+
break
|
|
372
|
+
}
|
|
373
|
+
}
|
|
340
374
|
}
|
|
341
375
|
}
|
|
342
376
|
|
|
343
|
-
element.addEventListener('
|
|
377
|
+
element.addEventListener('userAction', handleUserAction)
|
|
344
378
|
}
|
|
345
379
|
|
|
346
380
|
// Check if web component is already registered
|
|
@@ -370,18 +404,214 @@ function App() {
|
|
|
370
404
|
const element = webComponentRef.current
|
|
371
405
|
if (!element) return
|
|
372
406
|
|
|
373
|
-
const
|
|
407
|
+
const handleUserAction = (event) => {
|
|
408
|
+
if (event instanceof CustomEvent && event.detail.type === 'show-place') {
|
|
409
|
+
const placeId = event.detail.data.placeId
|
|
410
|
+
console.log('Place shown:', placeId)
|
|
411
|
+
// Handle place display
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
element.addEventListener('userAction', handleUserAction)
|
|
416
|
+
|
|
417
|
+
return () => {
|
|
418
|
+
element.removeEventListener('userAction', handleUserAction)
|
|
419
|
+
}
|
|
420
|
+
}, [])
|
|
421
|
+
|
|
422
|
+
return <mapvx-website ref={webComponentRef} api-key="your-api-key-here" />
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
#### Vue
|
|
427
|
+
|
|
428
|
+
```vue
|
|
429
|
+
<template>
|
|
430
|
+
<mapvx-website ref="webComponent" api-key="your-api-key-here" />
|
|
431
|
+
</template>
|
|
432
|
+
|
|
433
|
+
<script setup>
|
|
434
|
+
import { ref, onMounted, onUnmounted } from 'vue'
|
|
435
|
+
|
|
436
|
+
const webComponent = ref(null)
|
|
437
|
+
|
|
438
|
+
const handleUserAction = (event) => {
|
|
439
|
+
if (event instanceof CustomEvent && event.detail.type === 'show-place') {
|
|
440
|
+
const placeId = event.detail.data.placeId
|
|
441
|
+
console.log('Place shown:', placeId)
|
|
442
|
+
// Handle place display
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
onMounted(() => {
|
|
447
|
+
if (webComponent.value) {
|
|
448
|
+
webComponent.value.addEventListener('userAction', handleUserAction)
|
|
449
|
+
}
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
onUnmounted(() => {
|
|
453
|
+
if (webComponent.value) {
|
|
454
|
+
webComponent.value.removeEventListener('userAction', handleUserAction)
|
|
455
|
+
}
|
|
456
|
+
})
|
|
457
|
+
</script>
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
#### Angular
|
|
461
|
+
|
|
462
|
+
```typescript
|
|
463
|
+
import { Component, ElementRef, OnDestroy, ViewChild, AfterViewInit } from '@angular/core'
|
|
464
|
+
|
|
465
|
+
@Component({
|
|
466
|
+
selector: 'app-mapvx',
|
|
467
|
+
template: ` <mapvx-website #webComponent [attr.api-key]="apiKey" /> `,
|
|
468
|
+
})
|
|
469
|
+
export class MapvxComponent implements AfterViewInit, OnDestroy {
|
|
470
|
+
@ViewChild('webComponent', { static: false }) webComponentRef!: ElementRef<HTMLElement>
|
|
471
|
+
|
|
472
|
+
apiKey = 'your-api-key-here'
|
|
473
|
+
private userActionListener?: (event: Event) => void
|
|
474
|
+
|
|
475
|
+
ngAfterViewInit() {
|
|
476
|
+
this.setupUserActionListener()
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
private setupUserActionListener() {
|
|
480
|
+
const element = this.webComponentRef?.nativeElement
|
|
481
|
+
if (!element) {
|
|
482
|
+
// Retry if element is not yet available
|
|
483
|
+
setTimeout(() => this.setupUserActionListener(), 100)
|
|
484
|
+
return
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
this.userActionListener = (event: Event) => {
|
|
374
488
|
if (event instanceof CustomEvent) {
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
|
|
489
|
+
const action = event.detail as { type: string; data: any }
|
|
490
|
+
if (action.type === 'show-place') {
|
|
491
|
+
const placeId = action.data.placeId
|
|
492
|
+
console.log('Place shown:', placeId)
|
|
493
|
+
// Handle place display
|
|
494
|
+
}
|
|
378
495
|
}
|
|
379
496
|
}
|
|
380
497
|
|
|
381
|
-
element.addEventListener('
|
|
498
|
+
element.addEventListener('userAction', this.userActionListener)
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
ngOnDestroy() {
|
|
502
|
+
const element = this.webComponentRef?.nativeElement
|
|
503
|
+
if (element && this.userActionListener) {
|
|
504
|
+
element.removeEventListener('userAction', this.userActionListener)
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
### User Action Event
|
|
511
|
+
|
|
512
|
+
The `userAction` event provides detailed information about user interactions within the component. The event detail contains an object with `type` and `data` properties.
|
|
513
|
+
|
|
514
|
+
#### UserAction Types
|
|
515
|
+
|
|
516
|
+
| Type | Description | Data Structure |
|
|
517
|
+
| -------------------- | ------------------------------------------ | --------------------------- |
|
|
518
|
+
| `select-filter` | Emitted when a filter is selected | `{ filter: string }` |
|
|
519
|
+
| `show-place` | Emitted when a place is displayed | `{ placeId: string }` |
|
|
520
|
+
| `select-destination` | Emitted when a destination is selected | `{ destinationId: string }` |
|
|
521
|
+
| `search` | Emitted when a search is performed | `{ searchTerm: string }` |
|
|
522
|
+
| `return-to-home` | Emitted when user returns to the home view | `{ filter: string }` |
|
|
523
|
+
|
|
524
|
+
#### UserAction Usage Examples
|
|
525
|
+
|
|
526
|
+
#### Vanilla JavaScript
|
|
527
|
+
|
|
528
|
+
```javascript
|
|
529
|
+
// Wait for the web component to be registered
|
|
530
|
+
function setupUserActionListener() {
|
|
531
|
+
const element = document.querySelector('mapvx-website')
|
|
532
|
+
if (element) {
|
|
533
|
+
element.addEventListener('userAction', (event) => {
|
|
534
|
+
if (event instanceof CustomEvent) {
|
|
535
|
+
const action = event.detail
|
|
536
|
+
console.log('User action:', action.type, action.data)
|
|
537
|
+
|
|
538
|
+
// Handle different action types
|
|
539
|
+
switch (action.type) {
|
|
540
|
+
case 'select-filter':
|
|
541
|
+
console.log('Filter selected:', action.data.filter)
|
|
542
|
+
break
|
|
543
|
+
case 'show-place':
|
|
544
|
+
console.log('Place shown:', action.data.placeId)
|
|
545
|
+
break
|
|
546
|
+
case 'select-destination':
|
|
547
|
+
console.log('Destination selected:', action.data.destinationId)
|
|
548
|
+
break
|
|
549
|
+
case 'search':
|
|
550
|
+
console.log('Search performed:', action.data.searchTerm)
|
|
551
|
+
break
|
|
552
|
+
case 'return-to-home':
|
|
553
|
+
console.log('Returned to home with filter:', action.data.filter)
|
|
554
|
+
break
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
})
|
|
558
|
+
} else {
|
|
559
|
+
// Retry if element is not yet available
|
|
560
|
+
setTimeout(setupUserActionListener, 100)
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Check if web component is already registered
|
|
565
|
+
if (customElements.get('mapvx-website')) {
|
|
566
|
+
setupUserActionListener()
|
|
567
|
+
} else {
|
|
568
|
+
// Wait for registration
|
|
569
|
+
const checkInterval = setInterval(() => {
|
|
570
|
+
if (customElements.get('mapvx-website')) {
|
|
571
|
+
setupUserActionListener()
|
|
572
|
+
clearInterval(checkInterval)
|
|
573
|
+
}
|
|
574
|
+
}, 100)
|
|
575
|
+
}
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
#### React
|
|
579
|
+
|
|
580
|
+
```jsx
|
|
581
|
+
import { useEffect, useRef } from 'react'
|
|
582
|
+
|
|
583
|
+
function App() {
|
|
584
|
+
const webComponentRef = useRef(null)
|
|
585
|
+
|
|
586
|
+
useEffect(() => {
|
|
587
|
+
const element = webComponentRef.current
|
|
588
|
+
if (!element) return
|
|
589
|
+
|
|
590
|
+
const handleUserAction = (event) => {
|
|
591
|
+
if (event instanceof CustomEvent) {
|
|
592
|
+
const action = event.detail
|
|
593
|
+
console.log('User action:', action.type, action.data)
|
|
594
|
+
|
|
595
|
+
// Handle different action types
|
|
596
|
+
switch (action.type) {
|
|
597
|
+
case 'select-filter':
|
|
598
|
+
// Handle filter selection
|
|
599
|
+
break
|
|
600
|
+
case 'show-place':
|
|
601
|
+
// Handle place display
|
|
602
|
+
break
|
|
603
|
+
case 'search':
|
|
604
|
+
// Handle search
|
|
605
|
+
break
|
|
606
|
+
// ... other cases
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
element.addEventListener('userAction', handleUserAction)
|
|
382
612
|
|
|
383
613
|
return () => {
|
|
384
|
-
element.removeEventListener('
|
|
614
|
+
element.removeEventListener('userAction', handleUserAction)
|
|
385
615
|
}
|
|
386
616
|
}, [])
|
|
387
617
|
|
|
@@ -401,23 +631,36 @@ import { ref, onMounted, onUnmounted } from 'vue'
|
|
|
401
631
|
|
|
402
632
|
const webComponent = ref(null)
|
|
403
633
|
|
|
404
|
-
const
|
|
634
|
+
const handleUserAction = (event) => {
|
|
405
635
|
if (event instanceof CustomEvent) {
|
|
406
|
-
const
|
|
407
|
-
console.log('
|
|
408
|
-
|
|
636
|
+
const action = event.detail
|
|
637
|
+
console.log('User action:', action.type, action.data)
|
|
638
|
+
|
|
639
|
+
// Handle different action types
|
|
640
|
+
switch (action.type) {
|
|
641
|
+
case 'select-filter':
|
|
642
|
+
// Handle filter selection
|
|
643
|
+
break
|
|
644
|
+
case 'show-place':
|
|
645
|
+
// Handle place display
|
|
646
|
+
break
|
|
647
|
+
case 'search':
|
|
648
|
+
// Handle search
|
|
649
|
+
break
|
|
650
|
+
// ... other cases
|
|
651
|
+
}
|
|
409
652
|
}
|
|
410
653
|
}
|
|
411
654
|
|
|
412
655
|
onMounted(() => {
|
|
413
656
|
if (webComponent.value) {
|
|
414
|
-
webComponent.value.addEventListener('
|
|
657
|
+
webComponent.value.addEventListener('userAction', handleUserAction)
|
|
415
658
|
}
|
|
416
659
|
})
|
|
417
660
|
|
|
418
661
|
onUnmounted(() => {
|
|
419
662
|
if (webComponent.value) {
|
|
420
|
-
webComponent.value.removeEventListener('
|
|
663
|
+
webComponent.value.removeEventListener('userAction', handleUserAction)
|
|
421
664
|
}
|
|
422
665
|
})
|
|
423
666
|
</script>
|
|
@@ -426,7 +669,7 @@ onUnmounted(() => {
|
|
|
426
669
|
#### Angular
|
|
427
670
|
|
|
428
671
|
```typescript
|
|
429
|
-
import { Component, ElementRef,
|
|
672
|
+
import { Component, ElementRef, OnDestroy, ViewChild, AfterViewInit } from '@angular/core'
|
|
430
673
|
|
|
431
674
|
@Component({
|
|
432
675
|
selector: 'app-mapvx',
|
|
@@ -436,35 +679,48 @@ export class MapvxComponent implements AfterViewInit, OnDestroy {
|
|
|
436
679
|
@ViewChild('webComponent', { static: false }) webComponentRef!: ElementRef<HTMLElement>
|
|
437
680
|
|
|
438
681
|
apiKey = 'your-api-key-here'
|
|
439
|
-
private
|
|
682
|
+
private userActionListener?: (event: Event) => void
|
|
440
683
|
|
|
441
684
|
ngAfterViewInit() {
|
|
442
|
-
this.
|
|
685
|
+
this.setupUserActionListener()
|
|
443
686
|
}
|
|
444
687
|
|
|
445
|
-
private
|
|
688
|
+
private setupUserActionListener() {
|
|
446
689
|
const element = this.webComponentRef?.nativeElement
|
|
447
690
|
if (!element) {
|
|
448
691
|
// Retry if element is not yet available
|
|
449
|
-
setTimeout(() => this.
|
|
692
|
+
setTimeout(() => this.setupUserActionListener(), 100)
|
|
450
693
|
return
|
|
451
694
|
}
|
|
452
695
|
|
|
453
|
-
this.
|
|
696
|
+
this.userActionListener = (event: Event) => {
|
|
454
697
|
if (event instanceof CustomEvent) {
|
|
455
|
-
const
|
|
456
|
-
console.log('
|
|
457
|
-
|
|
698
|
+
const action = event.detail as { type: string; data: any }
|
|
699
|
+
console.log('User action:', action.type, action.data)
|
|
700
|
+
|
|
701
|
+
// Handle different action types
|
|
702
|
+
switch (action.type) {
|
|
703
|
+
case 'select-filter':
|
|
704
|
+
// Handle filter selection
|
|
705
|
+
break
|
|
706
|
+
case 'show-place':
|
|
707
|
+
// Handle place display
|
|
708
|
+
break
|
|
709
|
+
case 'search':
|
|
710
|
+
// Handle search
|
|
711
|
+
break
|
|
712
|
+
// ... other cases
|
|
713
|
+
}
|
|
458
714
|
}
|
|
459
715
|
}
|
|
460
716
|
|
|
461
|
-
element.addEventListener('
|
|
717
|
+
element.addEventListener('userAction', this.userActionListener)
|
|
462
718
|
}
|
|
463
719
|
|
|
464
720
|
ngOnDestroy() {
|
|
465
721
|
const element = this.webComponentRef?.nativeElement
|
|
466
|
-
if (element && this.
|
|
467
|
-
element.removeEventListener('
|
|
722
|
+
if (element && this.userActionListener) {
|
|
723
|
+
element.removeEventListener('userAction', this.userActionListener)
|
|
468
724
|
}
|
|
469
725
|
}
|
|
470
726
|
}
|