state-in-url 0.2.4 → 0.2.5

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/README.md CHANGED
@@ -25,8 +25,7 @@
25
25
 
26
26
  <h4 align="center">Don't hesitate to open issue if you found a bug</h4>
27
27
 
28
-
29
- https://github.com/asmyshlyaev177/state-in-url/assets/19854148/10456887-f149-4745-b3f1-e799e70b16cd
28
+ ![Demo-gif](https://github.com/asmyshlyaev177/state-in-url/assets/19854148/c9802601-4d42-4362-b3e4-37ff87c3b97f)
30
29
 
31
30
 
32
31
  <a href="https://state-in-url-asmyshlyaev177.vercel.app/" target="_blank">DEMO</a>
@@ -56,6 +55,7 @@ Add a ⭐️ to support the project!
56
55
  - 📘 **Typescript support and type Safety**: Preserves data types and structure, enhances developer experience with IDE suggestions, strong typing and JSDoc comments
57
56
  - ⚛️ **Framework Flexibility**: Separate hooks for Next.js and React.js applications, and functions for pure JS
58
57
  - ⚙ **Well tested**: Unit tests and Playwright tests
58
+ - ⚡ **Fast**: Minimal rerenders
59
59
  - 🪶 **Lightweight**: Zero dependencies for a smaller footprint
60
60
 
61
61
 
@@ -96,12 +96,21 @@ set `"moduleResolution": "Node16"` or `"moduleResolution": "NodeNext"` in your `
96
96
 
97
97
  #### Basic
98
98
 
99
+ 1. Define state shape
100
+ ```typescript
101
+ // countState.ts
102
+ // State shape should be stored in a constant, don't pass an object directly
103
+ export const countState: CountState = { count: 0 }
104
+
105
+ type CountState = { count: number }
106
+ ```
107
+
108
+ 2. Import it and use
99
109
  ```typescript
100
110
  'use client'
101
111
  import { useUrlState } from 'state-in-url';
102
112
 
103
- // State shape should be stored in a constant, don't pass an object directly
104
- const countState: { count: number } = { count: 0 };
113
+ import { countState } from './countState';
105
114
 
106
115
  function MyComponent() {
107
116
  // for use searchParams from server component
@@ -140,24 +149,28 @@ function MyComponent() {
140
149
  #### With complex state shape
141
150
 
142
151
  ```typescript
143
- 'use client'
144
- import { useUrlState } from 'state-in-url';
145
-
146
152
  interface UserSettings {
147
153
  theme: 'light' | 'dark';
148
154
  fontSize: number;
149
155
  notifications: boolean;
150
156
  }
151
157
 
152
- const defaultSettings: UserSettings {
158
+ export const userSettings: UserSettings {
153
159
  theme: 'light',
154
160
  fontSize: 16,
155
161
  notifications: true,
156
162
  }
163
+ ```
164
+
165
+ ```typescript
166
+ 'use client'
167
+ import { useUrlState } from 'state-in-url';
168
+
169
+ import { userSettings } from './userSettings';
157
170
 
158
171
  function SettingsComponent() {
159
172
  // `state` will infer from UserSettings type!
160
- const { state, updateUrl } = useUrlState(defaultSettings);
173
+ const { state, updateUrl } = useUrlState(userSettings);
161
174
 
162
175
  const toggleTheme = () => {
163
176
  updateUrl(current => ({
@@ -243,11 +256,14 @@ Accepts optional `defaultState` argument.
243
256
  ```typescript
244
257
  import { useUrlEncode } from 'state-in-url';
245
258
 
259
+ const ageState = { age: 0 };
260
+
246
261
  const Component = () => {
247
262
  const { parse, stringify } = useUrlEncode();
248
263
 
249
264
  const str = stringify({ age: 36 }); // age=∓36
250
- const obj = parse(str); // { age: 36 }
265
+ const obj = parse(str, ageState); // { age: 36 }
266
+ // Or const obj = parse(str); // { age: 36 }
251
267
 
252
268
  const currentParams = parse(window.location.search);
253
269
  // OR
@@ -328,7 +344,6 @@ Go to [localhost:3000](http://localhost:3000)
328
344
  ## Contact & Support
329
345
 
330
346
  - Create a [GitHub issue](https://github.com/asmyshlyaev177/state-in-url/issues) for bug reports, feature requests, or questions
331
- - Add a ⭐️ [star on GitHub](https://github.com/asmyshlyaev177/state-in-url) to support the project!
332
347
 
333
348
  ## [Changelog](https://github.com/asmyshlyaev177/state-in-url/blob/main/CHANGELOG.md)
334
349
 
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react"),t=require("next/navigation");function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var r=n(e);const o=e=>{const t=null===e,n=Array.isArray(e),r=e instanceof Date;return(t?"null":r&&"date")||n&&"array"||!t&&!r&&!n&&"object"==typeof e&&"object"||typeof e},c=()=>"undefined"==typeof window,s=e=>new URLSearchParams("string"==typeof e?e:e?.toString?.()||""),u=(e,t)=>JSON.stringify(e)===JSON.stringify(t),a={string:"◖",boolean:"🗵",null:"∙null",undefined:"∙undefined",number:"∓",date:"⏲"};function i(e){switch(o(e)){case"function":case"symbol":return"";case"date":return a.date+e.toISOString();case"string":return e.match(/^◖/)?encodeURIComponent(e):`${a.string}${encodeURIComponent(e)}`;case"number":return a.number+String(e);case"boolean":return a.boolean+String(e);case"object":case"array":return JSON.stringify(e,f).replaceAll('"',"'");case"null":return a.null;case"undefined":return a.undefined;default:return String(e)}}function l(e,t){return function(e,t){try{return JSON.parse(e,g)}catch{const n=d(e);return n!==p?n:t}}(e.replaceAll("'",'"'),t)}const f=(e,t)=>{const n=o(t);return e&&"object"!==n&&"array"!==n?i(t):t},d=e=>e===a.null?null:e!==a.undefined?e?.startsWith?.(a.number)?Number.parseFloat(e.replace(a.number,"")):e?.startsWith?.(a.boolean)?!!e.includes("true"):e?.startsWith?.(a.date)?new Date(e.slice(1)):e?.startsWith?.(a.string)?decodeURIComponent(e).replace(/^◖/,""):p:void 0,p=Symbol("isError"),g=(e,t)=>{const n="string"==typeof t,r=n&&d(t);return r===p?t:e&&n?r:t};const y=(e,t)=>{const n="string"==typeof t,r=n&&l(t?.replaceAll?.("'",'"'));return r===p?t:e&&n?r:t};function b(e,t){try{return JSON.parse(e,y)}catch{const n=d(e);return n!==p?n:t}}function S(e,t,n){const r=s(n);return Object.entries(e||{}).forEach((([e,n])=>{const o=t?.[e];JSON.stringify(n)!==JSON.stringify(o)&&r.set(e,i(n))})),r.toString()}function w(e,t){return{...t||{},...Object.fromEntries([...s(e).entries()].map((([e,n])=>{const r=t?.[e];return[e,l(n,r)??r]})))}}function h(e){const t=r.default.useCallback((function(t,n){return"object"===o(t)?S(t,e,n):""}),[e]);return{parse:r.default.useCallback((function(t){return w(t,e)}),[e]),stringify:t}}const m=new WeakMap,O=new WeakMap;exports.decode=l,exports.decodeState=w,exports.encode=i,exports.encodeState=S,exports.useUrlEncode=h,exports.useUrlState=function(e,n){const{state:o,getState:s,setState:a,stringify:i,parse:l}=function(e){const t=r.default.useRef(e),{parse:n,stringify:o}=h(t.current),[s,a]=r.default.useState((()=>{const e=m.get(t.current),r=n(c()?"":window.location.search);return e||m.set(t.current,r),r}));return r.default.useInsertionEffect((()=>{const n=O.get(t.current)||[],r=()=>{a(m.get(t.current)||e)};return O.set(t.current,n.concat(r)),()=>{const e=O.get(t.current)||[];O.set(t.current,e.filter((e=>e!==r)))}}),[]),{state:s,getState:r.default.useCallback((()=>m.get(t.current)||t.current),[]),setState:r.default.useCallback((e=>{const n=m.get(t.current)||t.current;if("function"==typeof e){const r=e(n);if(u(n,r))return!1;m.set(t.current,r),(O.get(t.current)||[]).forEach((e=>{e()}))}else{if(u(n,e))return!1;m.set(t.current,e),(O.get(t.current)||[]).forEach((e=>{e()}))}}),[]),parse:n,stringify:o}}(c()?function(e,t){return{...t,...b(JSON.stringify(e),t)}}(n,e):e);r.default.useInsertionEffect((()=>{const e=()=>{if(c())return;const e=l(window.location.search);a(e)},t="popstate";return window.addEventListener(t,e),()=>{window.removeEventListener(t,e)}}),[]);const f=t.useRouter();return{updateUrl:r.default.useCallback(((e,t)=>{a(e??s());const n=window.location.search,r=`${window.location.pathname}${n.length&&!n.includes("?")?"?":""}${n}`,o=i("function"==typeof e?e(s()):e??s()),c=`${window.location.pathname}${o.length?"?":""}${o}`;if(r!==c){const{replace:e,...n}=t||{};f[e?"replace":"push"](c,{scroll:!1,...n})}}),[f,i,s]),updateState:a,state:o}};
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react"),t=require("next/navigation");function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var r=n(e);const o=e=>{const t=null===e,n=Array.isArray(e),r=e instanceof Date;return(t?"null":r&&"date")||n&&"array"||!t&&!r&&!n&&"object"==typeof e&&"object"||typeof e},c=()=>"undefined"==typeof window,s=e=>new URLSearchParams("string"==typeof e?e:e?.toString?.()||""),u=(e,t)=>JSON.stringify(e)===JSON.stringify(t),a={string:"◖",boolean:"🗵",null:"∙null",undefined:"∙undefined",number:"∓",date:"⏲"};function i(e){switch(o(e)){case"function":case"symbol":return"";case"date":return a.date+e.toISOString();case"string":return e.match(/^◖/)?encodeURIComponent(e):`${a.string}${encodeURIComponent(e)}`;case"number":return a.number+String(e);case"boolean":return a.boolean+String(e);case"object":case"array":return JSON.stringify(e,f).replaceAll('"',"'");case"null":return a.null;case"undefined":return a.undefined;default:return String(e)}}function l(e,t){return function(e,t){try{return JSON.parse(e,g)}catch{const n=d(e);return n!==p?n:t}}(e.replaceAll("'",'"'),t)}const f=(e,t)=>{const n=o(t);return e&&"object"!==n&&"array"!==n?i(t):t},d=e=>e===a.null?null:e!==a.undefined?e?.startsWith?.(a.number)?Number.parseFloat(e.replace(a.number,"")):e?.startsWith?.(a.boolean)?!!e.includes("true"):e?.startsWith?.(a.date)?new Date(e.slice(1)):e?.startsWith?.(a.string)?decodeURIComponent(e).replace(/^◖/,""):p:void 0,p=Symbol("isError"),g=(e,t)=>{const n="string"==typeof t,r=n&&d(t);return r===p?t:e&&n?r:t};const y=(e,t)=>{const n="string"==typeof t,r=n&&l(t?.replaceAll?.("'",'"'));return r===p?t:e&&n?r:t};function b(e,t){try{return JSON.parse(e,y)}catch{const n=d(e);return n!==p?n:t}}function S(e,t,n){const r=s(n);return Object.entries(e||{}).forEach((([e,n])=>{const o=t?.[e];JSON.stringify(n)!==JSON.stringify(o)&&r.set(e,i(n))})),r.toString()}function w(e,t){return{...t||{},...Object.fromEntries([...s(e).entries()].map((([e,n])=>{const r=t?.[e];return[e,l(n,r)??r]})))}}function h(e){const t=r.default.useCallback((function(t,n){return"object"===o(t)?S(t,e,n):""}),[e]);return{parse:r.default.useCallback((function(t){return w(t,e)}),[e]),stringify:t}}const m=new WeakMap,O=new WeakMap;exports.decode=l,exports.decodeState=w,exports.encode=i,exports.encodeState=S,exports.useUrlEncode=h,exports.useUrlState=function(e,n){const{state:o,getState:s,setState:a,stringify:i}=function(e){const t=r.default.useRef(e),{parse:n,stringify:o}=h(t.current),[s,a]=r.default.useState((()=>{const e=m.get(t.current),r=n(c()?"":window.location.search);return e||m.set(t.current,r),r})),i=r.default.useCallback((e=>{const n=m.get(t.current)||t.current;if("function"==typeof e){const r=e(n);if(u(n,r))return!1;m.set(t.current,r),(O.get(t.current)||[]).forEach((e=>{e()}))}else{if(u(n,e))return!1;m.set(t.current,e),(O.get(t.current)||[]).forEach((e=>{e()}))}}),[]);return r.default.useInsertionEffect((()=>{const r=O.get(t.current)||[],o=()=>{a(m.get(t.current)||e)};O.set(t.current,r.concat(o));const c=()=>{const e=n(window.location.search);i(e)},s="popstate";return window.addEventListener(s,c),()=>{const e=O.get(t.current)||[];O.set(t.current,e.filter((e=>e!==o))),window.removeEventListener(s,c)}}),[i]),{state:s,getState:r.default.useCallback((()=>m.get(t.current)||t.current),[]),setState:i,stringify:o}}(c()?function(e,t){return{...t,...b(JSON.stringify(e),t)}}(n,e):e),l=t.useRouter();return{updateUrl:r.default.useCallback(((e,t)=>{a(e??s());const n=window.location.search,r=`${window.location.pathname}${n.length&&!n.includes("?")?"?":""}${n}`,o=i("function"==typeof e?e(s()):e??s()),c=`${window.location.pathname}${o.length?"?":""}${o}`;if(r!==c){const{replace:e,...n}=t||{};l[e?"replace":"push"](c,{scroll:!1,...n})}}),[l,i,s]),updateState:a,state:o}};
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import t from"react";import{useRouter as e}from"next/navigation";const n=t=>{const e=null===t,n=Array.isArray(t),r=t instanceof Date;return(e?"null":r&&"date")||n&&"array"||!e&&!r&&!n&&"object"==typeof t&&"object"||typeof t},r=()=>"undefined"==typeof window,c=t=>new URLSearchParams("string"==typeof t?t:t?.toString?.()||""),o=(t,e)=>JSON.stringify(t)===JSON.stringify(e),s={string:"◖",boolean:"🗵",null:"∙null",undefined:"∙undefined",number:"∓",date:"⏲"};function a(t){switch(n(t)){case"function":case"symbol":return"";case"date":return s.date+t.toISOString();case"string":return t.match(/^◖/)?encodeURIComponent(t):`${s.string}${encodeURIComponent(t)}`;case"number":return s.number+String(t);case"boolean":return s.boolean+String(t);case"object":case"array":return JSON.stringify(t,i).replaceAll('"',"'");case"null":return s.null;case"undefined":return s.undefined;default:return String(t)}}function u(t,e){return function(t,e){try{return JSON.parse(t,p)}catch{const n=l(t);return n!==f?n:e}}(t.replaceAll("'",'"'),e)}const i=(t,e)=>{const r=n(e);return t&&"object"!==r&&"array"!==r?a(e):e},l=t=>t===s.null?null:t!==s.undefined?t?.startsWith?.(s.number)?Number.parseFloat(t.replace(s.number,"")):t?.startsWith?.(s.boolean)?!!t.includes("true"):t?.startsWith?.(s.date)?new Date(t.slice(1)):t?.startsWith?.(s.string)?decodeURIComponent(t).replace(/^◖/,""):f:void 0,f=Symbol("isError"),p=(t,e)=>{const n="string"==typeof e,r=n&&l(e);return r===f?e:t&&n?r:e};const d=(t,e)=>{const n="string"==typeof e,r=n&&u(e?.replaceAll?.("'",'"'));return r===f?e:t&&n?r:e};function g(t,e){try{return JSON.parse(t,d)}catch{const n=l(t);return n!==f?n:e}}function y(t,e,n){const r=c(n);return Object.entries(t||{}).forEach((([t,n])=>{const c=e?.[t];JSON.stringify(n)!==JSON.stringify(c)&&r.set(t,a(n))})),r.toString()}function b(t,e){return{...e||{},...Object.fromEntries([...c(t).entries()].map((([t,n])=>{const r=e?.[t];return[t,u(n,r)??r]})))}}function S(e){const r=t.useCallback((function(t,r){return"object"===n(t)?y(t,e,r):""}),[e]);return{parse:t.useCallback((function(t){return b(t,e)}),[e]),stringify:r}}const m=new WeakMap,w=new WeakMap;function h(n,c){const{state:s,getState:a,setState:u,stringify:i,parse:l}=function(e){const n=t.useRef(e),{parse:c,stringify:s}=S(n.current),[a,u]=t.useState((()=>{const t=m.get(n.current),e=c(r()?"":window.location.search);return t||m.set(n.current,e),e}));return t.useInsertionEffect((()=>{const t=w.get(n.current)||[],r=()=>{u(m.get(n.current)||e)};return w.set(n.current,t.concat(r)),()=>{const t=w.get(n.current)||[];w.set(n.current,t.filter((t=>t!==r)))}}),[]),{state:a,getState:t.useCallback((()=>m.get(n.current)||n.current),[]),setState:t.useCallback((t=>{const e=m.get(n.current)||n.current;if("function"==typeof t){const r=t(e);if(o(e,r))return!1;m.set(n.current,r),(w.get(n.current)||[]).forEach((t=>{t()}))}else{if(o(e,t))return!1;m.set(n.current,t),(w.get(n.current)||[]).forEach((t=>{t()}))}}),[]),parse:c,stringify:s}}(r()?function(t,e){return{...e,...g(JSON.stringify(t),e)}}(c,n):n);t.useInsertionEffect((()=>{const t=()=>{if(r())return;const t=l(window.location.search);u(t)},e="popstate";return window.addEventListener(e,t),()=>{window.removeEventListener(e,t)}}),[]);const f=e();return{updateUrl:t.useCallback(((t,e)=>{u(t??a());const n=window.location.search,r=`${window.location.pathname}${n.length&&!n.includes("?")?"?":""}${n}`,c=i("function"==typeof t?t(a()):t??a()),o=`${window.location.pathname}${c.length?"?":""}${c}`;if(r!==o){const{replace:t,...n}=e||{};f[t?"replace":"push"](o,{scroll:!1,...n})}}),[f,i,a]),updateState:u,state:s}}export{u as decode,b as decodeState,a as encode,y as encodeState,S as useUrlEncode,h as useUrlState};
1
+ import t from"react";import{useRouter as e}from"next/navigation";const n=t=>{const e=null===t,n=Array.isArray(t),r=t instanceof Date;return(e?"null":r&&"date")||n&&"array"||!e&&!r&&!n&&"object"==typeof t&&"object"||typeof t},r=()=>"undefined"==typeof window,c=t=>new URLSearchParams("string"==typeof t?t:t?.toString?.()||""),o=(t,e)=>JSON.stringify(t)===JSON.stringify(e),s={string:"◖",boolean:"🗵",null:"∙null",undefined:"∙undefined",number:"∓",date:"⏲"};function a(t){switch(n(t)){case"function":case"symbol":return"";case"date":return s.date+t.toISOString();case"string":return t.match(/^◖/)?encodeURIComponent(t):`${s.string}${encodeURIComponent(t)}`;case"number":return s.number+String(t);case"boolean":return s.boolean+String(t);case"object":case"array":return JSON.stringify(t,i).replaceAll('"',"'");case"null":return s.null;case"undefined":return s.undefined;default:return String(t)}}function u(t,e){return function(t,e){try{return JSON.parse(t,d)}catch{const n=l(t);return n!==f?n:e}}(t.replaceAll("'",'"'),e)}const i=(t,e)=>{const r=n(e);return t&&"object"!==r&&"array"!==r?a(e):e},l=t=>t===s.null?null:t!==s.undefined?t?.startsWith?.(s.number)?Number.parseFloat(t.replace(s.number,"")):t?.startsWith?.(s.boolean)?!!t.includes("true"):t?.startsWith?.(s.date)?new Date(t.slice(1)):t?.startsWith?.(s.string)?decodeURIComponent(t).replace(/^◖/,""):f:void 0,f=Symbol("isError"),d=(t,e)=>{const n="string"==typeof e,r=n&&l(e);return r===f?e:t&&n?r:e};const g=(t,e)=>{const n="string"==typeof e,r=n&&u(e?.replaceAll?.("'",'"'));return r===f?e:t&&n?r:e};function p(t,e){try{return JSON.parse(t,g)}catch{const n=l(t);return n!==f?n:e}}function y(t,e,n){const r=c(n);return Object.entries(t||{}).forEach((([t,n])=>{const c=e?.[t];JSON.stringify(n)!==JSON.stringify(c)&&r.set(t,a(n))})),r.toString()}function b(t,e){return{...e||{},...Object.fromEntries([...c(t).entries()].map((([t,n])=>{const r=e?.[t];return[t,u(n,r)??r]})))}}function S(e){const r=t.useCallback((function(t,r){return"object"===n(t)?y(t,e,r):""}),[e]);return{parse:t.useCallback((function(t){return b(t,e)}),[e]),stringify:r}}const m=new WeakMap,w=new WeakMap;function h(n,c){const{state:s,getState:a,setState:u,stringify:i}=function(e){const n=t.useRef(e),{parse:c,stringify:s}=S(n.current),[a,u]=t.useState((()=>{const t=m.get(n.current),e=c(r()?"":window.location.search);return t||m.set(n.current,e),e})),i=t.useCallback((t=>{const e=m.get(n.current)||n.current;if("function"==typeof t){const r=t(e);if(o(e,r))return!1;m.set(n.current,r),(w.get(n.current)||[]).forEach((t=>{t()}))}else{if(o(e,t))return!1;m.set(n.current,t),(w.get(n.current)||[]).forEach((t=>{t()}))}}),[]);return t.useInsertionEffect((()=>{const t=w.get(n.current)||[],r=()=>{u(m.get(n.current)||e)};w.set(n.current,t.concat(r));const o=()=>{const t=c(window.location.search);i(t)},s="popstate";return window.addEventListener(s,o),()=>{const t=w.get(n.current)||[];w.set(n.current,t.filter((t=>t!==r))),window.removeEventListener(s,o)}}),[i]),{state:a,getState:t.useCallback((()=>m.get(n.current)||n.current),[]),setState:i,stringify:s}}(r()?function(t,e){return{...e,...p(JSON.stringify(t),e)}}(c,n):n),l=e();return{updateUrl:t.useCallback(((t,e)=>{u(t??a());const n=window.location.search,r=`${window.location.pathname}${n.length&&!n.includes("?")?"?":""}${n}`,c=i("function"==typeof t?t(a()):t??a()),o=`${window.location.pathname}${c.length?"?":""}${c}`;if(r!==o){const{replace:t,...n}=e||{};l[t?"replace":"push"](o,{scroll:!1,...n})}}),[l,i,a]),updateState:u,state:s}}export{u as decode,b as decodeState,a as encode,y as encodeState,S as useUrlEncode,h as useUrlState};
@@ -3,6 +3,5 @@ export declare function useState<T>(defaultState: JSONCompatible<T>): {
3
3
  state: JSONCompatible<T>;
4
4
  getState: () => JSONCompatible<T>;
5
5
  setState: (value: JSONCompatible<T> | DeepReadonly<JSONCompatible<T>> | ((currState: typeof defaultState | DeepReadonly<typeof defaultState>) => typeof defaultState | DeepReadonly<typeof defaultState>)) => false | undefined;
6
- parse: (strOrSearchParams: string | URLSearchParams) => DeepReadonly<JSONCompatible<T>>;
7
6
  stringify: (state: JSONCompatible<T> | DeepReadonly<JSONCompatible<T>>, paramsToKeep?: string | URLSearchParams | undefined) => string;
8
7
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "state-in-url",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Library to store state in browser URL, includes hook for NextJS, hook for pure React, and low level helpers.",
5
5
  "homepage": "https://github.com/asmyshlyaev177/state-in-url#readme",
6
6
  "keywords": [