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