@mapvx/website-component 0.8.1 → 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 +457 -1
- package/dist/browser/main.js +278 -123
- package/dist/browser/styles.css +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -253,7 +253,7 @@ The web component accepts the following input properties to customize its behavi
|
|
|
253
253
|
#### Required Properties Only
|
|
254
254
|
|
|
255
255
|
```html
|
|
256
|
-
<mapvx-website api-key="your-api-key-here"></mapvx-website>
|
|
256
|
+
<mapvx-website api-key="your-api-key-here" institution-id="institution-123"></mapvx-website>
|
|
257
257
|
```
|
|
258
258
|
|
|
259
259
|
#### With Optional Configuration
|
|
@@ -270,6 +270,462 @@ The web component accepts the following input properties to customize its behavi
|
|
|
270
270
|
</mapvx-website>
|
|
271
271
|
```
|
|
272
272
|
|
|
273
|
+
## 📤 Output Events
|
|
274
|
+
|
|
275
|
+
The web component emits custom events that you can listen to for user interactions:
|
|
276
|
+
|
|
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. |
|
|
281
|
+
|
|
282
|
+
### Output Usage Examples
|
|
283
|
+
|
|
284
|
+
#### Vanilla JavaScript
|
|
285
|
+
|
|
286
|
+
```javascript
|
|
287
|
+
// Wait for the web component to be registered
|
|
288
|
+
function setupUserActionListener() {
|
|
289
|
+
const element = document.querySelector('mapvx-website')
|
|
290
|
+
if (element) {
|
|
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
|
|
296
|
+
}
|
|
297
|
+
})
|
|
298
|
+
} else {
|
|
299
|
+
// Retry if element is not yet available
|
|
300
|
+
setTimeout(setupUserActionListener, 100)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Check if web component is already registered
|
|
305
|
+
if (customElements.get('mapvx-website')) {
|
|
306
|
+
setupUserActionListener()
|
|
307
|
+
} else {
|
|
308
|
+
// Wait for registration
|
|
309
|
+
const checkInterval = setInterval(() => {
|
|
310
|
+
if (customElements.get('mapvx-website')) {
|
|
311
|
+
setupUserActionListener()
|
|
312
|
+
clearInterval(checkInterval)
|
|
313
|
+
}
|
|
314
|
+
}, 100)
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
#### Update Browser URL with `history.pushState`
|
|
319
|
+
|
|
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.
|
|
321
|
+
|
|
322
|
+
```html
|
|
323
|
+
<script>
|
|
324
|
+
// Wait for the web component to be registered
|
|
325
|
+
function setupUrlUpdater() {
|
|
326
|
+
const element = document.querySelector('mapvx-website')
|
|
327
|
+
if (!element) {
|
|
328
|
+
setTimeout(setupUrlUpdater, 100)
|
|
329
|
+
return
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const handleUserAction = (event) => {
|
|
333
|
+
if (event instanceof CustomEvent) {
|
|
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
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
element.addEventListener('userAction', handleUserAction)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Check if web component is already registered
|
|
381
|
+
if (customElements.get('mapvx-website')) {
|
|
382
|
+
setupUrlUpdater()
|
|
383
|
+
} else {
|
|
384
|
+
// Wait for registration
|
|
385
|
+
const waitForRegistration = setInterval(() => {
|
|
386
|
+
if (customElements.get('mapvx-website')) {
|
|
387
|
+
clearInterval(waitForRegistration)
|
|
388
|
+
setupUrlUpdater()
|
|
389
|
+
}
|
|
390
|
+
}, 100)
|
|
391
|
+
}
|
|
392
|
+
</script>
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
#### React
|
|
396
|
+
|
|
397
|
+
```jsx
|
|
398
|
+
import { useEffect, useRef } from 'react'
|
|
399
|
+
|
|
400
|
+
function App() {
|
|
401
|
+
const webComponentRef = useRef(null)
|
|
402
|
+
|
|
403
|
+
useEffect(() => {
|
|
404
|
+
const element = webComponentRef.current
|
|
405
|
+
if (!element) return
|
|
406
|
+
|
|
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) => {
|
|
488
|
+
if (event instanceof CustomEvent) {
|
|
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
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
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)
|
|
612
|
+
|
|
613
|
+
return () => {
|
|
614
|
+
element.removeEventListener('userAction', handleUserAction)
|
|
615
|
+
}
|
|
616
|
+
}, [])
|
|
617
|
+
|
|
618
|
+
return <mapvx-website ref={webComponentRef} api-key="your-api-key-here" />
|
|
619
|
+
}
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
#### Vue
|
|
623
|
+
|
|
624
|
+
```vue
|
|
625
|
+
<template>
|
|
626
|
+
<mapvx-website ref="webComponent" api-key="your-api-key-here" />
|
|
627
|
+
</template>
|
|
628
|
+
|
|
629
|
+
<script setup>
|
|
630
|
+
import { ref, onMounted, onUnmounted } from 'vue'
|
|
631
|
+
|
|
632
|
+
const webComponent = ref(null)
|
|
633
|
+
|
|
634
|
+
const handleUserAction = (event) => {
|
|
635
|
+
if (event instanceof CustomEvent) {
|
|
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
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
onMounted(() => {
|
|
656
|
+
if (webComponent.value) {
|
|
657
|
+
webComponent.value.addEventListener('userAction', handleUserAction)
|
|
658
|
+
}
|
|
659
|
+
})
|
|
660
|
+
|
|
661
|
+
onUnmounted(() => {
|
|
662
|
+
if (webComponent.value) {
|
|
663
|
+
webComponent.value.removeEventListener('userAction', handleUserAction)
|
|
664
|
+
}
|
|
665
|
+
})
|
|
666
|
+
</script>
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
#### Angular
|
|
670
|
+
|
|
671
|
+
```typescript
|
|
672
|
+
import { Component, ElementRef, OnDestroy, ViewChild, AfterViewInit } from '@angular/core'
|
|
673
|
+
|
|
674
|
+
@Component({
|
|
675
|
+
selector: 'app-mapvx',
|
|
676
|
+
template: ` <mapvx-website #webComponent [attr.api-key]="apiKey" /> `,
|
|
677
|
+
})
|
|
678
|
+
export class MapvxComponent implements AfterViewInit, OnDestroy {
|
|
679
|
+
@ViewChild('webComponent', { static: false }) webComponentRef!: ElementRef<HTMLElement>
|
|
680
|
+
|
|
681
|
+
apiKey = 'your-api-key-here'
|
|
682
|
+
private userActionListener?: (event: Event) => void
|
|
683
|
+
|
|
684
|
+
ngAfterViewInit() {
|
|
685
|
+
this.setupUserActionListener()
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
private setupUserActionListener() {
|
|
689
|
+
const element = this.webComponentRef?.nativeElement
|
|
690
|
+
if (!element) {
|
|
691
|
+
// Retry if element is not yet available
|
|
692
|
+
setTimeout(() => this.setupUserActionListener(), 100)
|
|
693
|
+
return
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
this.userActionListener = (event: Event) => {
|
|
697
|
+
if (event instanceof CustomEvent) {
|
|
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
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
element.addEventListener('userAction', this.userActionListener)
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
ngOnDestroy() {
|
|
721
|
+
const element = this.webComponentRef?.nativeElement
|
|
722
|
+
if (element && this.userActionListener) {
|
|
723
|
+
element.removeEventListener('userAction', this.userActionListener)
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
```
|
|
728
|
+
|
|
273
729
|
## 🔧 Server-Side Rendering (SSR) Support
|
|
274
730
|
|
|
275
731
|
For applications using Server-Side Rendering (SSR) where you need to preload initial data on the server before sending it to the browser:
|