@ornery/web-components 1.1.5 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ornery/web-components",
3
- "version": "1.1.5",
3
+ "version": "2.0.0",
4
4
  "description": "WebComponents html loader and optional runtime mixins to enable creation of custom HTML elements using es6 template literal syntax in *.html files.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -1,34 +1,33 @@
1
1
  const EventMap = require('./event-map');
2
2
 
3
- module.exports = (() => {
4
- /**
3
+ /**
5
4
  * @class DataStore
6
5
  * @description Configuration values can be set and propagated to consuming
7
6
  * components via this static class or through
8
7
  * the corresponding wc-config element
9
8
  */
10
- const DataStore = new class {
11
- constructor() {
12
- this._state = new EventMap();
13
- }
9
+ class DataManager {
10
+ constructor() {
11
+ this._state = new EventMap();
12
+ }
14
13
 
15
- /**
14
+ /**
16
15
  * @memberOf DataStore
17
16
  * @param {String} key
18
17
  * @return {Object} the current value of the requested property name.
19
18
  */
20
- get(key) {
21
- return key ? this._state.get(key) : this._state.getAll();
22
- }
19
+ get(key) {
20
+ return key ? this._state.get(key) : this._state.getAll();
21
+ }
23
22
 
24
- /**
23
+ /**
25
24
  * @memberOf DataStore
26
25
  * @return {Object} the current state object.
27
26
  */
28
- getState() {
29
- return this._state.getAll();
30
- }
31
- /**
27
+ getState() {
28
+ return this._state.getAll();
29
+ }
30
+ /**
32
31
  * @memberOf DataStore
33
32
  * @param {String|Object} key the name of the value to set.
34
33
  * It can also be called with an {} query to set multiple values at once.
@@ -36,59 +35,59 @@ module.exports = (() => {
36
35
  * @return {{state}|*}
37
36
  * @description wraps this.set
38
37
  */
39
- set(key, value) {
40
- let query = key;
41
- if (value) {
42
- // we have a single value
43
- query = {[key]: value};
44
- }
45
- return this._state.replace({...this._state.getAll(), ...query});
38
+ set(key, value) {
39
+ let query = key;
40
+ if (value) {
41
+ // we have a single value
42
+ query = {[key]: value};
46
43
  }
44
+ return this._state.replace({...this._state.getAll(), ...query});
45
+ }
47
46
 
48
- /**
47
+ /**
49
48
  * @memberOf DataStore
50
49
  * @param {Object} newState the new state object.
51
50
  * @return {{state}|*}
52
51
  * @description wraps this.set
53
52
  */
54
- setState(newState) {
55
- return this.set(newState);
56
- }
57
- /**
53
+ setState(newState) {
54
+ return this.set(newState);
55
+ }
56
+ /**
58
57
  * @memberOf DataStore
59
58
  * @param {Function} callback is the function to execute when any property changes.
60
59
  * @return {{destroy}|*}
61
60
  * @description call destroy() on the returned object to remove the event listener.
62
61
  */
63
- subscribe(callback) {
64
- return callback && this._state.on('set', callback);
65
- }
62
+ subscribe(callback) {
63
+ return callback && this._state.on('set', callback);
64
+ }
66
65
 
67
- /**
66
+ /**
68
67
  * @memberOf DataStore
69
68
  * @param {Array} keys the property names to be notified when they mutate
70
69
  * @param {Function} callback the callback to be executed when any of the value for any of those keys have changed.
71
70
  * @return {{destroy}|*}
72
71
  * @description call destroy() on the returned object to remove the event listener.
73
72
  */
74
- subscribeTo(keys, callback) {
75
- keys = typeof (keys) === 'string' ? [keys] : keys;
76
- return this.subscribe((event, newState, oldState) => {
77
- let updates;
78
- keys.forEach((property) => {
79
- if (newState[property] !== oldState[property]) {
80
- updates = {
81
- ...updates,
82
- [property]: {
83
- oldValue: oldState[property],
84
- newValue: newState[property],
85
- },
86
- };
87
- }
88
- });
89
- updates && callback(updates, newState, oldState);
73
+ subscribeTo(keys, callback) {
74
+ keys = typeof (keys) === 'string' ? [keys] : keys;
75
+ return this.subscribe((event, newState, oldState) => {
76
+ let updates;
77
+ keys.forEach((property) => {
78
+ if (newState[property] !== oldState[property]) {
79
+ updates = {
80
+ ...updates,
81
+ [property]: {
82
+ oldValue: oldState[property],
83
+ newValue: newState[property],
84
+ },
85
+ };
86
+ }
90
87
  });
91
- }
92
- };
93
- return DataStore;
94
- })();
88
+ updates && callback(updates, newState, oldState);
89
+ });
90
+ }
91
+ }
92
+
93
+ module.exports = DataManager;
package/src/i18n.js CHANGED
@@ -1,16 +1,16 @@
1
- const DataManager = require("./data-manager");
2
- const { template, getFromObj, shouldEncode, encodeHTML } = require("./utils");
3
- const withContext = require("./context-binding");
1
+ const DataManager = require('./data-manager');
2
+ const {template, getFromObj, toLowerMap} = require('./utils');
4
3
 
5
- if (typeof HTMLElement === "undefined") {
6
- HTMLElement = class {};
4
+ if (typeof HTMLElement === 'undefined') {
5
+ // eslint-disable-next-line no-global-assign
6
+ HTMLElement = class {};
7
7
  }
8
8
 
9
9
  /**
10
10
  *
11
11
  * @class I18n
12
12
  * @description Import strings here and call I18n.addStrings() with the supported locale identifier
13
- * and the strings object exported from the language file
13
+ * and the strings object exported from the locale file
14
14
  * By default, it will set the values on window.i18n, if defined when loaded, as the starting messages.
15
15
  * This is useful if you wish to server-side render HTML certain content before laoding scripts on the client.
16
16
  * @example
@@ -36,296 +36,216 @@ if (typeof HTMLElement === "undefined") {
36
36
  * console.log(I18n.getMessages()) // will log the window.i18n object
37
37
  *
38
38
  */
39
- const I18n = new (class {
40
- constructor() {
41
- // language without region code;
42
- this._altLangRegex = /[_-]/i;
43
- this.setDefaultLang();
44
- this.setLang();
45
- if (typeof window !== "undefined") {
46
- if (typeof navigator !== "undefined") {
47
- this.setLang(navigator.language);
48
- }
49
- this.setMessages(window.i18n || {});
50
- } else {
51
- if (typeof process !== "undefined") {
52
- const { env } = process;
53
- this.setLang(
54
- env.LANG ||
55
- env.LANGUAGE ||
56
- env.LC_ALL ||
57
- env.LC_MESSAGES ||
58
- this._defaultLang
59
- );
60
- }
61
- }
62
- }
63
-
64
- setDefaultLang(lang = "en") {
65
- this._defaultLang = lang.toLowerCase();
66
- this._fallbackMessages = this.getMessages(this._defaultLang);
67
- }
68
- /**
69
- * @memberof I18n
70
- * @return {String} lang
71
- * @description returns the current language. defaults to the browser's navigator.language value.
72
- * @example
73
- *
74
- * navigator.language = 'en-US';
75
- * import { I18n } from '@ornery/web-components';
76
- *
77
- * console.log(I18n.getLang()) // "en-US"
78
- */
79
- getLang() {
80
- return DataManager.get("i18n-language") || '';
81
- }
82
- getRootLang() {
83
- return this.getLang().split(this._altLangRegex)[0];
84
- }
85
- /**
86
- * @memberof I18n
87
- * @param {String} lang
88
- * @description sets the current i18n language. This does not change the browser language.
89
- * @example
90
- *
91
- * import { I18n } from '@ornery/web-components';
92
- *
93
- * I18n.setLang('en-US')
94
- * console.log(I18n.getLang()) //'en-US'
95
- */
96
- setLang(lang= "") {
97
- return DataManager.set("i18n-language", lang)
98
- }
99
- /**
100
- * @memberof I18n
101
- * @return {String} lang
102
- * @description returns the current i18n messages set in the DataManager
103
- */
104
- getMessages(lang) {
105
- const allMessages = DataManager.get("i18n-messages") || {};
106
- if (lang === "all") {
107
- return allMessages;
108
- } else {
109
- lang = lang || this.getLang();
110
- const rootLang = lang
111
- ? lang.split(this._altLangRegex)[0]
112
- : this.getRootLang();
113
- return {
114
- ...allMessages[rootLang],
115
- ...allMessages[lang],
116
- };
117
- }
118
- }
119
- /**
120
- * @memberof I18n
121
- * @param {Object} values
122
- * @description sets the strings as a whole. This overrides all existing strings.
123
- * Use addMessages to add more strings to the existing set.
124
- * @example
125
- *
126
- * import { I18n } from '@ornery/web-components';
127
- *
128
- * I18n.setMessages({
129
- * 'en-US': {
130
- * 'translatable.message.name': "I'm a translated string from i18n",
131
- * 'tokenized.message': "I have a ${this.color} ${this.animal}"
132
- * }
133
- * })
134
- */
135
- setMessages(values) {
136
- return DataManager.set("i18n-messages", values);
137
- }
138
- /**
139
- * @memberof I18n
140
- * @param {String} lang
141
- * @param {Object} strings
142
- * @description add more strings to the existing language set.
143
- *
144
- * @example
145
- *
146
- * import { I18n } from '@ornery/web-components';
147
- * I18n.addMessages('en-US', {
148
- * 'translatable.message.name': "I'm a translated string from i18n",
149
- * 'tokenized.message': "I have a ${color} ${animal}"
150
- * });
151
- */
152
- addMessages(lang, newStrings) {
153
- if (typeof lang !== "string") {
154
- newStrings = lang;
155
- lang = this.getLang();
156
- }
157
- lang = lang.toLowerCase();
158
- const rootLang = lang.split(this._altLangRegex)[0];
159
- const existing = this.getMessages("all");
160
- existing[rootLang] = {
161
- ...(existing[rootLang] || {}),
162
- ...newStrings,
163
- };
164
- existing[lang] = {
165
- ...(existing[lang] || {}),
166
- ...newStrings,
167
- };
168
- this.setMessages(existing);
169
- if (lang === this._defaultLang) {
170
- this._fallbackMessages = this.getMessages(lang);
171
- } else if (rootLang === this._defaultLang) {
172
- this._fallbackMessages = this.getMessages(rootLang);
173
- }
174
- }
175
-
176
- /**
177
- * @memberof I18n
178
- * @param {String} key they key of the string to retrieve from the current language set.
179
- * @param {Object} data Optional, The data to process tokens in the string with.
180
- * @return {String} Returns the value for the key. Processed if a data context is provided as the second argument.
181
- * @description Returns the value for the key. If a context is provided as the second argument for tokens,
182
- * the tokens will be replaced in the returned string.
183
- * @example
184
- *
185
- * import { I18n } from '@ornery/web-components';
186
- * I18n.addMessages('en-US', {
187
- * 'tokenized.message': "I have a ${color} ${animal}"
188
- * });
189
- *
190
- * const stringTestData = {
191
- * color: "grey",
192
- * animal: "monkey"
193
- * };
194
- * console.log(I18n.get('tokenized.message', stringTestData)) // "I have a grey monkey"
195
- */
196
- get(key, data = {}) {
197
- const context = {
198
- ...this._fallbackMessages,
199
- ...this.getMessages(),
200
- ...data,
201
- };
202
- return template(getFromObj(key, context), context);
203
- }
204
-
205
- /**
206
- * @memberof I18n
207
- * @param {String} namespace
208
- * @return {Object} Returns all the messages for the given language. Filtered to namespace if provided.
209
- * @description If a namespace is provided, returns all the key value pairs for that
210
- * namespace without the namespace in the keys.
211
- *
212
- * @example
213
- *
214
- * import { I18n } from '@ornery/web-components';
215
- * I18n.addMessages('en-US', {
216
- * 'translatable.message.name': "I'm a translated string from i18n",
217
- * 'tokenized.message': "I have a ${color} ${animal}"
218
- * });
219
- *
220
- * console.log(I18n.getAll('tokenized', {color: "red", animal: "panda"})) // {"message": "I have a red panda"}
221
- */
222
- getAll(namespace, data) {
223
- if (namespace) {
224
- return Object.keys(this.getMessages()).reduce((reducer, key) => {
225
- if (key.startsWith(namespace)) {
226
- reducer[key.replace(namespace + ".", "")] = this.get(key, data);
227
- }
228
- return reducer;
229
- }, {});
230
- } else {
231
- return this.getMessages("all");
232
- }
233
- }
234
- })();
39
+ class I18n {
40
+ constructor(options) {
41
+ const {
42
+ store = new DataManager(),
43
+ messages = null,
44
+ locale = null,
45
+ fallbackLocale = {
46
+ 'default': 'en',
47
+ 'de-ch': ['fr', 'it'],
48
+ 'zh-hant': ['zh-hans'],
49
+ 'es-cl': ['es-ar'],
50
+ 'es': ['en'],
51
+ 'pt': ['es-ar'],
52
+ },
53
+ } = options;
54
+ this.store = store;
55
+ this.setFallbackLocale(fallbackLocale);
56
+ if (locale) this.setLocale(locale);
57
+ if (messages) this.setMessages(messages);
58
+ }
235
59
 
236
- /**
237
- * @class I18nMessage
238
- * @description <i18n-message> HTML element. Provides tranlsation and interpolation for
239
- * translatable strings
240
- * @param {String} key the key for the strings based on current language. can be set as the innerHTML or
241
- * defined as the attibutes: key, id, data-key, data-id
242
- * @param {JSON} values can be passed as data-* attributes or as a json-parseable object string as "data-values"
243
- * @param {String} dataAttributes
244
- * @example <caption>Given the following configuration</caption>
245
- * import { I18n } from '@ornery/web-components';
246
- * I18n.addMessages('en-US', {
247
- * 'translatable.message.name': "I'm a translated string from i18n",
248
- * 'tokenized.message': "I have a ${color} ${animal}"
249
- * });
250
- * @example @lang html <caption>With the following usage</caption>
251
- * <i18n-message>translatable.message.name</i18n-message>
252
- * <div>
253
- * <i18n-message data-values="{'color: 'grey', 'animal': 'monkey'}">tokenized.message</i18n-message>
254
- * <i18n-message data-color="grey" data-animal="monkey">tokenized.message</i18n-message>
255
- * <i18n-message key="tokenized.message"/>
256
- * <!-- React does not pass key or ref props so you can use "data-key" or "data-id" as well-->
257
- * <i18n-message data-key="tokenized.message"/>
258
- * <i18n-message id="translatable.message.name"/>
259
- * <i18n-message data-id="translatable.message.name"/>
260
- * </div>
261
- *
262
- * @example @lang html <caption>Renders the HTML</caption>
263
- * <i18n-message>I'm a translated string from i18n</i18n-message>
264
- * <i18n-message>I have a grey monkey</i18n-message>
265
- */
266
- class I18nMessage extends HTMLElement {
267
- constructor() {
268
- super();
269
- }
270
-
271
- static get observedAttributes() {
272
- return ["key", "id", "data-values"];
273
- }
60
+ setFallbackLocale(fallbackLocales) {
61
+ this._fallbackLocale = toLowerMap(fallbackLocales);
62
+ }
63
+ /**
64
+ * @memberof I18n
65
+ * @return {String} locale
66
+ * @description returns the current locale. defaults to the browser's navigator.locale value.
67
+ * @example
68
+ *
69
+ * navigator.locale = 'en-US';
70
+ * import { I18n } from '@ornery/web-components';
71
+ *
72
+ * console.log(I18n.getLocale()) // "en-US"
73
+ */
74
+ getLocale() {
75
+ return this.store.get('i18n-locale') || '';
76
+ }
274
77
 
275
- get useShadow() {
276
- if (this.hasAttribute("shadow")) {
277
- let current = this.getAttribute("shadow");
278
- if (current === "false") {
279
- return false;
280
- }
78
+ getFallbackLocale() {
79
+ const locale = this.getLocale();
80
+ const defaultLocale = this._fallbackLocale.default;
81
+ const fallbackMap = this._fallbackLocale[locale];
82
+ let fallbackLocale;
83
+ if (fallbackMap) {
84
+ const allMessages = this.store.get('i18n-messages') || {};
85
+ for (let i = 0; i < fallbackMap.length; i++) {
86
+ const fbl = fallbackMap[i];
87
+ if (allMessages[fbl]) {
88
+ fallbackLocale = fbl;
89
+ break;
281
90
  }
282
- return true;
283
- }
91
+ }
92
+ }
93
+ return fallbackLocale || defaultLocale || 'en';
94
+ }
95
+ /**
96
+ * @memberof I18n
97
+ * @param {String} locale
98
+ * @return {String} locale
99
+ * @description sets the current i18n locale. This does not change the browser locale.
100
+ * @example
101
+ *
102
+ * import { I18n } from '@ornery/web-components';
103
+ *
104
+ * I18n.setLocale('en-US')
105
+ * console.log(I18n.getLocale()) //'en-US'
106
+ */
107
+ setLocale(locale = '') {
108
+ return this.store.set('i18n-locale', locale);
109
+ }
110
+ /**
111
+ * @memberof I18n
112
+ * @param {String} locale
113
+ * @return {String} locale
114
+ * @description returns the current i18n messages set in the DataManager
115
+ */
116
+ getMessages(locale = null) {
117
+ const allMessages = this.store.get('i18n-messages') || {};
118
+ if (locale === 'all') {
119
+ return allMessages;
120
+ } else {
121
+ locale = locale || this.getLocale();
122
+ const fallbackLocale = this.getFallbackLocale();
123
+ return {
124
+ ...allMessages[fallbackLocale] || {},
125
+ ...allMessages[locale] || {},
126
+ };
127
+ }
128
+ }
129
+ /**
130
+ * @memberof I18n
131
+ * @param {Object} values
132
+ * @return {{state}|*}
133
+ * @description sets the strings as a whole. This overrides all existing strings.
134
+ * Use addMessages to add more strings to the existing set.
135
+ * @example
136
+ *
137
+ * import { I18n } from '@ornery/web-components';
138
+ *
139
+ * I18n.setMessages({
140
+ * 'en-US': {
141
+ * 'translatable.message.name': "I'm a translated string from i18n",
142
+ * 'tokenized.message': "I have a ${this.color} ${this.animal}"
143
+ * }
144
+ * })
145
+ */
146
+ setMessages(values) {
147
+ const response = this.store.set('i18n-messages', values);
148
+ return response;
149
+ }
150
+ /**
151
+ * @memberof I18n
152
+ * @param {{String}|{Object}} locale
153
+ * @param {Object} newStrings
154
+ * @description add more strings to the existing locale set.
155
+ *
156
+ * @example
157
+ *
158
+ * import { I18n } from '@ornery/web-components';
159
+ * I18n.addMessages('en-US', {
160
+ * 'translatable.message.name': "I'm a translated string from i18n",
161
+ * 'tokenized.message': "I have a ${color} ${animal}"
162
+ * });
163
+ */
164
+ addMessages(locale, newStrings) {
165
+ if (typeof locale !== 'string') {
166
+ newStrings = locale;
167
+ locale = this.getLocale();
168
+ }
169
+ locale = locale.toLowerCase();
170
+ const fallbackLocale = this.getFallbackLocale();
171
+ const existing = this.getMessages('all');
172
+ existing[locale] = {
173
+ ...(existing[locale] || {}),
174
+ ...newStrings,
175
+ };
176
+ if (fallbackLocale !== locale) {
177
+ existing[fallbackLocale] = {
178
+ ...(existing[fallbackLocale] || {}),
179
+ ...newStrings,
180
+ };
181
+ }
182
+ this.setMessages(existing);
183
+ }
284
184
 
285
- get translate() {
286
- return this.getAttribute("key") || this.getAttribute("id");
287
- }
185
+ /**
186
+ * @memberof I18n
187
+ * @param {String} key they key of the string to retrieve from the current locale set.
188
+ * @param {Object} data Optional, The data to process tokens in the string with.
189
+ * @return {String} Returns the value for the key. Processed if a data context is provided as the second argument.
190
+ * @description Returns the value for the key. If a context is provided as the second argument for tokens,
191
+ * the tokens will be replaced in the returned string.
192
+ * @example
193
+ *
194
+ * import { I18n } from '@ornery/web-components';
195
+ * I18n.addMessages('en-US', {
196
+ * 'tokenized.message': "I have a ${color} ${animal}"
197
+ * });
198
+ *
199
+ * const stringTestData = {
200
+ * color: "grey",
201
+ * animal: "monkey"
202
+ * };
203
+ * console.log(I18n.get('tokenized.message', stringTestData)) // "I have a grey monkey"
204
+ */
205
+ get(key, data = {}) {
206
+ const context = {
207
+ ...this.getMessages(),
208
+ ...data,
209
+ };
210
+ return template(getFromObj(key, context), context);
211
+ }
288
212
 
289
- attributeChangedCallback(name) {
290
- this.update();
291
- }
292
-
293
- update() {
294
- const root = this.shadowRoot || this;
295
- const context = { ...this.getAttribute("data-values"), ...this.dataset };
296
- let newMesage = I18n.get(this.translate, context);
297
- if (shouldEncode(newMesage)) {
298
- newMesage = encodeHTML(newMesage);
213
+ /**
214
+ * @memberof I18n
215
+ * @param {String} namespace
216
+ * @param {String} context
217
+ * @return {Object} Returns all the messages for the given locale. Filtered to namespace if provided.
218
+ * @description If a namespace is provided, returns all the key value pairs for that
219
+ * namespace without the namespace in the keys.
220
+ *
221
+ * @example
222
+ *
223
+ * import { I18n } from '@ornery/web-components';
224
+ * I18n.addMessages('en-US', {
225
+ * 'translatable.message.name': "I'm a translated string from i18n",
226
+ * 'tokenized.message': "I have a ${color} ${animal}"
227
+ * });
228
+ *
229
+ * console.log(I18n.getAll('tokenized', {color: "red", animal: "panda"})) // {"message": "I have a red panda"}
230
+ */
231
+ getAll(namespace, context) {
232
+ if (namespace) {
233
+ return Object.keys(this.getMessages()).reduce((reducer, key) => {
234
+ if (key.startsWith(namespace)) {
235
+ reducer[key.replace(namespace + '.', '')] = this.get(key, context);
299
236
  }
300
- if (!root.innerHTML) {
301
- root.innerHTML = newMesage;
302
- } else if (newMesage !== this.translate || newMesage !== root.innerHTML) {
303
- root.innerHTML = newMesage;
304
- }
305
- }
306
-
307
- connectedCallback() {
308
- if (this.useShadow && !this.shadowRoot) this.attachShadow({ mode: "open" });
309
- this._i18nListener = DataManager.subscribe((newVals) => {
310
- this.update();
311
- });
312
- const attrObserver = new MutationObserver(() => this.update());
313
- attrObserver.observe(this, { attributes: true, childList: this.useShadow });
237
+ return reducer;
238
+ }, {});
239
+ } else {
240
+ return this.getMessages('all');
314
241
  }
242
+ }
315
243
 
316
- disconnectedCallback() {
317
- if (this._i18nListener) {
318
- this._i18nListener.destroy();
319
- this._i18nListener = null;
320
- }
321
- }
322
- }
323
-
324
- if (typeof window !== "undefined" && typeof customElements !== "undefined" && !customElements.get("i18n-message")) {
325
- customElements.define("i18n-message", withContext(I18nMessage));
244
+ subscribe(callback) {
245
+ return this.store.subscribe(callback);
246
+ }
326
247
  }
327
248
 
328
249
  module.exports = {
329
- I18n,
330
- I18nMessage,
250
+ I18n,
331
251
  };
package/src/utils.js CHANGED
@@ -14,15 +14,15 @@
14
14
  *
15
15
  * result == 'bar';
16
16
  */
17
- const keyRegexp = /^[\w\-]+(\.[\w\-]+)+$/g
17
+ const keyRegexp = /^[\w-]+(\.[\w-]+)+$/g;
18
18
  const getFromObj = (path, obj = {}) => {
19
- path = path.trim();
20
- if (path != null){
19
+ path = path && path.trim();
20
+ if (path != null) {
21
21
  if (obj[path] != null) {
22
22
  return obj[path];
23
23
  } else if (keyRegexp.test(path)) {
24
24
  return path.split('.').reduce((res, key) => res[key] != null ? res[key] : path, obj);
25
- }
25
+ }
26
26
  }
27
27
  return path;
28
28
  };
@@ -44,9 +44,9 @@ const stripES6 = function(expr, context) {
44
44
  let result = expr.replace(thisRegex, '');
45
45
  let matchArr;
46
46
  while (matchArr = nestedES6.exec(result)) {
47
- let [wholeMatch, outerMatch, key] = matchArr;
48
- const replacement = getFromObj(key, context);
49
- result = stripES6(result.replace(outerMatch, replacement).trim(), context);
47
+ const [, outerMatch, key] = matchArr;
48
+ const replacement = getFromObj(key, context);
49
+ result = stripES6(result.replace(outerMatch, replacement).trim(), context);
50
50
  }
51
51
  return result.replace(es6Regex, (match, $1)=> getFromObj($1, context));
52
52
  };
@@ -104,12 +104,12 @@ const template = stripES6;
104
104
  const arrayParser = (val, key, params) => {
105
105
  let current = params[key];
106
106
  if (current) {
107
- if (!Array.isArray(current)) {
108
- current = [current];
109
- }
110
- current.push(val);
107
+ if (!Array.isArray(current)) {
108
+ current = [current];
109
+ }
110
+ current.push(val);
111
111
  } else {
112
- current = val;
112
+ current = val;
113
113
  }
114
114
  return current;
115
115
  };
@@ -144,12 +144,12 @@ const toParams = (str, options = {}) => {
144
144
  const queryString = parts[1] || '';
145
145
  const params = {};
146
146
  queryString.split('&').forEach((val) => {
147
- const innerParts = val.split('=');
148
- if (innerParts.length !== 2) return;
149
- const paramKey = decodeURIComponent(innerParts[0]);
150
- const paramVal = decodeURIComponent(innerParts[1]);
151
- const parser = options[paramKey] || (() => paramVal);
152
- params[paramKey] = arrayParser(parser(paramVal, paramKey, params), paramKey, params);
147
+ const innerParts = val.split('=');
148
+ if (innerParts.length !== 2) return;
149
+ const paramKey = decodeURIComponent(innerParts[0]);
150
+ const paramVal = decodeURIComponent(innerParts[1]);
151
+ const parser = options[paramKey] || (() => paramVal);
152
+ params[paramKey] = arrayParser(parser(paramVal, paramKey, params), paramKey, params);
153
153
  });
154
154
  return params;
155
155
  };
@@ -176,11 +176,11 @@ const toParams = (str, options = {}) => {
176
176
  const toSearch = (options) => {
177
177
  const filtered = Object.entries(options).filter((ent) => !!ent[1]);
178
178
  return encodeURI(`?${filtered.map((ent) => {
179
- if (Array.isArray(ent[1])) {
180
- return ent[1].map((val) => [ent[0], val].join('=')).join('&');
181
- } else {
182
- return ent.join('=');
183
- }
179
+ if (Array.isArray(ent[1])) {
180
+ return ent[1].map((val) => [ent[0], val].join('=')).join('&');
181
+ } else {
182
+ return ent.join('=');
183
+ }
184
184
  }).join('&')}`);
185
185
  };
186
186
 
@@ -210,13 +210,13 @@ const toSearch = (options) => {
210
210
  const prefixKeys = (obj, prefix) => {
211
211
  let keys = [];
212
212
  if (Array.isArray(obj)) {
213
- keys = obj.map((val, i) => i);
213
+ keys = obj.map((val, i) => i);
214
214
  } else {
215
- keys = Object.keys(obj);
215
+ keys = Object.keys(obj);
216
216
  }
217
217
  return Object.assign(
218
218
  {},
219
- ...keys.map((key) => ({[prefix + key]: obj[key]}))
219
+ ...keys.map((key) => ({[prefix + key]: obj[key]})),
220
220
  );
221
221
  };
222
222
 
@@ -257,16 +257,25 @@ const toDataAttrs = (obj) => {
257
257
  return prefixKeys(obj, 'data-');
258
258
  };
259
259
  const HTMLEncodable = /[\u00A0-\u9999<>]/g;
260
- const encodeHTML = (stringVal = "") => stringVal.replace(HTMLEncodable, i => `&#${i.charCodeAt(0)};`)
260
+ const encodeHTML = (stringVal = '') => stringVal.replace(HTMLEncodable, (i) => `&#${i.charCodeAt(0)};`);
261
261
 
262
262
  const withClosing = /<([^>]+?)([^>]*?)>(.*?)<\/\1>/gi;
263
263
  const selfClosing = /(<([^>]+)\/>)/ig;
264
- const shouldEncode = (str) => {
265
- return (str || '')
266
- .replace(withClosing, '')
267
- .replace(selfClosing, '')
268
- .trim();
269
- }
264
+ const shouldEncode = (str) => (str || '').replace(withClosing, '').replace(selfClosing, '').trim();
265
+
266
+ const toLowerMap = (obj = {}) => {
267
+ if (Array.isArray(obj)) {
268
+ return obj.map(toLowerMap);
269
+ }
270
+ if (typeof obj === 'string') {
271
+ return obj.toLowerCase();
272
+ }
273
+ return Object.entries(obj).reduce((acc, [key, val]) => {
274
+ const lck = key.toLowerCase();
275
+ acc[lck] = toLowerMap(val);
276
+ return acc;
277
+ }, {});
278
+ };
270
279
 
271
280
  module.exports = {
272
281
  getFromObj,
@@ -278,5 +287,6 @@ module.exports = {
278
287
  prefixKeys,
279
288
  toDataAttrs,
280
289
  shouldEncode,
281
- encodeHTML
290
+ encodeHTML,
291
+ toLowerMap,
282
292
  };