@@ -2696,118 +2696,131 @@ class ComponentRegistry {
2696
2696
}
2697
2697
}
2698
2698
2699
- class AdvancedURLSearchParams extends URLSearchParams {
2700
- set ( name , value ) {
2701
- if ( typeof value !== 'object' ) {
2702
- super . set ( name , value ) ;
2703
- }
2704
- else {
2705
- this . delete ( name ) ;
2706
- if ( Array . isArray ( value ) ) {
2707
- value . forEach ( ( v ) => {
2708
- this . append ( `${ name } []` , v ) ;
2709
- } ) ;
2699
+ function isObject ( subject ) {
2700
+ return typeof subject === 'object' && subject !== null ;
2701
+ }
2702
+ function toQueryString ( data ) {
2703
+ const buildQueryStringEntries = ( data , entries = { } , baseKey = '' ) => {
2704
+ Object . entries ( data ) . forEach ( ( [ iKey , iValue ] ) => {
2705
+ const key = baseKey === '' ? iKey : `${ baseKey } [${ iKey } ]` ;
2706
+ if ( ! isObject ( iValue ) ) {
2707
+ if ( iValue !== null ) {
2708
+ entries [ key ] = encodeURIComponent ( iValue )
2709
+ . replace ( / % 2 0 / g, '+' )
2710
+ . replace ( / % 2 C / g, ',' ) ;
2711
+ }
2710
2712
}
2711
2713
else {
2712
- Object . entries ( value ) . forEach ( ( [ index , v ] ) => {
2713
- if ( v !== null && v !== '' && v !== undefined ) {
2714
- this . append ( `${ name } [${ index } ]` , v ) ;
2715
- }
2716
- } ) ;
2714
+ entries = Object . assign ( Object . assign ( { } , entries ) , buildQueryStringEntries ( iValue , entries , key ) ) ;
2717
2715
}
2716
+ } ) ;
2717
+ return entries ;
2718
+ } ;
2719
+ const entries = buildQueryStringEntries ( data ) ;
2720
+ return Object . entries ( entries )
2721
+ . map ( ( [ key , value ] ) => `${ key } =${ value } ` )
2722
+ . join ( '&' ) ;
2723
+ }
2724
+ function fromQueryString ( search ) {
2725
+ search = search . replace ( '?' , '' ) ;
2726
+ if ( search === '' )
2727
+ return { } ;
2728
+ const insertDotNotatedValueIntoData = ( key , value , data ) => {
2729
+ const [ first , second , ...rest ] = key . split ( '.' ) ;
2730
+ if ( ! second )
2731
+ return ( data [ key ] = value ) ;
2732
+ if ( data [ first ] === undefined ) {
2733
+ data [ first ] = Number . isNaN ( second ) ? { } : [ ] ;
2734
+ }
2735
+ insertDotNotatedValueIntoData ( [ second , ...rest ] . join ( '.' ) , value , data [ first ] ) ;
2736
+ } ;
2737
+ const entries = search . split ( '&' ) . map ( ( i ) => i . split ( '=' ) ) ;
2738
+ const data = { } ;
2739
+ entries . forEach ( ( [ key , value ] ) => {
2740
+ if ( ! value )
2741
+ return ;
2742
+ value = decodeURIComponent ( value . replace ( / \+ / g, '%20' ) ) ;
2743
+ if ( ! key . includes ( '[' ) ) {
2744
+ data [ key ] = value ;
2745
+ }
2746
+ else {
2747
+ const dotNotatedKey = key . replace ( / \[ / g, '.' ) . replace ( / ] / g, '' ) ;
2748
+ insertDotNotatedValueIntoData ( dotNotatedKey , value , data ) ;
2718
2749
}
2750
+ } ) ;
2751
+ return data ;
2752
+ }
2753
+ class UrlUtils extends URL {
2754
+ has ( key ) {
2755
+ const data = this . getData ( ) ;
2756
+ return Object . keys ( data ) . includes ( key ) ;
2719
2757
}
2720
- delete ( name ) {
2721
- super . delete ( name ) ;
2722
- const pattern = new RegExp ( `^${ name } (\\[.*])?$` ) ;
2723
- for ( const key of Array . from ( this . keys ( ) ) ) {
2724
- if ( key . match ( pattern ) ) {
2725
- super . delete ( key ) ;
2726
- }
2758
+ set ( key , value ) {
2759
+ const data = this . getData ( ) ;
2760
+ data [ key ] = value ;
2761
+ this . setData ( data ) ;
2762
+ }
2763
+ get ( key ) {
2764
+ return this . getData ( ) [ key ] ;
2765
+ }
2766
+ remove ( key ) {
2767
+ const data = this . getData ( ) ;
2768
+ delete data [ key ] ;
2769
+ this . setData ( data ) ;
2770
+ }
2771
+ getData ( ) {
2772
+ if ( ! this . search ) {
2773
+ return { } ;
2727
2774
}
2775
+ return fromQueryString ( this . search ) ;
2776
+ }
2777
+ setData ( data ) {
2778
+ this . search = toQueryString ( data ) ;
2728
2779
}
2729
2780
}
2730
- function setQueryParam ( param , value ) {
2731
- const queryParams = new AdvancedURLSearchParams ( window . location . search ) ;
2732
- queryParams . set ( param , value ) ;
2733
- const url = urlFromQueryParams ( queryParams ) ;
2734
- history . replaceState ( history . state , '' , url ) ;
2735
- }
2736
- function removeQueryParam ( param ) {
2737
- const queryParams = new AdvancedURLSearchParams ( window . location . search ) ;
2738
- queryParams . delete ( param ) ;
2739
- const url = urlFromQueryParams ( queryParams ) ;
2740
- history . replaceState ( history . state , '' , url ) ;
2741
- }
2742
- function urlFromQueryParams ( queryParams ) {
2743
- let queryString = '' ;
2744
- if ( Array . from ( queryParams . entries ( ) ) . length > 0 ) {
2745
- queryString += '?' + queryParams . toString ( ) ;
2781
+ class HistoryStrategy {
2782
+ static replace ( url ) {
2783
+ history . replaceState ( history . state , '' , url ) ;
2746
2784
}
2747
- return window . location . origin + window . location . pathname + queryString + window . location . hash ;
2748
2785
}
2749
2786
2787
+ class Tracker {
2788
+ constructor ( mapping , initialValue , initiallyPresentInUrl ) {
2789
+ this . mapping = mapping ;
2790
+ this . initialValue = JSON . stringify ( initialValue ) ;
2791
+ this . initiallyPresentInUrl = initiallyPresentInUrl ;
2792
+ }
2793
+ hasReturnedToInitialValue ( currentValue ) {
2794
+ return JSON . stringify ( currentValue ) === this . initialValue ;
2795
+ }
2796
+ }
2750
2797
class QueryStringPlugin {
2751
2798
constructor ( mapping ) {
2752
- this . mapping = new Map ;
2753
- this . initialPropsValues = new Map ;
2754
- this . changedProps = { } ;
2755
- Object . entries ( mapping ) . forEach ( ( [ key , config ] ) => {
2756
- this . mapping . set ( key , config ) ;
2757
- } ) ;
2799
+ this . mapping = mapping ;
2800
+ this . trackers = new Map ;
2758
2801
}
2759
2802
attachToComponent ( component ) {
2760
2803
component . on ( 'connect' , ( component ) => {
2761
- for ( const model of this . mapping . keys ( ) ) {
2762
- for ( const prop of this . getNormalizedPropNames ( component . valueStore . get ( model ) , model ) ) {
2763
- this . initialPropsValues . set ( prop , component . valueStore . get ( prop ) ) ;
2764
- }
2765
- }
2804
+ const urlUtils = new UrlUtils ( window . location . href ) ;
2805
+ Object . entries ( this . mapping ) . forEach ( ( [ prop , mapping ] ) => {
2806
+ const tracker = new Tracker ( mapping , component . valueStore . get ( prop ) , urlUtils . has ( prop ) ) ;
2807
+ this . trackers . set ( prop , tracker ) ;
2808
+ } ) ;
2766
2809
} ) ;
2767
2810
component . on ( 'render:finished' , ( component ) => {
2768
- this . initialPropsValues . forEach ( ( initialValue , prop ) => {
2769
- var _a ;
2811
+ const urlUtils = new UrlUtils ( window . location . href ) ;
2812
+ this . trackers . forEach ( ( tracker , prop ) => {
2770
2813
const value = component . valueStore . get ( prop ) ;
2771
- ( _a = this . changedProps ) [ prop ] || ( _a [ prop ] = JSON . stringify ( value ) !== JSON . stringify ( initialValue ) ) ;
2772
- if ( this . changedProps ) {
2773
- this . updateUrlParam ( prop , value ) ;
2814
+ if ( ! tracker . initiallyPresentInUrl && tracker . hasReturnedToInitialValue ( value ) ) {
2815
+ urlUtils . remove ( tracker . mapping . name ) ;
2816
+ }
2817
+ else {
2818
+ urlUtils . set ( tracker . mapping . name , value ) ;
2774
2819
}
2775
2820
} ) ;
2821
+ HistoryStrategy . replace ( urlUtils ) ;
2776
2822
} ) ;
2777
2823
}
2778
- updateUrlParam ( model , value ) {
2779
- const paramName = this . getParamFromModel ( model ) ;
2780
- if ( paramName === undefined ) {
2781
- return ;
2782
- }
2783
- this . isValueEmpty ( value )
2784
- ? removeQueryParam ( paramName )
2785
- : setQueryParam ( paramName , value ) ;
2786
- }
2787
- getParamFromModel ( model ) {
2788
- const modelParts = model . split ( '.' ) ;
2789
- const rootPropMapping = this . mapping . get ( modelParts [ 0 ] ) ;
2790
- if ( rootPropMapping === undefined ) {
2791
- return undefined ;
2792
- }
2793
- return rootPropMapping . name + modelParts . slice ( 1 ) . map ( ( v ) => `[${ v } ]` ) . join ( '' ) ;
2794
- }
2795
- * getNormalizedPropNames ( value , propertyPath ) {
2796
- if ( this . isObjectValue ( value ) ) {
2797
- for ( const key in value ) {
2798
- yield * this . getNormalizedPropNames ( value [ key ] , `${ propertyPath } .${ key } ` ) ;
2799
- }
2800
- }
2801
- else {
2802
- yield propertyPath ;
2803
- }
2804
- }
2805
- isValueEmpty ( value ) {
2806
- return ( value === '' || value === null || value === undefined ) ;
2807
- }
2808
- isObjectValue ( value ) {
2809
- return ! ( Array . isArray ( value ) || value === null || typeof value !== 'object' ) ;
2810
- }
2811
2824
}
2812
2825
2813
2826
const getComponent = ( element ) => LiveControllerDefault . componentRegistry . getComponent ( element ) ;
0 commit comments