@@ -29,6 +29,8 @@ const SCHEME_QUERY_KEYS = Object.freeze([
2929 'customForegroundColor' ,
3030] ) ;
3131export const SCHEME_STORAGE_KEY = '4bit:scheme-search' ;
32+ const LIGHTNESS_STEP = 25 / 64 ;
33+ const QUANTIZE_EPSILON = 1e-9 ;
3234
3335function browserWindow ( ) {
3436 return typeof window !== 'undefined' ? window : null ;
@@ -66,6 +68,14 @@ function searchParamsFor(search = '') {
6668 return new URLSearchParams ( normalizedSearch ? normalizedSearch . slice ( 1 ) : '' ) ;
6769}
6870
71+ function serializeSearchParams ( params ) {
72+ return Array . from ( params . entries ( ) )
73+ . map ( ( [ key , value ] ) => (
74+ `${ encodeURIComponent ( key ) } =${ encodeURIComponent ( value ) . replace ( / % 2 C / gi, ',' ) } `
75+ ) )
76+ . join ( '&' ) ;
77+ }
78+
6979function safelyReadPersistedSearch ( storage ) {
7080 try {
7181 return normalizeSearch ( storage ?. getItem ( SCHEME_STORAGE_KEY ) || '' ) ;
@@ -96,7 +106,7 @@ function mergeSchemeSearch(currentSearch = '', schemeSearch = '') {
96106 params . set ( key , value ) ;
97107 } ) ;
98108
99- const mergedSearch = params . toString ( ) ;
109+ const mergedSearch = serializeSearchParams ( params ) ;
100110 return mergedSearch ? `?${ mergedSearch } ` : '' ;
101111}
102112
@@ -172,9 +182,81 @@ function sameValue(first, second) {
172182 return first === second ;
173183}
174184
185+ function serializeRoundedNumber ( value , maxDecimals ) {
186+ const roundedValue = Number ( value . toFixed ( maxDecimals ) ) ;
187+
188+ if ( Object . is ( roundedValue , - 0 ) ) {
189+ return '0' ;
190+ }
191+
192+ return String ( roundedValue ) ;
193+ }
194+
195+ function serializeQuantizedNumber ( value , { step, maxDecimals } ) {
196+ const numericValue = Number ( value ) ;
197+
198+ if ( ! Number . isFinite ( numericValue ) ) {
199+ return String ( value ) ;
200+ }
201+
202+ const quantizedValue = Math . round ( numericValue / step ) * step ;
203+
204+ if ( Math . abs ( numericValue - quantizedValue ) <= QUANTIZE_EPSILON ) {
205+ return serializeRoundedNumber ( quantizedValue , maxDecimals ) ;
206+ }
207+
208+ return String ( numericValue ) ;
209+ }
210+
211+ function serializeIntegerLike ( value ) {
212+ return serializeQuantizedNumber ( value , { step : 1 , maxDecimals : 0 } ) ;
213+ }
214+
215+ function serializePickerNumber ( value ) {
216+ return serializeQuantizedNumber ( value , { step : 0.01 , maxDecimals : 2 } ) ;
217+ }
218+
219+ function serializeLightnessValue ( value ) {
220+ return serializeQuantizedNumber ( value , { step : LIGHTNESS_STEP , maxDecimals : 6 } ) ;
221+ }
222+
223+ function serializeSchemeQueryValue ( key , value ) {
224+ switch ( key ) {
225+ case 'hue' :
226+ case 'hueDistance' :
227+ case 'degrees' :
228+ case 'saturation' :
229+ case 'saturationRange' :
230+ case 'lightnessRange' :
231+ return Array . isArray ( value )
232+ ? value . map ( serializeIntegerLike ) . join ( ',' )
233+ : serializeIntegerLike ( value ) ;
234+ case 'chromaticLightness' :
235+ case 'blackLightness' :
236+ case 'whiteLightness' :
237+ return value . map ( serializeLightnessValue ) . join ( ',' ) ;
238+ case 'dyeColor' :
239+ return [
240+ serializeIntegerLike ( value [ 0 ] ) ,
241+ serializePickerNumber ( value [ 1 ] ) ,
242+ serializePickerNumber ( value [ 2 ] ) ,
243+ serializePickerNumber ( value [ 3 ] ) ,
244+ ] . join ( ',' ) ;
245+ case 'customBackgroundColor' :
246+ case 'customForegroundColor' :
247+ return [
248+ serializeIntegerLike ( value [ 0 ] ) ,
249+ serializePickerNumber ( value [ 1 ] ) ,
250+ serializePickerNumber ( value [ 2 ] ) ,
251+ ] . join ( ',' ) ;
252+ default :
253+ return Array . isArray ( value ) ? value . join ( ',' ) : String ( value ) ;
254+ }
255+ }
256+
175257function setParamIfChanged ( params , key , value , defaultValue ) {
176258 if ( ! sameValue ( value , defaultValue ) ) {
177- params . set ( key , Array . isArray ( value ) ? value . join ( ',' ) : String ( value ) ) ;
259+ params . set ( key , serializeSchemeQueryValue ( key , value ) ) ;
178260 }
179261}
180262
@@ -397,7 +479,7 @@ export function buildSchemeQueryParams(scheme) {
397479}
398480
399481export function buildSchemeSearch ( scheme ) {
400- const params = buildSchemeQueryParams ( scheme ) . toString ( ) ;
482+ const params = serializeSearchParams ( buildSchemeQueryParams ( scheme ) ) ;
401483 return params ? `?${ params } ` : '' ;
402484}
403485
@@ -460,7 +542,7 @@ export class SchemeUrlSync {
460542 currentParams . set ( key , value ) ;
461543 } ) ;
462544
463- const nextSearch = currentParams . toString ( ) ;
545+ const nextSearch = serializeSearchParams ( currentParams ) ;
464546 const nextUrl = `${ this . location . pathname } ${ nextSearch ? `?${ nextSearch } ` : '' } ${ this . location . hash } ` ;
465547 const currentUrl = `${ this . location . pathname } ${ this . location . search } ${ this . location . hash } ` ;
466548
0 commit comments