@@ -2696,118 +2696,132 @@ class ComponentRegistry {
26962696 }
26972697}
26982698
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+ let buildQueryStringEntries = ( data , entries = { } , baseKey = '' ) => {
2704+ Object . entries ( data ) . forEach ( ( [ iKey , iValue ] ) => {
2705+ let 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+ }
27102712 }
27112713 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 ) ) ;
27172715 }
2716+ } ) ;
2717+ return entries ;
2718+ } ;
2719+ let 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+ let insertDotNotatedValueIntoData = ( key , value , data ) => {
2729+ let [ 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+ let entries = search . split ( '&' ) . map ( ( i ) => i . split ( '=' ) ) ;
2738+ let 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+ let dotNotatedKey = key . replace ( / \[ / g, '.' ) . replace ( / ] / g, '' ) ;
2748+ insertDotNotatedValueIntoData ( dotNotatedKey , value , data ) ;
27182749 }
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 ) ;
27192757 }
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 { } ;
27272774 }
2775+ return fromQueryString ( this . search ) ;
2776+ }
2777+ setData ( data ) {
2778+ this . search = toQueryString ( data ) ;
27282779 }
27292780}
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 ) ;
27462784 }
2747- return window . location . origin + window . location . pathname + queryString + window . location . hash ;
27482785}
27492786
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+ ;
2797+ }
27502798class QueryStringPlugin {
27512799 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- } ) ;
2800+ this . mapping = mapping ;
2801+ this . trackers = new Map ;
27582802 }
27592803 attachToComponent ( component ) {
27602804 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- }
2805+ const urlUtils = new UrlUtils ( window . location . href ) ;
2806+ Object . entries ( this . mapping ) . forEach ( ( [ prop , mapping ] ) => {
2807+ const tracker = new Tracker ( mapping , component . valueStore . get ( prop ) , urlUtils . has ( prop ) ) ;
2808+ this . trackers . set ( prop , tracker ) ;
2809+ } ) ;
27662810 } ) ;
27672811 component . on ( 'render:finished' , ( component ) => {
2768- this . initialPropsValues . forEach ( ( initialValue , prop ) => {
2769- var _a ;
2812+ let urlUtils = new UrlUtils ( window . location . href ) ;
2813+ this . trackers . forEach ( ( tracker , prop ) => {
27702814 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 ) ;
2815+ if ( ! tracker . initiallyPresentInUrl && tracker . hasReturnedToInitialValue ( value ) ) {
2816+ urlUtils . remove ( tracker . mapping . name ) ;
2817+ }
2818+ else {
2819+ urlUtils . set ( tracker . mapping . name , value ) ;
27742820 }
27752821 } ) ;
2822+ HistoryStrategy . replace ( urlUtils ) ;
27762823 } ) ;
27772824 }
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- }
28112825}
28122826
28132827const getComponent = ( element ) => LiveControllerDefault . componentRegistry . getComponent ( element ) ;
0 commit comments