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