diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 9d08a1a8..00000000 --- a/.editorconfig +++ /dev/null @@ -1,9 +0,0 @@ -root = true - -[*] -charset = utf-8 -indent_style = space -indent_size = 2 -end_of_line = lf -insert_final_newline = true -trim_trailing_whitespace = true diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index a6bde126..00000000 --- a/.eslintignore +++ /dev/null @@ -1,7 +0,0 @@ -dist -website/build -website/node_modules -website/src/code -node_modules -package-lock.json -yarn.lock \ No newline at end of file diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 9cf9d009..00000000 --- a/.eslintrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": ["react-app", "plugin:prettier/recommended"] -} diff --git a/.flowconfig b/.flowconfig deleted file mode 100644 index a4fc4842..00000000 --- a/.flowconfig +++ /dev/null @@ -1,12 +0,0 @@ -[ignore] - -[include] - -[libs] - -[lints] - -[options] -include_warnings=true - -[strict] diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml new file mode 100644 index 00000000..866cba81 --- /dev/null +++ b/.github/workflows/eslint.yml @@ -0,0 +1,15 @@ +name: "ESLint" +on: [pull_request] +jobs: + tests-e2e: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + - uses: pnpm/action-setup@v2 + with: + version: 10 + - name: Install dependencies + run: pnpm install --frozen-lockfile --recursive + - name: Run ESLint + run: pnpm lint diff --git a/.github/workflows/pending-changes.yml b/.github/workflows/pending-changes.yml new file mode 100644 index 00000000..7dfa2b89 --- /dev/null +++ b/.github/workflows/pending-changes.yml @@ -0,0 +1,16 @@ +name: "Pending changes" +on: [pull_request] +jobs: + tests-e2e: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + - uses: pnpm/action-setup@v2 + with: + version: 10 + - name: Install dependencies + run: pnpm install --frozen-lockfile --recursive + - uses: nickcharlton/diff-check@main + with: + command: pnpm run compile diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml new file mode 100644 index 00000000..47edf391 --- /dev/null +++ b/.github/workflows/prettier.yml @@ -0,0 +1,15 @@ +name: "Prettier" +on: [pull_request] +jobs: + tests-e2e: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + - uses: pnpm/action-setup@v2 + with: + version: 10 + - name: Install dependencies + run: pnpm install --frozen-lockfile --recursive + - name: Run Prettier + run: pnpm run prettier:ci diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml deleted file mode 100644 index d2919c64..00000000 --- a/.github/workflows/stale.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Close stale issues and PRs -on: - schedule: - - cron: '0 8 * * *' - -jobs: - stale: - runs-on: ubuntu-latest - steps: - - uses: actions/stale@v9.0.0 - with: - days-before-stale: 21 - days-before-close: 0 - exempt-issue-labels: do-not-close - exempt-pr-labels: do-not-close \ No newline at end of file diff --git a/.github/workflows/typescript.yml b/.github/workflows/typescript.yml new file mode 100644 index 00000000..af340c6a --- /dev/null +++ b/.github/workflows/typescript.yml @@ -0,0 +1,17 @@ +name: "TypeScript" +on: [pull_request] +jobs: + tests-e2e: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + - uses: pnpm/action-setup@v2 + with: + version: 10 + - name: Install dependencies + run: pnpm install --frozen-lockfile --recursive + - name: Build NPM package + run: pnpm build + - name: Run TypeScript + run: pnpm tsc diff --git a/.github/workflows/vitest.yml b/.github/workflows/vitest.yml new file mode 100644 index 00000000..2637cf60 --- /dev/null +++ b/.github/workflows/vitest.yml @@ -0,0 +1,17 @@ +name: "Vitest" +on: [pull_request] +jobs: + tests-e2e: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + - uses: pnpm/action-setup@v2 + with: + version: 10 + - name: Install dependencies + run: pnpm install --frozen-lockfile --recursive + - name: Build NPM packages + run: pnpm run build + - name: Run tests + run: pnpm run test:ci diff --git a/.gitignore b/.gitignore index 03059815..5b246b28 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* -# See https://help.github.com/ignore-files/ for more about ignoring files. - -# dependencies node_modules +/dist +/docs +*.local -# builds -build -dist - -# misc +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea .DS_Store -.env -.env.local -.env.development.local -.env.test.local -.env.production.local -.watchmanconfig - -package-lock.json -npm-debug.log* -yarn-debug.log* -yarn-error.log* +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 00000000..5ee7abd8 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +pnpm exec lint-staged diff --git a/.prettierignore b/.prettierignore index 7d3c5c74..2d3dd132 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,5 +1,3 @@ -dist/* -website/build/* -website/src/code/* -package-lock.json -yarn.lock \ No newline at end of file +dist +docs +generated \ No newline at end of file diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index c1a6f667..00000000 --- a/.prettierrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "singleQuote": true, - "trailingComma": "es5" -} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 119625f0..00000000 --- a/.travis.yml +++ /dev/null @@ -1,9 +0,0 @@ -language: node_js -node_js: - - lts/* -before_script: - - yarn -script: - - yarn lint - - yarn flow - - yarn test diff --git a/CHANGELOG.md b/CHANGELOG.md index c6ec921b..deef3029 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,136 +1,252 @@ -Changelog ------------- +# Changelog -### 1.8.11 -* Dependencies updated to include React 19 +## 2.0.0 -### 1.8.10 -* Fix scrollDirection when direction is RTL (#690) +Version 2 is a major rewrite that offers the following benefits: -### 1.8.9 -* Readme changes +- More ergonomic props API +- Automatic memoization of row/cell renderers and props/context +- Automatically sizing for `List` and `Grid` (no more need for `AutoSizer`) +- Native TypeScript support (no more need for `@types/react-window`) +- Smaller bundle size -### 1.8.8 -* 🐛 `scrollToItem` accounts for scrollbar size in the uncommon case where a List component has scrolling in the non-dominant direction (e.g. a "vertical" layout list also scrolls horizontally). +## Upgrade path -### 1.8.7 -* ✨ Updated peer dependencies to include React v18. +See the [documentation](https://react-window.vercel.app/) for more details, but here is an example of what the v1 to v2 upgrade might look like: -### 1.8.6 -* ✨ Updated peer dependencies to include React v17. +### Before -### 1.8.5 -* ✨ Added UMD (dev and prod) build - ([emmanueltouzery](https://github.com/emmanueltouzery) - [#281](https://github.com/bvaughn/react-window/pull/281)) +```tsx +import { FixedSizeList, type ListChildComponentProps } from "react-window"; -### 1.8.4 -* 🐛 Fixed size list and grid components now accurately report `visibleStopIndex` in `onItemsRendered`. (Previously this value was incorrectly reported as one index higher.) - ([justingrant](https://github.com/justingrant) - [#274](https://github.com/bvaughn/react-window/pull/274)) -* 🐛 Fixed size list and grid components `scrollToItem` "center" mode when the item being scrolled to is near the viewport edge. - ([justingrant](https://github.com/justingrant) - [#274](https://github.com/bvaughn/react-window/pull/274)) +function Example({ names }: { names: string[] }) { + const itemData = useMemo(() => ({ names }), [names]); -### 1.8.3 -* 🐛 Edge case bug-fix for `scrollToItem` when scrollbars are present ([MarkFalconbridge](https://github.com/MarkFalconbridge) - [#267](https://github.com/bvaughn/react-window/pull/267)) -* 🐛 Fixed RTL scroll offsets for non-Chromium Edge ([MarkFalconbridge](https://github.com/MarkFalconbridge) - [#268](https://github.com/bvaughn/react-window/pull/268)) -* 🐛 Flow types improved ([TrySound](https://github.com/TrySound) - [#260](https://github.com/bvaughn/react-window/pull/260)) + return ( + + ); +} +function Row({ + data, + index, + style +}: ListChildComponentProps<{ + names: string[]; +}>) { + const { names } = data; + const name = names[index]; + return
{name}
; +} +``` -### 1.8.2 -* ✨ Deprecated grid props `overscanColumnsCount` and `overscanRowsCount` props in favor of more consistently named `overscanColumnCount` and `overscanRowCount`. ([nihgwu](https://github.com/nihgwu) - [#229](https://github.com/bvaughn/react-window/pull/229)) -* 🐛 Fixed shaky elastic scroll problems present in iOS Safari. [#244](https://github.com/bvaughn/react-window/issues/244) -* 🐛 Fixed RTL edge case bugs and broken scroll-to-item behavior. [#159](https://github.com/bvaughn/react-window/issues/159) -* 🐛 Fixed broken synchronized scrolling for RTL lists/grids. [#198](https://github.com/bvaughn/react-window/issues/198) +### After -### 1.8.1 -* 🐛 Replaced an incorrect empty-string value for `pointer-events` with `undefined` ([oliviertassinari](https://github.com/oliviertassinari) - [#210](https://github.com/bvaughn/react-window/pull/210)) +```tsx +import { List, type RowComponentProps } from "react-window"; -### 1.8.0 -* 🎉 Added new "smart" align option for grid and list scroll-to-item methods ([gaearon](https://github.com/gaearon) - [#209](https://github.com/bvaughn/react-window/pull/209)) +function Example({ names }: { names: string[] }) { + return ( + + ); +} -### 1.7.2 -* 🐛 Add guards to avoid invalid scroll offsets when `scrollTo()` is called with a negative offset or when `scrollToItem` is called with invalid indices (negative or too large). +function RowComponent({ + index, + names, + style +}: RowComponentProps<{ + names: string[]; +}>) { + const name = names[index]; + return
{name}
; +} +``` -### 1.7.1 -* 🐛 Fix SSR regression introduced in 1.7.0 - ([Betree](https://github.com/Betree) - [#185](https://github.com/bvaughn/react-window/pull/185)) +### ⚠️ Version 2 requirements -### 1.7.0 -* 🎉 Grid `scrollToItem` supports optional `rowIndex` and `columnIndex` params ([jgoz](https://github.com/jgoz) - [#174](https://github.com/bvaughn/react-window/pull/174)) -* DEV mode checks for `WeakSet` support before using it to avoid requiring a polyfill for IE11 - ([jgoz](https://github.com/jgoz) - [#167](https://github.com/bvaughn/react-window/pull/167)) +The following requirements are new in version 2 and may be reasons to consider _not_ upgrading: -### 1.6.2 -* 🐛 Bugfix for RTL when scrolling back towards the beginning (right) of the list. +- Peer dependencies now require React version 18 or newer +- `ResizeObserver` primitive (or polyfill) is required _unless_ explicit pixel dimensions are provided via `style` prop; (see documentation for more) -### 1.6.1 -* 🐛 Bugfix to account for differences between Chrome and non-Chrome browsers with regard to RTL and "scroll" events. +## 1.8.11 -### 1.6.0 -* 🎉 RTL support added for lists and grids. Special thanks to [davidgarsan](https://github.com/davidgarsan) for his support. - [#156](https://github.com/bvaughn/react-window/pull/156) -* 🐛 Grid `scrollToItem` methods take scrollbar size into account when aligning items - [#153](https://github.com/bvaughn/react-window/issues/153) +- Dependencies updated to include React 19 -### 1.5.2 -* 🐛 Edge case bug fix for `VariableSizeList` and `VariableSizeGrid` when the number of items decreases while a scroll is in progress. - ([iamsolankiamit](https://github.com/iamsolankiamit) - [#138](https://github.com/bvaughn/react-window/pull/138)) +## 1.8.10 -### 1.5.1 -* 🐛 Updated `getDerivedState` Flow annotations to address a warning in a newer version of Flow. +- Fix scrollDirection when direction is RTL (#690) -### 1.5.0 -* 🎉 Added advanced memoization helpers methods `areEqual` and `shouldComponentUpdate` for item renderers. - [#114](https://github.com/bvaughn/react-window/issues/114) +## 1.8.9 -### 1.4.0 -* 🎉 List and Grid components now "overscan" (pre-render) in both directions when scrolling is not active. When scrolling is in progress, cells are only pre-rendered in the direction being scrolled. This change has been made in an effort to reduce visible flicker when scrolling starts without adding additional overhead during scroll (which is the most performance sensitive time). -* 🎉 Grid components now support separate `overscanColumnsCount` and `overscanRowsCount` props. Legacy `overscanCount` prop will continue to work, but with a deprecation warning in DEV mode. -* 🐛 Replaced `setTimeout` with `requestAnimationFrame` based timer, to avoid starvation issue for `isScrolling` reset. - [#106](https://github.com/bvaughn/react-window/issues/106) -* 🎉 Renamed List and Grid `innerTagName` and `outerTagName` props to `innerElementType` and `outerElementType` to formalize support for attaching arbitrary props (e.g. test ids) to List and Grid inner and outer DOM elements. Legacy `innerTagName` and `outerTagName` props will continue to work, but with a deprecation warning in DEV mode. -* 🐛 List re-renders items if `direction` prop changes. - [#104](https://github.com/bvaughn/react-window/issues/104) +- Readme changes -### 1.3.1 -* 🎉 Pass `itemData` value to custom `itemKey` callbacks when present - [#90](https://github.com/bvaughn/react-window/issues/90)) +## 1.8.8 -### 1.3.0 -* (Skipped) +- 🐛 `scrollToItem` accounts for scrollbar size in the uncommon case where a List component has scrolling in the non-dominant direction (e.g. a "vertical" layout list also scrolls horizontally). -### 1.2.4 -* 🐛 Added Flow annotations to memoized methods to avoid a Flow warning for newer versions of Flow +## 1.8.7 -### 1.2.3 -* 🐛 Relaxed `children` validation checks. They were too strict and didn't support new React APIs like `memo`. +- ✨ Updated peer dependencies to include React v18. -### 1.2.2 -* 🐛 Improved Flow types for class component item renderers - ([nicholas-l](https://github.com/nicholas-l) - [#77](https://github.com/bvaughn/react-window/pull/77)) +## 1.8.6 -### 1.2.1 -* 🎉 Improved Flow types to include optional `itemData` parameter. ([TrySound](https://github.com/TrySound) - [#66](https://github.com/bvaughn/react-window/pull/66)) -* 🐛 `VariableSizeList` and `VariableSizeGrid` no longer call size getter functions with invalid index when item count is zero. +- ✨ Updated peer dependencies to include React v17. -### 1.2.0 -* 🎉 Flow types added to NPM package. ([TrySound](https://github.com/TrySound) - [#40](https://github.com/bvaughn/react-window/pull/40)) -* 🎉 Relaxed grid `scrollTo` method to make `scrollLeft` and `scrollTop` params _optional_ (so you can only update one axis if desired). - [#63](https://github.com/bvaughn/react-window/pull/63)) -* 🐛 Fixed invalid `this` pointer in `VariableSizeGrid` that broke the `resetAfter*` methods - [#58](https://github.com/bvaughn/react-window/pull/58)) -* Upgraded to babel 7 and used shared runtime helpers to reduce package size slightly. ([TrySound](https://github.com/TrySound) - [#48](https://github.com/bvaughn/react-window/pull/48)) -* Remove `overflow:hidden` from inner container ([souporserious](https://github.com/souporserious) - [#56](https://github.com/bvaughn/react-window/pull/56)) +## 1.8.5 -### 1.1.2 -* 🐛 Fixed edge case `scrollToItem` bug that caused lists/grids with very few items to have negative scroll offsets. +- ✨ Added UMD (dev and prod) build - ([emmanueltouzery](https://github.com/emmanueltouzery) - [#281](https://github.com/bvaughn/react-window/pull/281)) -### 1.1.1 -* 🐛 `FixedSizeGrid` and `FixedSizeList` automatically clear style cache when item size props change. +## 1.8.4 -### 1.1.0 -* 🎉 Use explicit `constructor` and `super` to generate cleaner component code. ([Andarist](https://github.com/Andarist) - [#26](https://github.com/bvaughn/react-window/pull/26)) -* 🎉 Add optional `shouldForceUpdate` param reset-index methods to specify `forceUpdate` behavior. ([nihgwu](https://github.com/nihgwu) - [#32](https://github.com/bvaughn/react-window/pull/32)) +- 🐛 Fixed size list and grid components now accurately report `visibleStopIndex` in `onItemsRendered`. (Previously this value was incorrectly reported as one index higher.) - ([justingrant](https://github.com/justingrant) - [#274](https://github.com/bvaughn/react-window/pull/274)) +- 🐛 Fixed size list and grid components `scrollToItem` "center" mode when the item being scrolled to is near the viewport edge. - ([justingrant](https://github.com/justingrant) - [#274](https://github.com/bvaughn/react-window/pull/274)) -### 1.0.3 -* 🐛 Avoid unnecessary scrollbars for lists (e.g. no horizontal scrollbar for a vertical list) unless content requires them. +## 1.8.3 -### 1.0.2 +- 🐛 Edge case bug-fix for `scrollToItem` when scrollbars are present ([MarkFalconbridge](https://github.com/MarkFalconbridge) - [#267](https://github.com/bvaughn/react-window/pull/267)) +- 🐛 Fixed RTL scroll offsets for non-Chromium Edge ([MarkFalconbridge](https://github.com/MarkFalconbridge) - [#268](https://github.com/bvaughn/react-window/pull/268)) +- 🐛 Flow types improved ([TrySound](https://github.com/TrySound) - [#260](https://github.com/bvaughn/react-window/pull/260)) -* 🎉 Enable Babel `annotate-pure-calls` option so that classes compiled by "transform-es2015-classes" are annotated with `#__PURE__`. This enables [UglifyJS to remove them if they are not referenced](https://github.com/mishoo/UglifyJS2/pull/1448), improving dead code elimination in application code. ([Andarist](https://github.com/Andarist) - [#20](https://github.com/bvaughn/react-window/pull/20)) -* 🎉 Update "rollup-plugin-peer-deps-external" and use new `includeDependencies` flag so that the "memoize-one" dependency does not get inlined into the Rollup bundle. ([Andarist](https://github.com/Andarist) - [#19](https://github.com/bvaughn/react-window/pull/19)) -* 🎉 Enable [Babel "loose" mode](https://babeljs.io/docs/en/babel-preset-env#loose) to reduce package size (-8%). ([Andarist](https://github.com/Andarist) - [#18](https://github.com/bvaughn/react-window/pull/18)) +## 1.8.2 + +- ✨ Deprecated grid props `overscanColumnsCount` and `overscanRowsCount` props in favor of more consistently named `overscanColumnCount` and `overscanRowCount`. ([nihgwu](https://github.com/nihgwu) - [#229](https://github.com/bvaughn/react-window/pull/229)) +- 🐛 Fixed shaky elastic scroll problems present in iOS Safari. [#244](https://github.com/bvaughn/react-window/issues/244) +- 🐛 Fixed RTL edge case bugs and broken scroll-to-item behavior. [#159](https://github.com/bvaughn/react-window/issues/159) +- 🐛 Fixed broken synchronized scrolling for RTL lists/grids. [#198](https://github.com/bvaughn/react-window/issues/198) + +## 1.8.1 + +- 🐛 Replaced an incorrect empty-string value for `pointer-events` with `undefined` ([oliviertassinari](https://github.com/oliviertassinari) - [#210](https://github.com/bvaughn/react-window/pull/210)) + +## 1.8.0 + +- 🎉 Added new "smart" align option for grid and list scroll-to-item methods ([gaearon](https://github.com/gaearon) - [#209](https://github.com/bvaughn/react-window/pull/209)) + +## 1.7.2 + +- 🐛 Add guards to avoid invalid scroll offsets when `scrollTo()` is called with a negative offset or when `scrollToItem` is called with invalid indices (negative or too large). + +## 1.7.1 + +- 🐛 Fix SSR regression introduced in 1.7.0 - ([Betree](https://github.com/Betree) - [#185](https://github.com/bvaughn/react-window/pull/185)) + +## 1.7.0 + +- 🎉 Grid `scrollToItem` supports optional `rowIndex` and `columnIndex` params ([jgoz](https://github.com/jgoz) - [#174](https://github.com/bvaughn/react-window/pull/174)) +- DEV mode checks for `WeakSet` support before using it to avoid requiring a polyfill for IE11 - ([jgoz](https://github.com/jgoz) - [#167](https://github.com/bvaughn/react-window/pull/167)) + +## 1.6.2 + +- 🐛 Bugfix for RTL when scrolling back towards the beginning (right) of the list. + +## 1.6.1 + +- 🐛 Bugfix to account for differences between Chrome and non-Chrome browsers with regard to RTL and "scroll" events. + +## 1.6.0 + +- 🎉 RTL support added for lists and grids. Special thanks to [davidgarsan](https://github.com/davidgarsan) for his support. - [#156](https://github.com/bvaughn/react-window/pull/156) +- 🐛 Grid `scrollToItem` methods take scrollbar size into account when aligning items - [#153](https://github.com/bvaughn/react-window/issues/153) + +## 1.5.2 + +- 🐛 Edge case bug fix for `VariableSizeList` and `VariableSizeGrid` when the number of items decreases while a scroll is in progress. - ([iamsolankiamit](https://github.com/iamsolankiamit) - [#138](https://github.com/bvaughn/react-window/pull/138)) + +## 1.5.1 + +- 🐛 Updated `getDerivedState` Flow annotations to address a warning in a newer version of Flow. + +## 1.5.0 + +- 🎉 Added advanced memoization helpers methods `areEqual` and `shouldComponentUpdate` for item renderers. - [#114](https://github.com/bvaughn/react-window/issues/114) + +## 1.4.0 + +- 🎉 List and Grid components now "overscan" (pre-render) in both directions when scrolling is not active. When scrolling is in progress, cells are only pre-rendered in the direction being scrolled. This change has been made in an effort to reduce visible flicker when scrolling starts without adding additional overhead during scroll (which is the most performance sensitive time). +- 🎉 Grid components now support separate `overscanColumnsCount` and `overscanRowsCount` props. Legacy `overscanCount` prop will continue to work, but with a deprecation warning in DEV mode. +- 🐛 Replaced `setTimeout` with `requestAnimationFrame` based timer, to avoid starvation issue for `isScrolling` reset. - [#106](https://github.com/bvaughn/react-window/issues/106) +- 🎉 Renamed List and Grid `innerTagName` and `outerTagName` props to `innerElementType` and `outerElementType` to formalize support for attaching arbitrary props (e.g. test ids) to List and Grid inner and outer DOM elements. Legacy `innerTagName` and `outerTagName` props will continue to work, but with a deprecation warning in DEV mode. +- 🐛 List re-renders items if `direction` prop changes. - [#104](https://github.com/bvaughn/react-window/issues/104) + +## 1.3.1 + +- 🎉 Pass `itemData` value to custom `itemKey` callbacks when present - [#90](https://github.com/bvaughn/react-window/issues/90)) + +## 1.3.0 + +- (Skipped) + +## 1.2.4 + +- 🐛 Added Flow annotations to memoized methods to avoid a Flow warning for newer versions of Flow + +## 1.2.3 + +- 🐛 Relaxed `children` validation checks. They were too strict and didn't support new React APIs like `memo`. + +## 1.2.2 + +- 🐛 Improved Flow types for class component item renderers - ([nicholas-l](https://github.com/nicholas-l) - [#77](https://github.com/bvaughn/react-window/pull/77)) + +## 1.2.1 + +- 🎉 Improved Flow types to include optional `itemData` parameter. ([TrySound](https://github.com/TrySound) - [#66](https://github.com/bvaughn/react-window/pull/66)) +- 🐛 `VariableSizeList` and `VariableSizeGrid` no longer call size getter functions with invalid index when item count is zero. + +## 1.2.0 + +- 🎉 Flow types added to NPM package. ([TrySound](https://github.com/TrySound) - [#40](https://github.com/bvaughn/react-window/pull/40)) +- 🎉 Relaxed grid `scrollTo` method to make `scrollLeft` and `scrollTop` params _optional_ (so you can only update one axis if desired). - [#63](https://github.com/bvaughn/react-window/pull/63)) +- 🐛 Fixed invalid `this` pointer in `VariableSizeGrid` that broke the `resetAfter*` methods - [#58](https://github.com/bvaughn/react-window/pull/58)) +- Upgraded to babel 7 and used shared runtime helpers to reduce package size slightly. ([TrySound](https://github.com/TrySound) - [#48](https://github.com/bvaughn/react-window/pull/48)) +- Remove `overflow:hidden` from inner container ([souporserious](https://github.com/souporserious) - [#56](https://github.com/bvaughn/react-window/pull/56)) + +## 1.1.2 + +- 🐛 Fixed edge case `scrollToItem` bug that caused lists/grids with very few items to have negative scroll offsets. + +## 1.1.1 + +- 🐛 `FixedSizeGrid` and `FixedSizeList` automatically clear style cache when item size props change. + +## 1.1.0 + +- 🎉 Use explicit `constructor` and `super` to generate cleaner component code. ([Andarist](https://github.com/Andarist) - [#26](https://github.com/bvaughn/react-window/pull/26)) +- 🎉 Add optional `shouldForceUpdate` param reset-index methods to specify `forceUpdate` behavior. ([nihgwu](https://github.com/nihgwu) - [#32](https://github.com/bvaughn/react-window/pull/32)) + +## 1.0.3 + +- 🐛 Avoid unnecessary scrollbars for lists (e.g. no horizontal scrollbar for a vertical list) unless content requires them. + +## 1.0.2 + +- 🎉 Enable Babel `annotate-pure-calls` option so that classes compiled by "transform-es2015-classes" are annotated with `#__PURE__`. This enables [UglifyJS to remove them if they are not referenced](https://github.com/mishoo/UglifyJS2/pull/1448), improving dead code elimination in application code. ([Andarist](https://github.com/Andarist) - [#20](https://github.com/bvaughn/react-window/pull/20)) +- 🎉 Update "rollup-plugin-peer-deps-external" and use new `includeDependencies` flag so that the "memoize-one" dependency does not get inlined into the Rollup bundle. ([Andarist](https://github.com/Andarist) - [#19](https://github.com/bvaughn/react-window/pull/19)) +- 🎉 Enable [Babel "loose" mode](https://babeljs.io/docs/en/babel-preset-env#loose) to reduce package size (-8%). ([Andarist](https://github.com/Andarist) - [#18](https://github.com/bvaughn/react-window/pull/18)) + +## 1.0.1 -### 1.0.1 Updated `README.md` file to remove `@alpha` tag from NPM installation instructions. # 1.0.0 + Initial release of library. Includes the following components: -* `FixedSizeGrid` -* `FixedSizeList` -* `VariableSizeGrid` -* `VariableSizeList` + +- `FixedSizeGrid` +- `FixedSizeList` +- `VariableSizeGrid` +- `VariableSizeList` diff --git a/README-zh_CN.md b/README-zh_CN.md deleted file mode 100644 index 88f6a176..00000000 --- a/README-zh_CN.md +++ /dev/null @@ -1,104 +0,0 @@ -# react-window - -> 高效渲染大数据列表和表格的react组件 - -React window基于仅渲染大数据中的部分数据(刚好填充满视窗)的方法,来帮助我们解决一些常见的的性能瓶颈问题: - -1. 它减少了初始渲染和处理更新时的耗时 -2. 减少内存占用,从而避免大量DOM节点引起的内存泄漏. - -[![NPM registry](https://img.shields.io/npm/v/react-window.svg?style=for-the-badge)](https://yarnpkg.com/en/package/react-window) [![Travis](https://img.shields.io/badge/ci-travis-green.svg?style=for-the-badge)](https://travis-ci.org/bvaughn/react-window) [![NPM license](https://img.shields.io/badge/license-mit-red.svg?style=for-the-badge)](LICENSE.md) - -## 安装 - -```bash -# Yarn -yarn add react-window - -# NPM -npm install --save react-window -``` - -## 使用 - -Learn more at [react-window.now.sh](https://react-window.now.sh/): - -## 相关库信息 - -* [`react-virtualized-auto-sizer`](https://npmjs.com/package/react-virtualized-auto-sizer): 高阶组件:动态适配可用的空间,并且支持传入width和height值到子组件 -* [`react-window-infinite-loader`](https://npmjs.com/package/react-window-infinite-loader): 帮助大数据分组和根据视图滚动实时加载。也可以被用于无限加载的列表中(e.g. Facebook or Twitter). -* [`react-vtree`](https://www.npmjs.com/package/react-vtree): -一个轻量灵活的解决大数据目录结构渲染的库(比如文件系统目录结构) - -## 常见问题 - -### `react-window`和`react-virtualized`有何不同? -我几年前写了`react-virtualized`。当时,我对React和window的概念了解很浅。因此,我封装了一些API,但是后来我后悔了。原因之一是其中添加了太多没必要的特性和组件 。一旦你向开源项目添加些东西,删除它对用户是件很痛苦的事情 - -`react-window` 基于 `react-virtualized`完全重写. 我不用尝试解决那么多遗留问题或者支持那么多用例. 相反我聚焦在让包体积更小,运行效率更高的问题上来。我也花了很多心思在设计API和文档上来,让其对初学者更友好(当然window仍然是种高级用例)。 - -如果你的项目需要`react-window`提供的功能,我强烈建议用它替代`react-virtualized`. 然后如果需要一些仅`react-virtualized`提供的特性, 你有两个选择: - -1. 继续使用 `react-virtualized`. (它仍广泛的应用于各种成功案例) -2. 创建一个封装了`react-window`基类的组件并且添加你需要的功能。你也可以发布这个组件到npm上 🙂 - -PS: 添加一个`react-virtualized`列表到一个CRA(create-react-app)项目中, gzip构建包增加了33.5kb. 同样的的添加一个`react-window`列表到CRA项目中,gzip构建包仅增加了2kb - - -### 一个列表或者网格结构可以100%填充页面的宽高么? - -可以. 我推荐使用 [`react-virtualized-auto-sizer` 库](https://npmjs.com/package/react-virtualized-auto-sizer): - -screen shot 2019-03-07 at 7 29 08 pm - -这是一个 [Sandbox上的例子](https://codesandbox.io/s/3vnx878jk5). - -### 页面滚动的时候为什么会出现空白? - -如果你的页面看起来像这样... - - - -...那么你有可能是忘记`style`参数了! react-window这样的库基于绝对定位来展示 (通过一个内联样式), 所以不要忘记绑定到你渲染的DOM元素上! - -screen shot 2019-03-07 at 7 21 48 pm - -### 列表支持懒加载数据么? - -支持,推荐使用 [`react-window-infinite-loader` package](https://npmjs.com/package/react-window-infinite-loader): - -screen shot 2019-03-07 at 7 32 32 pm - -这是一个 [Sandbox上的例子](https://codesandbox.io/s/5wqo7z2np4). - -### 可以绑定自定义属性和事件处理么? - -可以, 使用 `outerElementType` 属性. - -Screen Shot 2019-03-12 at 8 58 09 AM - -这是一个 [Sandbox上的例子](https://codesandbox.io/s/4zqx79nww0). - -### 列表收尾可以添加padding么? - -可以, 虽然需要一点点内联样式. - -Screen Shot 2019-06-02 at 8 38 18 PM - -这是一个 [Sandbox上的例子](https://codesandbox.io/s/react-window-list-padding-dg0pq). - -### 列表的元素间可以添加gutter或者padding么? - -可以, 虽然需要一点点内联样式. - -Screen Shot 2019-03-26 at 6 33 56 PM - -这是一个 [Sandbox上的例子](https://codesandbox.io/s/2w8wmlm89p). - -### 支持内部元素sticky处理么? - -支持,不过需要一点代码修改. 这是一个 [Sandbox上的例子](https://codesandbox.io/s/0mk3qwpl4l). - -## License - -MIT © [bvaughn](https://github.com/bvaughn) diff --git a/README.md b/README.md index d45e7a1f..0bff3d3f 100644 --- a/README.md +++ b/README.md @@ -1,171 +1,19 @@ # react-window -> React components for efficiently rendering large lists and tabular data +`react-window` is a component library that helps render large lists of data quickly and without the performance problems that often go along with rendering a lot of data. It's used in a lot of places, from React DevTools to the Replay browser. -### If you like this project, 🎉 [become a sponsor](https://github.com/sponsors/bvaughn/) or ☕ [buy me a coffee](http://givebrian.coffee/) +## Installation ---- +Begin by installing the library from NPM: -React window works by only rendering *part* of a large data set (just enough to fill the viewport). This helps address some common performance bottlenecks: -1. It reduces the amount of work (and time) required to render the initial view and to process updates. -2. It reduces the memory footprint by avoiding over-allocation of DOM nodes. - -### Sponsors - -The following wonderful companies have sponsored react-window: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -[Learn more about becoming a sponsor!](https://opencollective.com/react-window#sponsor) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -## Install - -```bash -# Yarn -yarn add react-window - -# NPM -npm install --save react-window +```sh +npm install react-window ``` -## Usage - -Learn more at [react-window.now.sh](https://react-window.now.sh/): - -## Related libraries - -* [`react-virtualized-auto-sizer`](https://npmjs.com/package/react-virtualized-auto-sizer): HOC that grows to fit all of the available space and passes the width and height values to its child. -* [`react-window-infinite-loader`](https://npmjs.com/package/react-window-infinite-loader): Helps break large data sets down into chunks that can be just-in-time loaded as they are scrolled into view. It can also be used to create infinite loading lists (e.g. Facebook or Twitter). -* [`react-vtree`](https://www.npmjs.com/package/react-vtree): Lightweight and flexible solution to render large tree structures (e.g., file system). - -## Frequently asked questions - -### How is `react-window` different from `react-virtualized`? -I wrote `react-virtualized` several years ago. At the time, I was new to both React and the concept of windowing. Because of this, I made a few API decisions that I later came to regret. One of these was adding too many non-essential features and components. Once you add something to an open source project, removing it is pretty painful for users. - -`react-window` is a complete rewrite of `react-virtualized`. I didn't try to solve as many problems or support as many use cases. Instead I focused on making the package **smaller**1 and **faster**. I also put a lot of thought into making the API (and documentation) as beginner-friendly as possible (with the caveat that windowing is still kind of an advanced use case). - -If `react-window` provides the functionality your project needs, I would strongly recommend using it instead of `react-virtualized`. However if you need features that only `react-virtualized` provides, you have two options: - -1. Use `react-virtualized`. (It's still widely used by a lot of successful projects!) -2. Create a component that decorates one of the `react-window` primitives and adds the functionality you need. You may even want to release this component to NPM (as its own, standalone package)! 🙂 - -1 - Adding a `react-virtualized` list to a CRA project increases the (gzipped) build size by ~33.5 KB. Adding a `react-window` list to a CRA project increases the (gzipped) build size by <2 KB. - -### Can a list or a grid fill 100% the width or height of a page? - -Yes. I recommend using the [`react-virtualized-auto-sizer` package](https://npmjs.com/package/react-virtualized-auto-sizer): - -screen shot 2019-03-07 at 7 29 08 pm - -Here's a [Code Sandbox demo](https://codesandbox.io/s/3vnx878jk5). - -### Why is my list blank when I scroll? - -If your list looks something like this... - - - -...then you probably forgot to use the `style` parameter! Libraries like react-window work by absolutely positioning the list items (via an inline style), so don't forget to attach it to the DOM element you render! - -screen shot 2019-03-07 at 7 21 48 pm - -### Can I lazy load data for my list? - -Yes. I recommend using the [`react-window-infinite-loader` package](https://npmjs.com/package/react-window-infinite-loader): - -screen shot 2019-03-07 at 7 32 32 pm - -Here's a [Code Sandbox demo](https://codesandbox.io/s/5wqo7z2np4). - -### Can I attach custom properties or event handlers? - -Yes, using the `outerElementType` prop. - -Screen Shot 2019-03-12 at 8 58 09 AM - -Here's a [Code Sandbox demo](https://codesandbox.io/s/4zqx79nww0). - -### Can I add padding to the top and bottom of a list? - -Yes, although it requires a bit of inline styling. - -Screen Shot 2019-06-02 at 8 38 18 PM - -Here's a [Code Sandbox demo](https://codesandbox.io/s/react-window-list-padding-dg0pq). - -### Can I add gutter or padding between items? - -Yes, although it requires a bit of inline styling. - -Screen Shot 2019-03-26 at 6 33 56 PM - -Here's a [Code Sandbox demo](https://codesandbox.io/s/2w8wmlm89p). - -### Does this library support "sticky" items? +TypeScript definitions are included within the published dist folder and documentation is included within the docs folder. -Yes, although it requires a small amount of user code. Here's a [Code Sandbox demo](https://codesandbox.io/s/0mk3qwpl4l). +## Documentation -## License +Documentation for this project is available at [react-window.vercel.app](https://react-window.vercel.app/). -MIT © [bvaughn](https://github.com/bvaughn) +TypeScript definitions and version-specific documentation are also included with each published build. diff --git a/babel.config.js b/babel.config.js deleted file mode 100644 index b5ded1bc..00000000 --- a/babel.config.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - presets: [['@babel/env', { loose: true }], '@babel/flow'], - plugins: [ - ['@babel/proposal-class-properties', { loose: true }], - 'annotate-pure-calls', - ], -}; diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..d7e0e28f --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,23 @@ +import js from "@eslint/js"; +import globals from "globals"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; +import tseslint from "typescript-eslint"; +import { globalIgnores } from "eslint/config"; + +export default tseslint.config([ + globalIgnores(["dist", "docs", "generated"]), + { + files: ["**/*.{ts,tsx}"], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs["recommended-latest"], + reactRefresh.configs.vite + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser + } + } +]); diff --git a/flow-template b/flow-template deleted file mode 100644 index 6a6528bb..00000000 --- a/flow-template +++ /dev/null @@ -1,3 +0,0 @@ -// @flow - -export * from '../src'; diff --git a/index.css b/index.css new file mode 100644 index 00000000..225dd236 --- /dev/null +++ b/index.css @@ -0,0 +1,89 @@ +@import "tailwindcss"; + +:root { + font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + font-size: 12px; + + @media only screen and (max-width: 600px) { + font-size: 16px; + } + + color-scheme: dark; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +@keyframes background-gradient-animation { + 0% { + background-position: 0% 50%; + } + 50% { + background-position: 100% 50%; + } + 100% { + background-position: 0% 50%; + } +} + +#root { + width: 100dvw; + height: 100dvh; + overflow: auto; + background: linear-gradient( + -45deg, + var(--color-pink-500), + var(--color-sky-500), + var(--color-emerald-400) + ); + background-size: 400% 400%; + animation: background-gradient-animation 20s ease infinite; +} + +* { + scrollbar-width: thin; + scrollbar-color: var(--color-slate-600) transparent; + outline: none; + transition: + color 0.25s ease, + background-color 0.25s ease, + border-color 0.25s ease, + outline-color 0.25s ease; +} + +main { + a { + color: var(--color-teal-400); + &:hover { + color: var(--color-teal-300); + } + } +} + +*[data-focus] { + border: 2px solid transparent; + &:focus { + border: 2px solid var(--color-teal-300); + } +} + +*[data-focus-within] { + border: 2px solid transparent; + &:focus-within { + border: 2px solid var(--color-teal-300); + } + + &[data-focus-within="bold"] { + border-color: var(--color-teal-600); + &:focus-within { + border: 2px solid var(--color-teal-300); + } + } +} + +code { + color: var(--color-slate-300); +} diff --git a/index.html b/index.html new file mode 100644 index 00000000..c25ac881 --- /dev/null +++ b/index.html @@ -0,0 +1,30 @@ + + + + react-window | render everything + + + + + + + + + + + + + + + +
+ + + + diff --git a/index.tsx b/index.tsx new file mode 100644 index 00000000..7cce5685 --- /dev/null +++ b/index.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import "./index.css"; +import App from "./src/App.tsx"; + +createRoot(document.getElementById("root")!).render( + + + +); diff --git a/lib/components/grid/Grid.test.tsx b/lib/components/grid/Grid.test.tsx new file mode 100644 index 00000000..a266e136 --- /dev/null +++ b/lib/components/grid/Grid.test.tsx @@ -0,0 +1,7 @@ +import { describe, test } from "vitest"; + +describe("Grid", () => { + test("have tests", () => { + // TODO Write tests + }); +}); diff --git a/lib/components/grid/Grid.tsx b/lib/components/grid/Grid.tsx new file mode 100644 index 00000000..d3667ba6 --- /dev/null +++ b/lib/components/grid/Grid.tsx @@ -0,0 +1,248 @@ +import { + memo, + useEffect, + useImperativeHandle, + useMemo, + useState, + type ReactNode +} from "react"; +import { useIsRtl } from "../../core/useIsRtl"; +import { useVirtualizer } from "../../core/useVirtualizer"; +import { useMemoizedObject } from "../../hooks/useMemoizedObject"; +import type { Align } from "../../types"; +import { arePropsEqual } from "../../utils/arePropsEqual"; +import type { GridProps } from "./types"; + +export function Grid({ + cellComponent: CellComponentProp, + cellProps: cellPropsUnstable, + className, + columnCount, + columnWidth, + defaultHeight = 0, + defaultWidth = 0, + dir, + gridRef, + onCellsRendered, + onResize, + overscanCount = 3, + rowCount, + rowHeight, + style, + ...rest +}: GridProps) { + const cellProps = useMemoizedObject(cellPropsUnstable); + const CellComponent = useMemo( + () => memo(CellComponentProp, arePropsEqual), + [CellComponentProp] + ); + + const [element, setElement] = useState(null); + + const isRtl = useIsRtl(element, dir); + + const { + getCellBounds: getColumnBounds, + getEstimatedSize: getEstimatedWidth, + startIndex: columnStartIndex, + scrollToIndex: scrollToColumnIndex, + stopIndex: columnStopIndex + } = useVirtualizer({ + containerElement: element, + defaultContainerSize: defaultWidth, + direction: "horizontal", + isRtl, + itemCount: columnCount, + itemProps: cellProps, + itemSize: columnWidth, + onResize, + overscanCount + }); + + const { + getCellBounds: getRowBounds, + getEstimatedSize: getEstimatedHeight, + startIndex: rowStartIndex, + scrollToIndex: scrollToRowIndex, + stopIndex: rowStopIndex + } = useVirtualizer({ + containerElement: element, + defaultContainerSize: defaultHeight, + direction: "vertical", + itemCount: rowCount, + itemProps: cellProps, + itemSize: rowHeight, + onResize, + overscanCount + }); + + useImperativeHandle( + gridRef, + () => ({ + get element() { + return element; + }, + + scrollToCell({ + behavior = "auto", + columnAlign = "auto", + columnIndex, + rowAlign = "auto", + rowIndex + }: { + behavior?: ScrollBehavior; + columnAlign?: Align; + columnIndex: number; + rowAlign?: Align; + rowIndex: number; + }) { + scrollToRowIndex({ + align: rowAlign, + behavior, + containerScrollOffset: element?.scrollTop ?? 0, + index: rowIndex + }); + scrollToColumnIndex({ + align: columnAlign, + behavior, + containerScrollOffset: element?.scrollLeft ?? 0, + index: columnIndex + }); + }, + + scrollToColumn({ + align = "auto", + behavior = "auto", + index + }: { + align?: Align; + behavior?: ScrollBehavior; + index: number; + }) { + scrollToColumnIndex({ + align, + behavior, + containerScrollOffset: element?.scrollLeft ?? 0, + index + }); + }, + + scrollToRow({ + align = "auto", + behavior = "auto", + index + }: { + align?: Align; + behavior?: ScrollBehavior; + index: number; + }) { + scrollToRowIndex({ + align, + behavior, + containerScrollOffset: element?.scrollTop ?? 0, + index + }); + } + }), + [element, scrollToColumnIndex, scrollToRowIndex] + ); + + useEffect(() => { + if ( + columnStartIndex >= 0 && + columnStopIndex >= 0 && + rowStartIndex >= 0 && + rowStopIndex >= 0 && + onCellsRendered + ) { + onCellsRendered({ + columnStartIndex, + columnStopIndex, + rowStartIndex, + rowStopIndex + }); + } + }, [ + onCellsRendered, + columnStartIndex, + columnStopIndex, + rowStartIndex, + rowStopIndex + ]); + + const cells = useMemo(() => { + const children: ReactNode[] = []; + if (columnCount > 0 && rowCount > 0) { + for (let rowIndex = rowStartIndex; rowIndex <= rowStopIndex; rowIndex++) { + const rowBounds = getRowBounds(rowIndex); + for ( + let columnIndex = columnStartIndex; + columnIndex <= columnStopIndex; + columnIndex++ + ) { + const columnBounds = getColumnBounds(columnIndex); + + children.push( + 1 ? rowBounds.size : "100%", + width: columnBounds.size + }} + /> + ); + } + } + } + return children; + }, [ + CellComponent, + cellProps, + columnCount, + columnStartIndex, + columnStopIndex, + getColumnBounds, + getRowBounds, + isRtl, + rowCount, + rowStartIndex, + rowStopIndex + ]); + + return ( +
+
+ {cells} +
+
+ ); +} diff --git a/lib/components/grid/types.ts b/lib/components/grid/types.ts new file mode 100644 index 00000000..668cc48c --- /dev/null +++ b/lib/components/grid/types.ts @@ -0,0 +1,194 @@ +import type { + ComponentProps, + CSSProperties, + HTMLAttributes, + ReactNode, + Ref +} from "react"; + +type ForbiddenKeys = "columnIndex" | "rowIndex" | "style"; +type ExcludeForbiddenKeys = { + [Key in keyof Type]: Key extends ForbiddenKeys ? never : Type[Key]; +}; + +export type GridProps = Omit< + HTMLAttributes, + "onResize" +> & { + /** + * CSS class name. + */ + className?: string; + + /** + * React component responsible for rendering a cell. + * + * This component will receive an `index` and `style` prop by default. + * Additionally it will receive prop values passed to `cellProps`. + * + * ⚠️ The prop types for this component are exported as `CellComponentProps` + */ + cellComponent: ( + props: { + columnIndex: number; + rowIndex: number; + style: CSSProperties; + } & CellProps + ) => ReactNode; + + /** + * Additional props to be passed to the cell-rendering component. + * Grid will automatically re-render cells when values in this object change. + * + * ⚠️ This object must not contain either an `index` or `style` prop. + */ + cellProps: ExcludeForbiddenKeys; + + /** + * Number of columns to be rendered in the grid. + */ + columnCount: number; + + /** + * Column width; the following formats are supported: + * - number of pixels (number) + * - percentage of the grid's current width (string) + * - function that returns the row width (in pixels) given an index and `cellProps` + */ + columnWidth: + | number + | string + | ((index: number, cellProps: CellProps) => number); + + /** + * Default height of grid for initial render. + * This value is important for server rendering. + */ + defaultHeight?: number; + + /** + * Default width of grid for initial render. + * This value is important for server rendering. + */ + defaultWidth?: number; + + /** + * Corresponds to the HTML dir attribute: + * https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/dir + */ + dir?: "ltr" | "rtl"; + + /** + * Ref used to interact with this component's imperative API. + * + * This API has imperative methods for scrolling and a getter for the outermost DOM element. + * + * ⚠️ The `useGridRef` and `useGridCallbackRef` hooks are exported for convenience use in TypeScript projects. + */ + gridRef?: Ref; + + /** + * Callback notified when the range of rendered cells changes. + */ + onCellsRendered?: (args: { + columnStartIndex: number; + columnStopIndex: number; + rowStartIndex: number; + rowStopIndex: number; + }) => void; + + /** + * Callback notified when the Grid's outermost HTMLElement resizes. + * This may be used to (re)scroll a cell into view. + */ + onResize?: ( + size: { height: number; width: number }, + prevSize: { height: number; width: number } + ) => void; + + /** + * How many additional rows/columns to render outside of the visible area. + * This can reduce visual flickering near the edges of a grid when scrolling. + */ + overscanCount?: number; + + /** + * Number of rows to be rendered in the grid. + */ + rowCount: number; + + /** + * Row height; the following formats are supported: + * - number of pixels (number) + * - percentage of the grid's current height (string) + * - function that returns the row height (in pixels) given an index and `cellProps` + */ + rowHeight: + | number + | string + | ((index: number, cellProps: CellProps) => number); + + /** + * Optional CSS properties. + * The grid of cells will fill the height and width defined by this style. + */ + style?: CSSProperties; +}; + +export type CellComponent = + GridProps["cellComponent"]; +export type CellComponentProps = + ComponentProps>; + +export type ScrollState = { + prevScrollTop: number; + scrollTop: number; +}; + +export type OnCellsRendered = NonNullable["onCellsRendered"]>; + +export type CachedBounds = Map< + number, + { + height: number; + scrollTop: number; + } +>; + +export type GridImperativeAPI = { + get element(): HTMLDivElement | null; + + scrollToCell({ + behavior, + columnAlign, + columnIndex, + rowAlign, + rowIndex + }: { + behavior?: "auto" | "instant" | "smooth"; + columnAlign?: "auto" | "center" | "end" | "smart" | "start"; + columnIndex: number; + rowAlign?: "auto" | "center" | "end" | "smart" | "start"; + rowIndex: number; + }): void; + + scrollToColumn({ + align, + behavior, + index + }: { + align?: "auto" | "center" | "end" | "smart" | "start"; + behavior?: "auto" | "instant" | "smooth"; + index: number; + }): void; + + scrollToRow({ + align, + behavior, + index + }: { + align?: "auto" | "center" | "end" | "smart" | "start"; + behavior?: "auto" | "instant" | "smooth"; + index: number; + }): void; +}; diff --git a/lib/components/grid/useGridCallbackRef.ts b/lib/components/grid/useGridCallbackRef.ts new file mode 100644 index 00000000..ff7615e0 --- /dev/null +++ b/lib/components/grid/useGridCallbackRef.ts @@ -0,0 +1,10 @@ +import { useState } from "react"; +import type { GridImperativeAPI } from "./types"; + +/** + * Convenience hook to return a properly typed ref callback for the Grid component. + * + * Use this hook when you need to share the ref with another component or hook. + */ +export const useGridCallbackRef = + useState as typeof useState; diff --git a/lib/components/grid/useGridRef.ts b/lib/components/grid/useGridRef.ts new file mode 100644 index 00000000..f69ea2aa --- /dev/null +++ b/lib/components/grid/useGridRef.ts @@ -0,0 +1,7 @@ +import { useRef } from "react"; +import type { GridImperativeAPI } from "./types"; + +/** + * Convenience hook to return a properly typed ref for the Grid component. + */ +export const useGridRef = useRef as typeof useRef; diff --git a/lib/components/list/List.test.tsx b/lib/components/list/List.test.tsx new file mode 100644 index 00000000..66df53dc --- /dev/null +++ b/lib/components/list/List.test.tsx @@ -0,0 +1,556 @@ +import { act, render, screen } from "@testing-library/react"; +import { createRef, useLayoutEffect } from "react"; +import { beforeEach, describe, expect, test, vi } from "vitest"; +import { EMPTY_OBJECT } from "../../../src/constants"; +import { + disableForCurrentTest, + updateMockResizeObserver +} from "../../utils/test/mockResizeObserver"; +import { List } from "./List"; +import { type ListImperativeAPI, type RowComponentProps } from "./types"; +import { useListCallbackRef } from "./useListCallbackRef"; + +describe("List", () => { + const RowComponent = vi.fn(function Row(props: RowComponentProps) { + const { index, style } = props; + + useLayoutEffect(() => { + mountedRows.set(index, props); + return () => { + mountedRows.delete(index); + }; + }); + + return ( +
+ Row {index} +
+ ); + }); + + let mountedRows: Map> = new Map(); + + beforeEach(() => { + RowComponent.mockReset(); + + updateMockResizeObserver(new DOMRect(0, 0, 50, 100)); + + mountedRows = new Map(); + }); + + test("should render an empty list", () => { + render( + + ); + + const items = screen.queryAllByRole("listitem"); + expect(items).toHaveLength(0); + }); + + test("should render enough rows to fill the available height", () => { + const onResize = vi.fn(); + + render( + + ); + + let items = screen.queryAllByRole("listitem"); + expect(items).toHaveLength(4); + expect(items[0]).toHaveTextContent("Row 0"); + expect(items[3]).toHaveTextContent("Row 3"); + + expect(onResize).toBeCalledTimes(1); + expect(onResize).toHaveBeenLastCalledWith( + { + height: 100, + width: 50 + }, + { + height: 0, + width: 0 + } + ); + + act(() => { + updateMockResizeObserver(new DOMRect(0, 0, 50, 75)); + }); + + items = screen.queryAllByRole("listitem"); + expect(items).toHaveLength(3); + expect(items[0]).toHaveTextContent("Row 0"); + expect(items[2]).toHaveTextContent("Row 2"); + + expect(onResize).toBeCalledTimes(2); + expect(onResize).toHaveBeenLastCalledWith( + { + height: 75, + width: 50 + }, + { + height: 100, + width: 50 + } + ); + }); + + test("should render enough rows to fill the available height with overscan", () => { + render( + + ); + + let items = screen.queryAllByRole("listitem"); + expect(items).toHaveLength(6); + expect(items[0]).toHaveTextContent("Row 0"); + expect(items[5]).toHaveTextContent("Row 5"); + + act(() => { + updateMockResizeObserver(new DOMRect(0, 0, 50, 75)); + }); + + items = screen.queryAllByRole("listitem"); + expect(items).toHaveLength(5); + expect(items[0]).toHaveTextContent("Row 0"); + expect(items[4]).toHaveTextContent("Row 4"); + }); + + test("should pass rowProps to the rowComponent", () => { + render( + + ); + + expect(mountedRows.size).toEqual(5); + expect(mountedRows.get(0)).toMatchObject({ + foo: "abc", + bar: 123 + }); + }); + + test("should re-render items if rowComponent changes", () => { + const { rerender } = render( + + ); + + const NewRow = vi.fn(() => null); + + rerender( + + ); + + expect(NewRow).toHaveBeenCalled(); + }); + + test("should re-render items if rowHeight changes", () => { + const { rerender } = render( + + ); + expect(mountedRows).toHaveLength(5); + + rerender( + + ); + expect(mountedRows).toHaveLength(3); + expect(mountedRows.get(1)?.index).toEqual(1); + }); + + test("should re-render items if rowProps change", () => { + const { rerender } = render( + + ); + expect(mountedRows).toHaveLength(5); + expect(mountedRows.get(0)).toMatchObject({ + foo: "abc" + }); + + rerender( + + ); + expect(mountedRows).toHaveLength(5); + expect(mountedRows.get(1)?.index).toEqual(1); + expect(mountedRows.get(0)).toMatchObject({ + bar: 123 + }); + }); + + test("should use defaultHeight for initial mount", () => { + // Mimic server rendering + disableForCurrentTest(); + + render( + + ); + + const items = screen.queryAllByRole("listitem"); + expect(items).toHaveLength(3); + }); + + test("should call onRowsRendered", () => { + const onRowsRendered = vi.fn(); + + const { rerender } = render( + + ); + expect(onRowsRendered).toHaveBeenCalledTimes(1); + expect(onRowsRendered).toHaveBeenLastCalledWith({ + startIndex: 0, + stopIndex: 1 + }); + + rerender( + + ); + expect(onRowsRendered).toHaveBeenCalledTimes(2); + expect(onRowsRendered).toHaveBeenLastCalledWith({ + startIndex: 0, + stopIndex: 3 + }); + }); + + test("should support custom className and style props", () => { + render( + + ); + + const list = screen.queryByRole("list"); + expect(list).toHaveClass("foo"); + expect(list?.style.backgroundColor).toBe("red"); + }); + + test("should spread HTML rest attributes", () => { + render( + + ); + + expect(screen.queryByTestId("foo")).toHaveRole("list"); + }); + + describe("imperative API", () => { + test("should return the root element", () => { + const listRef = createRef(); + + render( + + ); + + expect(listRef.current?.element).toEqual(screen.queryByRole("list")); + }); + + test("should scroll to rows", () => { + const listRef = createRef(); + + render( + + ); + expect(HTMLElement.prototype.scrollTo).not.toHaveBeenCalled(); + + listRef.current?.scrollToRow({ index: 8 }); + + expect(HTMLElement.prototype.scrollTo).toHaveBeenCalledTimes(1); + expect(HTMLElement.prototype.scrollTo).toHaveBeenLastCalledWith({ + behavior: "auto", + top: 125 + }); + }); + }); + + test("should auto-memoize rowProps object using shallow equality", () => { + const { rerender } = render( + + ); + + expect(mountedRows).toHaveLength(5); + expect(mountedRows.get(0)).toMatchObject({ + foo: "abc", + abc: 123 + }); + + expect(RowComponent).toHaveBeenCalledTimes(5); + + rerender( + + ); + expect(RowComponent).toHaveBeenCalledTimes(5); + + rerender( + + ); + expect(RowComponent).toHaveBeenCalledTimes(10); + }); + + describe("edge cases", () => { + test("should restore scroll indices if rowProps changes", () => { + const listRef = createRef(); + const onRowsRendered = vi.fn(); + + const { rerender } = render( + + ); + + expect(onRowsRendered).toHaveBeenCalled(); + expect(onRowsRendered).toHaveBeenLastCalledWith({ + startIndex: 0, + stopIndex: 3 + }); + + onRowsRendered.mockReset(); + + act(() => { + listRef.current?.scrollToRow({ index: 10 }); + }); + expect(onRowsRendered).toHaveBeenCalledTimes(1); + expect(onRowsRendered).toHaveBeenLastCalledWith({ + startIndex: 7, + stopIndex: 10 + }); + + expect(RowComponent).toHaveBeenLastCalledWith( + expect.objectContaining({ + foo: 1 + }), + undefined + ); + + onRowsRendered.mockReset(); + RowComponent.mockReset(); + + rerender( + + ); + + // Visible range of rows should not have changes + expect(onRowsRendered).not.toHaveBeenCalled(); + + // But rows should have been re-rendered + expect(RowComponent).toHaveBeenCalledTimes(4); + expect(RowComponent).toHaveBeenLastCalledWith( + expect.objectContaining({ + foo: 2 + }), + undefined + ); + }); + + test("should handle temporarily invalid indices if rowCount decreases", () => { + function CustomRowComponent({ + index, + items, + style + }: RowComponentProps<{ items: string[] }>) { + return
{items[index].toUpperCase()}
; + } + + const { container, rerender } = render( + + ); + expect(container.textContent).toEqual("ABCD"); + + rerender( + + ); + expect(container.textContent).toEqual("AB"); + }); + + test("should not cause a cycle of List callback ref is passed in rowProps", () => { + function RowComponentWithRowProps({ + index, + style + }: RowComponentProps<{ listRef: ListImperativeAPI | null }>) { + return
{index}
; + } + + function Test() { + const [listRef, setListRef] = useListCallbackRef(null); + + return ( + + ); + } + + render(); + }); + }); +}); diff --git a/lib/components/list/List.tsx b/lib/components/list/List.tsx new file mode 100644 index 00000000..e32c4faa --- /dev/null +++ b/lib/components/list/List.tsx @@ -0,0 +1,140 @@ +import { + memo, + useEffect, + useImperativeHandle, + useMemo, + useState, + type ReactNode +} from "react"; +import { useVirtualizer } from "../../core/useVirtualizer"; +import { useMemoizedObject } from "../../hooks/useMemoizedObject"; +import type { Align } from "../../types"; +import { arePropsEqual } from "../../utils/arePropsEqual"; +import type { ListProps } from "./types"; + +export function List({ + className, + defaultHeight = 0, + listRef, + onResize, + onRowsRendered, + overscanCount = 3, + rowComponent: RowComponentProp, + rowCount, + rowHeight, + rowProps: rowPropsUnstable, + style, + ...rest +}: ListProps) { + const rowProps = useMemoizedObject(rowPropsUnstable); + const RowComponent = useMemo( + () => memo(RowComponentProp, arePropsEqual), + [RowComponentProp] + ); + + const [element, setElement] = useState(null); + + const { + getCellBounds, + getEstimatedSize, + scrollToIndex, + startIndex, + stopIndex + } = useVirtualizer({ + containerElement: element, + defaultContainerSize: defaultHeight, + direction: "vertical", + itemCount: rowCount, + itemProps: rowProps, + itemSize: rowHeight, + onResize, + overscanCount + }); + + useImperativeHandle( + listRef, + () => ({ + get element() { + return element; + }, + + scrollToRow({ + align = "auto", + behavior = "auto", + index + }: { + align?: Align; + behavior?: ScrollBehavior; + index: number; + }) { + scrollToIndex({ + align, + behavior, + containerScrollOffset: element?.scrollTop ?? 0, + index + }); + } + }), + [element, scrollToIndex] + ); + + useEffect(() => { + if (startIndex >= 0 && stopIndex >= 0 && onRowsRendered) { + onRowsRendered({ + startIndex, + stopIndex + }); + } + }, [onRowsRendered, startIndex, stopIndex]); + + const rows = useMemo(() => { + const children: ReactNode[] = []; + if (rowCount > 0) { + for (let index = startIndex; index <= stopIndex; index++) { + const bounds = getCellBounds(index); + + children.push( + + ); + } + } + return children; + }, [RowComponent, getCellBounds, rowCount, rowProps, startIndex, stopIndex]); + + return ( +
+
+ {rows} +
+
+ ); +} diff --git a/lib/components/list/types.ts b/lib/components/list/types.ts new file mode 100644 index 00000000..cdd6cef7 --- /dev/null +++ b/lib/components/list/types.ts @@ -0,0 +1,129 @@ +import type { + ComponentProps, + CSSProperties, + HTMLAttributes, + ReactNode, + Ref +} from "react"; + +type ForbiddenKeys = "index" | "style"; +type ExcludeForbiddenKeys = { + [Key in keyof Type]: Key extends ForbiddenKeys ? never : Type[Key]; +}; + +export type ListProps = Omit< + HTMLAttributes, + "onResize" +> & { + /** + * CSS class name. + */ + className?: string; + + /** + * Default height of list for initial render. + * This value is important for server rendering. + */ + defaultHeight?: number; + + /** + * Ref used to interact with this component's imperative API. + * + * This API has imperative methods for scrolling and a getter for the outermost DOM element. + * + * ⚠️ The `useListRef` and `useListCallbackRef` hooks are exported for convenience use in TypeScript projects. + */ + listRef?: Ref; + + /** + * Callback notified when the List's outermost HTMLElement resizes. + * This may be used to (re)scroll a row into view. + */ + onResize?: ( + size: { height: number; width: number }, + prevSize: { height: number; width: number } + ) => void; + + /** + * Callback notified when the range of visible rows changes. + */ + onRowsRendered?: (args: { startIndex: number; stopIndex: number }) => void; + + /** + * How many additional rows to render outside of the visible area. + * This can reduce visual flickering near the edges of a list when scrolling. + */ + overscanCount?: number; + + /** + * React component responsible for rendering a row. + * + * This component will receive an `index` and `style` prop by default. + * Additionally it will receive prop values passed to `rowProps`. + * + * ⚠️ The prop types for this component are exported as `RowComponentProps` + */ + rowComponent: ( + props: { + index: number; + style: CSSProperties; + } & RowProps + ) => ReactNode; + + /** + * Number of items to be rendered in the list. + */ + rowCount: number; + + /** + * Row height; the following formats are supported: + * - number of pixels (number) + * - percentage of the grid's current height (string) + * - function that returns the row height (in pixels) given an index and `cellProps` + */ + rowHeight: number | string | ((index: number, cellProps: RowProps) => number); + + /** + * Additional props to be passed to the row-rendering component. + * List will automatically re-render rows when values in this object change. + * + * ⚠️ This object must not contain either an `index` or `style` prop. + */ + rowProps: ExcludeForbiddenKeys; + + /** + * Optional CSS properties. + * The list of rows will fill the height defined by this style. + */ + style?: CSSProperties; +}; + +export type RowComponent = + ListProps["rowComponent"]; + +export type RowComponentProps = + ComponentProps>; + +export type OnRowsRendered = NonNullable["onRowsRendered"]>; + +export type CachedBounds = Map< + number, + { + height: number; + scrollTop: number; + } +>; + +export type ListImperativeAPI = { + get element(): HTMLDivElement | null; + + scrollToRow({ + align, + behavior, + index + }: { + align?: "auto" | "center" | "end" | "smart" | "start"; + behavior?: "auto" | "instant" | "smooth"; + index: number; + }): void; +}; diff --git a/lib/components/list/useListCallbackRef.ts b/lib/components/list/useListCallbackRef.ts new file mode 100644 index 00000000..1b3cd6db --- /dev/null +++ b/lib/components/list/useListCallbackRef.ts @@ -0,0 +1,10 @@ +import { useState } from "react"; +import type { ListImperativeAPI } from "./types"; + +/** + * Convenience hook to return a properly typed ref callback for the List component. + * + * Use this hook when you need to share the ref with another component or hook. + */ +export const useListCallbackRef = + useState as typeof useState; diff --git a/lib/components/list/useListRef.ts b/lib/components/list/useListRef.ts new file mode 100644 index 00000000..41af6934 --- /dev/null +++ b/lib/components/list/useListRef.ts @@ -0,0 +1,7 @@ +import { useRef } from "react"; +import type { ListImperativeAPI } from "./types"; + +/** + * Convenience hook to return a properly typed ref for the List component. + */ +export const useListRef = useRef as typeof useRef; diff --git a/lib/core/createCachedBounds.test.ts b/lib/core/createCachedBounds.test.ts new file mode 100644 index 00000000..367ac0fd --- /dev/null +++ b/lib/core/createCachedBounds.test.ts @@ -0,0 +1,69 @@ +import { describe, expect, test, vi } from "vitest"; +import { createCachedBounds } from "./createCachedBounds"; + +describe("createCachedBounds", () => { + test("should lazily measure items before the requested index", () => { + const itemSize = vi.fn((index: number) => 10 + index); + const cachedBounds = createCachedBounds({ + itemCount: 10, + itemProps: {}, + itemSize + }); + + expect(itemSize).not.toHaveBeenCalled(); + expect(cachedBounds.size).toBe(0); + + expect(cachedBounds.get(2)).toEqual({ + scrollOffset: 21, + size: 12 + }); + expect(itemSize).toHaveBeenCalledTimes(3); + expect(cachedBounds.size).toBe(3); + + expect(cachedBounds.get(3)).toEqual({ + scrollOffset: 33, + size: 13 + }); + expect(itemSize).toHaveBeenCalledTimes(4); + expect(cachedBounds.size).toBe(4); + }); + + test("should cached measured sizes", () => { + const itemSize = vi.fn(() => 10); + + const cachedBounds = createCachedBounds({ + itemCount: 10, + itemProps: {}, + itemSize + }); + + expect(itemSize).not.toHaveBeenCalled(); + expect(cachedBounds.size).toBe(0); + + cachedBounds.get(9); + + expect(itemSize).toHaveBeenCalledTimes(10); + expect(cachedBounds.size).toBe(10); + + for (let index = 0; index < 10; index++) { + cachedBounds.get(index); + } + + expect(itemSize).toHaveBeenCalledTimes(10); + expect(cachedBounds.size).toBe(10); + }); + + test("should gracefully handle an empty cache", () => { + const cachedBounds = createCachedBounds({ + itemCount: 0, + itemProps: {}, + itemSize: 10 + }); + + expect(cachedBounds.size).toBe(0); + + expect(() => { + cachedBounds.get(1); + }).toThrow("Invalid index 1"); + }); +}); diff --git a/lib/core/createCachedBounds.ts b/lib/core/createCachedBounds.ts new file mode 100644 index 00000000..3e527473 --- /dev/null +++ b/lib/core/createCachedBounds.ts @@ -0,0 +1,69 @@ +import { assert } from "../utils/assert"; +import type { Bounds, CachedBounds, SizeFunction } from "./types"; + +export function createCachedBounds({ + itemCount, + itemProps, + itemSize +}: { + itemCount: number; + itemProps: Props; + itemSize: number | SizeFunction; +}): CachedBounds { + const cache = new Map(); + + return { + get(index: number) { + assert(index < itemCount, `Invalid index ${index}`); + + while (cache.size - 1 < index) { + const currentIndex = cache.size; + + let size: number; + switch (typeof itemSize) { + case "function": { + size = itemSize(currentIndex, itemProps); + break; + } + case "number": { + size = itemSize; + break; + } + } + + if (currentIndex === 0) { + cache.set(currentIndex, { + size, + scrollOffset: 0 + }); + } else { + const previousRowBounds = cache.get(currentIndex - 1); + assert( + previousRowBounds !== undefined, + `Unexpected bounds cache miss for index ${index}` + ); + + cache.set(currentIndex, { + scrollOffset: + previousRowBounds.scrollOffset + previousRowBounds.size, + size + }); + } + } + + const bounds = cache.get(index); + assert( + bounds !== undefined, + `Unexpected bounds cache miss for index ${index}` + ); + + return bounds; + }, + set(index: number, bounds: Bounds) { + cache.set(index, bounds); + }, + get size() { + return cache.size; + } + }; +} diff --git a/lib/core/getEstimatedSize.test.ts b/lib/core/getEstimatedSize.test.ts new file mode 100644 index 00000000..0b65fd0a --- /dev/null +++ b/lib/core/getEstimatedSize.test.ts @@ -0,0 +1,89 @@ +import { describe, expect, test } from "vitest"; +import { getEstimatedSize } from "./getEstimatedSize"; +import { createCachedBounds } from "./createCachedBounds"; +import { EMPTY_OBJECT } from "../../src/constants"; + +describe("getEstimatedSize", () => { + describe("itemSize: function", () => { + const itemSize = (index: number) => 10 + index * 10; + + test("should return 0 if no measurements can be taken", () => { + expect( + getEstimatedSize({ + cachedBounds: createCachedBounds({ + itemCount: 0, + itemProps: EMPTY_OBJECT, + itemSize + }), + itemCount: 0, + itemSize + }) + ).toBe(0); + }); + + test("should return an average size based on the first item if no measurements have been taken", () => { + expect( + getEstimatedSize({ + cachedBounds: createCachedBounds({ + itemCount: 10, + itemProps: EMPTY_OBJECT, + itemSize + }), + itemCount: 10, + itemSize + }) + ).toBe(100); + }); + + test("should return estimated size based on averages of what has been measured so far", () => { + const cachedBounds = createCachedBounds({ + itemCount: 10, + itemProps: EMPTY_OBJECT, + itemSize + }); + cachedBounds.get(4); + + expect( + getEstimatedSize({ + cachedBounds, + itemCount: 10, + itemSize + }) + ).toBe(300); + }); + + test("should return exact size if all content has been measured", () => { + const cachedBounds = createCachedBounds({ + itemCount: 10, + itemProps: EMPTY_OBJECT, + itemSize + }); + + cachedBounds.get(9); + + expect( + getEstimatedSize({ + cachedBounds, + itemCount: 10, + itemSize + }) + ).toBe(550); + }); + }); + + describe("itemSize: number", () => { + test("should return exact size even if no measurements have been taken", () => { + expect( + getEstimatedSize({ + cachedBounds: createCachedBounds({ + itemCount: 10, + itemProps: EMPTY_OBJECT, + itemSize: 25 + }), + itemCount: 10, + itemSize: 25 + }) + ).toBe(250); + }); + }); +}); diff --git a/lib/core/getEstimatedSize.ts b/lib/core/getEstimatedSize.ts new file mode 100644 index 00000000..c0c2bd51 --- /dev/null +++ b/lib/core/getEstimatedSize.ts @@ -0,0 +1,28 @@ +import type { CachedBounds, SizeFunction } from "./types"; +import { assert } from "../utils/assert"; + +export function getEstimatedSize({ + cachedBounds, + itemCount, + itemSize +}: { + cachedBounds: CachedBounds; + itemCount: number; + itemSize: number | SizeFunction; +}) { + if (itemCount === 0) { + return 0; + } else if (typeof itemSize === "number") { + return itemCount * itemSize; + } else { + const bounds = cachedBounds.get( + cachedBounds.size === 0 ? 0 : cachedBounds.size - 1 + ); + assert(bounds !== undefined, "Unexpected bounds cache miss"); + + const averageItemSize = + (bounds.scrollOffset + bounds.size) / cachedBounds.size; + + return itemCount * averageItemSize; + } +} diff --git a/lib/core/getOffsetForIndex.test.ts b/lib/core/getOffsetForIndex.test.ts new file mode 100644 index 00000000..71915eab --- /dev/null +++ b/lib/core/getOffsetForIndex.test.ts @@ -0,0 +1,128 @@ +import { beforeEach, describe, expect, test } from "vitest"; +import { EMPTY_OBJECT } from "../../src/constants"; +import type { Align } from "../types"; +import { setScrollbarSizeForTests } from "../utils/getScrollbarSize"; +import { createCachedBounds } from "./createCachedBounds"; +import { getOffsetForIndex } from "./getOffsetForIndex"; + +describe("getOffsetForIndex", () => { + beforeEach(() => { + setScrollbarSizeForTests(0); + }); + + // Mimic Size function but with fixed height to simplify tests + const itemSize = () => 10; + + type Params = Parameters[0]; + const DEFAULT_ARGS: Params = { + align: "auto", + cachedBounds: createCachedBounds({ + itemCount: 10, + itemProps: EMPTY_OBJECT, + itemSize + }), + containerScrollOffset: 0, + containerSize: 50, + index: 0, + itemCount: 10, + itemSize + }; + + describe("align", () => { + function createTestHelper(align: Align) { + return function testHelperAuto( + index: number, + expectedOffset: number, + containerScrollOffset: number = 0 + ) { + expect( + getOffsetForIndex({ + ...DEFAULT_ARGS, + align, + index, + containerScrollOffset + }) + ).toBe(expectedOffset); + }; + } + + test("auto", () => { + const testHelper = createTestHelper("auto"); + + // Scroll forward + testHelper(0, 0); + testHelper(4, 0); + testHelper(5, 10); + testHelper(9, 50); + + // Scroll backward + testHelper(0, 0, 100); + testHelper(4, 40, 100); + }); + + test("center", () => { + const testHelper = createTestHelper("center"); + + testHelper(0, 0); + testHelper(1, 0); + testHelper(2, 0); + testHelper(3, 10); + testHelper(4, 20); + testHelper(5, 30); + testHelper(6, 40); + testHelper(7, 50); + testHelper(8, 50); + testHelper(9, 50); + }); + + test("start", () => { + const testHelper = createTestHelper("start"); + + testHelper(0, 0); + testHelper(1, 10); + testHelper(2, 20); + testHelper(3, 30); + testHelper(4, 40); + testHelper(4, 40); + testHelper(5, 50); + testHelper(6, 50); + testHelper(7, 50); + testHelper(8, 50); + testHelper(9, 50); + }); + + test("end", () => { + const testHelper = createTestHelper("end"); + + testHelper(0, 0); + testHelper(1, 0); + testHelper(2, 0); + testHelper(3, 0); + testHelper(4, 0); + testHelper(4, 0); + testHelper(5, 10); + testHelper(6, 20); + testHelper(7, 30); + testHelper(8, 40); + testHelper(9, 50); + }); + + test("smart", () => { + const testHelper = createTestHelper("smart"); + + // Shouldn't scroll if already visible + testHelper(0, 0); + testHelper(3, 0); + testHelper(3, 30, 30); + testHelper(7, 30, 30); + testHelper(7, 50, 50); + testHelper(9, 50, 100); + + // Should center align if not visible + testHelper(3, 10, 100); + testHelper(4, 20, 100); + testHelper(6, 40, 0); + testHelper(7, 50, 0); + }); + }); +}); diff --git a/lib/core/getOffsetForIndex.ts b/lib/core/getOffsetForIndex.ts new file mode 100644 index 00000000..b27ee4f2 --- /dev/null +++ b/lib/core/getOffsetForIndex.ts @@ -0,0 +1,84 @@ +import type { Align } from "../types"; +import { getEstimatedSize } from "./getEstimatedSize"; +import type { CachedBounds, SizeFunction } from "./types"; + +export function getOffsetForIndex({ + align, + cachedBounds, + index, + itemCount, + itemSize, + containerScrollOffset, + containerSize +}: { + align: Align; + cachedBounds: CachedBounds; + index: number; + itemCount: number; + itemSize: number | SizeFunction; + containerScrollOffset: number; + containerSize: number; +}) { + const estimatedTotalSize = getEstimatedSize({ + cachedBounds, + itemCount, + itemSize + }); + + const bounds = cachedBounds.get(index); + const maxOffset = Math.max( + 0, + Math.min(estimatedTotalSize - containerSize, bounds.scrollOffset) + ); + const minOffset = Math.max( + 0, + bounds.scrollOffset - containerSize + bounds.size + ); + + if (align === "smart") { + if ( + containerScrollOffset >= minOffset && + containerScrollOffset <= maxOffset + ) { + align = "auto"; + } else { + align = "center"; + } + } + + switch (align) { + case "start": { + return maxOffset; + } + case "end": { + return minOffset; + } + case "center": { + if (bounds.scrollOffset <= containerSize / 2) { + // Too near the beginning to center-align + return 0; + } else if ( + bounds.scrollOffset + bounds.size / 2 >= + estimatedTotalSize - containerSize / 2 + ) { + // Too near the end to center-align + return estimatedTotalSize - containerSize; + } else { + return bounds.scrollOffset + bounds.size / 2 - containerSize / 2; + } + } + case "auto": + default: { + if ( + containerScrollOffset >= minOffset && + containerScrollOffset <= maxOffset + ) { + return containerScrollOffset; + } else if (containerScrollOffset < minOffset) { + return minOffset; + } else { + return maxOffset; + } + } + } +} diff --git a/lib/core/getStartStopIndices.test.ts b/lib/core/getStartStopIndices.test.ts new file mode 100644 index 00000000..77ce20df --- /dev/null +++ b/lib/core/getStartStopIndices.test.ts @@ -0,0 +1,160 @@ +import { describe, expect, test } from "vitest"; +import { createCachedBounds } from "./createCachedBounds"; +import { getStartStopIndices } from "./getStartStopIndices"; + +describe("getStartStopIndices", () => { + function getIndices({ + containerScrollOffset, + containerSize, + itemCount, + itemSize, + overscanCount = 0 + }: { + containerScrollOffset: number; + containerSize: number; + itemCount: number; + itemSize: number; + overscanCount?: number; + }) { + const cachedBounds = createCachedBounds({ + itemCount: itemCount, + itemProps: {}, + itemSize + }); + + return getStartStopIndices({ + cachedBounds, + containerScrollOffset, + containerSize, + itemCount, + overscanCount + }); + } + + test("empty list", () => { + expect( + getIndices({ + containerScrollOffset: 0, + containerSize: 100, + itemCount: 0, + itemSize: 25 + }) + ).toEqual([0, -1]); + }); + + test("edge case: not enough rows to fill available height", () => { + expect( + getIndices({ + containerScrollOffset: 0, + containerSize: 100, + itemCount: 2, + itemSize: 25 + }) + ).toEqual([0, 1]); + }); + + test("initial set of rows", () => { + expect( + getIndices({ + containerScrollOffset: 0, + containerSize: 100, + itemCount: 10, + itemSize: 25 + }) + ).toEqual([0, 3]); + }); + + test("middle set of list", () => { + expect( + getIndices({ + containerScrollOffset: 100, + containerSize: 100, + itemCount: 10, + itemSize: 25 + }) + ).toEqual([4, 7]); + }); + + test("final set of rows", () => { + expect( + getIndices({ + containerScrollOffset: 150, + containerSize: 100, + itemCount: 10, + itemSize: 25 + }) + ).toEqual([6, 9]); + }); + + test("should not under-scroll", () => { + expect( + getIndices({ + containerScrollOffset: -50, + containerSize: 100, + itemCount: 10, + itemSize: 25 + }) + ).toEqual([0, 1]); + }); + + test("should not over-scroll", () => { + expect( + getIndices({ + containerScrollOffset: 200, + containerSize: 100, + itemCount: 10, + itemSize: 25 + }) + ).toEqual([8, 9]); + }); + + describe("with overscan", () => { + test("edge case: not enough rows to fill available height", () => { + expect( + getIndices({ + containerScrollOffset: 0, + containerSize: 100, + itemCount: 2, + itemSize: 25, + overscanCount: 2 + }) + ).toEqual([0, 1]); + }); + + test("edge case: no rows before", () => { + expect( + getIndices({ + containerScrollOffset: 0, + containerSize: 100, + itemCount: 100, + itemSize: 25, + overscanCount: 2 + }) + ).toEqual([0, 5]); + }); + + test("edge case: no rows after", () => { + expect( + getIndices({ + containerScrollOffset: 2400, + containerSize: 100, + itemCount: 100, + itemSize: 25, + overscanCount: 2 + }) + ).toEqual([94, 99]); + }); + + test("rows before and after", () => { + expect( + getIndices({ + containerScrollOffset: 100, + containerSize: 100, + itemCount: 100, + itemSize: 25, + overscanCount: 2 + }) + ).toEqual([2, 9]); + }); + }); +}); diff --git a/lib/core/getStartStopIndices.ts b/lib/core/getStartStopIndices.ts new file mode 100644 index 00000000..60abee45 --- /dev/null +++ b/lib/core/getStartStopIndices.ts @@ -0,0 +1,52 @@ +import type { CachedBounds } from "./types"; + +export function getStartStopIndices({ + cachedBounds, + containerScrollOffset, + containerSize, + itemCount, + overscanCount +}: { + cachedBounds: CachedBounds; + containerScrollOffset: number; + containerSize: number; + itemCount: number; + overscanCount: number; +}): [number, number] { + const maxIndex = itemCount - 1; + + let startIndex = 0; + let stopIndex = -1; + let currentIndex = 0; + + while (currentIndex < maxIndex) { + const bounds = cachedBounds.get(currentIndex); + + if (bounds.scrollOffset + bounds.size > containerScrollOffset) { + break; + } + + currentIndex++; + } + + startIndex = currentIndex; + startIndex = Math.max(0, startIndex - overscanCount); + + while (currentIndex < maxIndex) { + const bounds = cachedBounds.get(currentIndex); + + if ( + bounds.scrollOffset + bounds.size >= + containerScrollOffset + containerSize + ) { + break; + } + + currentIndex++; + } + + stopIndex = Math.min(maxIndex, currentIndex); + stopIndex = Math.min(itemCount - 1, stopIndex + overscanCount); + + return startIndex < 0 ? [0, -1] : [startIndex, stopIndex]; +} diff --git a/lib/core/types.ts b/lib/core/types.ts new file mode 100644 index 00000000..d9f409f6 --- /dev/null +++ b/lib/core/types.ts @@ -0,0 +1,17 @@ +export type Bounds = { + size: number; + scrollOffset: number; +}; + +export type CachedBounds = { + get(index: number): Bounds; + set(index: number, bounds: Bounds): void; + size: number; +}; + +export type Direction = "horizontal" | "vertical"; + +export type SizeFunction = ( + index: number, + props: Props +) => number; diff --git a/lib/core/useCachedBounds.test.ts b/lib/core/useCachedBounds.test.ts new file mode 100644 index 00000000..0c3dc2d3 --- /dev/null +++ b/lib/core/useCachedBounds.test.ts @@ -0,0 +1,43 @@ +import { renderHook } from "@testing-library/react"; +import { describe, expect, test } from "vitest"; +import { EMPTY_OBJECT } from "../../src/constants"; +import { useCachedBounds } from "./useCachedBounds"; + +describe("useCachedBounds", () => { + test("should cache the CachedBounds unless props change", () => { + const { result, rerender } = renderHook( + (props?: Partial>[0]) => + useCachedBounds({ + itemCount: 1, + itemProps: EMPTY_OBJECT, + itemSize: 10, + ...props + }) + ); + + const cachedBoundsA = result.current; + + rerender({ + itemCount: 1, + itemProps: EMPTY_OBJECT, + itemSize: 10 + }); + expect(result.current).toBe(cachedBoundsA); + + rerender({ + itemCount: 1, + itemProps: EMPTY_OBJECT, + itemSize: 5 + }); + expect(result.current).not.toBe(cachedBoundsA); + + const cachedBoundsB = result.current; + + rerender({ + itemCount: 1, + itemProps: EMPTY_OBJECT, + itemSize: 5 + }); + expect(result.current).toBe(cachedBoundsB); + }); +}); diff --git a/lib/core/useCachedBounds.ts b/lib/core/useCachedBounds.ts new file mode 100644 index 00000000..ce368d8e --- /dev/null +++ b/lib/core/useCachedBounds.ts @@ -0,0 +1,23 @@ +import { useMemo } from "react"; +import { createCachedBounds } from "./createCachedBounds"; +import type { CachedBounds, SizeFunction } from "./types"; + +export function useCachedBounds({ + itemCount, + itemProps, + itemSize +}: { + itemCount: number; + itemProps: Props; + itemSize: number | SizeFunction; +}): CachedBounds { + return useMemo( + () => + createCachedBounds({ + itemCount, + itemProps, + itemSize + }), + [itemCount, itemProps, itemSize] + ); +} diff --git a/lib/core/useIsRtl.ts b/lib/core/useIsRtl.ts new file mode 100644 index 00000000..ee687b5f --- /dev/null +++ b/lib/core/useIsRtl.ts @@ -0,0 +1,19 @@ +import { useLayoutEffect, useState, type HTMLAttributes } from "react"; +import { isRtl } from "../utils/isRtl"; + +export function useIsRtl( + element: HTMLElement | null, + dir: HTMLAttributes["dir"] +) { + const [value, setValue] = useState(dir === "rtl"); + + useLayoutEffect(() => { + if (element) { + if (!dir) { + setValue(isRtl(element)); + } + } + }, [dir, element]); + + return value; +} diff --git a/lib/core/useItemSize.ts b/lib/core/useItemSize.ts new file mode 100644 index 00000000..b86520e3 --- /dev/null +++ b/lib/core/useItemSize.ts @@ -0,0 +1,33 @@ +import { assert } from "../utils/assert"; +import type { SizeFunction } from "./types"; + +export function useItemSize({ + containerSize, + itemSize: itemSizeProp +}: { + containerSize: number; + itemSize: number | string | SizeFunction; +}) { + let itemSize: number | SizeFunction; + switch (typeof itemSizeProp) { + case "string": { + assert( + itemSizeProp.endsWith("%"), + `Invalid item size: "${itemSizeProp}"; string values must be percentages (e.g. "100%")` + ); + assert( + containerSize !== undefined, + "Container size must be defined if a percentage item size is specified" + ); + + itemSize = (containerSize * parseInt(itemSizeProp)) / 100; + break; + } + default: { + itemSize = itemSizeProp; + break; + } + } + + return itemSize; +} diff --git a/lib/core/useVirtualizer.test.ts b/lib/core/useVirtualizer.test.ts new file mode 100644 index 00000000..419ef655 --- /dev/null +++ b/lib/core/useVirtualizer.test.ts @@ -0,0 +1,179 @@ +import { renderHook } from "@testing-library/react"; +import { beforeEach, describe, expect, test } from "vitest"; +import { EMPTY_OBJECT, NOOP_FUNCTION } from "../../src/constants"; +import { updateMockResizeObserver } from "../utils/test/mockResizeObserver"; +import { useVirtualizer } from "./useVirtualizer"; + +describe("useVirtualizer", () => { + const DEFAULT_ARGS: Parameters[0] = { + containerElement: document.body, + defaultContainerSize: 100, + direction: "vertical", + itemCount: 25, + itemProps: EMPTY_OBJECT, + itemSize: 25, + onResize: NOOP_FUNCTION, + overscanCount: 0 + }; + + beforeEach(() => { + updateMockResizeObserver(new DOMRect(0, 0, 50, 100)); + }); + + describe("getCellBounds", () => { + test("itemSize type: number", () => { + const { result } = renderHook(() => + useVirtualizer({ + ...DEFAULT_ARGS, + defaultContainerSize: 100, + itemSize: 25 + }) + ); + expect(result.current.getCellBounds(0)).toEqual({ + scrollOffset: 0, + size: 25 + }); + expect(result.current.getCellBounds(24)).toEqual({ + scrollOffset: 600, + size: 25 + }); + }); + + test("itemSize type: string", () => { + const { result } = renderHook(() => + useVirtualizer({ + ...DEFAULT_ARGS, + defaultContainerSize: 100, + itemSize: "50%" + }) + ); + expect(result.current.getCellBounds(0)).toEqual({ + scrollOffset: 0, + size: 50 + }); + expect(result.current.getCellBounds(24)).toEqual({ + scrollOffset: 1200, + size: 50 + }); + }); + + test("itemSize type: function", () => { + const itemSize = (index: number) => 20 + index * 20; + + const { result } = renderHook(() => + useVirtualizer({ + ...DEFAULT_ARGS, + defaultContainerSize: 100, + itemCount: 10, + itemSize + }) + ); + expect(result.current.getCellBounds(0)).toEqual({ + scrollOffset: 0, + size: 20 + }); + expect(result.current.getCellBounds(9)).toEqual({ + scrollOffset: 900, + size: 200 + }); + }); + }); + + describe("getEstimatedSize", () => { + test("itemSize type: number", () => { + const { result } = renderHook(() => + useVirtualizer({ + ...DEFAULT_ARGS, + defaultContainerSize: 100, + itemSize: 25 + }) + ); + expect(result.current.getEstimatedSize()).toBe(625); + }); + + test("itemSize type: string", () => { + const { result } = renderHook(() => + useVirtualizer({ + ...DEFAULT_ARGS, + defaultContainerSize: 100, + itemSize: "50%" + }) + ); + expect(result.current.getEstimatedSize()).toBe(1250); + }); + + test("itemSize type: function", () => { + const itemSize = (index: number) => 20 + index * 20; + + const { result } = renderHook(() => + useVirtualizer({ + ...DEFAULT_ARGS, + defaultContainerSize: 100, + itemCount: 10, + itemSize + }) + ); + + // Actual size is 1,100 + // Based on the rows measured so far, the estimated size is 400 + expect(result.current.getEstimatedSize()).toBe(400); + + // Finish measuring the rows and the actual size should be returned now + result.current.getCellBounds(9); + expect(result.current.getEstimatedSize()).toBe(1100); + }); + }); + + // scrollToIndex is mostly covered by getOffsetForIndex tests + + describe("startIndex/stopIndex", () => { + test("itemSize type: number", () => { + const { result } = renderHook(() => + useVirtualizer({ + ...DEFAULT_ARGS, + defaultContainerSize: 100, + itemSize: 25 + }) + ); + expect(result.current.startIndex).toBe(0); + expect(result.current.stopIndex).toBe(3); + }); + + test("itemSize type: string", () => { + const { result } = renderHook(() => + useVirtualizer({ + ...DEFAULT_ARGS, + defaultContainerSize: 100, + itemSize: "50%" + }) + ); + expect(result.current.startIndex).toBe(0); + expect(result.current.stopIndex).toBe(1); + }); + + test("itemSize type: function", () => { + const itemSize = (index: number) => 20 + index * 20; + + const { result } = renderHook(() => + useVirtualizer({ + ...DEFAULT_ARGS, + defaultContainerSize: 100, + itemSize + }) + ); + expect(result.current.startIndex).toBe(0); + expect(result.current.stopIndex).toBe(2); + }); + }); + + describe("jsdom", () => { + test("should gracefully degrade if scrollTo method is missing", () => { + // @ts-expect-error Testing + HTMLElement.prototype.scrollTo = undefined; + + const { result } = renderHook(() => useVirtualizer(DEFAULT_ARGS)); + + result.current.scrollToIndex({ containerScrollOffset: 0, index: 5 }); + }); + }); +}); diff --git a/lib/core/useVirtualizer.ts b/lib/core/useVirtualizer.ts new file mode 100644 index 00000000..fa555f39 --- /dev/null +++ b/lib/core/useVirtualizer.ts @@ -0,0 +1,254 @@ +import { + useCallback, + useLayoutEffect, + useRef, + useState, + type CSSProperties +} from "react"; +import { useIsomorphicLayoutEffect } from "../hooks/useIsomorphicLayoutEffect"; +import { useResizeObserver } from "../hooks/useResizeObserver"; +import { useStableCallback } from "../hooks/useStableCallback"; +import type { Align } from "../types"; +import { adjustScrollOffsetForRtl } from "../utils/adjustScrollOffsetForRtl"; +import { getEstimatedSize as getEstimatedSizeUtil } from "./getEstimatedSize"; +import { getOffsetForIndex } from "./getOffsetForIndex"; +import { getStartStopIndices as getStartStopIndicesUtil } from "./getStartStopIndices"; +import type { Direction, SizeFunction } from "./types"; +import { useCachedBounds } from "./useCachedBounds"; +import { useItemSize } from "./useItemSize"; + +export function useVirtualizer({ + containerElement, + containerStyle, + defaultContainerSize = 0, + direction, + isRtl = false, + itemCount, + itemProps, + itemSize: itemSizeProp, + onResize, + overscanCount +}: { + containerElement: HTMLElement | null; + containerStyle?: CSSProperties; + defaultContainerSize?: number; + direction: Direction; + isRtl?: boolean; + itemCount: number; + itemProps: Props; + itemSize: number | string | SizeFunction; + onResize: + | (( + size: { height: number; width: number }, + prevSize: { height: number; width: number } + ) => void) + | undefined; + overscanCount: number; +}) { + const [indices, setIndices] = useState([0, -1]); + + // Guard against temporarily invalid indices that may occur when item count decreases + // Cached bounds object will be re-created and a second render will restore things + const [startIndex, stopIndex] = [ + Math.min(itemCount - 1, indices[0]), + Math.min(itemCount - 1, indices[1]) + ]; + + const { height = defaultContainerSize, width = defaultContainerSize } = + useResizeObserver({ + defaultHeight: + direction === "vertical" ? defaultContainerSize : undefined, + defaultWidth: + direction === "horizontal" ? defaultContainerSize : undefined, + element: containerElement, + mode: direction === "vertical" ? "only-height" : "only-width", + style: containerStyle + }); + + const prevSizeRef = useRef<{ height: number; width: number }>({ + height: 0, + width: 0 + }); + + const containerSize = direction === "vertical" ? height : width; + + const itemSize = useItemSize({ containerSize, itemSize: itemSizeProp }); + + useLayoutEffect(() => { + if (typeof onResize === "function") { + const prevSize = prevSizeRef.current; + + if (prevSize.height !== height || prevSize.width !== width) { + onResize({ height, width }, { ...prevSize }); + + prevSize.height = height; + prevSize.width = width; + } + } + }, [height, onResize, width]); + + const cachedBounds = useCachedBounds({ + itemCount, + itemProps, + itemSize + }); + + const getCellBounds = useCallback( + (index: number) => cachedBounds.get(index), + [cachedBounds] + ); + + const getEstimatedSize = useCallback( + () => + getEstimatedSizeUtil({ + cachedBounds, + itemCount, + itemSize + }), + [cachedBounds, itemCount, itemSize] + ); + + const getStartStopIndices = useCallback( + (scrollOffset: number) => { + const containerScrollOffset = adjustScrollOffsetForRtl({ + containerElement, + direction, + isRtl, + scrollOffset + }); + + return getStartStopIndicesUtil({ + cachedBounds, + containerScrollOffset, + containerSize, + itemCount, + overscanCount + }); + }, + [ + cachedBounds, + containerElement, + containerSize, + direction, + isRtl, + itemCount, + overscanCount + ] + ); + + useIsomorphicLayoutEffect(() => { + const scrollOffset = + (direction === "vertical" + ? containerElement?.scrollTop + : containerElement?.scrollLeft) ?? 0; + + setIndices(getStartStopIndices(scrollOffset)); + }, [containerElement, direction, getStartStopIndices]); + + useIsomorphicLayoutEffect(() => { + if (!containerElement) { + return; + } + + const onScroll = () => { + setIndices((prev) => { + const { scrollLeft, scrollTop } = containerElement; + + const scrollOffset = adjustScrollOffsetForRtl({ + containerElement, + direction, + isRtl, + scrollOffset: direction === "vertical" ? scrollTop : scrollLeft + }); + + const next = getStartStopIndicesUtil({ + cachedBounds, + containerScrollOffset: scrollOffset, + containerSize, + itemCount, + overscanCount + }); + + if (next[0] === prev[0] && next[1] === prev[1]) { + return prev; + } + + return next; + }); + }; + + containerElement.addEventListener("scroll", onScroll); + + return () => { + containerElement.removeEventListener("scroll", onScroll); + }; + }, [ + cachedBounds, + containerElement, + containerSize, + direction, + itemCount, + overscanCount + ]); + + const scrollToIndex = useStableCallback( + ({ + align = "auto", + behavior = "auto", + containerScrollOffset, + index + }: { + align?: Align; + behavior?: ScrollBehavior; + containerScrollOffset: number; + index: number; + }) => { + let scrollOffset = getOffsetForIndex({ + align, + cachedBounds, + containerScrollOffset, + containerSize, + index, + itemCount, + itemSize + }); + + if (containerElement) { + scrollOffset = adjustScrollOffsetForRtl({ + containerElement, + direction, + isRtl, + scrollOffset + }); + + if (typeof containerElement.scrollTo === "function") { + if (direction === "horizontal") { + containerElement.scrollTo({ + left: scrollOffset, + behavior: behavior || undefined + }); + } else { + containerElement.scrollTo({ + behavior: behavior || undefined, + top: scrollOffset + }); + } + } else { + // Special case for environments like jsdom that don't implement scrollTo + const next = getStartStopIndices(scrollOffset); + if (next[0] !== startIndex || next[1] !== stopIndex) { + setIndices(next); + } + } + } + } + ); + + return { + getCellBounds, + getEstimatedSize, + scrollToIndex, + startIndex, + stopIndex + }; +} diff --git a/lib/hooks/useIsomorphicLayoutEffect.ts b/lib/hooks/useIsomorphicLayoutEffect.ts new file mode 100644 index 00000000..ea6a0bc0 --- /dev/null +++ b/lib/hooks/useIsomorphicLayoutEffect.ts @@ -0,0 +1,4 @@ +import { useEffect, useLayoutEffect } from "react"; + +export const useIsomorphicLayoutEffect = + typeof window !== "undefined" ? useLayoutEffect : useEffect; diff --git a/lib/hooks/useMemoizedObject.test.ts b/lib/hooks/useMemoizedObject.test.ts new file mode 100644 index 00000000..cfac99ca --- /dev/null +++ b/lib/hooks/useMemoizedObject.test.ts @@ -0,0 +1,52 @@ +import { renderHook } from "@testing-library/react"; +import { describe, expect, test } from "vitest"; +import { useMemoizedObject } from "./useMemoizedObject"; + +describe("useMemoizedObject", () => { + test("should memoize", () => { + const { result, rerender } = renderHook((props: object) => + useMemoizedObject({ + foo: 123, + bar: "abc", + ...props + }) + ); + + expect(result.current).toEqual({ + foo: 123, + bar: "abc" + }); + + const initial = result.current; + + rerender({ + foo: 123, + bar: "abc" + }); + + expect(result.current).toBe(initial); + }); + + test("should recreate object when a value changes", () => { + const { result, rerender } = renderHook((props: object) => + useMemoizedObject({ + foo: 123, + bar: "abc", + ...props + }) + ); + + const initial = result.current; + + rerender({ + foo: 234, + bar: "abc" + }); + + expect(result.current).not.toBe(initial); + expect(result.current).toEqual({ + foo: 234, + bar: "abc" + }); + }); +}); diff --git a/lib/hooks/useMemoizedObject.ts b/lib/hooks/useMemoizedObject.ts new file mode 100644 index 00000000..c905c73e --- /dev/null +++ b/lib/hooks/useMemoizedObject.ts @@ -0,0 +1,10 @@ +import { useMemo } from "react"; + +export function useMemoizedObject( + unstableObject: Type +): Type { + return useMemo(() => { + return unstableObject; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, Object.values(unstableObject)); +} diff --git a/lib/hooks/useResizeObserver.test.ts b/lib/hooks/useResizeObserver.test.ts new file mode 100644 index 00000000..262b8d1c --- /dev/null +++ b/lib/hooks/useResizeObserver.test.ts @@ -0,0 +1,181 @@ +import { act, renderHook } from "@testing-library/react"; +import { beforeEach, describe, expect, test } from "vitest"; +import { + simulateUnsupportedEnvironmentForTest, + updateMockResizeObserver +} from "../utils/test/mockResizeObserver"; +import { useResizeObserver } from "./useResizeObserver"; + +describe("useResizeObserver", () => { + beforeEach(() => { + updateMockResizeObserver({ height: 100, width: 50 }); + }); + + test("should use default width/height if disabled", () => { + simulateUnsupportedEnvironmentForTest(); + + const element = document.createElement("div"); + + const { result, unmount } = renderHook(() => + useResizeObserver({ + defaultHeight: 50, + defaultWidth: 50, + element, + disabled: true + }) + ); + + // Initial size on mount should be ignored + expect(result.current).toEqual({ + height: 50, + width: 50 + }); + + act(() => { + // Updates should be ignored as well + updateMockResizeObserver({ height: 25, target: element }); + }); + + expect(result.current).toEqual({ + height: 50, + width: 50 + }); + + unmount(); + }); + + test("should update on mount with the measured dimensions", () => { + const element = document.createElement("div"); + + const { result, unmount } = renderHook(() => + useResizeObserver({ + element + }) + ); + + expect(result.current).toEqual({ + height: 100, + width: 50 + }); + + unmount(); + }); + + test("should update when dimensions change", () => { + const element = document.createElement("div"); + + const { result, unmount } = renderHook(() => + useResizeObserver({ + element + }) + ); + + expect(result.current).toEqual({ + height: 100, + width: 50 + }); + + act(() => { + updateMockResizeObserver({ + height: 50, + target: element + }); + }); + + expect(result.current).toEqual({ + height: 50, + width: 50 + }); + + unmount(); + }); + + test("should ignore resize events from other elements", () => { + const otherElement = document.createElement("div"); + + const { result, unmount } = renderHook(() => + useResizeObserver({ + element: document.createElement("div") + }) + ); + + act(() => { + updateMockResizeObserver({ + height: 50, + target: otherElement + }); + }); + + expect(result.current).toEqual({ + height: 100, + width: 50 + }); + + unmount(); + }); + + describe("skip ResizeObserver", () => { + test("if an explicit pixel height is specified", () => { + simulateUnsupportedEnvironmentForTest(); + + const element = document.createElement("div"); + + const { result } = renderHook(() => + useResizeObserver({ + element, + mode: "only-height", + style: { + height: "25px" + } + }) + ); + + expect(result.current).toEqual({ + height: 25, + width: undefined + }); + }); + + test("if an explicit pixel width is specified", () => { + simulateUnsupportedEnvironmentForTest(); + + const element = document.createElement("div"); + + const { result } = renderHook(() => + useResizeObserver({ + element, + mode: "only-width", + style: { + width: "15px" + } + }) + ); + + expect(result.current).toEqual({ + height: undefined, + width: 15 + }); + }); + + test("if an explicit pixel width and height are specified", () => { + simulateUnsupportedEnvironmentForTest(); + + const element = document.createElement("div"); + + const { result } = renderHook(() => + useResizeObserver({ + element, + style: { + height: "25px", + width: "15px" + } + }) + ); + + expect(result.current).toEqual({ + height: 25, + width: 15 + }); + }); + }); +}); diff --git a/lib/hooks/useResizeObserver.ts b/lib/hooks/useResizeObserver.ts new file mode 100644 index 00000000..8058a3a6 --- /dev/null +++ b/lib/hooks/useResizeObserver.ts @@ -0,0 +1,83 @@ +import { useMemo, useState, type CSSProperties } from "react"; +import { parseNumericStyleValue } from "../utils/parseNumericStyleValue"; +import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect"; + +export function useResizeObserver({ + box, + defaultHeight, + defaultWidth, + disabled: disabledProp, + element, + mode, + style +}: { + box?: ResizeObserverBoxOptions; + defaultHeight?: number; + defaultWidth?: number; + disabled?: boolean; + element: HTMLElement | null; + mode?: "only-height" | "only-width"; + style?: CSSProperties; +}) { + const { styleHeight, styleWidth } = useMemo( + () => ({ + styleHeight: parseNumericStyleValue(style?.height), + styleWidth: parseNumericStyleValue(style?.width) + }), + [style?.height, style?.width] + ); + + const [state, setState] = useState<{ + height: number | undefined; + width: number | undefined; + }>({ + height: defaultHeight, + width: defaultWidth + }); + + const disabled = + disabledProp || + (mode === "only-height" && styleHeight !== undefined) || + (mode === "only-width" && styleWidth !== undefined) || + (styleHeight !== undefined && styleWidth !== undefined); + + useIsomorphicLayoutEffect(() => { + if (element === null || disabled) { + return; + } + + const resizeObserver = new ResizeObserver((entries) => { + for (const entry of entries) { + const { contentRect, target } = entry; + if (element === target) { + setState((prevState) => { + if ( + prevState.height === contentRect.height && + prevState.width === contentRect.width + ) { + return prevState; + } + + return { + height: contentRect.height, + width: contentRect.width + }; + }); + } + } + }); + resizeObserver.observe(element, { box }); + + return () => { + resizeObserver?.unobserve(element); + }; + }, [box, disabled, element, styleHeight, styleWidth]); + + return useMemo( + () => ({ + height: styleHeight ?? state.height, + width: styleWidth ?? state.width + }), + [state, styleHeight, styleWidth] + ); +} diff --git a/lib/hooks/useStableCallback.test.tsx b/lib/hooks/useStableCallback.test.tsx new file mode 100644 index 00000000..be126d5e --- /dev/null +++ b/lib/hooks/useStableCallback.test.tsx @@ -0,0 +1,26 @@ +import { fireEvent, render, renderHook, screen } from "@testing-library/react"; +import { describe, expect, test, vi } from "vitest"; + +import { useStableCallback } from "./useStableCallback"; + +describe("useStableCallback()", () => { + test("should not call the callback during render", () => { + const fn = vi.fn(); + const { result } = renderHook(() => useStableCallback(fn)); + + render(); + + expect(fn).not.toHaveBeenCalled(); + }); + + test("should call the callback when the event is triggered", () => { + const fn = vi.fn(); + const { result } = renderHook(() => useStableCallback(fn)); + + render(); + + fireEvent.click(screen.getByText("Click me")); + + expect(fn).toHaveBeenCalled(); + }); +}); diff --git a/lib/hooks/useStableCallback.ts b/lib/hooks/useStableCallback.ts new file mode 100644 index 00000000..e6240de3 --- /dev/null +++ b/lib/hooks/useStableCallback.ts @@ -0,0 +1,19 @@ +import { useCallback, useRef } from "react"; +import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect"; + +// Forked from useEventCallback (usehooks-ts) +export function useStableCallback( + fn: (args: Args) => Return +): (args: Args) => Return { + const ref = useRef(() => { + throw new Error("Cannot call an event handler while rendering."); + }); + + useIsomorphicLayoutEffect(() => { + ref.current = fn; + }, [fn]); + + return useCallback((args: Args) => ref.current?.(args), [ref]) as ( + args: Args + ) => Return; +} diff --git a/lib/index.ts b/lib/index.ts new file mode 100644 index 00000000..452fbb8d --- /dev/null +++ b/lib/index.ts @@ -0,0 +1,18 @@ +export { Grid } from "./components/grid/Grid"; +export { + type CellComponentProps, + type GridImperativeAPI, + type GridProps +} from "./components/grid/types"; +export { useGridCallbackRef } from "./components/grid/useGridCallbackRef"; +export { useGridRef } from "./components/grid/useGridRef"; +export { List } from "./components/list/List"; +export { + type ListImperativeAPI, + type ListProps, + type RowComponentProps +} from "./components/list/types"; +export { useListCallbackRef } from "./components/list/useListCallbackRef"; +export { useListRef } from "./components/list/useListRef"; +export { type Align } from "./types"; +export { getScrollbarSize } from "./utils/getScrollbarSize"; diff --git a/lib/types.ts b/lib/types.ts new file mode 100644 index 00000000..bc9b48fb --- /dev/null +++ b/lib/types.ts @@ -0,0 +1 @@ +export type Align = "auto" | "center" | "end" | "smart" | "start"; diff --git a/lib/utils/adjustScrollOffsetForRtl.ts b/lib/utils/adjustScrollOffsetForRtl.ts new file mode 100644 index 00000000..49bef8d2 --- /dev/null +++ b/lib/utils/adjustScrollOffsetForRtl.ts @@ -0,0 +1,35 @@ +import type { Direction } from "../core/types"; +import { getRTLOffsetType } from "./getRTLOffsetType"; + +export function adjustScrollOffsetForRtl({ + containerElement, + direction, + isRtl, + scrollOffset +}: { + containerElement: HTMLElement | null; + direction: Direction; + isRtl: boolean; + scrollOffset: number; +}) { + // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements. + // This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left). + // So we need to determine which browser behavior we're dealing with, and mimic it. + if (direction === "horizontal") { + if (isRtl) { + switch (getRTLOffsetType()) { + case "negative": { + return -scrollOffset; + } + case "positive-descending": { + if (containerElement) { + const { clientWidth, scrollLeft, scrollWidth } = containerElement; + return scrollWidth - clientWidth - scrollLeft; + } + break; + } + } + } + } + return scrollOffset; +} diff --git a/lib/utils/arePropsEqual.ts b/lib/utils/arePropsEqual.ts new file mode 100644 index 00000000..cbfcba10 --- /dev/null +++ b/lib/utils/arePropsEqual.ts @@ -0,0 +1,17 @@ +import type { CSSProperties } from "react"; +import { shallowCompare } from "./shallowCompare"; + +// Custom comparison function for React.memo() +// It knows to compare individual style props and ignore the wrapper object. +// See https://react.dev/reference/react/memo#memo +export function arePropsEqual( + prevProps: { style: CSSProperties }, + nextProps: { style: CSSProperties } +): boolean { + const { style: prevStyle, ...prevRest } = prevProps; + const { style: nextStyle, ...nextRest } = nextProps; + + return ( + shallowCompare(prevStyle, nextStyle) && shallowCompare(prevRest, nextRest) + ); +} diff --git a/lib/utils/assert.ts b/lib/utils/assert.ts new file mode 100644 index 00000000..ff8d5684 --- /dev/null +++ b/lib/utils/assert.ts @@ -0,0 +1,10 @@ +export function assert( + expectedCondition: unknown, + message: string = "Assertion error" +): asserts expectedCondition { + if (!expectedCondition) { + console.error(message); + + throw Error(message); + } +} diff --git a/lib/utils/getRTLOffsetType.ts b/lib/utils/getRTLOffsetType.ts new file mode 100644 index 00000000..fb5e8b5a --- /dev/null +++ b/lib/utils/getRTLOffsetType.ts @@ -0,0 +1,49 @@ +export type RTLOffsetType = + | "negative" + | "positive-descending" + | "positive-ascending"; + +let cachedRTLResult: RTLOffsetType | null = null; + +// TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements. +// Chrome does not seem to adhere; its scrollLeft values are positive (measured relative to the left). +// Safari's elastic bounce makes detecting this even more complicated wrt potential false positives. +// The safest way to check this is to intentionally set a negative offset, +// and then verify that the subsequent "scroll" event matches the negative offset. +// If it does not match, then we can assume a non-standard RTL scroll implementation. +export function getRTLOffsetType(recalculate: boolean = false): RTLOffsetType { + if (cachedRTLResult === null || recalculate) { + const outerDiv = document.createElement("div"); + const outerStyle = outerDiv.style; + outerStyle.width = "50px"; + outerStyle.height = "50px"; + outerStyle.overflow = "scroll"; + outerStyle.direction = "rtl"; + + const innerDiv = document.createElement("div"); + const innerStyle = innerDiv.style; + innerStyle.width = "100px"; + innerStyle.height = "100px"; + + outerDiv.appendChild(innerDiv); + + document.body.appendChild(outerDiv); + + if (outerDiv.scrollLeft > 0) { + cachedRTLResult = "positive-descending"; + } else { + outerDiv.scrollLeft = 1; + if (outerDiv.scrollLeft === 0) { + cachedRTLResult = "negative"; + } else { + cachedRTLResult = "positive-ascending"; + } + } + + document.body.removeChild(outerDiv); + + return cachedRTLResult; + } + + return cachedRTLResult; +} diff --git a/lib/utils/getScrollbarSize.ts b/lib/utils/getScrollbarSize.ts new file mode 100644 index 00000000..ca385ed8 --- /dev/null +++ b/lib/utils/getScrollbarSize.ts @@ -0,0 +1,23 @@ +let size: number = -1; + +export function getScrollbarSize(recalculate: boolean = false): number { + if (size === -1 || recalculate) { + const div = document.createElement("div"); + const style = div.style; + style.width = "50px"; + style.height = "50px"; + style.overflow = "scroll"; + + document.body.appendChild(div); + + size = div.offsetWidth - div.clientWidth; + + document.body.removeChild(div); + } + + return size; +} + +export function setScrollbarSizeForTests(value: number) { + size = value; +} diff --git a/lib/utils/isRtl.ts b/lib/utils/isRtl.ts new file mode 100644 index 00000000..f466bcaa --- /dev/null +++ b/lib/utils/isRtl.ts @@ -0,0 +1,12 @@ +export function isRtl(element: HTMLElement) { + let currentElement: HTMLElement | null = element; + while (currentElement) { + if (currentElement.dir) { + return currentElement.dir === "rtl"; + } + + currentElement = currentElement.parentElement; + } + + return false; +} diff --git a/lib/utils/parseNumericStyleValue.test.ts b/lib/utils/parseNumericStyleValue.test.ts new file mode 100644 index 00000000..c631b0cf --- /dev/null +++ b/lib/utils/parseNumericStyleValue.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, test } from "vitest"; +import { parseNumericStyleValue } from "./parseNumericStyleValue"; + +describe("parseNumericStyleValue", () => { + test("should handle undefined styles", () => { + expect(parseNumericStyleValue(undefined)).toBe(undefined); + }); + + test("should return numeric styles", () => { + expect(parseNumericStyleValue(20)).toBe(20); + expect(parseNumericStyleValue(10.5)).toBe(10.5); + }); + + test("should parse string pixels styles", () => { + expect(parseNumericStyleValue("20px")).toBe(20); + expect(parseNumericStyleValue("10.5px")).toBe(10.5); + }); + + test("should ignore string percentage styles", () => { + expect(parseNumericStyleValue("100%")).toBe(undefined); + }); + + test("should ignore string relative styles", () => { + expect(parseNumericStyleValue("1rem")).toBe(undefined); + expect(parseNumericStyleValue("1em")).toBe(undefined); + expect(parseNumericStyleValue("1vh")).toBe(undefined); + }); +}); diff --git a/lib/utils/parseNumericStyleValue.ts b/lib/utils/parseNumericStyleValue.ts new file mode 100644 index 00000000..ff0a13a6 --- /dev/null +++ b/lib/utils/parseNumericStyleValue.ts @@ -0,0 +1,19 @@ +import type { CSSProperties } from "react"; + +export function parseNumericStyleValue( + value: CSSProperties["height"] +): number | undefined { + if (value !== undefined) { + switch (typeof value) { + case "number": { + return value; + } + case "string": { + if (value.endsWith("px")) { + return parseFloat(value); + } + break; + } + } + } +} diff --git a/lib/utils/shallowCompare.test.ts b/lib/utils/shallowCompare.test.ts new file mode 100644 index 00000000..10934f7f --- /dev/null +++ b/lib/utils/shallowCompare.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, test } from "vitest"; +import { shallowCompare } from "./shallowCompare"; + +describe("shallowCompare", () => { + test("should respect shallow equality", () => { + const value = { + number: 1, + string: "string", + boolean: true, + symbol: Symbol.for("symbol"), + array: [1, 2, 3], + object: { abc: 123 } + }; + + expect(shallowCompare(value, { ...value })).toBe(true); + }); + + test("should identify changes", () => { + expect( + shallowCompare( + { + foo: 1 + }, + { foo: 2 } + ) + ).toBe(false); + + expect( + shallowCompare( + { + foo: 1 + }, + { bar: 1 } + ) + ).toBe(false); + }); +}); diff --git a/lib/utils/shallowCompare.ts b/lib/utils/shallowCompare.ts new file mode 100644 index 00000000..3e324ca2 --- /dev/null +++ b/lib/utils/shallowCompare.ts @@ -0,0 +1,29 @@ +import { assert } from "./assert"; + +export function shallowCompare( + a: Type | undefined, + b: Type | undefined +) { + if (a === b) { + return true; + } + + if (!!a !== !!b) { + return false; + } + + assert(a !== undefined); + assert(b !== undefined); + + if (Object.keys(a).length !== Object.keys(b).length) { + return false; + } + + for (const key in a) { + if (!Object.is(b[key], a[key])) { + return false; + } + } + + return true; +} diff --git a/lib/utils/test/mockResizeObserver.ts b/lib/utils/test/mockResizeObserver.ts new file mode 100644 index 00000000..52a49d7c --- /dev/null +++ b/lib/utils/test/mockResizeObserver.ts @@ -0,0 +1,104 @@ +import EventEmitter from "node:events"; + +const emitter = new EventEmitter(); +emitter.setMaxListeners(25); + +let disabled: boolean = false; +let entrySize: DOMRectReadOnly = new DOMRect(0, 0, 0, 0); + +export function disableForCurrentTest() { + disabled = true; +} + +export function simulateUnsupportedEnvironmentForTest() { + // @ts-expect-error Simulate API being unsupported + window.ResizeObserver = null; +} + +export function updateMockResizeObserver({ + height, + target, + width +}: { + height?: number; + target?: HTMLElement; + width?: number; +}): void { + entrySize = new DOMRect( + 0, + 0, + width ?? entrySize.width, + height ?? entrySize.height + ); + + emitter.emit("change", target); +} + +export function mockResizeObserver() { + disabled = false; + + const originalResizeObserver = window.ResizeObserver; + + window.ResizeObserver = class implements ResizeObserver { + readonly #callback: ResizeObserverCallback; + #disconnected: boolean = false; + #elements: Set = new Set(); + + constructor(callback: ResizeObserverCallback) { + this.#callback = callback; + + emitter.addListener("change", this.#onChange); + } + + observe(element: HTMLElement) { + if (this.#disconnected) { + return; + } + + this.#elements.add(element); + this.#notify(element); + } + + unobserve(element: HTMLElement) { + this.#elements.delete(element); + } + + disconnect() { + this.#disconnected = true; + this.#elements.clear(); + + emitter.removeListener("change", this.#onChange); + } + + #notify(target: HTMLElement) { + if (disabled) { + return; + } + + this.#callback( + [ + { + borderBoxSize: [], + contentBoxSize: [], + contentRect: entrySize, + devicePixelContentBoxSize: [], + target + } + ], + this + ); + } + + #onChange = (target?: HTMLElement) => { + if (target) { + this.#notify(target); + } else { + this.#elements.forEach((element) => this.#notify(element)); + } + }; + }; + + return function unmockResizeObserver() { + window.ResizeObserver = originalResizeObserver; + }; +} diff --git a/lib/utils/test/mockScrollTo.ts b/lib/utils/test/mockScrollTo.ts new file mode 100644 index 00000000..34bfc62a --- /dev/null +++ b/lib/utils/test/mockScrollTo.ts @@ -0,0 +1,33 @@ +import { vi } from "vitest"; + +export function mockScrollTo() { + const originalScrollTo = HTMLElement.prototype.scrollTo; + + // @ts-expect-error Support subset of the API for testing + HTMLElement.prototype.scrollTo = function scrollTo({ + left, + top + }: ScrollToOptions) { + if (left !== undefined) { + this.scrollLeft = left; + } + if (top !== undefined) { + this.scrollTop = top; + } + + const event = new Event("scroll", { + bubbles: true, + cancelable: true, + composed: false + }); + + this.dispatchEvent(event); + }; + + // @ts-expect-error Support subset of the API for testing + HTMLElement.prototype.scrollTo = vi.fn(HTMLElement.prototype.scrollTo); + + return function unmockScrollTo() { + HTMLElement.prototype.scrollTo = originalScrollTo; + }; +} diff --git a/package.json b/package.json index 65262eb3..f62b651f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "react-window", - "version": "1.8.11", - "description": "React components for efficiently rendering large, scrollable lists and tabular data", + "version": "2.0.0-alpha.7", + "type": "module", "author": "Brian Vaughn (https://github.com/bvaughn/)", "contributors": [ "Brian Vaughn (https://github.com/bvaughn/)" @@ -12,103 +12,84 @@ "type": "git", "url": "https://github.com/bvaughn/react-window.git" }, - "bugs": { - "url": "https://github.com/bvaughn/react-window/issues" - }, - "engines": { - "node": ">8.0.0" - }, - "keywords": [ - "react", - "reactjs", - "virtual", - "window", - "windowed", - "list", - "scrolling", - "infinite", - "virtualized", - "table", - "grid", - "spreadsheet" - ], - "main": "dist/index.cjs.js", - "module": "dist/index.esm.js", + "main": "dist/react-window.cjs", + "module": "dist/react-window.js", + "types": "dist/react-window.d.ts", "files": [ "dist", - "src/*.js" + "docs" ], - "sideEffects": false, "scripts": { - "flow": "flow check --max-warnings=0 src && flow check website", - "precommit": "lint-staged", - "prettier": "prettier --write '**/*.{js,json,css}'", - "linc": "lint-staged", - "lint": "eslint '**/*.js'", - "test": "cross-env CI=1 react-scripts test --env=jsdom", - "test:watch": "react-scripts test --env=jsdom", - "build:flow": "cp flow-template dist/index.cjs.js.flow && cp flow-template dist/index.esm.js.flow", - "build:source": "rollup -c", - "build": "del dist && mkdir dist && yarn build:flow && yarn build:source", - "start": "rollup -c -w", - "prepare": "yarn run build", - "website:build": "cd website && yarn build", - "website:deploy": "cd website && yarn deploy", - "website:run": "cd website && yarn start" + "dev": "vite", + "build": "pnpm run build:lib && pnpm run build:docs", + "build:docs": "TARGET=docs vite build", + "build:lib": "TARGET=lib vite build", + "compile": "pnpm run compile:code-snippets && pnpm run compile:docs", + "compile:code-snippets": "node ./scripts/code-snippets/run.mjs", + "compile:docs": "node ./scripts/docs/run.mjs", + "lint": "eslint .", + "prerelease": "rm -rf dist && rm -rf docs && pnpm run build", + "prettier": "prettier --write \"**/*.{css,html,js,json,jsx,ts,tsx}\"", + "prettier:ci": "prettier --check \"**/*.{css,html,js,json,jsx,ts,tsx}\"", + "preview": "vite preview", + "test": "vitest", + "test:ci": "vitest run", + "test:debug": "vitest --inspect-brk=127.0.0.1:3000 --no-file-parallelism", + "tsc": "tsc -b" }, "lint-staged": { - "{website,src}/**/*.{js,json,css}": [ - "prettier --write", - "git add" - ], - "**/*.js": "eslint --max-warnings 0" - }, - "dependencies": { - "@babel/runtime": "^7.0.0", - "memoize-one": ">=3.1.1 <6" + "**/*": "prettier --write --ignore-unknown" }, "peerDependencies": { - "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" }, "devDependencies": { - "@babel/core": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/preset-env": "^7.0.0", - "@babel/preset-flow": "^7.0.0", - "babel-core": "^7.0.0-bridge.0", - "babel-eslint": "^9.0.0", - "babel-plugin-annotate-pure-calls": "^0.3.0", - "cross-env": "^5.1.4", - "del-cli": "^1.1.0", - "eslint": "^4.19.1", - "eslint-config-prettier": "^2.9.0", - "eslint-config-react-app": "^2.1.0", - "eslint-config-standard": "^11.0.0", - "eslint-config-standard-react": "^6.0.0", - "eslint-plugin-flowtype": "^2.47.1", - "eslint-plugin-import": "^2.11.0", - "eslint-plugin-jsx-a11y": "^5", - "eslint-plugin-node": "^6.0.1", - "eslint-plugin-prettier": "^2.6.0", - "eslint-plugin-promise": "^3.7.0", - "eslint-plugin-react": "^7.7.0", - "eslint-plugin-standard": "^3.0.1", - "flow-bin": "^0.111.0", - "gh-pages": "^1.1.0", - "lint-staged": "^7.0.5", - "prettier": "^1.12.1", - "react": "^17.0.1", - "react-dom": "^17.0.1", - "react-is": "^17.0.1", - "react-scripts": "^1.1.1", - "react-test-renderer": "^17.0.1", - "rollup": "^1.4.1", - "rollup-plugin-babel": "^4.3.2", - "rollup-plugin-commonjs": "^9.2.1", - "rollup-plugin-node-resolve": "^4.0.1", - "rollup-plugin-replace": "^2.2.0", - "rollup-plugin-terser": "^5.1.0" + "@codemirror/lang-css": "latest", + "@codemirror/lang-html": "latest", + "@codemirror/lang-javascript": "latest", + "@codemirror/lang-markdown": "latest", + "@codemirror/language": "latest", + "@codemirror/state": "latest", + "@eslint/js": "^9.30.1", + "@headlessui/react": "^2.2.4", + "@headlessui/tailwindcss": "^0.2.2", + "@heroicons/react": "^2.2.0", + "@lezer/highlight": "latest", + "@tailwindcss/vite": "^4.1.11", + "@tailwindplus/elements": "^1.0.5", + "@testing-library/jest-dom": "^6.6.4", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/node": "^24.2.0", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@vitejs/plugin-react-swc": "^3.10.2", + "clsx": "^2.1.1", + "eslint": "^9.30.1", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^16.3.0", + "husky": "^9.1.7", + "jsdom": "^26.1.0", + "lint-staged": "^16.1.4", + "prettier": "3.6.2", + "react": "^19.1.0", + "react-docgen-typescript": "^2.4.0", + "react-dom": "^19.1.0", + "react-error-boundary": "^6.0.0", + "react-router-dom": "^7.6.3", + "rollup-plugin-visualizer": "^6.0.3", + "tailwind-merge": "^3.3.1", + "tailwindcss": "^4.1.11", + "ts-blank-space": "^0.6.2", + "typescript": "~5.8.3", + "typescript-eslint": "^8.35.1", + "typescript-json-schema": "^0.65.1", + "vite": "^7.0.4", + "vite-plugin-dts": "^4.5.4", + "vite-plugin-svgr": "^4.3.0", + "vitest": "^3.2.4", + "zustand": "^5.0.7" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 00000000..af4e7c4e --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,7103 @@ +lockfileVersion: "9.0" + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + .: + devDependencies: + "@codemirror/lang-css": + specifier: latest + version: 6.3.1 + "@codemirror/lang-html": + specifier: latest + version: 6.4.9 + "@codemirror/lang-javascript": + specifier: latest + version: 6.2.4 + "@codemirror/lang-markdown": + specifier: latest + version: 6.3.4 + "@codemirror/language": + specifier: latest + version: 6.11.2 + "@codemirror/state": + specifier: latest + version: 6.5.2 + "@eslint/js": + specifier: ^9.30.1 + version: 9.30.1 + "@headlessui/react": + specifier: ^2.2.4 + version: 2.2.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + "@headlessui/tailwindcss": + specifier: ^0.2.2 + version: 0.2.2(tailwindcss@4.1.11) + "@heroicons/react": + specifier: ^2.2.0 + version: 2.2.0(react@19.1.0) + "@lezer/highlight": + specifier: latest + version: 1.2.1 + "@tailwindcss/vite": + specifier: ^4.1.11 + version: 4.1.11(vite@7.0.6(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)) + "@tailwindplus/elements": + specifier: ^1.0.5 + version: 1.0.5 + "@testing-library/jest-dom": + specifier: ^6.6.4 + version: 6.6.4 + "@testing-library/react": + specifier: ^16.3.0 + version: 16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + "@testing-library/user-event": + specifier: ^14.6.1 + version: 14.6.1(@testing-library/dom@10.4.0) + "@types/node": + specifier: ^24.2.0 + version: 24.2.0 + "@types/react": + specifier: ^19.1.8 + version: 19.1.8 + "@types/react-dom": + specifier: ^19.1.6 + version: 19.1.6(@types/react@19.1.8) + "@vitejs/plugin-react-swc": + specifier: ^3.10.2 + version: 3.10.2(@swc/helpers@0.5.17)(vite@7.0.6(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)) + clsx: + specifier: ^2.1.1 + version: 2.1.1 + eslint: + specifier: ^9.30.1 + version: 9.30.1(jiti@2.4.2) + eslint-plugin-react-hooks: + specifier: ^5.2.0 + version: 5.2.0(eslint@9.30.1(jiti@2.4.2)) + eslint-plugin-react-refresh: + specifier: ^0.4.20 + version: 0.4.20(eslint@9.30.1(jiti@2.4.2)) + globals: + specifier: ^16.3.0 + version: 16.3.0 + husky: + specifier: ^9.1.7 + version: 9.1.7 + jsdom: + specifier: ^26.1.0 + version: 26.1.0 + lint-staged: + specifier: ^16.1.4 + version: 16.1.4 + prettier: + specifier: 3.6.2 + version: 3.6.2 + react: + specifier: ^19.1.0 + version: 19.1.0 + react-docgen-typescript: + specifier: ^2.4.0 + version: 2.4.0(typescript@5.8.3) + react-dom: + specifier: ^19.1.0 + version: 19.1.0(react@19.1.0) + react-error-boundary: + specifier: ^6.0.0 + version: 6.0.0(react@19.1.0) + react-router-dom: + specifier: ^7.6.3 + version: 7.6.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rollup-plugin-visualizer: + specifier: ^6.0.3 + version: 6.0.3(rollup@4.44.2) + tailwind-merge: + specifier: ^3.3.1 + version: 3.3.1 + tailwindcss: + specifier: ^4.1.11 + version: 4.1.11 + ts-blank-space: + specifier: ^0.6.2 + version: 0.6.2 + typescript: + specifier: ~5.8.3 + version: 5.8.3 + typescript-eslint: + specifier: ^8.35.1 + version: 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + typescript-json-schema: + specifier: ^0.65.1 + version: 0.65.1(@swc/core@1.12.9(@swc/helpers@0.5.17)) + vite: + specifier: ^7.0.4 + version: 7.0.6(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0) + vite-plugin-dts: + specifier: ^4.5.4 + version: 4.5.4(@types/node@24.2.0)(rollup@4.44.2)(typescript@5.8.3)(vite@7.0.6(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)) + vite-plugin-svgr: + specifier: ^4.3.0 + version: 4.3.0(rollup@4.44.2)(typescript@5.8.3)(vite@7.0.6(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)) + vitest: + specifier: ^3.2.4 + version: 3.2.4(@types/node@24.2.0)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(yaml@2.8.0) + zustand: + specifier: ^5.0.7 + version: 5.0.7(@types/react@19.1.8)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)) + +packages: + "@adobe/css-tools@4.4.3": + resolution: + { + integrity: sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA== + } + + "@ampproject/remapping@2.3.0": + resolution: + { + integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + } + engines: { node: ">=6.0.0" } + + "@asamuzakjp/css-color@3.2.0": + resolution: + { + integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw== + } + + "@babel/code-frame@7.27.1": + resolution: + { + integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== + } + engines: { node: ">=6.9.0" } + + "@babel/compat-data@7.28.0": + resolution: + { + integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw== + } + engines: { node: ">=6.9.0" } + + "@babel/core@7.28.0": + resolution: + { + integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ== + } + engines: { node: ">=6.9.0" } + + "@babel/generator@7.28.0": + resolution: + { + integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg== + } + engines: { node: ">=6.9.0" } + + "@babel/helper-compilation-targets@7.27.2": + resolution: + { + integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== + } + engines: { node: ">=6.9.0" } + + "@babel/helper-globals@7.28.0": + resolution: + { + integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + } + engines: { node: ">=6.9.0" } + + "@babel/helper-module-imports@7.27.1": + resolution: + { + integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== + } + engines: { node: ">=6.9.0" } + + "@babel/helper-module-transforms@7.27.3": + resolution: + { + integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg== + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0 + + "@babel/helper-string-parser@7.27.1": + resolution: + { + integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + } + engines: { node: ">=6.9.0" } + + "@babel/helper-validator-identifier@7.27.1": + resolution: + { + integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== + } + engines: { node: ">=6.9.0" } + + "@babel/helper-validator-option@7.27.1": + resolution: + { + integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + } + engines: { node: ">=6.9.0" } + + "@babel/helpers@7.27.6": + resolution: + { + integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug== + } + engines: { node: ">=6.9.0" } + + "@babel/parser@7.28.0": + resolution: + { + integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g== + } + engines: { node: ">=6.0.0" } + hasBin: true + + "@babel/runtime@7.27.6": + resolution: + { + integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q== + } + engines: { node: ">=6.9.0" } + + "@babel/template@7.27.2": + resolution: + { + integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== + } + engines: { node: ">=6.9.0" } + + "@babel/traverse@7.28.0": + resolution: + { + integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg== + } + engines: { node: ">=6.9.0" } + + "@babel/types@7.28.0": + resolution: + { + integrity: sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg== + } + engines: { node: ">=6.9.0" } + + "@codemirror/autocomplete@6.18.6": + resolution: + { + integrity: sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg== + } + + "@codemirror/lang-css@6.3.1": + resolution: + { + integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg== + } + + "@codemirror/lang-html@6.4.9": + resolution: + { + integrity: sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q== + } + + "@codemirror/lang-javascript@6.2.4": + resolution: + { + integrity: sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA== + } + + "@codemirror/lang-markdown@6.3.4": + resolution: + { + integrity: sha512-fBm0BO03azXnTAsxhONDYHi/qWSI+uSEIpzKM7h/bkIc9fHnFp9y7KTMXKON0teNT97pFhc1a9DQTtWBYEZ7ug== + } + + "@codemirror/language@6.11.2": + resolution: + { + integrity: sha512-p44TsNArL4IVXDTbapUmEkAlvWs2CFQbcfc0ymDsis1kH2wh0gcY96AS29c/vp2d0y2Tquk1EDSaawpzilUiAw== + } + + "@codemirror/lint@6.8.5": + resolution: + { + integrity: sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA== + } + + "@codemirror/state@6.5.2": + resolution: + { + integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA== + } + + "@codemirror/view@6.38.1": + resolution: + { + integrity: sha512-RmTOkE7hRU3OVREqFVITWHz6ocgBjv08GoePscAakgVQfciA3SGCEk7mb9IzwW61cKKmlTpHXG6DUE5Ubx+MGQ== + } + + "@cspotcode/source-map-support@0.8.1": + resolution: + { + integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + } + engines: { node: ">=12" } + + "@csstools/color-helpers@5.0.2": + resolution: + { + integrity: sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA== + } + engines: { node: ">=18" } + + "@csstools/css-calc@2.1.4": + resolution: + { + integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ== + } + engines: { node: ">=18" } + peerDependencies: + "@csstools/css-parser-algorithms": ^3.0.5 + "@csstools/css-tokenizer": ^3.0.4 + + "@csstools/css-color-parser@3.0.10": + resolution: + { + integrity: sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg== + } + engines: { node: ">=18" } + peerDependencies: + "@csstools/css-parser-algorithms": ^3.0.5 + "@csstools/css-tokenizer": ^3.0.4 + + "@csstools/css-parser-algorithms@3.0.5": + resolution: + { + integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ== + } + engines: { node: ">=18" } + peerDependencies: + "@csstools/css-tokenizer": ^3.0.4 + + "@csstools/css-tokenizer@3.0.4": + resolution: + { + integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw== + } + engines: { node: ">=18" } + + "@esbuild/aix-ppc64@0.25.5": + resolution: + { + integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA== + } + engines: { node: ">=18" } + cpu: [ppc64] + os: [aix] + + "@esbuild/android-arm64@0.25.5": + resolution: + { + integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg== + } + engines: { node: ">=18" } + cpu: [arm64] + os: [android] + + "@esbuild/android-arm@0.25.5": + resolution: + { + integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA== + } + engines: { node: ">=18" } + cpu: [arm] + os: [android] + + "@esbuild/android-x64@0.25.5": + resolution: + { + integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw== + } + engines: { node: ">=18" } + cpu: [x64] + os: [android] + + "@esbuild/darwin-arm64@0.25.5": + resolution: + { + integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ== + } + engines: { node: ">=18" } + cpu: [arm64] + os: [darwin] + + "@esbuild/darwin-x64@0.25.5": + resolution: + { + integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ== + } + engines: { node: ">=18" } + cpu: [x64] + os: [darwin] + + "@esbuild/freebsd-arm64@0.25.5": + resolution: + { + integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw== + } + engines: { node: ">=18" } + cpu: [arm64] + os: [freebsd] + + "@esbuild/freebsd-x64@0.25.5": + resolution: + { + integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw== + } + engines: { node: ">=18" } + cpu: [x64] + os: [freebsd] + + "@esbuild/linux-arm64@0.25.5": + resolution: + { + integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg== + } + engines: { node: ">=18" } + cpu: [arm64] + os: [linux] + + "@esbuild/linux-arm@0.25.5": + resolution: + { + integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw== + } + engines: { node: ">=18" } + cpu: [arm] + os: [linux] + + "@esbuild/linux-ia32@0.25.5": + resolution: + { + integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA== + } + engines: { node: ">=18" } + cpu: [ia32] + os: [linux] + + "@esbuild/linux-loong64@0.25.5": + resolution: + { + integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg== + } + engines: { node: ">=18" } + cpu: [loong64] + os: [linux] + + "@esbuild/linux-mips64el@0.25.5": + resolution: + { + integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg== + } + engines: { node: ">=18" } + cpu: [mips64el] + os: [linux] + + "@esbuild/linux-ppc64@0.25.5": + resolution: + { + integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ== + } + engines: { node: ">=18" } + cpu: [ppc64] + os: [linux] + + "@esbuild/linux-riscv64@0.25.5": + resolution: + { + integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA== + } + engines: { node: ">=18" } + cpu: [riscv64] + os: [linux] + + "@esbuild/linux-s390x@0.25.5": + resolution: + { + integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ== + } + engines: { node: ">=18" } + cpu: [s390x] + os: [linux] + + "@esbuild/linux-x64@0.25.5": + resolution: + { + integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw== + } + engines: { node: ">=18" } + cpu: [x64] + os: [linux] + + "@esbuild/netbsd-arm64@0.25.5": + resolution: + { + integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw== + } + engines: { node: ">=18" } + cpu: [arm64] + os: [netbsd] + + "@esbuild/netbsd-x64@0.25.5": + resolution: + { + integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ== + } + engines: { node: ">=18" } + cpu: [x64] + os: [netbsd] + + "@esbuild/openbsd-arm64@0.25.5": + resolution: + { + integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw== + } + engines: { node: ">=18" } + cpu: [arm64] + os: [openbsd] + + "@esbuild/openbsd-x64@0.25.5": + resolution: + { + integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg== + } + engines: { node: ">=18" } + cpu: [x64] + os: [openbsd] + + "@esbuild/sunos-x64@0.25.5": + resolution: + { + integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA== + } + engines: { node: ">=18" } + cpu: [x64] + os: [sunos] + + "@esbuild/win32-arm64@0.25.5": + resolution: + { + integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw== + } + engines: { node: ">=18" } + cpu: [arm64] + os: [win32] + + "@esbuild/win32-ia32@0.25.5": + resolution: + { + integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ== + } + engines: { node: ">=18" } + cpu: [ia32] + os: [win32] + + "@esbuild/win32-x64@0.25.5": + resolution: + { + integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g== + } + engines: { node: ">=18" } + cpu: [x64] + os: [win32] + + "@eslint-community/eslint-utils@4.7.0": + resolution: + { + integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw== + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + "@eslint-community/regexpp@4.12.1": + resolution: + { + integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== + } + engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } + + "@eslint/config-array@0.21.0": + resolution: + { + integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/config-helpers@0.3.0": + resolution: + { + integrity: sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/core@0.14.0": + resolution: + { + integrity: sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/core@0.15.1": + resolution: + { + integrity: sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/eslintrc@3.3.1": + resolution: + { + integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/js@9.30.1": + resolution: + { + integrity: sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/object-schema@2.1.6": + resolution: + { + integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/plugin-kit@0.3.3": + resolution: + { + integrity: sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@floating-ui/core@1.7.2": + resolution: + { + integrity: sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw== + } + + "@floating-ui/dom@1.7.2": + resolution: + { + integrity: sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA== + } + + "@floating-ui/react-dom@2.1.4": + resolution: + { + integrity: sha512-JbbpPhp38UmXDDAu60RJmbeme37Jbgsm7NrHGgzYYFKmblzRUh6Pa641dII6LsjwF4XlScDrde2UAzDo/b9KPw== + } + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + + "@floating-ui/react@0.26.28": + resolution: + { + integrity: sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw== + } + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + + "@floating-ui/utils@0.2.10": + resolution: + { + integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ== + } + + "@headlessui/react@2.2.4": + resolution: + { + integrity: sha512-lz+OGcAH1dK93rgSMzXmm1qKOJkBUqZf1L4M8TWLNplftQD3IkoEDdUFNfAn4ylsN6WOTVtWaLmvmaHOUk1dTA== + } + engines: { node: ">=10" } + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + react-dom: ^18 || ^19 || ^19.0.0-rc + + "@headlessui/tailwindcss@0.2.2": + resolution: + { + integrity: sha512-xNe42KjdyA4kfUKLLPGzME9zkH7Q3rOZ5huFihWNWOQFxnItxPB3/67yBI8/qBfY8nwBRx5GHn4VprsoluVMGw== + } + engines: { node: ">=10" } + peerDependencies: + tailwindcss: ^3.0 || ^4.0 + + "@heroicons/react@2.2.0": + resolution: + { + integrity: sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ== + } + peerDependencies: + react: ">= 16 || ^19.0.0-rc" + + "@humanfs/core@0.19.1": + resolution: + { + integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== + } + engines: { node: ">=18.18.0" } + + "@humanfs/node@0.16.6": + resolution: + { + integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw== + } + engines: { node: ">=18.18.0" } + + "@humanwhocodes/module-importer@1.0.1": + resolution: + { + integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + } + engines: { node: ">=12.22" } + + "@humanwhocodes/retry@0.3.1": + resolution: + { + integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== + } + engines: { node: ">=18.18" } + + "@humanwhocodes/retry@0.4.3": + resolution: + { + integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== + } + engines: { node: ">=18.18" } + + "@isaacs/balanced-match@4.0.1": + resolution: + { + integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ== + } + engines: { node: 20 || >=22 } + + "@isaacs/brace-expansion@5.0.0": + resolution: + { + integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA== + } + engines: { node: 20 || >=22 } + + "@isaacs/fs-minipass@4.0.1": + resolution: + { + integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w== + } + engines: { node: ">=18.0.0" } + + "@jridgewell/gen-mapping@0.3.12": + resolution: + { + integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg== + } + + "@jridgewell/resolve-uri@3.1.2": + resolution: + { + integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + } + engines: { node: ">=6.0.0" } + + "@jridgewell/sourcemap-codec@1.5.4": + resolution: + { + integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw== + } + + "@jridgewell/trace-mapping@0.3.29": + resolution: + { + integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ== + } + + "@jridgewell/trace-mapping@0.3.9": + resolution: + { + integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + } + + "@lezer/common@1.2.3": + resolution: + { + integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA== + } + + "@lezer/css@1.3.0": + resolution: + { + integrity: sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw== + } + + "@lezer/highlight@1.2.1": + resolution: + { + integrity: sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA== + } + + "@lezer/html@1.3.10": + resolution: + { + integrity: sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w== + } + + "@lezer/javascript@1.5.1": + resolution: + { + integrity: sha512-ATOImjeVJuvgm3JQ/bpo2Tmv55HSScE2MTPnKRMRIPx2cLhHGyX2VnqpHhtIV1tVzIjZDbcWQm+NCTF40ggZVw== + } + + "@lezer/lr@1.4.2": + resolution: + { + integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA== + } + + "@lezer/markdown@1.4.3": + resolution: + { + integrity: sha512-kfw+2uMrQ/wy/+ONfrH83OkdFNM0ye5Xq96cLlaCy7h5UT9FO54DU4oRoIc0CSBh5NWmWuiIJA7NGLMJbQ+Oxg== + } + + "@marijn/find-cluster-break@1.0.2": + resolution: + { + integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g== + } + + "@microsoft/api-extractor-model@7.30.7": + resolution: + { + integrity: sha512-TBbmSI2/BHpfR9YhQA7nH0nqVmGgJ0xH0Ex4D99/qBDAUpnhA2oikGmdXanbw9AWWY/ExBYIpkmY8dBHdla3YQ== + } + + "@microsoft/api-extractor@7.52.10": + resolution: + { + integrity: sha512-LhKytJM5ZJkbHQVfW/3o747rZUNs/MGg6j/wt/9qwwqEOfvUDTYXXxIBuMgrRXhJ528p41iyz4zjBVHZU74Odg== + } + hasBin: true + + "@microsoft/tsdoc-config@0.17.1": + resolution: + { + integrity: sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw== + } + + "@microsoft/tsdoc@0.15.1": + resolution: + { + integrity: sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw== + } + + "@nodelib/fs.scandir@2.1.5": + resolution: + { + integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + } + engines: { node: ">= 8" } + + "@nodelib/fs.stat@2.0.5": + resolution: + { + integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + } + engines: { node: ">= 8" } + + "@nodelib/fs.walk@1.2.8": + resolution: + { + integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + } + engines: { node: ">= 8" } + + "@react-aria/focus@3.20.5": + resolution: + { + integrity: sha512-JpFtXmWQ0Oca7FcvkqgjSyo6xEP7v3oQOLUId6o0xTvm4AD5W0mU2r3lYrbhsJ+XxdUUX4AVR5473sZZ85kU4A== + } + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + "@react-aria/interactions@3.25.3": + resolution: + { + integrity: sha512-J1bhlrNtjPS/fe5uJQ+0c7/jiXniwa4RQlP+Emjfc/iuqpW2RhbF9ou5vROcLzWIyaW8tVMZ468J68rAs/aZ5A== + } + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + "@react-aria/ssr@3.9.9": + resolution: + { + integrity: sha512-2P5thfjfPy/np18e5wD4WPt8ydNXhij1jwA8oehxZTFqlgVMGXzcWKxTb4RtJrLFsqPO7RUQTiY8QJk0M4Vy2g== + } + engines: { node: ">= 12" } + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + "@react-aria/utils@3.29.1": + resolution: + { + integrity: sha512-yXMFVJ73rbQ/yYE/49n5Uidjw7kh192WNN9PNQGV0Xoc7EJUlSOxqhnpHmYTyO0EotJ8fdM1fMH8durHjUSI8g== + } + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + "@react-stately/flags@3.1.2": + resolution: + { + integrity: sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg== + } + + "@react-stately/utils@3.10.7": + resolution: + { + integrity: sha512-cWvjGAocvy4abO9zbr6PW6taHgF24Mwy/LbQ4TC4Aq3tKdKDntxyD+sh7AkSRfJRT2ccMVaHVv2+FfHThd3PKQ== + } + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + "@react-types/shared@3.30.0": + resolution: + { + integrity: sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog== + } + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + "@rolldown/pluginutils@1.0.0-beta.11": + resolution: + { + integrity: sha512-L/gAA/hyCSuzTF1ftlzUSI/IKr2POHsv1Dd78GfqkR83KMNuswWD61JxGV2L7nRwBBBSDr6R1gCkdTmoN7W4ag== + } + + "@rollup/pluginutils@5.2.0": + resolution: + { + integrity: sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw== + } + engines: { node: ">=14.0.0" } + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + "@rollup/rollup-android-arm-eabi@4.44.2": + resolution: + { + integrity: sha512-g0dF8P1e2QYPOj1gu7s/3LVP6kze9A7m6x0BZ9iTdXK8N5c2V7cpBKHV3/9A4Zd8xxavdhK0t4PnqjkqVmUc9Q== + } + cpu: [arm] + os: [android] + + "@rollup/rollup-android-arm64@4.44.2": + resolution: + { + integrity: sha512-Yt5MKrOosSbSaAK5Y4J+vSiID57sOvpBNBR6K7xAaQvk3MkcNVV0f9fE20T+41WYN8hDn6SGFlFrKudtx4EoxA== + } + cpu: [arm64] + os: [android] + + "@rollup/rollup-darwin-arm64@4.44.2": + resolution: + { + integrity: sha512-EsnFot9ZieM35YNA26nhbLTJBHD0jTwWpPwmRVDzjylQT6gkar+zenfb8mHxWpRrbn+WytRRjE0WKsfaxBkVUA== + } + cpu: [arm64] + os: [darwin] + + "@rollup/rollup-darwin-x64@4.44.2": + resolution: + { + integrity: sha512-dv/t1t1RkCvJdWWxQ2lWOO+b7cMsVw5YFaS04oHpZRWehI1h0fV1gF4wgGCTyQHHjJDfbNpwOi6PXEafRBBezw== + } + cpu: [x64] + os: [darwin] + + "@rollup/rollup-freebsd-arm64@4.44.2": + resolution: + { + integrity: sha512-W4tt4BLorKND4qeHElxDoim0+BsprFTwb+vriVQnFFtT/P6v/xO5I99xvYnVzKWrK6j7Hb0yp3x7V5LUbaeOMg== + } + cpu: [arm64] + os: [freebsd] + + "@rollup/rollup-freebsd-x64@4.44.2": + resolution: + { + integrity: sha512-tdT1PHopokkuBVyHjvYehnIe20fxibxFCEhQP/96MDSOcyjM/shlTkZZLOufV3qO6/FQOSiJTBebhVc12JyPTA== + } + cpu: [x64] + os: [freebsd] + + "@rollup/rollup-linux-arm-gnueabihf@4.44.2": + resolution: + { + integrity: sha512-+xmiDGGaSfIIOXMzkhJ++Oa0Gwvl9oXUeIiwarsdRXSe27HUIvjbSIpPxvnNsRebsNdUo7uAiQVgBD1hVriwSQ== + } + cpu: [arm] + os: [linux] + + "@rollup/rollup-linux-arm-musleabihf@4.44.2": + resolution: + { + integrity: sha512-bDHvhzOfORk3wt8yxIra8N4k/N0MnKInCW5OGZaeDYa/hMrdPaJzo7CSkjKZqX4JFUWjUGm88lI6QJLCM7lDrA== + } + cpu: [arm] + os: [linux] + + "@rollup/rollup-linux-arm64-gnu@4.44.2": + resolution: + { + integrity: sha512-NMsDEsDiYghTbeZWEGnNi4F0hSbGnsuOG+VnNvxkKg0IGDvFh7UVpM/14mnMwxRxUf9AdAVJgHPvKXf6FpMB7A== + } + cpu: [arm64] + os: [linux] + + "@rollup/rollup-linux-arm64-musl@4.44.2": + resolution: + { + integrity: sha512-lb5bxXnxXglVq+7imxykIp5xMq+idehfl+wOgiiix0191av84OqbjUED+PRC5OA8eFJYj5xAGcpAZ0pF2MnW+A== + } + cpu: [arm64] + os: [linux] + + "@rollup/rollup-linux-loongarch64-gnu@4.44.2": + resolution: + { + integrity: sha512-Yl5Rdpf9pIc4GW1PmkUGHdMtbx0fBLE1//SxDmuf3X0dUC57+zMepow2LK0V21661cjXdTn8hO2tXDdAWAqE5g== + } + cpu: [loong64] + os: [linux] + + "@rollup/rollup-linux-powerpc64le-gnu@4.44.2": + resolution: + { + integrity: sha512-03vUDH+w55s680YYryyr78jsO1RWU9ocRMaeV2vMniJJW/6HhoTBwyyiiTPVHNWLnhsnwcQ0oH3S9JSBEKuyqw== + } + cpu: [ppc64] + os: [linux] + + "@rollup/rollup-linux-riscv64-gnu@4.44.2": + resolution: + { + integrity: sha512-iYtAqBg5eEMG4dEfVlkqo05xMOk6y/JXIToRca2bAWuqjrJYJlx/I7+Z+4hSrsWU8GdJDFPL4ktV3dy4yBSrzg== + } + cpu: [riscv64] + os: [linux] + + "@rollup/rollup-linux-riscv64-musl@4.44.2": + resolution: + { + integrity: sha512-e6vEbgaaqz2yEHqtkPXa28fFuBGmUJ0N2dOJK8YUfijejInt9gfCSA7YDdJ4nYlv67JfP3+PSWFX4IVw/xRIPg== + } + cpu: [riscv64] + os: [linux] + + "@rollup/rollup-linux-s390x-gnu@4.44.2": + resolution: + { + integrity: sha512-evFOtkmVdY3udE+0QKrV5wBx7bKI0iHz5yEVx5WqDJkxp9YQefy4Mpx3RajIVcM6o7jxTvVd/qpC1IXUhGc1Mw== + } + cpu: [s390x] + os: [linux] + + "@rollup/rollup-linux-x64-gnu@4.44.2": + resolution: + { + integrity: sha512-/bXb0bEsWMyEkIsUL2Yt5nFB5naLAwyOWMEviQfQY1x3l5WsLKgvZf66TM7UTfED6erckUVUJQ/jJ1FSpm3pRQ== + } + cpu: [x64] + os: [linux] + + "@rollup/rollup-linux-x64-musl@4.44.2": + resolution: + { + integrity: sha512-3D3OB1vSSBXmkGEZR27uiMRNiwN08/RVAcBKwhUYPaiZ8bcvdeEwWPvbnXvvXHY+A/7xluzcN+kaiOFNiOZwWg== + } + cpu: [x64] + os: [linux] + + "@rollup/rollup-win32-arm64-msvc@4.44.2": + resolution: + { + integrity: sha512-VfU0fsMK+rwdK8mwODqYeM2hDrF2WiHaSmCBrS7gColkQft95/8tphyzv2EupVxn3iE0FI78wzffoULH1G+dkw== + } + cpu: [arm64] + os: [win32] + + "@rollup/rollup-win32-ia32-msvc@4.44.2": + resolution: + { + integrity: sha512-+qMUrkbUurpE6DVRjiJCNGZBGo9xM4Y0FXU5cjgudWqIBWbcLkjE3XprJUsOFgC6xjBClwVa9k6O3A7K3vxb5Q== + } + cpu: [ia32] + os: [win32] + + "@rollup/rollup-win32-x64-msvc@4.44.2": + resolution: + { + integrity: sha512-3+QZROYfJ25PDcxFF66UEk8jGWigHJeecZILvkPkyQN7oc5BvFo4YEXFkOs154j3FTMp9mn9Ky8RCOwastduEA== + } + cpu: [x64] + os: [win32] + + "@rushstack/node-core-library@5.14.0": + resolution: + { + integrity: sha512-eRong84/rwQUlATGFW3TMTYVyqL1vfW9Lf10PH+mVGfIb9HzU3h5AASNIw+axnBLjnD0n3rT5uQBwu9fvzATrg== + } + peerDependencies: + "@types/node": "*" + peerDependenciesMeta: + "@types/node": + optional: true + + "@rushstack/rig-package@0.5.3": + resolution: + { + integrity: sha512-olzSSjYrvCNxUFZowevC3uz8gvKr3WTpHQ7BkpjtRpA3wK+T0ybep/SRUMfr195gBzJm5gaXw0ZMgjIyHqJUow== + } + + "@rushstack/terminal@0.15.4": + resolution: + { + integrity: sha512-OQSThV0itlwVNHV6thoXiAYZlQh4Fgvie2CzxFABsbO2MWQsI4zOh3LRNigYSTrmS+ba2j0B3EObakPzf/x6Zg== + } + peerDependencies: + "@types/node": "*" + peerDependenciesMeta: + "@types/node": + optional: true + + "@rushstack/ts-command-line@5.0.2": + resolution: + { + integrity: sha512-+AkJDbu1GFMPIU8Sb7TLVXDv/Q7Mkvx+wAjEl8XiXVVq+p1FmWW6M3LYpJMmoHNckSofeMecgWg5lfMwNAAsEQ== + } + + "@svgr/babel-plugin-add-jsx-attribute@8.0.0": + resolution: + { + integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g== + } + engines: { node: ">=14" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@svgr/babel-plugin-remove-jsx-attribute@8.0.0": + resolution: + { + integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA== + } + engines: { node: ">=14" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0": + resolution: + { + integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA== + } + engines: { node: ">=14" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0": + resolution: + { + integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ== + } + engines: { node: ">=14" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@svgr/babel-plugin-svg-dynamic-title@8.0.0": + resolution: + { + integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og== + } + engines: { node: ">=14" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@svgr/babel-plugin-svg-em-dimensions@8.0.0": + resolution: + { + integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g== + } + engines: { node: ">=14" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@svgr/babel-plugin-transform-react-native-svg@8.1.0": + resolution: + { + integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q== + } + engines: { node: ">=14" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@svgr/babel-plugin-transform-svg-component@8.0.0": + resolution: + { + integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw== + } + engines: { node: ">=12" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@svgr/babel-preset@8.1.0": + resolution: + { + integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug== + } + engines: { node: ">=14" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@svgr/core@8.1.0": + resolution: + { + integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA== + } + engines: { node: ">=14" } + + "@svgr/hast-util-to-babel-ast@8.0.0": + resolution: + { + integrity: sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q== + } + engines: { node: ">=14" } + + "@svgr/plugin-jsx@8.1.0": + resolution: + { + integrity: sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA== + } + engines: { node: ">=14" } + peerDependencies: + "@svgr/core": "*" + + "@swc/core-darwin-arm64@1.12.9": + resolution: + { + integrity: sha512-GACFEp4nD6V+TZNR2JwbMZRHB+Yyvp14FrcmB6UCUYmhuNWjkxi+CLnEvdbuiKyQYv0zA+TRpCHZ+whEs6gwfA== + } + engines: { node: ">=10" } + cpu: [arm64] + os: [darwin] + + "@swc/core-darwin-x64@1.12.9": + resolution: + { + integrity: sha512-hv2kls7Ilkm2EpeJz+I9MCil7pGS3z55ZAgZfxklEuYsxpICycxeH+RNRv4EraggN44ms+FWCjtZFu0LGg2V3g== + } + engines: { node: ">=10" } + cpu: [x64] + os: [darwin] + + "@swc/core-linux-arm-gnueabihf@1.12.9": + resolution: + { + integrity: sha512-od9tDPiG+wMU9wKtd6y3nYJdNqgDOyLdgRRcrj1/hrbHoUPOM8wZQZdwQYGarw63iLXGgsw7t5HAF9Yc51ilFA== + } + engines: { node: ">=10" } + cpu: [arm] + os: [linux] + + "@swc/core-linux-arm64-gnu@1.12.9": + resolution: + { + integrity: sha512-6qx1ka9LHcLzxIgn2Mros+CZLkHK2TawlXzi/h7DJeNnzi8F1Hw0Yzjp8WimxNCg6s2n+o3jnmin1oXB7gg8rw== + } + engines: { node: ">=10" } + cpu: [arm64] + os: [linux] + + "@swc/core-linux-arm64-musl@1.12.9": + resolution: + { + integrity: sha512-yghFZWKPVVGbUdqiD7ft23G0JX6YFGDJPz9YbLLAwGuKZ9th3/jlWoQDAw1Naci31LQhVC+oIji6ozihSuwB2A== + } + engines: { node: ">=10" } + cpu: [arm64] + os: [linux] + + "@swc/core-linux-x64-gnu@1.12.9": + resolution: + { + integrity: sha512-SFUxyhWLZRNL8QmgGNqdi2Q43PNyFVkRZ2zIif30SOGFSxnxcf2JNeSeBgKIGVgaLSuk6xFVVCtJ3KIeaStgRg== + } + engines: { node: ">=10" } + cpu: [x64] + os: [linux] + + "@swc/core-linux-x64-musl@1.12.9": + resolution: + { + integrity: sha512-9FB0wM+6idCGTI20YsBNBg9xSWtkDBymnpaTCsZM3qDc0l4uOpJMqbfWhQvp17x7r/ulZfb2QY8RDvQmCL6AcQ== + } + engines: { node: ">=10" } + cpu: [x64] + os: [linux] + + "@swc/core-win32-arm64-msvc@1.12.9": + resolution: + { + integrity: sha512-zHOusMVbOH9ik5RtRrMiGzLpKwxrPXgXkBm3SbUCa65HAdjV33NZ0/R9Rv1uPESALtEl2tzMYLUxYA5ECFDFhA== + } + engines: { node: ">=10" } + cpu: [arm64] + os: [win32] + + "@swc/core-win32-ia32-msvc@1.12.9": + resolution: + { + integrity: sha512-aWZf0PqE0ot7tCuhAjRkDFf41AzzSQO0x2xRfTbnhpROp57BRJ/N5eee1VULO/UA2PIJRG7GKQky5bSGBYlFug== + } + engines: { node: ">=10" } + cpu: [ia32] + os: [win32] + + "@swc/core-win32-x64-msvc@1.12.9": + resolution: + { + integrity: sha512-C25fYftXOras3P3anSUeXXIpxmEkdAcsIL9yrr0j1xepTZ/yKwpnQ6g3coj8UXdeJy4GTVlR6+Ow/QiBgZQNOg== + } + engines: { node: ">=10" } + cpu: [x64] + os: [win32] + + "@swc/core@1.12.9": + resolution: + { + integrity: sha512-O+LfT2JlVMsIMWG9x+rdxg8GzpzeGtCZQfXV7cKc1PjIKUkLFf1QJ7okuseA4f/9vncu37dQ2ZcRrPKy0Ndd5g== + } + engines: { node: ">=10" } + peerDependencies: + "@swc/helpers": ">=0.5.17" + peerDependenciesMeta: + "@swc/helpers": + optional: true + + "@swc/counter@0.1.3": + resolution: + { + integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== + } + + "@swc/helpers@0.5.17": + resolution: + { + integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A== + } + + "@swc/types@0.1.23": + resolution: + { + integrity: sha512-u1iIVZV9Q0jxY+yM2vw/hZGDNudsN85bBpTqzAQ9rzkxW9D+e3aEM4Han+ow518gSewkXgjmEK0BD79ZcNVgPw== + } + + "@tailwindcss/node@4.1.11": + resolution: + { + integrity: sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q== + } + + "@tailwindcss/oxide-android-arm64@4.1.11": + resolution: + { + integrity: sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg== + } + engines: { node: ">= 10" } + cpu: [arm64] + os: [android] + + "@tailwindcss/oxide-darwin-arm64@4.1.11": + resolution: + { + integrity: sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ== + } + engines: { node: ">= 10" } + cpu: [arm64] + os: [darwin] + + "@tailwindcss/oxide-darwin-x64@4.1.11": + resolution: + { + integrity: sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw== + } + engines: { node: ">= 10" } + cpu: [x64] + os: [darwin] + + "@tailwindcss/oxide-freebsd-x64@4.1.11": + resolution: + { + integrity: sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA== + } + engines: { node: ">= 10" } + cpu: [x64] + os: [freebsd] + + "@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11": + resolution: + { + integrity: sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg== + } + engines: { node: ">= 10" } + cpu: [arm] + os: [linux] + + "@tailwindcss/oxide-linux-arm64-gnu@4.1.11": + resolution: + { + integrity: sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ== + } + engines: { node: ">= 10" } + cpu: [arm64] + os: [linux] + + "@tailwindcss/oxide-linux-arm64-musl@4.1.11": + resolution: + { + integrity: sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ== + } + engines: { node: ">= 10" } + cpu: [arm64] + os: [linux] + + "@tailwindcss/oxide-linux-x64-gnu@4.1.11": + resolution: + { + integrity: sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg== + } + engines: { node: ">= 10" } + cpu: [x64] + os: [linux] + + "@tailwindcss/oxide-linux-x64-musl@4.1.11": + resolution: + { + integrity: sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q== + } + engines: { node: ">= 10" } + cpu: [x64] + os: [linux] + + "@tailwindcss/oxide-wasm32-wasi@4.1.11": + resolution: + { + integrity: sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g== + } + engines: { node: ">=14.0.0" } + cpu: [wasm32] + bundledDependencies: + - "@napi-rs/wasm-runtime" + - "@emnapi/core" + - "@emnapi/runtime" + - "@tybys/wasm-util" + - "@emnapi/wasi-threads" + - tslib + + "@tailwindcss/oxide-win32-arm64-msvc@4.1.11": + resolution: + { + integrity: sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w== + } + engines: { node: ">= 10" } + cpu: [arm64] + os: [win32] + + "@tailwindcss/oxide-win32-x64-msvc@4.1.11": + resolution: + { + integrity: sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg== + } + engines: { node: ">= 10" } + cpu: [x64] + os: [win32] + + "@tailwindcss/oxide@4.1.11": + resolution: + { + integrity: sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg== + } + engines: { node: ">= 10" } + + "@tailwindcss/vite@4.1.11": + resolution: + { + integrity: sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw== + } + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 + + "@tailwindplus/elements@1.0.5": + resolution: + { + integrity: sha512-Qwid2wofBbkB0tRNxlNgfcuICta3QITi558B349nLUPtW8E4sOxhu5NUpw6IpuO2NuIglWxrCw8u7Zfvmhv6jg== + } + + "@tanstack/react-virtual@3.13.12": + resolution: + { + integrity: sha512-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA== + } + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + "@tanstack/virtual-core@3.13.12": + resolution: + { + integrity: sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA== + } + + "@testing-library/dom@10.4.0": + resolution: + { + integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ== + } + engines: { node: ">=18" } + + "@testing-library/jest-dom@6.6.4": + resolution: + { + integrity: sha512-xDXgLjVunjHqczScfkCJ9iyjdNOVHvvCdqHSSxwM9L0l/wHkTRum67SDc020uAlCoqktJplgO2AAQeLP1wgqDQ== + } + engines: { node: ">=14", npm: ">=6", yarn: ">=1" } + + "@testing-library/react@16.3.0": + resolution: + { + integrity: sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw== + } + engines: { node: ">=18" } + peerDependencies: + "@testing-library/dom": ^10.0.0 + "@types/react": ^18.0.0 || ^19.0.0 + "@types/react-dom": ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@testing-library/user-event@14.6.1": + resolution: + { + integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw== + } + engines: { node: ">=12", npm: ">=6" } + peerDependencies: + "@testing-library/dom": ">=7.21.4" + + "@tsconfig/node10@1.0.11": + resolution: + { + integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== + } + + "@tsconfig/node12@1.0.11": + resolution: + { + integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + } + + "@tsconfig/node14@1.0.3": + resolution: + { + integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + } + + "@tsconfig/node16@1.0.4": + resolution: + { + integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + } + + "@types/argparse@1.0.38": + resolution: + { + integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA== + } + + "@types/aria-query@5.0.4": + resolution: + { + integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw== + } + + "@types/chai@5.2.2": + resolution: + { + integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg== + } + + "@types/deep-eql@4.0.2": + resolution: + { + integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw== + } + + "@types/estree@1.0.8": + resolution: + { + integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== + } + + "@types/json-schema@7.0.15": + resolution: + { + integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + } + + "@types/node@18.19.121": + resolution: + { + integrity: sha512-bHOrbyztmyYIi4f1R0s17QsPs1uyyYnGcXeZoGEd227oZjry0q6XQBQxd82X1I57zEfwO8h9Xo+Kl5gX1d9MwQ== + } + + "@types/node@24.2.0": + resolution: + { + integrity: sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw== + } + + "@types/react-dom@19.1.6": + resolution: + { + integrity: sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw== + } + peerDependencies: + "@types/react": ^19.0.0 + + "@types/react@19.1.8": + resolution: + { + integrity: sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g== + } + + "@typescript-eslint/eslint-plugin@8.35.1": + resolution: + { + integrity: sha512-9XNTlo7P7RJxbVeICaIIIEipqxLKguyh+3UbXuT2XQuFp6d8VOeDEGuz5IiX0dgZo8CiI6aOFLg4e8cF71SFVg== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + "@typescript-eslint/parser": ^8.35.1 + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <5.9.0" + + "@typescript-eslint/parser@8.35.1": + resolution: + { + integrity: sha512-3MyiDfrfLeK06bi/g9DqJxP5pV74LNv4rFTyvGDmT3x2p1yp1lOd+qYZfiRPIOf/oON+WRZR5wxxuF85qOar+w== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <5.9.0" + + "@typescript-eslint/project-service@8.35.1": + resolution: + { + integrity: sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: ">=4.8.4 <5.9.0" + + "@typescript-eslint/scope-manager@8.35.1": + resolution: + { + integrity: sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@typescript-eslint/tsconfig-utils@8.35.1": + resolution: + { + integrity: sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: ">=4.8.4 <5.9.0" + + "@typescript-eslint/type-utils@8.35.1": + resolution: + { + integrity: sha512-HOrUBlfVRz5W2LIKpXzZoy6VTZzMu2n8q9C2V/cFngIC5U1nStJgv0tMV4sZPzdf4wQm9/ToWUFPMN9Vq9VJQQ== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <5.9.0" + + "@typescript-eslint/types@8.35.1": + resolution: + { + integrity: sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@typescript-eslint/typescript-estree@8.35.1": + resolution: + { + integrity: sha512-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: ">=4.8.4 <5.9.0" + + "@typescript-eslint/utils@8.35.1": + resolution: + { + integrity: sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <5.9.0" + + "@typescript-eslint/visitor-keys@8.35.1": + resolution: + { + integrity: sha512-VRwixir4zBWCSTP/ljEo091lbpypz57PoeAQ9imjG+vbeof9LplljsL1mos4ccG6H9IjfrVGM359RozUnuFhpw== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@vitejs/plugin-react-swc@3.10.2": + resolution: + { + integrity: sha512-xD3Rdvrt5LgANug7WekBn1KhcvLn1H3jNBfJRL3reeOIua/WnZOEV5qi5qIBq5T8R0jUDmRtxuvk4bPhzGHDWw== + } + peerDependencies: + vite: ^4 || ^5 || ^6 || ^7.0.0-beta.0 + + "@vitest/expect@3.2.4": + resolution: + { + integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig== + } + + "@vitest/mocker@3.2.4": + resolution: + { + integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ== + } + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + "@vitest/pretty-format@3.2.4": + resolution: + { + integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA== + } + + "@vitest/runner@3.2.4": + resolution: + { + integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ== + } + + "@vitest/snapshot@3.2.4": + resolution: + { + integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ== + } + + "@vitest/spy@3.2.4": + resolution: + { + integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw== + } + + "@vitest/utils@3.2.4": + resolution: + { + integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA== + } + + "@volar/language-core@2.4.23": + resolution: + { + integrity: sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ== + } + + "@volar/source-map@2.4.23": + resolution: + { + integrity: sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q== + } + + "@volar/typescript@2.4.23": + resolution: + { + integrity: sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag== + } + + "@vue/compiler-core@3.5.18": + resolution: + { + integrity: sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw== + } + + "@vue/compiler-dom@3.5.18": + resolution: + { + integrity: sha512-RMbU6NTU70++B1JyVJbNbeFkK+A+Q7y9XKE2EM4NLGm2WFR8x9MbAtWxPPLdm0wUkuZv9trpwfSlL6tjdIa1+A== + } + + "@vue/compiler-vue2@2.7.16": + resolution: + { + integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A== + } + + "@vue/language-core@2.2.0": + resolution: + { + integrity: sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw== + } + peerDependencies: + typescript: "*" + peerDependenciesMeta: + typescript: + optional: true + + "@vue/shared@3.5.18": + resolution: + { + integrity: sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA== + } + + acorn-jsx@5.3.2: + resolution: + { + integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + } + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-walk@8.3.4: + resolution: + { + integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + } + engines: { node: ">=0.4.0" } + + acorn@8.15.0: + resolution: + { + integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== + } + engines: { node: ">=0.4.0" } + hasBin: true + + agent-base@7.1.3: + resolution: + { + integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw== + } + engines: { node: ">= 14" } + + ajv-draft-04@1.0.0: + resolution: + { + integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw== + } + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-formats@3.0.1: + resolution: + { + integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ== + } + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@6.12.6: + resolution: + { + integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + } + + ajv@8.12.0: + resolution: + { + integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== + } + + ajv@8.13.0: + resolution: + { + integrity: sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA== + } + + alien-signals@0.4.14: + resolution: + { + integrity: sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q== + } + + ansi-escapes@7.0.0: + resolution: + { + integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw== + } + engines: { node: ">=18" } + + ansi-regex@5.0.1: + resolution: + { + integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + } + engines: { node: ">=8" } + + ansi-regex@6.1.0: + resolution: + { + integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== + } + engines: { node: ">=12" } + + ansi-styles@4.3.0: + resolution: + { + integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + } + engines: { node: ">=8" } + + ansi-styles@5.2.0: + resolution: + { + integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + } + engines: { node: ">=10" } + + ansi-styles@6.2.1: + resolution: + { + integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + } + engines: { node: ">=12" } + + arg@4.1.3: + resolution: + { + integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + } + + argparse@1.0.10: + resolution: + { + integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + } + + argparse@2.0.1: + resolution: + { + integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + } + + aria-query@5.3.0: + resolution: + { + integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== + } + + aria-query@5.3.2: + resolution: + { + integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw== + } + engines: { node: ">= 0.4" } + + assertion-error@2.0.1: + resolution: + { + integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== + } + engines: { node: ">=12" } + + balanced-match@1.0.2: + resolution: + { + integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + } + + brace-expansion@1.1.12: + resolution: + { + integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== + } + + brace-expansion@2.0.2: + resolution: + { + integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== + } + + braces@3.0.3: + resolution: + { + integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + } + engines: { node: ">=8" } + + browserslist@4.25.1: + resolution: + { + integrity: sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw== + } + engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } + hasBin: true + + cac@6.7.14: + resolution: + { + integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== + } + engines: { node: ">=8" } + + callsites@3.1.0: + resolution: + { + integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + } + engines: { node: ">=6" } + + camelcase@6.3.0: + resolution: + { + integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + } + engines: { node: ">=10" } + + caniuse-lite@1.0.30001726: + resolution: + { + integrity: sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw== + } + + chai@5.2.1: + resolution: + { + integrity: sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A== + } + engines: { node: ">=18" } + + chalk@4.1.2: + resolution: + { + integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + } + engines: { node: ">=10" } + + chalk@5.4.1: + resolution: + { + integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w== + } + engines: { node: ^12.17.0 || ^14.13 || >=16.0.0 } + + check-error@2.1.1: + resolution: + { + integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw== + } + engines: { node: ">= 16" } + + chownr@3.0.0: + resolution: + { + integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g== + } + engines: { node: ">=18" } + + cli-cursor@5.0.0: + resolution: + { + integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw== + } + engines: { node: ">=18" } + + cli-truncate@4.0.0: + resolution: + { + integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA== + } + engines: { node: ">=18" } + + cliui@8.0.1: + resolution: + { + integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + } + engines: { node: ">=12" } + + clsx@2.1.1: + resolution: + { + integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== + } + engines: { node: ">=6" } + + color-convert@2.0.1: + resolution: + { + integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + } + engines: { node: ">=7.0.0" } + + color-name@1.1.4: + resolution: + { + integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + } + + colorette@2.0.20: + resolution: + { + integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + } + + commander@14.0.0: + resolution: + { + integrity: sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA== + } + engines: { node: ">=20" } + + compare-versions@6.1.1: + resolution: + { + integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg== + } + + concat-map@0.0.1: + resolution: + { + integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + } + + confbox@0.1.8: + resolution: + { + integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w== + } + + confbox@0.2.2: + resolution: + { + integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ== + } + + convert-source-map@2.0.0: + resolution: + { + integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + } + + cookie@1.0.2: + resolution: + { + integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA== + } + engines: { node: ">=18" } + + cosmiconfig@8.3.6: + resolution: + { + integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA== + } + engines: { node: ">=14" } + peerDependencies: + typescript: ">=4.9.5" + peerDependenciesMeta: + typescript: + optional: true + + create-require@1.1.1: + resolution: + { + integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + } + + crelt@1.0.6: + resolution: + { + integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g== + } + + cross-spawn@7.0.6: + resolution: + { + integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + } + engines: { node: ">= 8" } + + css.escape@1.5.1: + resolution: + { + integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== + } + + cssstyle@4.6.0: + resolution: + { + integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg== + } + engines: { node: ">=18" } + + csstype@3.1.3: + resolution: + { + integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + } + + data-urls@5.0.0: + resolution: + { + integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg== + } + engines: { node: ">=18" } + + de-indent@1.0.2: + resolution: + { + integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg== + } + + debug@4.4.1: + resolution: + { + integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== + } + engines: { node: ">=6.0" } + peerDependencies: + supports-color: "*" + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js@10.6.0: + resolution: + { + integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg== + } + + deep-eql@5.0.2: + resolution: + { + integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q== + } + engines: { node: ">=6" } + + deep-is@0.1.4: + resolution: + { + integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + } + + define-lazy-prop@2.0.0: + resolution: + { + integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== + } + engines: { node: ">=8" } + + dequal@2.0.3: + resolution: + { + integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== + } + engines: { node: ">=6" } + + detect-libc@2.0.4: + resolution: + { + integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA== + } + engines: { node: ">=8" } + + diff@4.0.2: + resolution: + { + integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + } + engines: { node: ">=0.3.1" } + + dom-accessibility-api@0.5.16: + resolution: + { + integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== + } + + dom-accessibility-api@0.6.3: + resolution: + { + integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w== + } + + dot-case@3.0.4: + resolution: + { + integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== + } + + electron-to-chromium@1.5.179: + resolution: + { + integrity: sha512-UWKi/EbBopgfFsc5k61wFpV7WrnnSlSzW/e2XcBmS6qKYTivZlLtoll5/rdqRTxGglGHkmkW0j0pFNJG10EUIQ== + } + + emoji-regex@10.4.0: + resolution: + { + integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw== + } + + emoji-regex@8.0.0: + resolution: + { + integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + } + + enhanced-resolve@5.18.2: + resolution: + { + integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ== + } + engines: { node: ">=10.13.0" } + + entities@4.5.0: + resolution: + { + integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + } + engines: { node: ">=0.12" } + + entities@6.0.1: + resolution: + { + integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g== + } + engines: { node: ">=0.12" } + + environment@1.1.0: + resolution: + { + integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q== + } + engines: { node: ">=18" } + + error-ex@1.3.2: + resolution: + { + integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + } + + es-module-lexer@1.7.0: + resolution: + { + integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== + } + + esbuild@0.25.5: + resolution: + { + integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ== + } + engines: { node: ">=18" } + hasBin: true + + escalade@3.2.0: + resolution: + { + integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + } + engines: { node: ">=6" } + + escape-string-regexp@4.0.0: + resolution: + { + integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + } + engines: { node: ">=10" } + + eslint-plugin-react-hooks@5.2.0: + resolution: + { + integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg== + } + engines: { node: ">=10" } + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react-refresh@0.4.20: + resolution: + { + integrity: sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA== + } + peerDependencies: + eslint: ">=8.40" + + eslint-scope@8.4.0: + resolution: + { + integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + eslint-visitor-keys@3.4.3: + resolution: + { + integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + + eslint-visitor-keys@4.2.1: + resolution: + { + integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + eslint@9.30.1: + resolution: + { + integrity: sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + hasBin: true + peerDependencies: + jiti: "*" + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: + { + integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + esquery@1.6.0: + resolution: + { + integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + } + engines: { node: ">=0.10" } + + esrecurse@4.3.0: + resolution: + { + integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + } + engines: { node: ">=4.0" } + + estraverse@5.3.0: + resolution: + { + integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + } + engines: { node: ">=4.0" } + + estree-walker@2.0.2: + resolution: + { + integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + } + + estree-walker@3.0.3: + resolution: + { + integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== + } + + esutils@2.0.3: + resolution: + { + integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + } + engines: { node: ">=0.10.0" } + + eventemitter3@5.0.1: + resolution: + { + integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + } + + expect-type@1.2.2: + resolution: + { + integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA== + } + engines: { node: ">=12.0.0" } + + exsolve@1.0.7: + resolution: + { + integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw== + } + + fast-deep-equal@3.1.3: + resolution: + { + integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + } + + fast-glob@3.3.3: + resolution: + { + integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== + } + engines: { node: ">=8.6.0" } + + fast-json-stable-stringify@2.1.0: + resolution: + { + integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + } + + fast-levenshtein@2.0.6: + resolution: + { + integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + } + + fastq@1.19.1: + resolution: + { + integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== + } + + fdir@6.4.6: + resolution: + { + integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w== + } + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: + { + integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== + } + engines: { node: ">=16.0.0" } + + fill-range@7.1.1: + resolution: + { + integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + } + engines: { node: ">=8" } + + find-up@5.0.0: + resolution: + { + integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + } + engines: { node: ">=10" } + + flat-cache@4.0.1: + resolution: + { + integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== + } + engines: { node: ">=16" } + + flatted@3.3.3: + resolution: + { + integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== + } + + fs-extra@11.3.0: + resolution: + { + integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew== + } + engines: { node: ">=14.14" } + + fs.realpath@1.0.0: + resolution: + { + integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + } + + fsevents@2.3.3: + resolution: + { + integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + } + engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } + os: [darwin] + + function-bind@1.1.2: + resolution: + { + integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + } + + gensync@1.0.0-beta.2: + resolution: + { + integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + } + engines: { node: ">=6.9.0" } + + get-caller-file@2.0.5: + resolution: + { + integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + } + engines: { node: 6.* || 8.* || >= 10.* } + + get-east-asian-width@1.3.0: + resolution: + { + integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ== + } + engines: { node: ">=18" } + + glob-parent@5.1.2: + resolution: + { + integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + } + engines: { node: ">= 6" } + + glob-parent@6.0.2: + resolution: + { + integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + } + engines: { node: ">=10.13.0" } + + glob@7.2.3: + resolution: + { + integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + } + deprecated: Glob versions prior to v9 are no longer supported + + globals@14.0.0: + resolution: + { + integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== + } + engines: { node: ">=18" } + + globals@16.3.0: + resolution: + { + integrity: sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ== + } + engines: { node: ">=18" } + + graceful-fs@4.2.11: + resolution: + { + integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + } + + graphemer@1.4.0: + resolution: + { + integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + } + + has-flag@4.0.0: + resolution: + { + integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + } + engines: { node: ">=8" } + + hasown@2.0.2: + resolution: + { + integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + } + engines: { node: ">= 0.4" } + + he@1.2.0: + resolution: + { + integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + } + hasBin: true + + html-encoding-sniffer@4.0.0: + resolution: + { + integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ== + } + engines: { node: ">=18" } + + http-proxy-agent@7.0.2: + resolution: + { + integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== + } + engines: { node: ">= 14" } + + https-proxy-agent@7.0.6: + resolution: + { + integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== + } + engines: { node: ">= 14" } + + husky@9.1.7: + resolution: + { + integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA== + } + engines: { node: ">=18" } + hasBin: true + + iconv-lite@0.6.3: + resolution: + { + integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + } + engines: { node: ">=0.10.0" } + + ignore@5.3.2: + resolution: + { + integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + } + engines: { node: ">= 4" } + + ignore@7.0.5: + resolution: + { + integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== + } + engines: { node: ">= 4" } + + import-fresh@3.3.1: + resolution: + { + integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== + } + engines: { node: ">=6" } + + import-lazy@4.0.0: + resolution: + { + integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw== + } + engines: { node: ">=8" } + + imurmurhash@0.1.4: + resolution: + { + integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + } + engines: { node: ">=0.8.19" } + + indent-string@4.0.0: + resolution: + { + integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + } + engines: { node: ">=8" } + + inflight@1.0.6: + resolution: + { + integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + } + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: + { + integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + } + + is-arrayish@0.2.1: + resolution: + { + integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + } + + is-core-module@2.16.1: + resolution: + { + integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + } + engines: { node: ">= 0.4" } + + is-docker@2.2.1: + resolution: + { + integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + } + engines: { node: ">=8" } + hasBin: true + + is-extglob@2.1.1: + resolution: + { + integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + } + engines: { node: ">=0.10.0" } + + is-fullwidth-code-point@3.0.0: + resolution: + { + integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + } + engines: { node: ">=8" } + + is-fullwidth-code-point@4.0.0: + resolution: + { + integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ== + } + engines: { node: ">=12" } + + is-fullwidth-code-point@5.0.0: + resolution: + { + integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA== + } + engines: { node: ">=18" } + + is-glob@4.0.3: + resolution: + { + integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + } + engines: { node: ">=0.10.0" } + + is-number@7.0.0: + resolution: + { + integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + } + engines: { node: ">=0.12.0" } + + is-potential-custom-element-name@1.0.1: + resolution: + { + integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + } + + is-wsl@2.2.0: + resolution: + { + integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + } + engines: { node: ">=8" } + + isexe@2.0.0: + resolution: + { + integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + } + + jiti@2.4.2: + resolution: + { + integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A== + } + hasBin: true + + jju@1.4.0: + resolution: + { + integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA== + } + + js-tokens@4.0.0: + resolution: + { + integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + } + + js-tokens@9.0.1: + resolution: + { + integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ== + } + + js-yaml@4.1.0: + resolution: + { + integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + } + hasBin: true + + jsdom@26.1.0: + resolution: + { + integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg== + } + engines: { node: ">=18" } + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + + jsesc@3.1.0: + resolution: + { + integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + } + engines: { node: ">=6" } + hasBin: true + + json-buffer@3.0.1: + resolution: + { + integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + } + + json-parse-even-better-errors@2.3.1: + resolution: + { + integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + } + + json-schema-traverse@0.4.1: + resolution: + { + integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + } + + json-schema-traverse@1.0.0: + resolution: + { + integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + } + + json-stable-stringify-without-jsonify@1.0.1: + resolution: + { + integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + } + + json5@2.2.3: + resolution: + { + integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + } + engines: { node: ">=6" } + hasBin: true + + jsonfile@6.1.0: + resolution: + { + integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + } + + keyv@4.5.4: + resolution: + { + integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + } + + kolorist@1.8.0: + resolution: + { + integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ== + } + + levn@0.4.1: + resolution: + { + integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + } + engines: { node: ">= 0.8.0" } + + lightningcss-darwin-arm64@1.30.1: + resolution: + { + integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ== + } + engines: { node: ">= 12.0.0" } + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.1: + resolution: + { + integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA== + } + engines: { node: ">= 12.0.0" } + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.1: + resolution: + { + integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig== + } + engines: { node: ">= 12.0.0" } + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.1: + resolution: + { + integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q== + } + engines: { node: ">= 12.0.0" } + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.1: + resolution: + { + integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw== + } + engines: { node: ">= 12.0.0" } + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.1: + resolution: + { + integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ== + } + engines: { node: ">= 12.0.0" } + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.1: + resolution: + { + integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw== + } + engines: { node: ">= 12.0.0" } + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.1: + resolution: + { + integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ== + } + engines: { node: ">= 12.0.0" } + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.1: + resolution: + { + integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA== + } + engines: { node: ">= 12.0.0" } + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.1: + resolution: + { + integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg== + } + engines: { node: ">= 12.0.0" } + cpu: [x64] + os: [win32] + + lightningcss@1.30.1: + resolution: + { + integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg== + } + engines: { node: ">= 12.0.0" } + + lilconfig@3.1.3: + resolution: + { + integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== + } + engines: { node: ">=14" } + + lines-and-columns@1.2.4: + resolution: + { + integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + } + + lint-staged@16.1.4: + resolution: + { + integrity: sha512-xy7rnzQrhTVGKMpv6+bmIA3C0yET31x8OhKBYfvGo0/byeZ6E0BjGARrir3Kg/RhhYHutpsi01+2J5IpfVoueA== + } + engines: { node: ">=20.17" } + hasBin: true + + listr2@9.0.1: + resolution: + { + integrity: sha512-SL0JY3DaxylDuo/MecFeiC+7pedM0zia33zl0vcjgwcq1q1FWWF1To9EIauPbl8GbMCU0R2e0uJ8bZunhYKD2g== + } + engines: { node: ">=20.0.0" } + + local-pkg@1.1.2: + resolution: + { + integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A== + } + engines: { node: ">=14" } + + locate-path@6.0.0: + resolution: + { + integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + } + engines: { node: ">=10" } + + lodash.merge@4.6.2: + resolution: + { + integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + } + + lodash@4.17.21: + resolution: + { + integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + } + + log-update@6.1.0: + resolution: + { + integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w== + } + engines: { node: ">=18" } + + loupe@3.1.4: + resolution: + { + integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg== + } + + lower-case@2.0.2: + resolution: + { + integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== + } + + lru-cache@10.4.3: + resolution: + { + integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + } + + lru-cache@5.1.1: + resolution: + { + integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + } + + lru-cache@6.0.0: + resolution: + { + integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + } + engines: { node: ">=10" } + + lz-string@1.5.0: + resolution: + { + integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== + } + hasBin: true + + magic-string@0.30.17: + resolution: + { + integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== + } + + make-error@1.3.6: + resolution: + { + integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + } + + merge2@1.4.1: + resolution: + { + integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + } + engines: { node: ">= 8" } + + micromatch@4.0.8: + resolution: + { + integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + } + engines: { node: ">=8.6" } + + mimic-function@5.0.1: + resolution: + { + integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA== + } + engines: { node: ">=18" } + + min-indent@1.0.1: + resolution: + { + integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + } + engines: { node: ">=4" } + + minimatch@10.0.3: + resolution: + { + integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw== + } + engines: { node: 20 || >=22 } + + minimatch@3.1.2: + resolution: + { + integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + } + + minimatch@9.0.5: + resolution: + { + integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + } + engines: { node: ">=16 || 14 >=14.17" } + + minipass@7.1.2: + resolution: + { + integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + } + engines: { node: ">=16 || 14 >=14.17" } + + minizlib@3.0.2: + resolution: + { + integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA== + } + engines: { node: ">= 18" } + + mkdirp@3.0.1: + resolution: + { + integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== + } + engines: { node: ">=10" } + hasBin: true + + mlly@1.7.4: + resolution: + { + integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw== + } + + ms@2.1.3: + resolution: + { + integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + } + + muggle-string@0.4.1: + resolution: + { + integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ== + } + + nano-spawn@1.0.2: + resolution: + { + integrity: sha512-21t+ozMQDAL/UGgQVBbZ/xXvNO10++ZPuTmKRO8k9V3AClVRht49ahtDjfY8l1q6nSHOrE5ASfthzH3ol6R/hg== + } + engines: { node: ">=20.17" } + + nanoid@3.3.11: + resolution: + { + integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== + } + engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } + hasBin: true + + natural-compare@1.4.0: + resolution: + { + integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + } + + no-case@3.0.4: + resolution: + { + integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== + } + + node-releases@2.0.19: + resolution: + { + integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== + } + + nwsapi@2.2.20: + resolution: + { + integrity: sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA== + } + + once@1.4.0: + resolution: + { + integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + } + + onetime@7.0.0: + resolution: + { + integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ== + } + engines: { node: ">=18" } + + open@8.4.2: + resolution: + { + integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== + } + engines: { node: ">=12" } + + optionator@0.9.4: + resolution: + { + integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + } + engines: { node: ">= 0.8.0" } + + p-limit@3.1.0: + resolution: + { + integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + } + engines: { node: ">=10" } + + p-locate@5.0.0: + resolution: + { + integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + } + engines: { node: ">=10" } + + parent-module@1.0.1: + resolution: + { + integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + } + engines: { node: ">=6" } + + parse-json@5.2.0: + resolution: + { + integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + } + engines: { node: ">=8" } + + parse5@7.3.0: + resolution: + { + integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw== + } + + path-browserify@1.0.1: + resolution: + { + integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== + } + + path-equal@1.2.5: + resolution: + { + integrity: sha512-i73IctDr3F2W+bsOWDyyVm/lqsXO47aY9nsFZUjTT/aljSbkxHxxCoyZ9UUrM8jK0JVod+An+rl48RCsvWM+9g== + } + + path-exists@4.0.0: + resolution: + { + integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + } + engines: { node: ">=8" } + + path-is-absolute@1.0.1: + resolution: + { + integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + } + engines: { node: ">=0.10.0" } + + path-key@3.1.1: + resolution: + { + integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + } + engines: { node: ">=8" } + + path-parse@1.0.7: + resolution: + { + integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + } + + path-type@4.0.0: + resolution: + { + integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + } + engines: { node: ">=8" } + + pathe@2.0.3: + resolution: + { + integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== + } + + pathval@2.0.1: + resolution: + { + integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ== + } + engines: { node: ">= 14.16" } + + picocolors@1.1.1: + resolution: + { + integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + } + + picomatch@2.3.1: + resolution: + { + integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + } + engines: { node: ">=8.6" } + + picomatch@4.0.3: + resolution: + { + integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== + } + engines: { node: ">=12" } + + pidtree@0.6.0: + resolution: + { + integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== + } + engines: { node: ">=0.10" } + hasBin: true + + pkg-types@1.3.1: + resolution: + { + integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ== + } + + pkg-types@2.3.0: + resolution: + { + integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig== + } + + postcss@8.5.6: + resolution: + { + integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== + } + engines: { node: ^10 || ^12 || >=14 } + + prelude-ls@1.2.1: + resolution: + { + integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + } + engines: { node: ">= 0.8.0" } + + prettier@3.6.2: + resolution: + { + integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ== + } + engines: { node: ">=14" } + hasBin: true + + pretty-format@27.5.1: + resolution: + { + integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== + } + engines: { node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0 } + + punycode@2.3.1: + resolution: + { + integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + } + engines: { node: ">=6" } + + quansync@0.2.11: + resolution: + { + integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA== + } + + queue-microtask@1.2.3: + resolution: + { + integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + } + + react-docgen-typescript@2.4.0: + resolution: + { + integrity: sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg== + } + peerDependencies: + typescript: ">= 4.3.x" + + react-dom@19.1.0: + resolution: + { + integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g== + } + peerDependencies: + react: ^19.1.0 + + react-error-boundary@6.0.0: + resolution: + { + integrity: sha512-gdlJjD7NWr0IfkPlaREN2d9uUZUlksrfOx7SX62VRerwXbMY6ftGCIZua1VG1aXFNOimhISsTq+Owp725b9SiA== + } + peerDependencies: + react: ">=16.13.1" + + react-is@17.0.2: + resolution: + { + integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + } + + react-router-dom@7.6.3: + resolution: + { + integrity: sha512-DiWJm9qdUAmiJrVWaeJdu4TKu13+iB/8IEi0EW/XgaHCjW/vWGrwzup0GVvaMteuZjKnh5bEvJP/K0MDnzawHw== + } + engines: { node: ">=20.0.0" } + peerDependencies: + react: ">=18" + react-dom: ">=18" + + react-router@7.6.3: + resolution: + { + integrity: sha512-zf45LZp5skDC6I3jDLXQUu0u26jtuP4lEGbc7BbdyxenBN1vJSTA18czM2D+h5qyMBuMrD+9uB+mU37HIoKGRA== + } + engines: { node: ">=20.0.0" } + peerDependencies: + react: ">=18" + react-dom: ">=18" + peerDependenciesMeta: + react-dom: + optional: true + + react@19.1.0: + resolution: + { + integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg== + } + engines: { node: ">=0.10.0" } + + redent@3.0.0: + resolution: + { + integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + } + engines: { node: ">=8" } + + require-directory@2.1.1: + resolution: + { + integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + } + engines: { node: ">=0.10.0" } + + require-from-string@2.0.2: + resolution: + { + integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + } + engines: { node: ">=0.10.0" } + + resolve-from@4.0.0: + resolution: + { + integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + } + engines: { node: ">=4" } + + resolve@1.22.10: + resolution: + { + integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== + } + engines: { node: ">= 0.4" } + hasBin: true + + restore-cursor@5.1.0: + resolution: + { + integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA== + } + engines: { node: ">=18" } + + reusify@1.1.0: + resolution: + { + integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== + } + engines: { iojs: ">=1.0.0", node: ">=0.10.0" } + + rfdc@1.4.1: + resolution: + { + integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== + } + + rollup-plugin-visualizer@6.0.3: + resolution: + { + integrity: sha512-ZU41GwrkDcCpVoffviuM9Clwjy5fcUxlz0oMoTXTYsK+tcIFzbdacnrr2n8TXcHxbGKKXtOdjxM2HUS4HjkwIw== + } + engines: { node: ">=18" } + hasBin: true + peerDependencies: + rolldown: 1.x || ^1.0.0-beta + rollup: 2.x || 3.x || 4.x + peerDependenciesMeta: + rolldown: + optional: true + rollup: + optional: true + + rollup@4.44.2: + resolution: + { + integrity: sha512-PVoapzTwSEcelaWGth3uR66u7ZRo6qhPHc0f2uRO9fX6XDVNrIiGYS0Pj9+R8yIIYSD/mCx2b16Ws9itljKSPg== + } + engines: { node: ">=18.0.0", npm: ">=8.0.0" } + hasBin: true + + rrweb-cssom@0.8.0: + resolution: + { + integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw== + } + + run-parallel@1.2.0: + resolution: + { + integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + } + + safe-stable-stringify@2.5.0: + resolution: + { + integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA== + } + engines: { node: ">=10" } + + safer-buffer@2.1.2: + resolution: + { + integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + } + + saxes@6.0.0: + resolution: + { + integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA== + } + engines: { node: ">=v12.22.7" } + + scheduler@0.26.0: + resolution: + { + integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA== + } + + semver@6.3.1: + resolution: + { + integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + } + hasBin: true + + semver@7.5.4: + resolution: + { + integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + } + engines: { node: ">=10" } + hasBin: true + + semver@7.7.2: + resolution: + { + integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== + } + engines: { node: ">=10" } + hasBin: true + + set-cookie-parser@2.7.1: + resolution: + { + integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ== + } + + shebang-command@2.0.0: + resolution: + { + integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + } + engines: { node: ">=8" } + + shebang-regex@3.0.0: + resolution: + { + integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + } + engines: { node: ">=8" } + + siginfo@2.0.0: + resolution: + { + integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== + } + + signal-exit@4.1.0: + resolution: + { + integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + } + engines: { node: ">=14" } + + slice-ansi@5.0.0: + resolution: + { + integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ== + } + engines: { node: ">=12" } + + slice-ansi@7.1.0: + resolution: + { + integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg== + } + engines: { node: ">=18" } + + snake-case@3.0.4: + resolution: + { + integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg== + } + + source-map-js@1.2.1: + resolution: + { + integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + } + engines: { node: ">=0.10.0" } + + source-map@0.6.1: + resolution: + { + integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + } + engines: { node: ">=0.10.0" } + + source-map@0.7.4: + resolution: + { + integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== + } + engines: { node: ">= 8" } + + sprintf-js@1.0.3: + resolution: + { + integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + } + + stackback@0.0.2: + resolution: + { + integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== + } + + std-env@3.9.0: + resolution: + { + integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw== + } + + string-argv@0.3.2: + resolution: + { + integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== + } + engines: { node: ">=0.6.19" } + + string-width@4.2.3: + resolution: + { + integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + } + engines: { node: ">=8" } + + string-width@7.2.0: + resolution: + { + integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ== + } + engines: { node: ">=18" } + + strip-ansi@6.0.1: + resolution: + { + integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + } + engines: { node: ">=8" } + + strip-ansi@7.1.0: + resolution: + { + integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + } + engines: { node: ">=12" } + + strip-indent@3.0.0: + resolution: + { + integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + } + engines: { node: ">=8" } + + strip-json-comments@3.1.1: + resolution: + { + integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + } + engines: { node: ">=8" } + + strip-literal@3.0.0: + resolution: + { + integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA== + } + + style-mod@4.1.2: + resolution: + { + integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw== + } + + supports-color@7.2.0: + resolution: + { + integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + } + engines: { node: ">=8" } + + supports-color@8.1.1: + resolution: + { + integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + } + engines: { node: ">=10" } + + supports-preserve-symlinks-flag@1.0.0: + resolution: + { + integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + } + engines: { node: ">= 0.4" } + + svg-parser@2.0.4: + resolution: + { + integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== + } + + symbol-tree@3.2.4: + resolution: + { + integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + } + + tabbable@6.2.0: + resolution: + { + integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew== + } + + tailwind-merge@3.3.1: + resolution: + { + integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g== + } + + tailwindcss@4.1.11: + resolution: + { + integrity: sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA== + } + + tapable@2.2.2: + resolution: + { + integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg== + } + engines: { node: ">=6" } + + tar@7.4.3: + resolution: + { + integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw== + } + engines: { node: ">=18" } + + tinybench@2.9.0: + resolution: + { + integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== + } + + tinyexec@0.3.2: + resolution: + { + integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== + } + + tinyglobby@0.2.14: + resolution: + { + integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ== + } + engines: { node: ">=12.0.0" } + + tinypool@1.1.1: + resolution: + { + integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg== + } + engines: { node: ^18.0.0 || >=20.0.0 } + + tinyrainbow@2.0.0: + resolution: + { + integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw== + } + engines: { node: ">=14.0.0" } + + tinyspy@4.0.3: + resolution: + { + integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A== + } + engines: { node: ">=14.0.0" } + + tldts-core@6.1.86: + resolution: + { + integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA== + } + + tldts@6.1.86: + resolution: + { + integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ== + } + hasBin: true + + to-regex-range@5.0.1: + resolution: + { + integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + } + engines: { node: ">=8.0" } + + tough-cookie@5.1.2: + resolution: + { + integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A== + } + engines: { node: ">=16" } + + tr46@5.1.1: + resolution: + { + integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw== + } + engines: { node: ">=18" } + + ts-api-utils@2.1.0: + resolution: + { + integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ== + } + engines: { node: ">=18.12" } + peerDependencies: + typescript: ">=4.8.4" + + ts-blank-space@0.6.2: + resolution: + { + integrity: sha512-hZjcHdHrveEKI67v8OzI90a1bizgoDkY7ekE4fH89qJhZgxvmjfBOv98aibCU7OpKbvV3R9p/qd3DrzZqT1cFQ== + } + engines: { node: ">=18.0.0" } + + ts-node@10.9.2: + resolution: + { + integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + } + hasBin: true + peerDependencies: + "@swc/core": ">=1.2.50" + "@swc/wasm": ">=1.2.50" + "@types/node": "*" + typescript: ">=2.7" + peerDependenciesMeta: + "@swc/core": + optional: true + "@swc/wasm": + optional: true + + tslib@2.8.1: + resolution: + { + integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + } + + type-check@0.4.0: + resolution: + { + integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + } + engines: { node: ">= 0.8.0" } + + typescript-eslint@8.35.1: + resolution: + { + integrity: sha512-xslJjFzhOmHYQzSB/QTeASAHbjmxOGEP6Coh93TXmUBFQoJ1VU35UHIDmG06Jd6taf3wqqC1ntBnCMeymy5Ovw== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <5.9.0" + + typescript-json-schema@0.65.1: + resolution: + { + integrity: sha512-tuGH7ff2jPaUYi6as3lHyHcKpSmXIqN7/mu50x3HlYn0EHzLpmt3nplZ7EuhUkO0eqDRc9GqWNkfjgBPIS9kxg== + } + hasBin: true + + typescript@5.5.4: + resolution: + { + integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== + } + engines: { node: ">=14.17" } + hasBin: true + + typescript@5.8.2: + resolution: + { + integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ== + } + engines: { node: ">=14.17" } + hasBin: true + + typescript@5.8.3: + resolution: + { + integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== + } + engines: { node: ">=14.17" } + hasBin: true + + ufo@1.6.1: + resolution: + { + integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA== + } + + undici-types@5.26.5: + resolution: + { + integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + } + + undici-types@7.10.0: + resolution: + { + integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag== + } + + universalify@2.0.1: + resolution: + { + integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + } + engines: { node: ">= 10.0.0" } + + update-browserslist-db@1.1.3: + resolution: + { + integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== + } + hasBin: true + peerDependencies: + browserslist: ">= 4.21.0" + + uri-js@4.4.1: + resolution: + { + integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + } + + use-sync-external-store@1.5.0: + resolution: + { + integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A== + } + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + v8-compile-cache-lib@3.0.1: + resolution: + { + integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + } + + vite-node@3.2.4: + resolution: + { + integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg== + } + engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 } + hasBin: true + + vite-plugin-dts@4.5.4: + resolution: + { + integrity: sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg== + } + peerDependencies: + typescript: "*" + vite: "*" + peerDependenciesMeta: + vite: + optional: true + + vite-plugin-svgr@4.3.0: + resolution: + { + integrity: sha512-Jy9qLB2/PyWklpYy0xk0UU3TlU0t2UMpJXZvf+hWII1lAmRHrOUKi11Uw8N3rxoNk7atZNYO3pR3vI1f7oi+6w== + } + peerDependencies: + vite: ">=2.6.0" + + vite@7.0.6: + resolution: + { + integrity: sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg== + } + engines: { node: ^20.19.0 || >=22.12.0 } + hasBin: true + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + jiti: ">=1.21.0" + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + "@types/node": + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@3.2.4: + resolution: + { + integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A== + } + engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 } + hasBin: true + peerDependencies: + "@edge-runtime/vm": "*" + "@types/debug": ^4.1.12 + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + "@vitest/browser": 3.2.4 + "@vitest/ui": 3.2.4 + happy-dom: "*" + jsdom: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@types/debug": + optional: true + "@types/node": + optional: true + "@vitest/browser": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + vscode-uri@3.1.0: + resolution: + { + integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ== + } + + w3c-keyname@2.2.8: + resolution: + { + integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ== + } + + w3c-xmlserializer@5.0.0: + resolution: + { + integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA== + } + engines: { node: ">=18" } + + webidl-conversions@7.0.0: + resolution: + { + integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + } + engines: { node: ">=12" } + + whatwg-encoding@3.1.1: + resolution: + { + integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ== + } + engines: { node: ">=18" } + + whatwg-mimetype@4.0.0: + resolution: + { + integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg== + } + engines: { node: ">=18" } + + whatwg-url@14.2.0: + resolution: + { + integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw== + } + engines: { node: ">=18" } + + which@2.0.2: + resolution: + { + integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + } + engines: { node: ">= 8" } + hasBin: true + + why-is-node-running@2.3.0: + resolution: + { + integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w== + } + engines: { node: ">=8" } + hasBin: true + + word-wrap@1.2.5: + resolution: + { + integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + } + engines: { node: ">=0.10.0" } + + wrap-ansi@7.0.0: + resolution: + { + integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + } + engines: { node: ">=10" } + + wrap-ansi@9.0.0: + resolution: + { + integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q== + } + engines: { node: ">=18" } + + wrappy@1.0.2: + resolution: + { + integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + } + + ws@8.18.3: + resolution: + { + integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg== + } + engines: { node: ">=10.0.0" } + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: + { + integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg== + } + engines: { node: ">=18" } + + xmlchars@2.2.0: + resolution: + { + integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + } + + y18n@5.0.8: + resolution: + { + integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + } + engines: { node: ">=10" } + + yallist@3.1.1: + resolution: + { + integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + } + + yallist@4.0.0: + resolution: + { + integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + } + + yallist@5.0.0: + resolution: + { + integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== + } + engines: { node: ">=18" } + + yaml@2.8.0: + resolution: + { + integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ== + } + engines: { node: ">= 14.6" } + hasBin: true + + yargs-parser@21.1.1: + resolution: + { + integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + } + engines: { node: ">=12" } + + yargs@17.7.2: + resolution: + { + integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + } + engines: { node: ">=12" } + + yn@3.1.1: + resolution: + { + integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + } + engines: { node: ">=6" } + + yocto-queue@0.1.0: + resolution: + { + integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + } + engines: { node: ">=10" } + + zustand@5.0.7: + resolution: + { + integrity: sha512-Ot6uqHDW/O2VdYsKLLU8GQu8sCOM1LcoE8RwvLv9uuRT9s6SOHCKs0ZEOhxg+I1Ld+A1Q5lwx+UlKXXUoCZITg== + } + engines: { node: ">=12.20.0" } + peerDependencies: + "@types/react": ">=18.0.0" + immer: ">=9.0.6" + react: ">=18.0.0" + use-sync-external-store: ">=1.2.0" + peerDependenciesMeta: + "@types/react": + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + +snapshots: + "@adobe/css-tools@4.4.3": {} + + "@ampproject/remapping@2.3.0": + dependencies: + "@jridgewell/gen-mapping": 0.3.12 + "@jridgewell/trace-mapping": 0.3.29 + + "@asamuzakjp/css-color@3.2.0": + dependencies: + "@csstools/css-calc": 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + "@csstools/css-color-parser": 3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + "@csstools/css-parser-algorithms": 3.0.5(@csstools/css-tokenizer@3.0.4) + "@csstools/css-tokenizer": 3.0.4 + lru-cache: 10.4.3 + + "@babel/code-frame@7.27.1": + dependencies: + "@babel/helper-validator-identifier": 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + "@babel/compat-data@7.28.0": {} + + "@babel/core@7.28.0": + dependencies: + "@ampproject/remapping": 2.3.0 + "@babel/code-frame": 7.27.1 + "@babel/generator": 7.28.0 + "@babel/helper-compilation-targets": 7.27.2 + "@babel/helper-module-transforms": 7.27.3(@babel/core@7.28.0) + "@babel/helpers": 7.27.6 + "@babel/parser": 7.28.0 + "@babel/template": 7.27.2 + "@babel/traverse": 7.28.0 + "@babel/types": 7.28.0 + convert-source-map: 2.0.0 + debug: 4.4.1 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + "@babel/generator@7.28.0": + dependencies: + "@babel/parser": 7.28.0 + "@babel/types": 7.28.0 + "@jridgewell/gen-mapping": 0.3.12 + "@jridgewell/trace-mapping": 0.3.29 + jsesc: 3.1.0 + + "@babel/helper-compilation-targets@7.27.2": + dependencies: + "@babel/compat-data": 7.28.0 + "@babel/helper-validator-option": 7.27.1 + browserslist: 4.25.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + "@babel/helper-globals@7.28.0": {} + + "@babel/helper-module-imports@7.27.1": + dependencies: + "@babel/traverse": 7.28.0 + "@babel/types": 7.28.0 + transitivePeerDependencies: + - supports-color + + "@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0)": + dependencies: + "@babel/core": 7.28.0 + "@babel/helper-module-imports": 7.27.1 + "@babel/helper-validator-identifier": 7.27.1 + "@babel/traverse": 7.28.0 + transitivePeerDependencies: + - supports-color + + "@babel/helper-string-parser@7.27.1": {} + + "@babel/helper-validator-identifier@7.27.1": {} + + "@babel/helper-validator-option@7.27.1": {} + + "@babel/helpers@7.27.6": + dependencies: + "@babel/template": 7.27.2 + "@babel/types": 7.28.0 + + "@babel/parser@7.28.0": + dependencies: + "@babel/types": 7.28.0 + + "@babel/runtime@7.27.6": {} + + "@babel/template@7.27.2": + dependencies: + "@babel/code-frame": 7.27.1 + "@babel/parser": 7.28.0 + "@babel/types": 7.28.0 + + "@babel/traverse@7.28.0": + dependencies: + "@babel/code-frame": 7.27.1 + "@babel/generator": 7.28.0 + "@babel/helper-globals": 7.28.0 + "@babel/parser": 7.28.0 + "@babel/template": 7.27.2 + "@babel/types": 7.28.0 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + "@babel/types@7.28.0": + dependencies: + "@babel/helper-string-parser": 7.27.1 + "@babel/helper-validator-identifier": 7.27.1 + + "@codemirror/autocomplete@6.18.6": + dependencies: + "@codemirror/language": 6.11.2 + "@codemirror/state": 6.5.2 + "@codemirror/view": 6.38.1 + "@lezer/common": 1.2.3 + + "@codemirror/lang-css@6.3.1": + dependencies: + "@codemirror/autocomplete": 6.18.6 + "@codemirror/language": 6.11.2 + "@codemirror/state": 6.5.2 + "@lezer/common": 1.2.3 + "@lezer/css": 1.3.0 + + "@codemirror/lang-html@6.4.9": + dependencies: + "@codemirror/autocomplete": 6.18.6 + "@codemirror/lang-css": 6.3.1 + "@codemirror/lang-javascript": 6.2.4 + "@codemirror/language": 6.11.2 + "@codemirror/state": 6.5.2 + "@codemirror/view": 6.38.1 + "@lezer/common": 1.2.3 + "@lezer/css": 1.3.0 + "@lezer/html": 1.3.10 + + "@codemirror/lang-javascript@6.2.4": + dependencies: + "@codemirror/autocomplete": 6.18.6 + "@codemirror/language": 6.11.2 + "@codemirror/lint": 6.8.5 + "@codemirror/state": 6.5.2 + "@codemirror/view": 6.38.1 + "@lezer/common": 1.2.3 + "@lezer/javascript": 1.5.1 + + "@codemirror/lang-markdown@6.3.4": + dependencies: + "@codemirror/autocomplete": 6.18.6 + "@codemirror/lang-html": 6.4.9 + "@codemirror/language": 6.11.2 + "@codemirror/state": 6.5.2 + "@codemirror/view": 6.38.1 + "@lezer/common": 1.2.3 + "@lezer/markdown": 1.4.3 + + "@codemirror/language@6.11.2": + dependencies: + "@codemirror/state": 6.5.2 + "@codemirror/view": 6.38.1 + "@lezer/common": 1.2.3 + "@lezer/highlight": 1.2.1 + "@lezer/lr": 1.4.2 + style-mod: 4.1.2 + + "@codemirror/lint@6.8.5": + dependencies: + "@codemirror/state": 6.5.2 + "@codemirror/view": 6.38.1 + crelt: 1.0.6 + + "@codemirror/state@6.5.2": + dependencies: + "@marijn/find-cluster-break": 1.0.2 + + "@codemirror/view@6.38.1": + dependencies: + "@codemirror/state": 6.5.2 + crelt: 1.0.6 + style-mod: 4.1.2 + w3c-keyname: 2.2.8 + + "@cspotcode/source-map-support@0.8.1": + dependencies: + "@jridgewell/trace-mapping": 0.3.9 + + "@csstools/color-helpers@5.0.2": {} + + "@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)": + dependencies: + "@csstools/css-parser-algorithms": 3.0.5(@csstools/css-tokenizer@3.0.4) + "@csstools/css-tokenizer": 3.0.4 + + "@csstools/css-color-parser@3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)": + dependencies: + "@csstools/color-helpers": 5.0.2 + "@csstools/css-calc": 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + "@csstools/css-parser-algorithms": 3.0.5(@csstools/css-tokenizer@3.0.4) + "@csstools/css-tokenizer": 3.0.4 + + "@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)": + dependencies: + "@csstools/css-tokenizer": 3.0.4 + + "@csstools/css-tokenizer@3.0.4": {} + + "@esbuild/aix-ppc64@0.25.5": + optional: true + + "@esbuild/android-arm64@0.25.5": + optional: true + + "@esbuild/android-arm@0.25.5": + optional: true + + "@esbuild/android-x64@0.25.5": + optional: true + + "@esbuild/darwin-arm64@0.25.5": + optional: true + + "@esbuild/darwin-x64@0.25.5": + optional: true + + "@esbuild/freebsd-arm64@0.25.5": + optional: true + + "@esbuild/freebsd-x64@0.25.5": + optional: true + + "@esbuild/linux-arm64@0.25.5": + optional: true + + "@esbuild/linux-arm@0.25.5": + optional: true + + "@esbuild/linux-ia32@0.25.5": + optional: true + + "@esbuild/linux-loong64@0.25.5": + optional: true + + "@esbuild/linux-mips64el@0.25.5": + optional: true + + "@esbuild/linux-ppc64@0.25.5": + optional: true + + "@esbuild/linux-riscv64@0.25.5": + optional: true + + "@esbuild/linux-s390x@0.25.5": + optional: true + + "@esbuild/linux-x64@0.25.5": + optional: true + + "@esbuild/netbsd-arm64@0.25.5": + optional: true + + "@esbuild/netbsd-x64@0.25.5": + optional: true + + "@esbuild/openbsd-arm64@0.25.5": + optional: true + + "@esbuild/openbsd-x64@0.25.5": + optional: true + + "@esbuild/sunos-x64@0.25.5": + optional: true + + "@esbuild/win32-arm64@0.25.5": + optional: true + + "@esbuild/win32-ia32@0.25.5": + optional: true + + "@esbuild/win32-x64@0.25.5": + optional: true + + "@eslint-community/eslint-utils@4.7.0(eslint@9.30.1(jiti@2.4.2))": + dependencies: + eslint: 9.30.1(jiti@2.4.2) + eslint-visitor-keys: 3.4.3 + + "@eslint-community/regexpp@4.12.1": {} + + "@eslint/config-array@0.21.0": + dependencies: + "@eslint/object-schema": 2.1.6 + debug: 4.4.1 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + "@eslint/config-helpers@0.3.0": {} + + "@eslint/core@0.14.0": + dependencies: + "@types/json-schema": 7.0.15 + + "@eslint/core@0.15.1": + dependencies: + "@types/json-schema": 7.0.15 + + "@eslint/eslintrc@3.3.1": + dependencies: + ajv: 6.12.6 + debug: 4.4.1 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + "@eslint/js@9.30.1": {} + + "@eslint/object-schema@2.1.6": {} + + "@eslint/plugin-kit@0.3.3": + dependencies: + "@eslint/core": 0.15.1 + levn: 0.4.1 + + "@floating-ui/core@1.7.2": + dependencies: + "@floating-ui/utils": 0.2.10 + + "@floating-ui/dom@1.7.2": + dependencies: + "@floating-ui/core": 1.7.2 + "@floating-ui/utils": 0.2.10 + + "@floating-ui/react-dom@2.1.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + dependencies: + "@floating-ui/dom": 1.7.2 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + "@floating-ui/react@0.26.28(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + dependencies: + "@floating-ui/react-dom": 2.1.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + "@floating-ui/utils": 0.2.10 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + tabbable: 6.2.0 + + "@floating-ui/utils@0.2.10": {} + + "@headlessui/react@2.2.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + dependencies: + "@floating-ui/react": 0.26.28(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + "@react-aria/focus": 3.20.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + "@react-aria/interactions": 3.25.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + "@tanstack/react-virtual": 3.13.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + use-sync-external-store: 1.5.0(react@19.1.0) + + "@headlessui/tailwindcss@0.2.2(tailwindcss@4.1.11)": + dependencies: + tailwindcss: 4.1.11 + + "@heroicons/react@2.2.0(react@19.1.0)": + dependencies: + react: 19.1.0 + + "@humanfs/core@0.19.1": {} + + "@humanfs/node@0.16.6": + dependencies: + "@humanfs/core": 0.19.1 + "@humanwhocodes/retry": 0.3.1 + + "@humanwhocodes/module-importer@1.0.1": {} + + "@humanwhocodes/retry@0.3.1": {} + + "@humanwhocodes/retry@0.4.3": {} + + "@isaacs/balanced-match@4.0.1": {} + + "@isaacs/brace-expansion@5.0.0": + dependencies: + "@isaacs/balanced-match": 4.0.1 + + "@isaacs/fs-minipass@4.0.1": + dependencies: + minipass: 7.1.2 + + "@jridgewell/gen-mapping@0.3.12": + dependencies: + "@jridgewell/sourcemap-codec": 1.5.4 + "@jridgewell/trace-mapping": 0.3.29 + + "@jridgewell/resolve-uri@3.1.2": {} + + "@jridgewell/sourcemap-codec@1.5.4": {} + + "@jridgewell/trace-mapping@0.3.29": + dependencies: + "@jridgewell/resolve-uri": 3.1.2 + "@jridgewell/sourcemap-codec": 1.5.4 + + "@jridgewell/trace-mapping@0.3.9": + dependencies: + "@jridgewell/resolve-uri": 3.1.2 + "@jridgewell/sourcemap-codec": 1.5.4 + + "@lezer/common@1.2.3": {} + + "@lezer/css@1.3.0": + dependencies: + "@lezer/common": 1.2.3 + "@lezer/highlight": 1.2.1 + "@lezer/lr": 1.4.2 + + "@lezer/highlight@1.2.1": + dependencies: + "@lezer/common": 1.2.3 + + "@lezer/html@1.3.10": + dependencies: + "@lezer/common": 1.2.3 + "@lezer/highlight": 1.2.1 + "@lezer/lr": 1.4.2 + + "@lezer/javascript@1.5.1": + dependencies: + "@lezer/common": 1.2.3 + "@lezer/highlight": 1.2.1 + "@lezer/lr": 1.4.2 + + "@lezer/lr@1.4.2": + dependencies: + "@lezer/common": 1.2.3 + + "@lezer/markdown@1.4.3": + dependencies: + "@lezer/common": 1.2.3 + "@lezer/highlight": 1.2.1 + + "@marijn/find-cluster-break@1.0.2": {} + + "@microsoft/api-extractor-model@7.30.7(@types/node@24.2.0)": + dependencies: + "@microsoft/tsdoc": 0.15.1 + "@microsoft/tsdoc-config": 0.17.1 + "@rushstack/node-core-library": 5.14.0(@types/node@24.2.0) + transitivePeerDependencies: + - "@types/node" + + "@microsoft/api-extractor@7.52.10(@types/node@24.2.0)": + dependencies: + "@microsoft/api-extractor-model": 7.30.7(@types/node@24.2.0) + "@microsoft/tsdoc": 0.15.1 + "@microsoft/tsdoc-config": 0.17.1 + "@rushstack/node-core-library": 5.14.0(@types/node@24.2.0) + "@rushstack/rig-package": 0.5.3 + "@rushstack/terminal": 0.15.4(@types/node@24.2.0) + "@rushstack/ts-command-line": 5.0.2(@types/node@24.2.0) + lodash: 4.17.21 + minimatch: 10.0.3 + resolve: 1.22.10 + semver: 7.5.4 + source-map: 0.6.1 + typescript: 5.8.2 + transitivePeerDependencies: + - "@types/node" + + "@microsoft/tsdoc-config@0.17.1": + dependencies: + "@microsoft/tsdoc": 0.15.1 + ajv: 8.12.0 + jju: 1.4.0 + resolve: 1.22.10 + + "@microsoft/tsdoc@0.15.1": {} + + "@nodelib/fs.scandir@2.1.5": + dependencies: + "@nodelib/fs.stat": 2.0.5 + run-parallel: 1.2.0 + + "@nodelib/fs.stat@2.0.5": {} + + "@nodelib/fs.walk@1.2.8": + dependencies: + "@nodelib/fs.scandir": 2.1.5 + fastq: 1.19.1 + + "@react-aria/focus@3.20.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + dependencies: + "@react-aria/interactions": 3.25.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + "@react-aria/utils": 3.29.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + "@react-types/shared": 3.30.0(react@19.1.0) + "@swc/helpers": 0.5.17 + clsx: 2.1.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + "@react-aria/interactions@3.25.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + dependencies: + "@react-aria/ssr": 3.9.9(react@19.1.0) + "@react-aria/utils": 3.29.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + "@react-stately/flags": 3.1.2 + "@react-types/shared": 3.30.0(react@19.1.0) + "@swc/helpers": 0.5.17 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + "@react-aria/ssr@3.9.9(react@19.1.0)": + dependencies: + "@swc/helpers": 0.5.17 + react: 19.1.0 + + "@react-aria/utils@3.29.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + dependencies: + "@react-aria/ssr": 3.9.9(react@19.1.0) + "@react-stately/flags": 3.1.2 + "@react-stately/utils": 3.10.7(react@19.1.0) + "@react-types/shared": 3.30.0(react@19.1.0) + "@swc/helpers": 0.5.17 + clsx: 2.1.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + "@react-stately/flags@3.1.2": + dependencies: + "@swc/helpers": 0.5.17 + + "@react-stately/utils@3.10.7(react@19.1.0)": + dependencies: + "@swc/helpers": 0.5.17 + react: 19.1.0 + + "@react-types/shared@3.30.0(react@19.1.0)": + dependencies: + react: 19.1.0 + + "@rolldown/pluginutils@1.0.0-beta.11": {} + + "@rollup/pluginutils@5.2.0(rollup@4.44.2)": + dependencies: + "@types/estree": 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.44.2 + + "@rollup/rollup-android-arm-eabi@4.44.2": + optional: true + + "@rollup/rollup-android-arm64@4.44.2": + optional: true + + "@rollup/rollup-darwin-arm64@4.44.2": + optional: true + + "@rollup/rollup-darwin-x64@4.44.2": + optional: true + + "@rollup/rollup-freebsd-arm64@4.44.2": + optional: true + + "@rollup/rollup-freebsd-x64@4.44.2": + optional: true + + "@rollup/rollup-linux-arm-gnueabihf@4.44.2": + optional: true + + "@rollup/rollup-linux-arm-musleabihf@4.44.2": + optional: true + + "@rollup/rollup-linux-arm64-gnu@4.44.2": + optional: true + + "@rollup/rollup-linux-arm64-musl@4.44.2": + optional: true + + "@rollup/rollup-linux-loongarch64-gnu@4.44.2": + optional: true + + "@rollup/rollup-linux-powerpc64le-gnu@4.44.2": + optional: true + + "@rollup/rollup-linux-riscv64-gnu@4.44.2": + optional: true + + "@rollup/rollup-linux-riscv64-musl@4.44.2": + optional: true + + "@rollup/rollup-linux-s390x-gnu@4.44.2": + optional: true + + "@rollup/rollup-linux-x64-gnu@4.44.2": + optional: true + + "@rollup/rollup-linux-x64-musl@4.44.2": + optional: true + + "@rollup/rollup-win32-arm64-msvc@4.44.2": + optional: true + + "@rollup/rollup-win32-ia32-msvc@4.44.2": + optional: true + + "@rollup/rollup-win32-x64-msvc@4.44.2": + optional: true + + "@rushstack/node-core-library@5.14.0(@types/node@24.2.0)": + dependencies: + ajv: 8.13.0 + ajv-draft-04: 1.0.0(ajv@8.13.0) + ajv-formats: 3.0.1(ajv@8.13.0) + fs-extra: 11.3.0 + import-lazy: 4.0.0 + jju: 1.4.0 + resolve: 1.22.10 + semver: 7.5.4 + optionalDependencies: + "@types/node": 24.2.0 + + "@rushstack/rig-package@0.5.3": + dependencies: + resolve: 1.22.10 + strip-json-comments: 3.1.1 + + "@rushstack/terminal@0.15.4(@types/node@24.2.0)": + dependencies: + "@rushstack/node-core-library": 5.14.0(@types/node@24.2.0) + supports-color: 8.1.1 + optionalDependencies: + "@types/node": 24.2.0 + + "@rushstack/ts-command-line@5.0.2(@types/node@24.2.0)": + dependencies: + "@rushstack/terminal": 0.15.4(@types/node@24.2.0) + "@types/argparse": 1.0.38 + argparse: 1.0.10 + string-argv: 0.3.2 + transitivePeerDependencies: + - "@types/node" + + "@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.28.0)": + dependencies: + "@babel/core": 7.28.0 + + "@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.28.0)": + dependencies: + "@babel/core": 7.28.0 + + "@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.28.0)": + dependencies: + "@babel/core": 7.28.0 + + "@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.28.0)": + dependencies: + "@babel/core": 7.28.0 + + "@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.28.0)": + dependencies: + "@babel/core": 7.28.0 + + "@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.28.0)": + dependencies: + "@babel/core": 7.28.0 + + "@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.28.0)": + dependencies: + "@babel/core": 7.28.0 + + "@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.28.0)": + dependencies: + "@babel/core": 7.28.0 + + "@svgr/babel-preset@8.1.0(@babel/core@7.28.0)": + dependencies: + "@babel/core": 7.28.0 + "@svgr/babel-plugin-add-jsx-attribute": 8.0.0(@babel/core@7.28.0) + "@svgr/babel-plugin-remove-jsx-attribute": 8.0.0(@babel/core@7.28.0) + "@svgr/babel-plugin-remove-jsx-empty-expression": 8.0.0(@babel/core@7.28.0) + "@svgr/babel-plugin-replace-jsx-attribute-value": 8.0.0(@babel/core@7.28.0) + "@svgr/babel-plugin-svg-dynamic-title": 8.0.0(@babel/core@7.28.0) + "@svgr/babel-plugin-svg-em-dimensions": 8.0.0(@babel/core@7.28.0) + "@svgr/babel-plugin-transform-react-native-svg": 8.1.0(@babel/core@7.28.0) + "@svgr/babel-plugin-transform-svg-component": 8.0.0(@babel/core@7.28.0) + + "@svgr/core@8.1.0(typescript@5.8.3)": + dependencies: + "@babel/core": 7.28.0 + "@svgr/babel-preset": 8.1.0(@babel/core@7.28.0) + camelcase: 6.3.0 + cosmiconfig: 8.3.6(typescript@5.8.3) + snake-case: 3.0.4 + transitivePeerDependencies: + - supports-color + - typescript + + "@svgr/hast-util-to-babel-ast@8.0.0": + dependencies: + "@babel/types": 7.28.0 + entities: 4.5.0 + + "@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.8.3))": + dependencies: + "@babel/core": 7.28.0 + "@svgr/babel-preset": 8.1.0(@babel/core@7.28.0) + "@svgr/core": 8.1.0(typescript@5.8.3) + "@svgr/hast-util-to-babel-ast": 8.0.0 + svg-parser: 2.0.4 + transitivePeerDependencies: + - supports-color + + "@swc/core-darwin-arm64@1.12.9": + optional: true + + "@swc/core-darwin-x64@1.12.9": + optional: true + + "@swc/core-linux-arm-gnueabihf@1.12.9": + optional: true + + "@swc/core-linux-arm64-gnu@1.12.9": + optional: true + + "@swc/core-linux-arm64-musl@1.12.9": + optional: true + + "@swc/core-linux-x64-gnu@1.12.9": + optional: true + + "@swc/core-linux-x64-musl@1.12.9": + optional: true + + "@swc/core-win32-arm64-msvc@1.12.9": + optional: true + + "@swc/core-win32-ia32-msvc@1.12.9": + optional: true + + "@swc/core-win32-x64-msvc@1.12.9": + optional: true + + "@swc/core@1.12.9(@swc/helpers@0.5.17)": + dependencies: + "@swc/counter": 0.1.3 + "@swc/types": 0.1.23 + optionalDependencies: + "@swc/core-darwin-arm64": 1.12.9 + "@swc/core-darwin-x64": 1.12.9 + "@swc/core-linux-arm-gnueabihf": 1.12.9 + "@swc/core-linux-arm64-gnu": 1.12.9 + "@swc/core-linux-arm64-musl": 1.12.9 + "@swc/core-linux-x64-gnu": 1.12.9 + "@swc/core-linux-x64-musl": 1.12.9 + "@swc/core-win32-arm64-msvc": 1.12.9 + "@swc/core-win32-ia32-msvc": 1.12.9 + "@swc/core-win32-x64-msvc": 1.12.9 + "@swc/helpers": 0.5.17 + + "@swc/counter@0.1.3": {} + + "@swc/helpers@0.5.17": + dependencies: + tslib: 2.8.1 + + "@swc/types@0.1.23": + dependencies: + "@swc/counter": 0.1.3 + + "@tailwindcss/node@4.1.11": + dependencies: + "@ampproject/remapping": 2.3.0 + enhanced-resolve: 5.18.2 + jiti: 2.4.2 + lightningcss: 1.30.1 + magic-string: 0.30.17 + source-map-js: 1.2.1 + tailwindcss: 4.1.11 + + "@tailwindcss/oxide-android-arm64@4.1.11": + optional: true + + "@tailwindcss/oxide-darwin-arm64@4.1.11": + optional: true + + "@tailwindcss/oxide-darwin-x64@4.1.11": + optional: true + + "@tailwindcss/oxide-freebsd-x64@4.1.11": + optional: true + + "@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11": + optional: true + + "@tailwindcss/oxide-linux-arm64-gnu@4.1.11": + optional: true + + "@tailwindcss/oxide-linux-arm64-musl@4.1.11": + optional: true + + "@tailwindcss/oxide-linux-x64-gnu@4.1.11": + optional: true + + "@tailwindcss/oxide-linux-x64-musl@4.1.11": + optional: true + + "@tailwindcss/oxide-wasm32-wasi@4.1.11": + optional: true + + "@tailwindcss/oxide-win32-arm64-msvc@4.1.11": + optional: true + + "@tailwindcss/oxide-win32-x64-msvc@4.1.11": + optional: true + + "@tailwindcss/oxide@4.1.11": + dependencies: + detect-libc: 2.0.4 + tar: 7.4.3 + optionalDependencies: + "@tailwindcss/oxide-android-arm64": 4.1.11 + "@tailwindcss/oxide-darwin-arm64": 4.1.11 + "@tailwindcss/oxide-darwin-x64": 4.1.11 + "@tailwindcss/oxide-freebsd-x64": 4.1.11 + "@tailwindcss/oxide-linux-arm-gnueabihf": 4.1.11 + "@tailwindcss/oxide-linux-arm64-gnu": 4.1.11 + "@tailwindcss/oxide-linux-arm64-musl": 4.1.11 + "@tailwindcss/oxide-linux-x64-gnu": 4.1.11 + "@tailwindcss/oxide-linux-x64-musl": 4.1.11 + "@tailwindcss/oxide-wasm32-wasi": 4.1.11 + "@tailwindcss/oxide-win32-arm64-msvc": 4.1.11 + "@tailwindcss/oxide-win32-x64-msvc": 4.1.11 + + "@tailwindcss/vite@4.1.11(vite@7.0.6(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))": + dependencies: + "@tailwindcss/node": 4.1.11 + "@tailwindcss/oxide": 4.1.11 + tailwindcss: 4.1.11 + vite: 7.0.6(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0) + + "@tailwindplus/elements@1.0.5": {} + + "@tanstack/react-virtual@3.13.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + dependencies: + "@tanstack/virtual-core": 3.13.12 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + "@tanstack/virtual-core@3.13.12": {} + + "@testing-library/dom@10.4.0": + dependencies: + "@babel/code-frame": 7.27.1 + "@babel/runtime": 7.27.6 + "@types/aria-query": 5.0.4 + aria-query: 5.3.0 + chalk: 4.1.2 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + pretty-format: 27.5.1 + + "@testing-library/jest-dom@6.6.4": + dependencies: + "@adobe/css-tools": 4.4.3 + aria-query: 5.3.2 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + lodash: 4.17.21 + picocolors: 1.1.1 + redent: 3.0.0 + + "@testing-library/react@16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + dependencies: + "@babel/runtime": 7.27.6 + "@testing-library/dom": 10.4.0 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + "@types/react": 19.1.8 + "@types/react-dom": 19.1.6(@types/react@19.1.8) + + "@testing-library/user-event@14.6.1(@testing-library/dom@10.4.0)": + dependencies: + "@testing-library/dom": 10.4.0 + + "@tsconfig/node10@1.0.11": {} + + "@tsconfig/node12@1.0.11": {} + + "@tsconfig/node14@1.0.3": {} + + "@tsconfig/node16@1.0.4": {} + + "@types/argparse@1.0.38": {} + + "@types/aria-query@5.0.4": {} + + "@types/chai@5.2.2": + dependencies: + "@types/deep-eql": 4.0.2 + + "@types/deep-eql@4.0.2": {} + + "@types/estree@1.0.8": {} + + "@types/json-schema@7.0.15": {} + + "@types/node@18.19.121": + dependencies: + undici-types: 5.26.5 + + "@types/node@24.2.0": + dependencies: + undici-types: 7.10.0 + + "@types/react-dom@19.1.6(@types/react@19.1.8)": + dependencies: + "@types/react": 19.1.8 + + "@types/react@19.1.8": + dependencies: + csstype: 3.1.3 + + "@typescript-eslint/eslint-plugin@8.35.1(@typescript-eslint/parser@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)": + dependencies: + "@eslint-community/regexpp": 4.12.1 + "@typescript-eslint/parser": 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + "@typescript-eslint/scope-manager": 8.35.1 + "@typescript-eslint/type-utils": 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + "@typescript-eslint/utils": 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + "@typescript-eslint/visitor-keys": 8.35.1 + eslint: 9.30.1(jiti@2.4.2) + graphemer: 1.4.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/parser@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)": + dependencies: + "@typescript-eslint/scope-manager": 8.35.1 + "@typescript-eslint/types": 8.35.1 + "@typescript-eslint/typescript-estree": 8.35.1(typescript@5.8.3) + "@typescript-eslint/visitor-keys": 8.35.1 + debug: 4.4.1 + eslint: 9.30.1(jiti@2.4.2) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/project-service@8.35.1(typescript@5.8.3)": + dependencies: + "@typescript-eslint/tsconfig-utils": 8.35.1(typescript@5.8.3) + "@typescript-eslint/types": 8.35.1 + debug: 4.4.1 + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/scope-manager@8.35.1": + dependencies: + "@typescript-eslint/types": 8.35.1 + "@typescript-eslint/visitor-keys": 8.35.1 + + "@typescript-eslint/tsconfig-utils@8.35.1(typescript@5.8.3)": + dependencies: + typescript: 5.8.3 + + "@typescript-eslint/type-utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)": + dependencies: + "@typescript-eslint/typescript-estree": 8.35.1(typescript@5.8.3) + "@typescript-eslint/utils": 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + debug: 4.4.1 + eslint: 9.30.1(jiti@2.4.2) + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/types@8.35.1": {} + + "@typescript-eslint/typescript-estree@8.35.1(typescript@5.8.3)": + dependencies: + "@typescript-eslint/project-service": 8.35.1(typescript@5.8.3) + "@typescript-eslint/tsconfig-utils": 8.35.1(typescript@5.8.3) + "@typescript-eslint/types": 8.35.1 + "@typescript-eslint/visitor-keys": 8.35.1 + debug: 4.4.1 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.2 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)": + dependencies: + "@eslint-community/eslint-utils": 4.7.0(eslint@9.30.1(jiti@2.4.2)) + "@typescript-eslint/scope-manager": 8.35.1 + "@typescript-eslint/types": 8.35.1 + "@typescript-eslint/typescript-estree": 8.35.1(typescript@5.8.3) + eslint: 9.30.1(jiti@2.4.2) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/visitor-keys@8.35.1": + dependencies: + "@typescript-eslint/types": 8.35.1 + eslint-visitor-keys: 4.2.1 + + "@vitejs/plugin-react-swc@3.10.2(@swc/helpers@0.5.17)(vite@7.0.6(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))": + dependencies: + "@rolldown/pluginutils": 1.0.0-beta.11 + "@swc/core": 1.12.9(@swc/helpers@0.5.17) + vite: 7.0.6(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0) + transitivePeerDependencies: + - "@swc/helpers" + + "@vitest/expect@3.2.4": + dependencies: + "@types/chai": 5.2.2 + "@vitest/spy": 3.2.4 + "@vitest/utils": 3.2.4 + chai: 5.2.1 + tinyrainbow: 2.0.0 + + "@vitest/mocker@3.2.4(vite@7.0.6(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))": + dependencies: + "@vitest/spy": 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 7.0.6(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0) + + "@vitest/pretty-format@3.2.4": + dependencies: + tinyrainbow: 2.0.0 + + "@vitest/runner@3.2.4": + dependencies: + "@vitest/utils": 3.2.4 + pathe: 2.0.3 + strip-literal: 3.0.0 + + "@vitest/snapshot@3.2.4": + dependencies: + "@vitest/pretty-format": 3.2.4 + magic-string: 0.30.17 + pathe: 2.0.3 + + "@vitest/spy@3.2.4": + dependencies: + tinyspy: 4.0.3 + + "@vitest/utils@3.2.4": + dependencies: + "@vitest/pretty-format": 3.2.4 + loupe: 3.1.4 + tinyrainbow: 2.0.0 + + "@volar/language-core@2.4.23": + dependencies: + "@volar/source-map": 2.4.23 + + "@volar/source-map@2.4.23": {} + + "@volar/typescript@2.4.23": + dependencies: + "@volar/language-core": 2.4.23 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + "@vue/compiler-core@3.5.18": + dependencies: + "@babel/parser": 7.28.0 + "@vue/shared": 3.5.18 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + "@vue/compiler-dom@3.5.18": + dependencies: + "@vue/compiler-core": 3.5.18 + "@vue/shared": 3.5.18 + + "@vue/compiler-vue2@2.7.16": + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + + "@vue/language-core@2.2.0(typescript@5.8.3)": + dependencies: + "@volar/language-core": 2.4.23 + "@vue/compiler-dom": 3.5.18 + "@vue/compiler-vue2": 2.7.16 + "@vue/shared": 3.5.18 + alien-signals: 0.4.14 + minimatch: 9.0.5 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + optionalDependencies: + typescript: 5.8.3 + + "@vue/shared@3.5.18": {} + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn-walk@8.3.4: + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + agent-base@7.1.3: {} + + ajv-draft-04@1.0.0(ajv@8.13.0): + optionalDependencies: + ajv: 8.13.0 + + ajv-formats@3.0.1(ajv@8.13.0): + optionalDependencies: + ajv: 8.13.0 + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.12.0: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + ajv@8.13.0: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + alien-signals@0.4.14: {} + + ansi-escapes@7.0.0: + dependencies: + environment: 1.1.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.1.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.1: {} + + arg@4.1.3: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + aria-query@5.3.2: {} + + assertion-error@2.0.1: {} + + balanced-match@1.0.2: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.25.1: + dependencies: + caniuse-lite: 1.0.30001726 + electron-to-chromium: 1.5.179 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.25.1) + + cac@6.7.14: {} + + callsites@3.1.0: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001726: {} + + chai@5.2.1: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.4 + pathval: 2.0.1 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.4.1: {} + + check-error@2.1.1: {} + + chownr@3.0.0: {} + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-truncate@4.0.0: + dependencies: + slice-ansi: 5.0.0 + string-width: 7.2.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clsx@2.1.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + colorette@2.0.20: {} + + commander@14.0.0: {} + + compare-versions@6.1.1: {} + + concat-map@0.0.1: {} + + confbox@0.1.8: {} + + confbox@0.2.2: {} + + convert-source-map@2.0.0: {} + + cookie@1.0.2: {} + + cosmiconfig@8.3.6(typescript@5.8.3): + dependencies: + import-fresh: 3.3.1 + js-yaml: 4.1.0 + parse-json: 5.2.0 + path-type: 4.0.0 + optionalDependencies: + typescript: 5.8.3 + + create-require@1.1.1: {} + + crelt@1.0.6: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css.escape@1.5.1: {} + + cssstyle@4.6.0: + dependencies: + "@asamuzakjp/css-color": 3.2.0 + rrweb-cssom: 0.8.0 + + csstype@3.1.3: {} + + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + + de-indent@1.0.2: {} + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + decimal.js@10.6.0: {} + + deep-eql@5.0.2: {} + + deep-is@0.1.4: {} + + define-lazy-prop@2.0.0: {} + + dequal@2.0.3: {} + + detect-libc@2.0.4: {} + + diff@4.0.2: {} + + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + + dot-case@3.0.4: + dependencies: + no-case: 3.0.4 + tslib: 2.8.1 + + electron-to-chromium@1.5.179: {} + + emoji-regex@10.4.0: {} + + emoji-regex@8.0.0: {} + + enhanced-resolve@5.18.2: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.2 + + entities@4.5.0: {} + + entities@6.0.1: {} + + environment@1.1.0: {} + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + es-module-lexer@1.7.0: {} + + esbuild@0.25.5: + optionalDependencies: + "@esbuild/aix-ppc64": 0.25.5 + "@esbuild/android-arm": 0.25.5 + "@esbuild/android-arm64": 0.25.5 + "@esbuild/android-x64": 0.25.5 + "@esbuild/darwin-arm64": 0.25.5 + "@esbuild/darwin-x64": 0.25.5 + "@esbuild/freebsd-arm64": 0.25.5 + "@esbuild/freebsd-x64": 0.25.5 + "@esbuild/linux-arm": 0.25.5 + "@esbuild/linux-arm64": 0.25.5 + "@esbuild/linux-ia32": 0.25.5 + "@esbuild/linux-loong64": 0.25.5 + "@esbuild/linux-mips64el": 0.25.5 + "@esbuild/linux-ppc64": 0.25.5 + "@esbuild/linux-riscv64": 0.25.5 + "@esbuild/linux-s390x": 0.25.5 + "@esbuild/linux-x64": 0.25.5 + "@esbuild/netbsd-arm64": 0.25.5 + "@esbuild/netbsd-x64": 0.25.5 + "@esbuild/openbsd-arm64": 0.25.5 + "@esbuild/openbsd-x64": 0.25.5 + "@esbuild/sunos-x64": 0.25.5 + "@esbuild/win32-arm64": 0.25.5 + "@esbuild/win32-ia32": 0.25.5 + "@esbuild/win32-x64": 0.25.5 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@5.2.0(eslint@9.30.1(jiti@2.4.2)): + dependencies: + eslint: 9.30.1(jiti@2.4.2) + + eslint-plugin-react-refresh@0.4.20(eslint@9.30.1(jiti@2.4.2)): + dependencies: + eslint: 9.30.1(jiti@2.4.2) + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.30.1(jiti@2.4.2): + dependencies: + "@eslint-community/eslint-utils": 4.7.0(eslint@9.30.1(jiti@2.4.2)) + "@eslint-community/regexpp": 4.12.1 + "@eslint/config-array": 0.21.0 + "@eslint/config-helpers": 0.3.0 + "@eslint/core": 0.14.0 + "@eslint/eslintrc": 3.3.1 + "@eslint/js": 9.30.1 + "@eslint/plugin-kit": 0.3.3 + "@humanfs/node": 0.16.6 + "@humanwhocodes/module-importer": 1.0.1 + "@humanwhocodes/retry": 0.4.3 + "@types/estree": 1.0.8 + "@types/json-schema": 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.1 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.4.2 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + "@types/estree": 1.0.8 + + esutils@2.0.3: {} + + eventemitter3@5.0.1: {} + + expect-type@1.2.2: {} + + exsolve@1.0.7: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + "@nodelib/fs.stat": 2.0.5 + "@nodelib/fs.walk": 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fdir@6.4.6(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + fs-extra@11.3.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.3.0: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@14.0.0: {} + + globals@16.3.0: {} + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + has-flag@4.0.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + he@1.2.0: {} + + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.3 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.3 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + husky@9.1.7: {} + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-lazy@4.0.0: {} + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + is-arrayish@0.2.1: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-docker@2.2.1: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-fullwidth-code-point@4.0.0: {} + + is-fullwidth-code-point@5.0.0: + dependencies: + get-east-asian-width: 1.3.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-potential-custom-element-name@1.0.1: {} + + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + + isexe@2.0.0: {} + + jiti@2.4.2: {} + + jju@1.4.0: {} + + js-tokens@4.0.0: {} + + js-tokens@9.0.1: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsdom@26.1.0: + dependencies: + cssstyle: 4.6.0 + data-urls: 5.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.20 + parse5: 7.3.0 + rrweb-cssom: 0.8.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 5.1.2 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + ws: 8.18.3 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + jsonfile@6.1.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kolorist@1.8.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-darwin-arm64@1.30.1: + optional: true + + lightningcss-darwin-x64@1.30.1: + optional: true + + lightningcss-freebsd-x64@1.30.1: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.1: + optional: true + + lightningcss-linux-arm64-gnu@1.30.1: + optional: true + + lightningcss-linux-arm64-musl@1.30.1: + optional: true + + lightningcss-linux-x64-gnu@1.30.1: + optional: true + + lightningcss-linux-x64-musl@1.30.1: + optional: true + + lightningcss-win32-arm64-msvc@1.30.1: + optional: true + + lightningcss-win32-x64-msvc@1.30.1: + optional: true + + lightningcss@1.30.1: + dependencies: + detect-libc: 2.0.4 + optionalDependencies: + lightningcss-darwin-arm64: 1.30.1 + lightningcss-darwin-x64: 1.30.1 + lightningcss-freebsd-x64: 1.30.1 + lightningcss-linux-arm-gnueabihf: 1.30.1 + lightningcss-linux-arm64-gnu: 1.30.1 + lightningcss-linux-arm64-musl: 1.30.1 + lightningcss-linux-x64-gnu: 1.30.1 + lightningcss-linux-x64-musl: 1.30.1 + lightningcss-win32-arm64-msvc: 1.30.1 + lightningcss-win32-x64-msvc: 1.30.1 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + lint-staged@16.1.4: + dependencies: + chalk: 5.4.1 + commander: 14.0.0 + debug: 4.4.1 + lilconfig: 3.1.3 + listr2: 9.0.1 + micromatch: 4.0.8 + nano-spawn: 1.0.2 + pidtree: 0.6.0 + string-argv: 0.3.2 + yaml: 2.8.0 + transitivePeerDependencies: + - supports-color + + listr2@9.0.1: + dependencies: + cli-truncate: 4.0.0 + colorette: 2.0.20 + eventemitter3: 5.0.1 + log-update: 6.1.0 + rfdc: 1.4.1 + wrap-ansi: 9.0.0 + + local-pkg@1.1.2: + dependencies: + mlly: 1.7.4 + pkg-types: 2.3.0 + quansync: 0.2.11 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + lodash@4.17.21: {} + + log-update@6.1.0: + dependencies: + ansi-escapes: 7.0.0 + cli-cursor: 5.0.0 + slice-ansi: 7.1.0 + strip-ansi: 7.1.0 + wrap-ansi: 9.0.0 + + loupe@3.1.4: {} + + lower-case@2.0.2: + dependencies: + tslib: 2.8.1 + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + + lz-string@1.5.0: {} + + magic-string@0.30.17: + dependencies: + "@jridgewell/sourcemap-codec": 1.5.4 + + make-error@1.3.6: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mimic-function@5.0.1: {} + + min-indent@1.0.1: {} + + minimatch@10.0.3: + dependencies: + "@isaacs/brace-expansion": 5.0.0 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minipass@7.1.2: {} + + minizlib@3.0.2: + dependencies: + minipass: 7.1.2 + + mkdirp@3.0.1: {} + + mlly@1.7.4: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.1 + + ms@2.1.3: {} + + muggle-string@0.4.1: {} + + nano-spawn@1.0.2: {} + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + no-case@3.0.4: + dependencies: + lower-case: 2.0.2 + tslib: 2.8.1 + + node-releases@2.0.19: {} + + nwsapi@2.2.20: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + open@8.4.2: + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + "@babel/code-frame": 7.27.1 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + path-browserify@1.0.1: {} + + path-equal@1.2.5: {} + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-type@4.0.0: {} + + pathe@2.0.3: {} + + pathval@2.0.1: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pidtree@0.6.0: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.7.4 + pathe: 2.0.3 + + pkg-types@2.3.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.7 + pathe: 2.0.3 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier@3.6.2: {} + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + punycode@2.3.1: {} + + quansync@0.2.11: {} + + queue-microtask@1.2.3: {} + + react-docgen-typescript@2.4.0(typescript@5.8.3): + dependencies: + typescript: 5.8.3 + + react-dom@19.1.0(react@19.1.0): + dependencies: + react: 19.1.0 + scheduler: 0.26.0 + + react-error-boundary@6.0.0(react@19.1.0): + dependencies: + "@babel/runtime": 7.27.6 + react: 19.1.0 + + react-is@17.0.2: {} + + react-router-dom@7.6.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-router: 7.6.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + + react-router@7.6.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + cookie: 1.0.2 + react: 19.1.0 + set-cookie-parser: 2.7.1 + optionalDependencies: + react-dom: 19.1.0(react@19.1.0) + + react@19.1.0: {} + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + resolve-from@4.0.0: {} + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + reusify@1.1.0: {} + + rfdc@1.4.1: {} + + rollup-plugin-visualizer@6.0.3(rollup@4.44.2): + dependencies: + open: 8.4.2 + picomatch: 4.0.3 + source-map: 0.7.4 + yargs: 17.7.2 + optionalDependencies: + rollup: 4.44.2 + + rollup@4.44.2: + dependencies: + "@types/estree": 1.0.8 + optionalDependencies: + "@rollup/rollup-android-arm-eabi": 4.44.2 + "@rollup/rollup-android-arm64": 4.44.2 + "@rollup/rollup-darwin-arm64": 4.44.2 + "@rollup/rollup-darwin-x64": 4.44.2 + "@rollup/rollup-freebsd-arm64": 4.44.2 + "@rollup/rollup-freebsd-x64": 4.44.2 + "@rollup/rollup-linux-arm-gnueabihf": 4.44.2 + "@rollup/rollup-linux-arm-musleabihf": 4.44.2 + "@rollup/rollup-linux-arm64-gnu": 4.44.2 + "@rollup/rollup-linux-arm64-musl": 4.44.2 + "@rollup/rollup-linux-loongarch64-gnu": 4.44.2 + "@rollup/rollup-linux-powerpc64le-gnu": 4.44.2 + "@rollup/rollup-linux-riscv64-gnu": 4.44.2 + "@rollup/rollup-linux-riscv64-musl": 4.44.2 + "@rollup/rollup-linux-s390x-gnu": 4.44.2 + "@rollup/rollup-linux-x64-gnu": 4.44.2 + "@rollup/rollup-linux-x64-musl": 4.44.2 + "@rollup/rollup-win32-arm64-msvc": 4.44.2 + "@rollup/rollup-win32-ia32-msvc": 4.44.2 + "@rollup/rollup-win32-x64-msvc": 4.44.2 + fsevents: 2.3.3 + + rrweb-cssom@0.8.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-stable-stringify@2.5.0: {} + + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.26.0: {} + + semver@6.3.1: {} + + semver@7.5.4: + dependencies: + lru-cache: 6.0.0 + + semver@7.7.2: {} + + set-cookie-parser@2.7.1: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + siginfo@2.0.0: {} + + signal-exit@4.1.0: {} + + slice-ansi@5.0.0: + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 4.0.0 + + slice-ansi@7.1.0: + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 5.0.0 + + snake-case@3.0.4: + dependencies: + dot-case: 3.0.4 + tslib: 2.8.1 + + source-map-js@1.2.1: {} + + source-map@0.6.1: {} + + source-map@0.7.4: {} + + sprintf-js@1.0.3: {} + + stackback@0.0.2: {} + + std-env@3.9.0: {} + + string-argv@0.3.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.4.0 + get-east-asian-width: 1.3.0 + strip-ansi: 7.1.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-json-comments@3.1.1: {} + + strip-literal@3.0.0: + dependencies: + js-tokens: 9.0.1 + + style-mod@4.1.2: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + svg-parser@2.0.4: {} + + symbol-tree@3.2.4: {} + + tabbable@6.2.0: {} + + tailwind-merge@3.3.1: {} + + tailwindcss@4.1.11: {} + + tapable@2.2.2: {} + + tar@7.4.3: + dependencies: + "@isaacs/fs-minipass": 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.0.2 + mkdirp: 3.0.1 + yallist: 5.0.0 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.14: + dependencies: + fdir: 6.4.6(picomatch@4.0.3) + picomatch: 4.0.3 + + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.3: {} + + tldts-core@6.1.86: {} + + tldts@6.1.86: + dependencies: + tldts-core: 6.1.86 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tough-cookie@5.1.2: + dependencies: + tldts: 6.1.86 + + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + + ts-api-utils@2.1.0(typescript@5.8.3): + dependencies: + typescript: 5.8.3 + + ts-blank-space@0.6.2: + dependencies: + typescript: 5.8.3 + + ts-node@10.9.2(@swc/core@1.12.9(@swc/helpers@0.5.17))(@types/node@18.19.121)(typescript@5.5.4): + dependencies: + "@cspotcode/source-map-support": 0.8.1 + "@tsconfig/node10": 1.0.11 + "@tsconfig/node12": 1.0.11 + "@tsconfig/node14": 1.0.3 + "@tsconfig/node16": 1.0.4 + "@types/node": 18.19.121 + acorn: 8.15.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.5.4 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + optionalDependencies: + "@swc/core": 1.12.9(@swc/helpers@0.5.17) + + tslib@2.8.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3): + dependencies: + "@typescript-eslint/eslint-plugin": 8.35.1(@typescript-eslint/parser@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + "@typescript-eslint/parser": 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + "@typescript-eslint/utils": 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.30.1(jiti@2.4.2) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + typescript-json-schema@0.65.1(@swc/core@1.12.9(@swc/helpers@0.5.17)): + dependencies: + "@types/json-schema": 7.0.15 + "@types/node": 18.19.121 + glob: 7.2.3 + path-equal: 1.2.5 + safe-stable-stringify: 2.5.0 + ts-node: 10.9.2(@swc/core@1.12.9(@swc/helpers@0.5.17))(@types/node@18.19.121)(typescript@5.5.4) + typescript: 5.5.4 + yargs: 17.7.2 + transitivePeerDependencies: + - "@swc/core" + - "@swc/wasm" + + typescript@5.5.4: {} + + typescript@5.8.2: {} + + typescript@5.8.3: {} + + ufo@1.6.1: {} + + undici-types@5.26.5: {} + + undici-types@7.10.0: {} + + universalify@2.0.1: {} + + update-browserslist-db@1.1.3(browserslist@4.25.1): + dependencies: + browserslist: 4.25.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + use-sync-external-store@1.5.0(react@19.1.0): + dependencies: + react: 19.1.0 + + v8-compile-cache-lib@3.0.1: {} + + vite-node@3.2.4(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0): + dependencies: + cac: 6.7.14 + debug: 4.4.1 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 7.0.6(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0) + transitivePeerDependencies: + - "@types/node" + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite-plugin-dts@4.5.4(@types/node@24.2.0)(rollup@4.44.2)(typescript@5.8.3)(vite@7.0.6(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)): + dependencies: + "@microsoft/api-extractor": 7.52.10(@types/node@24.2.0) + "@rollup/pluginutils": 5.2.0(rollup@4.44.2) + "@volar/typescript": 2.4.23 + "@vue/language-core": 2.2.0(typescript@5.8.3) + compare-versions: 6.1.1 + debug: 4.4.1 + kolorist: 1.8.0 + local-pkg: 1.1.2 + magic-string: 0.30.17 + typescript: 5.8.3 + optionalDependencies: + vite: 7.0.6(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0) + transitivePeerDependencies: + - "@types/node" + - rollup + - supports-color + + vite-plugin-svgr@4.3.0(rollup@4.44.2)(typescript@5.8.3)(vite@7.0.6(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)): + dependencies: + "@rollup/pluginutils": 5.2.0(rollup@4.44.2) + "@svgr/core": 8.1.0(typescript@5.8.3) + "@svgr/plugin-jsx": 8.1.0(@svgr/core@8.1.0(typescript@5.8.3)) + vite: 7.0.6(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0) + transitivePeerDependencies: + - rollup + - supports-color + - typescript + + vite@7.0.6(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0): + dependencies: + esbuild: 0.25.5 + fdir: 6.4.6(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.44.2 + tinyglobby: 0.2.14 + optionalDependencies: + "@types/node": 24.2.0 + fsevents: 2.3.3 + jiti: 2.4.2 + lightningcss: 1.30.1 + yaml: 2.8.0 + + vitest@3.2.4(@types/node@24.2.0)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(yaml@2.8.0): + dependencies: + "@types/chai": 5.2.2 + "@vitest/expect": 3.2.4 + "@vitest/mocker": 3.2.4(vite@7.0.6(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)) + "@vitest/pretty-format": 3.2.4 + "@vitest/runner": 3.2.4 + "@vitest/snapshot": 3.2.4 + "@vitest/spy": 3.2.4 + "@vitest/utils": 3.2.4 + chai: 5.2.1 + debug: 4.4.1 + expect-type: 1.2.2 + magic-string: 0.30.17 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.14 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 7.0.6(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0) + vite-node: 3.2.4(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0) + why-is-node-running: 2.3.0 + optionalDependencies: + "@types/node": 24.2.0 + jsdom: 26.1.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vscode-uri@3.1.0: {} + + w3c-keyname@2.2.8: {} + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@7.0.0: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@9.0.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 7.2.0 + strip-ansi: 7.1.0 + + wrappy@1.0.2: {} + + ws@8.18.3: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yallist@4.0.0: {} + + yallist@5.0.0: {} + + yaml@2.8.0: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yn@3.1.1: {} + + yocto-queue@0.1.0: {} + + zustand@5.0.7(@types/react@19.1.8)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)): + optionalDependencies: + "@types/react": 19.1.8 + react: 19.1.0 + use-sync-external-store: 1.5.0(react@19.1.0) diff --git a/prettier.config.mjs b/prettier.config.mjs new file mode 100644 index 00000000..7b04f3ad --- /dev/null +++ b/prettier.config.mjs @@ -0,0 +1,10 @@ +export default { + overrides: [ + { + files: "*.ts, *.tsx", + excludeFiles: "dist, docs, public" + } + ], + singleQuote: false, + trailingComma: "none" +}; diff --git a/public/data/addresses.json b/public/data/addresses.json new file mode 100644 index 00000000..d7812c69 --- /dev/null +++ b/public/data/addresses.json @@ -0,0 +1,23932 @@ +[ + { + "city": "Yuma", + "state": "Arizona", + "zip": "85364" + }, + { + "city": "Dorchester Center", + "state": "Massachusetts", + "zip": "2124" + }, + { + "city": "Snohomish", + "state": "Washington", + "zip": "98296" + }, + { + "city": "Dallas", + "state": "Texas", + "zip": "75232" + }, + { + "city": "Greenville", + "state": "North Carolina", + "zip": "27834" + }, + { + "city": "Ponce", + "state": "Puerto Rico", + "zip": "728" + }, + { + "city": "Oak Forest", + "state": "Illinois", + "zip": "60452" + }, + { + "city": "Pooler", + "state": "Georgia", + "zip": "31322" + }, + { + "city": "Rockledge", + "state": "Florida", + "zip": "32955" + }, + { + "city": "Fort Lauderdale", + "state": "Florida", + "zip": "33311" + }, + { + "city": "Clarksville", + "state": "Tennessee", + "zip": "37042" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11233" + }, + { + "city": "Cleveland", + "state": "Ohio", + "zip": "44120" + }, + { + "city": "Joplin", + "state": "Missouri", + "zip": "64801" + }, + { + "city": "Irving", + "state": "Texas", + "zip": "75060" + }, + { + "city": "Camuy", + "state": "Puerto Rico", + "zip": "627" + }, + { + "city": "Turlock", + "state": "California", + "zip": "95382" + }, + { + "city": "Alliance", + "state": "Ohio", + "zip": "44601" + }, + { + "city": "Cleveland", + "state": "Ohio", + "zip": "44105" + }, + { + "city": "Bedford", + "state": "Indiana", + "zip": "47421" + }, + { + "city": "Pico Rivera", + "state": "California", + "zip": "90660" + }, + { + "city": "Bozeman", + "state": "Montana", + "zip": "59715" + }, + { + "city": "Abilene", + "state": "Texas", + "zip": "79605" + }, + { + "city": "San Sebastian", + "state": "Puerto Rico", + "zip": "685" + }, + { + "city": "West Springfield", + "state": "Massachusetts", + "zip": "1089" + }, + { + "city": "College Park", + "state": "Maryland", + "zip": "20740" + }, + { + "city": "Cumberland", + "state": "Maryland", + "zip": "21502" + }, + { + "city": "Charlotte", + "state": "North Carolina", + "zip": "28262" + }, + { + "city": "Concord", + "state": "California", + "zip": "94518" + }, + { + "city": "Sacramento", + "state": "California", + "zip": "95815" + }, + { + "city": "Nashville", + "state": "Tennessee", + "zip": "37217" + }, + { + "city": "New York", + "state": "New York", + "zip": "10028" + }, + { + "city": "Allison Park", + "state": "Pennsylvania", + "zip": "15101" + }, + { + "city": "Riverside", + "state": "California", + "zip": "92508" + }, + { + "city": "New York", + "state": "New York", + "zip": "10021" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77075" + }, + { + "city": "San Tan Valley", + "state": "Arizona", + "zip": "85140" + }, + { + "city": "Diamond Bar", + "state": "California", + "zip": "91765" + }, + { + "city": "Albany", + "state": "Oregon", + "zip": "97322" + }, + { + "city": "Bronx", + "state": "New York", + "zip": "10472" + }, + { + "city": "Alexandria", + "state": "Virginia", + "zip": "22309" + }, + { + "city": "Asheville", + "state": "North Carolina", + "zip": "28803" + }, + { + "city": "Fayetteville", + "state": "Georgia", + "zip": "30215" + }, + { + "city": "Bay City", + "state": "Michigan", + "zip": "48708" + }, + { + "city": "Somerville", + "state": "Massachusetts", + "zip": "2143" + }, + { + "city": "Austin", + "state": "Texas", + "zip": "78749" + }, + { + "city": "Caguas", + "state": "Puerto Rico", + "zip": "727" + }, + { + "city": "Tewksbury", + "state": "Massachusetts", + "zip": "1876" + }, + { + "city": "New York", + "state": "New York", + "zip": "10016" + }, + { + "city": "Lancaster", + "state": "Pennsylvania", + "zip": "17601" + }, + { + "city": "Laurinburg", + "state": "North Carolina", + "zip": "28352" + }, + { + "city": "Hollywood", + "state": "Florida", + "zip": "33020" + }, + { + "city": "Cookeville", + "state": "Tennessee", + "zip": "38501" + }, + { + "city": "Franklin", + "state": "Ohio", + "zip": "45005" + }, + { + "city": "Madison", + "state": "Wisconsin", + "zip": "53705" + }, + { + "city": "South Portland", + "state": "Maine", + "zip": "4106" + }, + { + "city": "Parsippany", + "state": "New Jersey", + "zip": "7054" + }, + { + "city": "Warminster", + "state": "Pennsylvania", + "zip": "18974" + }, + { + "city": "Adelanto", + "state": "California", + "zip": "92301" + }, + { + "city": "Livermore", + "state": "California", + "zip": "94551" + }, + { + "city": "Wellington", + "state": "Florida", + "zip": "33414" + }, + { + "city": "Detroit", + "state": "Michigan", + "zip": "48228" + }, + { + "city": "Peabody", + "state": "Massachusetts", + "zip": "1960" + }, + { + "city": "Newbury Park", + "state": "California", + "zip": "91320" + }, + { + "city": "Woodbridge", + "state": "Virginia", + "zip": "22193" + }, + { + "city": "Charlotte", + "state": "North Carolina", + "zip": "28214" + }, + { + "city": "Elkhart", + "state": "Indiana", + "zip": "46516" + }, + { + "city": "Oak Park", + "state": "Michigan", + "zip": "48237" + }, + { + "city": "Green Bay", + "state": "Wisconsin", + "zip": "54304" + }, + { + "city": "San Leandro", + "state": "California", + "zip": "94578" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60643" + }, + { + "city": "The Colony", + "state": "Texas", + "zip": "75056" + }, + { + "city": "Richmond", + "state": "Texas", + "zip": "77407" + }, + { + "city": "Tucson", + "state": "Arizona", + "zip": "85718" + }, + { + "city": "Oakland", + "state": "California", + "zip": "94621" + }, + { + "city": "San Jose", + "state": "California", + "zip": "95129" + }, + { + "city": "Muskogee", + "state": "Oklahoma", + "zip": "74403" + }, + { + "city": "West Jordan", + "state": "Utah", + "zip": "84084" + }, + { + "city": "Rio Rancho", + "state": "New Mexico", + "zip": "87144" + }, + { + "city": "Port Orchard", + "state": "Washington", + "zip": "98367" + }, + { + "city": "Yakima", + "state": "Washington", + "zip": "98901" + }, + { + "city": "Castaic", + "state": "California", + "zip": "91384" + }, + { + "city": "Rancho Cucamonga", + "state": "California", + "zip": "91730" + }, + { + "city": "Vista", + "state": "California", + "zip": "92081" + }, + { + "city": "Novato", + "state": "California", + "zip": "94947" + }, + { + "city": "Winchester", + "state": "Virginia", + "zip": "22601" + }, + { + "city": "Camp Lejeune", + "state": "North Carolina", + "zip": "28547" + }, + { + "city": "Cleveland", + "state": "Georgia", + "zip": "30528" + }, + { + "city": "Westwego", + "state": "Louisiana", + "zip": "70094" + }, + { + "city": "Greenfield", + "state": "Indiana", + "zip": "46140" + }, + { + "city": "Moncks Corner", + "state": "South Carolina", + "zip": "29461" + }, + { + "city": "Lafayette", + "state": "Louisiana", + "zip": "70506" + }, + { + "city": "Winter Haven", + "state": "Florida", + "zip": "33884" + }, + { + "city": "Birmingham", + "state": "Alabama", + "zip": "35209" + }, + { + "city": "Nashville", + "state": "Tennessee", + "zip": "37207" + }, + { + "city": "Toledo", + "state": "Ohio", + "zip": "43605" + }, + { + "city": "Milwaukee", + "state": "Wisconsin", + "zip": "53217" + }, + { + "city": "Valencia", + "state": "California", + "zip": "91355" + }, + { + "city": "Yuba City", + "state": "California", + "zip": "95993" + }, + { + "city": "Dorado", + "state": "Puerto Rico", + "zip": "646" + }, + { + "city": "Indian Trail", + "state": "North Carolina", + "zip": "28079" + }, + { + "city": "Dallas", + "state": "Texas", + "zip": "75214" + }, + { + "city": "Morgan Hill", + "state": "California", + "zip": "95037" + }, + { + "city": "San Jose", + "state": "California", + "zip": "95121" + }, + { + "city": "Glendale", + "state": "Arizona", + "zip": "85302" + }, + { + "city": "Deltona", + "state": "Florida", + "zip": "32738" + }, + { + "city": "Dunedin", + "state": "Florida", + "zip": "34698" + }, + { + "city": "San Francisco", + "state": "California", + "zip": "94123" + }, + { + "city": "Jersey City", + "state": "New Jersey", + "zip": "7306" + }, + { + "city": "Coral Springs", + "state": "Florida", + "zip": "33071" + }, + { + "city": "Wake Forest", + "state": "North Carolina", + "zip": "27587" + }, + { + "city": "Jackson", + "state": "Missouri", + "zip": "63755" + }, + { + "city": "Austin", + "state": "Texas", + "zip": "78748" + }, + { + "city": "Casper", + "state": "Wyoming", + "zip": "82604" + }, + { + "city": "Knoxville", + "state": "Tennessee", + "zip": "37923" + }, + { + "city": "Fairborn", + "state": "Ohio", + "zip": "45324" + }, + { + "city": "Troy", + "state": "Ohio", + "zip": "45373" + }, + { + "city": "Saint Paul", + "state": "Minnesota", + "zip": "55126" + }, + { + "city": "Rochester", + "state": "New York", + "zip": "14609" + }, + { + "city": "Glendora", + "state": "California", + "zip": "91741" + }, + { + "city": "Palmdale", + "state": "California", + "zip": "93550" + }, + { + "city": "Granger", + "state": "Indiana", + "zip": "46530" + }, + { + "city": "Chicopee", + "state": "Massachusetts", + "zip": "1020" + }, + { + "city": "Worcester", + "state": "Massachusetts", + "zip": "1605" + }, + { + "city": "Harlingen", + "state": "Texas", + "zip": "78552" + }, + { + "city": "El Paso", + "state": "Texas", + "zip": "79907" + }, + { + "city": "Fresno", + "state": "California", + "zip": "93705" + }, + { + "city": "Citrus Heights", + "state": "California", + "zip": "95621" + }, + { + "city": "Saint Paul", + "state": "Minnesota", + "zip": "55117" + }, + { + "city": "Everett", + "state": "Washington", + "zip": "98201" + }, + { + "city": "Fresno", + "state": "California", + "zip": "93727" + }, + { + "city": "New York", + "state": "New York", + "zip": "10036" + }, + { + "city": "Hickory", + "state": "North Carolina", + "zip": "28601" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77092" + }, + { + "city": "Overland Park", + "state": "Kansas", + "zip": "66212" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77047" + }, + { + "city": "Dallas", + "state": "Texas", + "zip": "75241" + }, + { + "city": "Nampa", + "state": "Idaho", + "zip": "83651" + }, + { + "city": "Feasterville Trevose", + "state": "Pennsylvania", + "zip": "19053" + }, + { + "city": "Elkhorn", + "state": "Nebraska", + "zip": "68022" + }, + { + "city": "Lincoln", + "state": "Nebraska", + "zip": "68521" + }, + { + "city": "Denton", + "state": "Texas", + "zip": "76201" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77099" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89131" + }, + { + "city": "Saugus", + "state": "Massachusetts", + "zip": "1906" + }, + { + "city": "Jamaica", + "state": "New York", + "zip": "11433" + }, + { + "city": "Palatka", + "state": "Florida", + "zip": "32177" + }, + { + "city": "Fort Myers", + "state": "Florida", + "zip": "33905" + }, + { + "city": "Butte", + "state": "Montana", + "zip": "59701" + }, + { + "city": "Stillwater", + "state": "Oklahoma", + "zip": "74074" + }, + { + "city": "Dothan", + "state": "Alabama", + "zip": "36301" + }, + { + "city": "Romulus", + "state": "Michigan", + "zip": "48174" + }, + { + "city": "Gurabo", + "state": "Puerto Rico", + "zip": "778" + }, + { + "city": "Southampton", + "state": "Pennsylvania", + "zip": "18966" + }, + { + "city": "Conway", + "state": "South Carolina", + "zip": "29526" + }, + { + "city": "Belvidere", + "state": "Illinois", + "zip": "61008" + }, + { + "city": "Reno", + "state": "Nevada", + "zip": "89511" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19152" + }, + { + "city": "Fort Lauderdale", + "state": "Florida", + "zip": "33313" + }, + { + "city": "Avon", + "state": "Indiana", + "zip": "46123" + }, + { + "city": "Indianapolis", + "state": "Indiana", + "zip": "46201" + }, + { + "city": "Kirkland", + "state": "Washington", + "zip": "98034" + }, + { + "city": "Vancouver", + "state": "Washington", + "zip": "98685" + }, + { + "city": "Studio City", + "state": "California", + "zip": "91604" + }, + { + "city": "Auburn", + "state": "Washington", + "zip": "98001" + }, + { + "city": "Post Falls", + "state": "Idaho", + "zip": "83854" + }, + { + "city": "Kennesaw", + "state": "Georgia", + "zip": "30152" + }, + { + "city": "Barberton", + "state": "Ohio", + "zip": "44203" + }, + { + "city": "Dearborn", + "state": "Michigan", + "zip": "48124" + }, + { + "city": "Kalamazoo", + "state": "Michigan", + "zip": "49006" + }, + { + "city": "Milwaukee", + "state": "Wisconsin", + "zip": "53207" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78253" + }, + { + "city": "Queen Creek", + "state": "Arizona", + "zip": "85142" + }, + { + "city": "Goodyear", + "state": "Arizona", + "zip": "85395" + }, + { + "city": "Schertz", + "state": "Texas", + "zip": "78154" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90065" + }, + { + "city": "Lakewood", + "state": "California", + "zip": "90713" + }, + { + "city": "Oceanside", + "state": "California", + "zip": "92057" + }, + { + "city": "Santa Ana", + "state": "California", + "zip": "92701" + }, + { + "city": "Lebanon", + "state": "Oregon", + "zip": "97355" + }, + { + "city": "San Juan", + "state": "Puerto Rico", + "zip": "926" + }, + { + "city": "Ossining", + "state": "New York", + "zip": "10562" + }, + { + "city": "Jamaica", + "state": "New York", + "zip": "11434" + }, + { + "city": "Myrtle Beach", + "state": "South Carolina", + "zip": "29588" + }, + { + "city": "Shakopee", + "state": "Minnesota", + "zip": "55379" + }, + { + "city": "Cherry Hill", + "state": "New Jersey", + "zip": "8003" + }, + { + "city": "Roanoke", + "state": "Virginia", + "zip": "24018" + }, + { + "city": "Saint Johns", + "state": "Florida", + "zip": "32259" + }, + { + "city": "Jamestown", + "state": "New York", + "zip": "14701" + }, + { + "city": "Alexandria", + "state": "Virginia", + "zip": "22306" + }, + { + "city": "Roanoke Rapids", + "state": "North Carolina", + "zip": "27870" + }, + { + "city": "Geneva", + "state": "Illinois", + "zip": "60134" + }, + { + "city": "Peoria", + "state": "Illinois", + "zip": "61614" + }, + { + "city": "Papillion", + "state": "Nebraska", + "zip": "68046" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90034" + }, + { + "city": "San Jose", + "state": "California", + "zip": "95124" + }, + { + "city": "Dorchester", + "state": "Massachusetts", + "zip": "2121" + }, + { + "city": "Logansport", + "state": "Indiana", + "zip": "46947" + }, + { + "city": "New Haven", + "state": "Connecticut", + "zip": "6511" + }, + { + "city": "Reading", + "state": "Pennsylvania", + "zip": "19606" + }, + { + "city": "Garner", + "state": "North Carolina", + "zip": "27529" + }, + { + "city": "Indianapolis", + "state": "Indiana", + "zip": "46239" + }, + { + "city": "Huntington", + "state": "Indiana", + "zip": "46750" + }, + { + "city": "Benton", + "state": "Arkansas", + "zip": "72015" + }, + { + "city": "Canonsburg", + "state": "Pennsylvania", + "zip": "15317" + }, + { + "city": "Fayetteville", + "state": "North Carolina", + "zip": "28306" + }, + { + "city": "West Covina", + "state": "California", + "zip": "91790" + }, + { + "city": "Hollis", + "state": "New York", + "zip": "11423" + }, + { + "city": "Auburn", + "state": "California", + "zip": "95603" + }, + { + "city": "Springfield", + "state": "Tennessee", + "zip": "37172" + }, + { + "city": "Waterloo", + "state": "Iowa", + "zip": "50701" + }, + { + "city": "State College", + "state": "Pennsylvania", + "zip": "16803" + }, + { + "city": "Tucson", + "state": "Arizona", + "zip": "85743" + }, + { + "city": "Longview", + "state": "Washington", + "zip": "98632" + }, + { + "city": "Yakima", + "state": "Washington", + "zip": "98902" + }, + { + "city": "Madison", + "state": "Wisconsin", + "zip": "53703" + }, + { + "city": "Greenwood", + "state": "South Carolina", + "zip": "29649" + }, + { + "city": "Simpsonville", + "state": "South Carolina", + "zip": "29680" + }, + { + "city": "Reno", + "state": "Nevada", + "zip": "89502" + }, + { + "city": "Youngstown", + "state": "Ohio", + "zip": "44512" + }, + { + "city": "Knoxville", + "state": "Tennessee", + "zip": "37922" + }, + { + "city": "Pompano Beach", + "state": "Florida", + "zip": "33063" + }, + { + "city": "Ruskin", + "state": "Florida", + "zip": "33570" + }, + { + "city": "Shelton", + "state": "Connecticut", + "zip": "6484" + }, + { + "city": "Bronx", + "state": "New York", + "zip": "10454" + }, + { + "city": "Plainview", + "state": "New York", + "zip": "11803" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11226" + }, + { + "city": "Albany", + "state": "New York", + "zip": "12203" + }, + { + "city": "Gibsonia", + "state": "Pennsylvania", + "zip": "15044" + }, + { + "city": "Annapolis", + "state": "Maryland", + "zip": "21401" + }, + { + "city": "Durham", + "state": "North Carolina", + "zip": "27705" + }, + { + "city": "Saint Louis", + "state": "Missouri", + "zip": "63130" + }, + { + "city": "Killeen", + "state": "Texas", + "zip": "76542" + }, + { + "city": "Dallas", + "state": "Texas", + "zip": "75212" + }, + { + "city": "West Hills", + "state": "California", + "zip": "91307" + }, + { + "city": "Highland", + "state": "California", + "zip": "92346" + }, + { + "city": "Lincoln Park", + "state": "Michigan", + "zip": "48146" + }, + { + "city": "Orlando", + "state": "Florida", + "zip": "32822" + }, + { + "city": "Dublin", + "state": "Ohio", + "zip": "43017" + }, + { + "city": "Nazareth", + "state": "Pennsylvania", + "zip": "18064" + }, + { + "city": "Willowbrook", + "state": "Illinois", + "zip": "60527" + }, + { + "city": "Littleton", + "state": "Colorado", + "zip": "80123" + }, + { + "city": "Seattle", + "state": "Washington", + "zip": "98178" + }, + { + "city": "Williamstown", + "state": "New Jersey", + "zip": "8094" + }, + { + "city": "Henrico", + "state": "Virginia", + "zip": "23231" + }, + { + "city": "Atlanta", + "state": "Georgia", + "zip": "30309" + }, + { + "city": "Ada", + "state": "Oklahoma", + "zip": "74820" + }, + { + "city": "Elmont", + "state": "New York", + "zip": "11003" + }, + { + "city": "Tampa", + "state": "Florida", + "zip": "33611" + }, + { + "city": "Elkhart", + "state": "Indiana", + "zip": "46517" + }, + { + "city": "Eagle Pass", + "state": "Texas", + "zip": "78852" + }, + { + "city": "Atlanta", + "state": "Georgia", + "zip": "30342" + }, + { + "city": "Tulsa", + "state": "Oklahoma", + "zip": "74133" + }, + { + "city": "Aiken", + "state": "South Carolina", + "zip": "29801" + }, + { + "city": "Elkhart", + "state": "Indiana", + "zip": "46514" + }, + { + "city": "Forest Lake", + "state": "Minnesota", + "zip": "55025" + }, + { + "city": "Palm Harbor", + "state": "Florida", + "zip": "34684" + }, + { + "city": "Grand Rapids", + "state": "Michigan", + "zip": "49505" + }, + { + "city": "Kenosha", + "state": "Wisconsin", + "zip": "53142" + }, + { + "city": "Paragould", + "state": "Arkansas", + "zip": "72450" + }, + { + "city": "West Valley City", + "state": "Utah", + "zip": "84128" + }, + { + "city": "Lake Oswego", + "state": "Oregon", + "zip": "97035" + }, + { + "city": "Tyler", + "state": "Texas", + "zip": "75702" + }, + { + "city": "Richmond", + "state": "Texas", + "zip": "77406" + }, + { + "city": "Lithia Springs", + "state": "Georgia", + "zip": "30122" + }, + { + "city": "Trussville", + "state": "Alabama", + "zip": "35173" + }, + { + "city": "Fort Lauderdale", + "state": "Florida", + "zip": "33317" + }, + { + "city": "Lakeland", + "state": "Florida", + "zip": "33813" + }, + { + "city": "Livonia", + "state": "Michigan", + "zip": "48154" + }, + { + "city": "Oklahoma City", + "state": "Oklahoma", + "zip": "73110" + }, + { + "city": "Hacienda Heights", + "state": "California", + "zip": "91745" + }, + { + "city": "New York", + "state": "New York", + "zip": "10035" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33145" + }, + { + "city": "Fort Myers", + "state": "Florida", + "zip": "33967" + }, + { + "city": "Bridgewater", + "state": "Massachusetts", + "zip": "2324" + }, + { + "city": "Westbury", + "state": "New York", + "zip": "11590" + }, + { + "city": "Dekalb", + "state": "Illinois", + "zip": "60115" + }, + { + "city": "Marion", + "state": "Illinois", + "zip": "62959" + }, + { + "city": "El Monte", + "state": "California", + "zip": "91731" + }, + { + "city": "Rocky Mount", + "state": "North Carolina", + "zip": "27804" + }, + { + "city": "Southlake", + "state": "Texas", + "zip": "76092" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85023" + }, + { + "city": "Glendale", + "state": "Arizona", + "zip": "85306" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89178" + }, + { + "city": "Long Beach", + "state": "California", + "zip": "90805" + }, + { + "city": "Rancho Santa Margarita", + "state": "California", + "zip": "92688" + }, + { + "city": "Smyrna", + "state": "Georgia", + "zip": "30082" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33138" + }, + { + "city": "New Baltimore", + "state": "Michigan", + "zip": "48047" + }, + { + "city": "Hopkinsville", + "state": "Kentucky", + "zip": "42240" + }, + { + "city": "San Diego", + "state": "California", + "zip": "92123" + }, + { + "city": "Montgomery", + "state": "Texas", + "zip": "77316" + }, + { + "city": "Beaumont", + "state": "Texas", + "zip": "77705" + }, + { + "city": "New Braunfels", + "state": "Texas", + "zip": "78130" + }, + { + "city": "New York", + "state": "New York", + "zip": "10013" + }, + { + "city": "Orlando", + "state": "Florida", + "zip": "32812" + }, + { + "city": "Denver", + "state": "Colorado", + "zip": "80228" + }, + { + "city": "Richmond", + "state": "Virginia", + "zip": "23220" + }, + { + "city": "Columbia", + "state": "South Carolina", + "zip": "29210" + }, + { + "city": "Easley", + "state": "South Carolina", + "zip": "29642" + }, + { + "city": "Kennesaw", + "state": "Georgia", + "zip": "30144" + }, + { + "city": "Stevens Point", + "state": "Wisconsin", + "zip": "54481" + }, + { + "city": "Poway", + "state": "California", + "zip": "92064" + }, + { + "city": "San Lorenzo", + "state": "Puerto Rico", + "zip": "754" + }, + { + "city": "Elmhurst", + "state": "New York", + "zip": "11373" + }, + { + "city": "Indiana", + "state": "Pennsylvania", + "zip": "15701" + }, + { + "city": "Lewes", + "state": "Delaware", + "zip": "19958" + }, + { + "city": "North Tonawanda", + "state": "New York", + "zip": "14120" + }, + { + "city": "Brunswick", + "state": "Georgia", + "zip": "31525" + }, + { + "city": "Jacksonville", + "state": "Florida", + "zip": "32244" + }, + { + "city": "Fort Dodge", + "state": "Iowa", + "zip": "50501" + }, + { + "city": "Huntington Station", + "state": "New York", + "zip": "11746" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33161" + }, + { + "city": "Sidney", + "state": "Ohio", + "zip": "45365" + }, + { + "city": "Troy", + "state": "Michigan", + "zip": "48085" + }, + { + "city": "Lansdowne", + "state": "Pennsylvania", + "zip": "19050" + }, + { + "city": "Zachary", + "state": "Louisiana", + "zip": "70791" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90066" + }, + { + "city": "Washington", + "state": "District of Columbia", + "zip": "20010" + }, + { + "city": "Clarksburg", + "state": "West Virginia", + "zip": "26301" + }, + { + "city": "Columbus", + "state": "Ohio", + "zip": "43204" + }, + { + "city": "Dayton", + "state": "Ohio", + "zip": "45417" + }, + { + "city": "Green Bay", + "state": "Wisconsin", + "zip": "54302" + }, + { + "city": "Yauco", + "state": "Puerto Rico", + "zip": "698" + }, + { + "city": "Framingham", + "state": "Massachusetts", + "zip": "1701" + }, + { + "city": "Middletown", + "state": "New Jersey", + "zip": "7748" + }, + { + "city": "Columbus", + "state": "Nebraska", + "zip": "68601" + }, + { + "city": "Pacifica", + "state": "California", + "zip": "94044" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33169" + }, + { + "city": "De Pere", + "state": "Wisconsin", + "zip": "54115" + }, + { + "city": "Saint Cloud", + "state": "Minnesota", + "zip": "56303" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90044" + }, + { + "city": "Anchorage", + "state": "Alaska", + "zip": "99507" + }, + { + "city": "Woodstock", + "state": "Georgia", + "zip": "30189" + }, + { + "city": "La Puente", + "state": "California", + "zip": "91746" + }, + { + "city": "Hialeah", + "state": "Florida", + "zip": "33014" + }, + { + "city": "Biloxi", + "state": "Mississippi", + "zip": "39532" + }, + { + "city": "Riverton", + "state": "Utah", + "zip": "84065" + }, + { + "city": "Evansville", + "state": "Indiana", + "zip": "47714" + }, + { + "city": "Billings", + "state": "Montana", + "zip": "59102" + }, + { + "city": "Terrell", + "state": "Texas", + "zip": "75160" + }, + { + "city": "Benicia", + "state": "California", + "zip": "94510" + }, + { + "city": "Citrus Heights", + "state": "California", + "zip": "95610" + }, + { + "city": "Green Cove Springs", + "state": "Florida", + "zip": "32043" + }, + { + "city": "Ocean Springs", + "state": "Mississippi", + "zip": "39564" + }, + { + "city": "Columbus", + "state": "Ohio", + "zip": "43207" + }, + { + "city": "Utica", + "state": "Michigan", + "zip": "48315" + }, + { + "city": "Marshalltown", + "state": "Iowa", + "zip": "50158" + }, + { + "city": "Bethesda", + "state": "Maryland", + "zip": "20817" + }, + { + "city": "Myrtle Beach", + "state": "South Carolina", + "zip": "29577" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77091" + }, + { + "city": "Wenatchee", + "state": "Washington", + "zip": "98801" + }, + { + "city": "Spokane", + "state": "Washington", + "zip": "99206" + }, + { + "city": "Sewell", + "state": "New Jersey", + "zip": "8080" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33193" + }, + { + "city": "Valrico", + "state": "Florida", + "zip": "33596" + }, + { + "city": "Pearl", + "state": "Mississippi", + "zip": "39208" + }, + { + "city": "Goshen", + "state": "Indiana", + "zip": "46526" + }, + { + "city": "Fargo", + "state": "North Dakota", + "zip": "58102" + }, + { + "city": "Naperville", + "state": "Illinois", + "zip": "60563" + }, + { + "city": "New Orleans", + "state": "Louisiana", + "zip": "70127" + }, + { + "city": "Baton Rouge", + "state": "Louisiana", + "zip": "70810" + }, + { + "city": "Longview", + "state": "Texas", + "zip": "75605" + }, + { + "city": "Blackfoot", + "state": "Idaho", + "zip": "83221" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85086" + }, + { + "city": "Prescott Valley", + "state": "Arizona", + "zip": "86314" + }, + { + "city": "San Bernardino", + "state": "California", + "zip": "92410" + }, + { + "city": "Anaheim", + "state": "California", + "zip": "92806" + }, + { + "city": "Milwaukee", + "state": "Wisconsin", + "zip": "53218" + }, + { + "city": "Janesville", + "state": "Wisconsin", + "zip": "53546" + }, + { + "city": "Bloomington", + "state": "Illinois", + "zip": "61704" + }, + { + "city": "Omaha", + "state": "Nebraska", + "zip": "68134" + }, + { + "city": "Portland", + "state": "Oregon", + "zip": "97220" + }, + { + "city": "Bloomfield", + "state": "New Jersey", + "zip": "7003" + }, + { + "city": "Old Bridge", + "state": "New Jersey", + "zip": "8857" + }, + { + "city": "Schenectady", + "state": "New York", + "zip": "12309" + }, + { + "city": "Potomac", + "state": "Maryland", + "zip": "20854" + }, + { + "city": "Orlando", + "state": "Florida", + "zip": "32837" + }, + { + "city": "Lafayette", + "state": "Indiana", + "zip": "47909" + }, + { + "city": "Kaukauna", + "state": "Wisconsin", + "zip": "54130" + }, + { + "city": "Hastings", + "state": "Minnesota", + "zip": "55033" + }, + { + "city": "Helena", + "state": "Montana", + "zip": "59601" + }, + { + "city": "Bayonne", + "state": "New Jersey", + "zip": "7002" + }, + { + "city": "Atlanta", + "state": "Georgia", + "zip": "30329" + }, + { + "city": "Hephzibah", + "state": "Georgia", + "zip": "30815" + }, + { + "city": "Augusta", + "state": "Georgia", + "zip": "30909" + }, + { + "city": "Medina", + "state": "Ohio", + "zip": "44256" + }, + { + "city": "Carson", + "state": "California", + "zip": "90746" + }, + { + "city": "Clovis", + "state": "California", + "zip": "93612" + }, + { + "city": "Beeville", + "state": "Texas", + "zip": "78102" + }, + { + "city": "San Diego", + "state": "California", + "zip": "92104" + }, + { + "city": "Holbrook", + "state": "New York", + "zip": "11741" + }, + { + "city": "Jupiter", + "state": "Florida", + "zip": "33458" + }, + { + "city": "Mobile", + "state": "Alabama", + "zip": "36608" + }, + { + "city": "Grand Rapids", + "state": "Michigan", + "zip": "49548" + }, + { + "city": "Ann Arbor", + "state": "Michigan", + "zip": "48105" + }, + { + "city": "Marion", + "state": "North Carolina", + "zip": "28752" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60618" + }, + { + "city": "Ogden", + "state": "Utah", + "zip": "84401" + }, + { + "city": "Fort Lauderdale", + "state": "Florida", + "zip": "33351" + }, + { + "city": "La Vergne", + "state": "Tennessee", + "zip": "37086" + }, + { + "city": "Memphis", + "state": "Tennessee", + "zip": "38111" + }, + { + "city": "Tualatin", + "state": "Oregon", + "zip": "97062" + }, + { + "city": "Alton", + "state": "Illinois", + "zip": "62002" + }, + { + "city": "Kuna", + "state": "Idaho", + "zip": "83634" + }, + { + "city": "Deland", + "state": "Florida", + "zip": "32720" + }, + { + "city": "Tampa", + "state": "Florida", + "zip": "33618" + }, + { + "city": "Poplar Bluff", + "state": "Missouri", + "zip": "63901" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33156" + }, + { + "city": "Arlington Heights", + "state": "Illinois", + "zip": "60005" + }, + { + "city": "Lafayette", + "state": "Louisiana", + "zip": "70508" + }, + { + "city": "Ogden", + "state": "Utah", + "zip": "84405" + }, + { + "city": "Chesterfield", + "state": "Virginia", + "zip": "23832" + }, + { + "city": "Riverdale", + "state": "Georgia", + "zip": "30296" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33168" + }, + { + "city": "Lafayette", + "state": "Colorado", + "zip": "80026" + }, + { + "city": "Bothell", + "state": "Washington", + "zip": "98021" + }, + { + "city": "New York", + "state": "New York", + "zip": "10033" + }, + { + "city": "Irving", + "state": "Texas", + "zip": "75063" + }, + { + "city": "Arlington", + "state": "Texas", + "zip": "76001" + }, + { + "city": "Killeen", + "state": "Texas", + "zip": "76543" + }, + { + "city": "Auburndale", + "state": "Florida", + "zip": "33823" + }, + { + "city": "Yakima", + "state": "Washington", + "zip": "98908" + }, + { + "city": "East Wenatchee", + "state": "Washington", + "zip": "98802" + }, + { + "city": "Leominster", + "state": "Massachusetts", + "zip": "1453" + }, + { + "city": "Staten Island", + "state": "New York", + "zip": "10314" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60615" + }, + { + "city": "Burbank", + "state": "California", + "zip": "91504" + }, + { + "city": "Sacramento", + "state": "California", + "zip": "95835" + }, + { + "city": "Humacao", + "state": "Puerto Rico", + "zip": "791" + }, + { + "city": "Charlotte", + "state": "North Carolina", + "zip": "28213" + }, + { + "city": "Rochester", + "state": "Michigan", + "zip": "48309" + }, + { + "city": "San Diego", + "state": "California", + "zip": "92103" + }, + { + "city": "Springboro", + "state": "Ohio", + "zip": "45066" + }, + { + "city": "Jacksonville", + "state": "Florida", + "zip": "32210" + }, + { + "city": "New Port Richey", + "state": "Florida", + "zip": "34655" + }, + { + "city": "Memphis", + "state": "Tennessee", + "zip": "38118" + }, + { + "city": "Des Moines", + "state": "Iowa", + "zip": "50310" + }, + { + "city": "Conway", + "state": "Arkansas", + "zip": "72034" + }, + { + "city": "Southington", + "state": "Connecticut", + "zip": "6489" + }, + { + "city": "Rome", + "state": "Georgia", + "zip": "30165" + }, + { + "city": "Providence", + "state": "Rhode Island", + "zip": "2908" + }, + { + "city": "Omaha", + "state": "Nebraska", + "zip": "68104" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77004" + }, + { + "city": "Muskego", + "state": "Wisconsin", + "zip": "53150" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60638" + }, + { + "city": "Durango", + "state": "Colorado", + "zip": "81301" + }, + { + "city": "Avon Park", + "state": "Florida", + "zip": "33825" + }, + { + "city": "Oklahoma City", + "state": "Oklahoma", + "zip": "73162" + }, + { + "city": "Evanston", + "state": "Illinois", + "zip": "60202" + }, + { + "city": "Jacksonville", + "state": "Texas", + "zip": "75766" + }, + { + "city": "Broomfield", + "state": "Colorado", + "zip": "80020" + }, + { + "city": "Saint George", + "state": "Utah", + "zip": "84770" + }, + { + "city": "Hillsboro", + "state": "Oregon", + "zip": "97124" + }, + { + "city": "Tonawanda", + "state": "New York", + "zip": "14150" + }, + { + "city": "Melrose", + "state": "Massachusetts", + "zip": "2176" + }, + { + "city": "Brockton", + "state": "Massachusetts", + "zip": "2301" + }, + { + "city": "Florissant", + "state": "Missouri", + "zip": "63033" + }, + { + "city": "Springfield", + "state": "Missouri", + "zip": "65802" + }, + { + "city": "Wadsworth", + "state": "Ohio", + "zip": "44281" + }, + { + "city": "West Covina", + "state": "California", + "zip": "91791" + }, + { + "city": "New Port Richey", + "state": "Florida", + "zip": "34653" + }, + { + "city": "Baldwin", + "state": "New York", + "zip": "11510" + }, + { + "city": "Marina", + "state": "California", + "zip": "93933" + }, + { + "city": "Manchester", + "state": "Connecticut", + "zip": "6040" + }, + { + "city": "Corona", + "state": "New York", + "zip": "11368" + }, + { + "city": "Raleigh", + "state": "North Carolina", + "zip": "27603" + }, + { + "city": "Memphis", + "state": "Tennessee", + "zip": "38128" + }, + { + "city": "Eau Claire", + "state": "Wisconsin", + "zip": "54701" + }, + { + "city": "Ontario", + "state": "California", + "zip": "91761" + }, + { + "city": "Kent", + "state": "Washington", + "zip": "98032" + }, + { + "city": "Yucca Valley", + "state": "California", + "zip": "92284" + }, + { + "city": "Bellflower", + "state": "California", + "zip": "90706" + }, + { + "city": "San Jose", + "state": "California", + "zip": "95131" + }, + { + "city": "Seattle", + "state": "Washington", + "zip": "98188" + }, + { + "city": "Wasco", + "state": "California", + "zip": "93280" + }, + { + "city": "Smithtown", + "state": "New York", + "zip": "11787" + }, + { + "city": "Ithaca", + "state": "New York", + "zip": "14850" + }, + { + "city": "Norristown", + "state": "Pennsylvania", + "zip": "19401" + }, + { + "city": "Wilson", + "state": "North Carolina", + "zip": "27893" + }, + { + "city": "Marietta", + "state": "Georgia", + "zip": "30064" + }, + { + "city": "Montgomery", + "state": "Illinois", + "zip": "60538" + }, + { + "city": "Gainesville", + "state": "Texas", + "zip": "76240" + }, + { + "city": "Harker Heights", + "state": "Texas", + "zip": "76548" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77009" + }, + { + "city": "Tucson", + "state": "Arizona", + "zip": "85705" + }, + { + "city": "Las Cruces", + "state": "New Mexico", + "zip": "88001" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90049" + }, + { + "city": "Temecula", + "state": "California", + "zip": "92591" + }, + { + "city": "Madison", + "state": "Mississippi", + "zip": "39110" + }, + { + "city": "Paducah", + "state": "Kentucky", + "zip": "42001" + }, + { + "city": "Newark", + "state": "Ohio", + "zip": "43055" + }, + { + "city": "Oceanside", + "state": "California", + "zip": "92058" + }, + { + "city": "Eagle River", + "state": "Alaska", + "zip": "99577" + }, + { + "city": "Helotes", + "state": "Texas", + "zip": "78023" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90064" + }, + { + "city": "Sherman Oaks", + "state": "California", + "zip": "91403" + }, + { + "city": "Imperial Beach", + "state": "California", + "zip": "91932" + }, + { + "city": "Fresno", + "state": "California", + "zip": "93726" + }, + { + "city": "Cave Creek", + "state": "Arizona", + "zip": "85331" + }, + { + "city": "Daytona Beach", + "state": "Florida", + "zip": "32117" + }, + { + "city": "Pompano Beach", + "state": "Florida", + "zip": "33064" + }, + { + "city": "New York", + "state": "New York", + "zip": "10032" + }, + { + "city": "Deland", + "state": "Florida", + "zip": "32724" + }, + { + "city": "Dayton", + "state": "Ohio", + "zip": "45424" + }, + { + "city": "Orland Park", + "state": "Illinois", + "zip": "60467" + }, + { + "city": "Windsor", + "state": "Connecticut", + "zip": "6095" + }, + { + "city": "Jamaica", + "state": "New York", + "zip": "11435" + }, + { + "city": "Washington", + "state": "District of Columbia", + "zip": "20001" + }, + { + "city": "Roanoke", + "state": "Virginia", + "zip": "24019" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33189" + }, + { + "city": "Jackson", + "state": "Mississippi", + "zip": "39212" + }, + { + "city": "Starkville", + "state": "Mississippi", + "zip": "39759" + }, + { + "city": "Austin", + "state": "Texas", + "zip": "78744" + }, + { + "city": "Pacoima", + "state": "California", + "zip": "91331" + }, + { + "city": "Frisco", + "state": "Texas", + "zip": "75034" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89166" + }, + { + "city": "Branford", + "state": "Connecticut", + "zip": "6405" + }, + { + "city": "Rockwall", + "state": "Texas", + "zip": "75087" + }, + { + "city": "Aguada", + "state": "Puerto Rico", + "zip": "602" + }, + { + "city": "Friendswood", + "state": "Texas", + "zip": "77546" + }, + { + "city": "Atwater", + "state": "California", + "zip": "95301" + }, + { + "city": "Chico", + "state": "California", + "zip": "95926" + }, + { + "city": "Monroe Township", + "state": "New Jersey", + "zip": "8831" + }, + { + "city": "Savannah", + "state": "Georgia", + "zip": "31404" + }, + { + "city": "Lapeer", + "state": "Michigan", + "zip": "48446" + }, + { + "city": "Milwaukee", + "state": "Wisconsin", + "zip": "53213" + }, + { + "city": "Flower Mound", + "state": "Texas", + "zip": "75022" + }, + { + "city": "Deer Park", + "state": "Texas", + "zip": "77536" + }, + { + "city": "Boise", + "state": "Idaho", + "zip": "83705" + }, + { + "city": "Mesa", + "state": "Arizona", + "zip": "85205" + }, + { + "city": "Opa Locka", + "state": "Florida", + "zip": "33054" + }, + { + "city": "Ann Arbor", + "state": "Michigan", + "zip": "48104" + }, + { + "city": "Hudson", + "state": "Wisconsin", + "zip": "54016" + }, + { + "city": "Tucson", + "state": "Arizona", + "zip": "85710" + }, + { + "city": "Jurupa Valley", + "state": "California", + "zip": "92509" + }, + { + "city": "Stockton", + "state": "California", + "zip": "95219" + }, + { + "city": "Mokena", + "state": "Illinois", + "zip": "60448" + }, + { + "city": "Ruston", + "state": "Louisiana", + "zip": "71270" + }, + { + "city": "Orange", + "state": "Texas", + "zip": "77630" + }, + { + "city": "Jacksonville", + "state": "Florida", + "zip": "32208" + }, + { + "city": "Hudson", + "state": "Ohio", + "zip": "44236" + }, + { + "city": "Maryville", + "state": "Tennessee", + "zip": "37801" + }, + { + "city": "Sheridan", + "state": "Wyoming", + "zip": "82801" + }, + { + "city": "Bayamon", + "state": "Puerto Rico", + "zip": "957" + }, + { + "city": "Jamaica Plain", + "state": "Massachusetts", + "zip": "2130" + }, + { + "city": "Reston", + "state": "Virginia", + "zip": "20191" + }, + { + "city": "Plainfield", + "state": "Illinois", + "zip": "60586" + }, + { + "city": "Tempe", + "state": "Arizona", + "zip": "85282" + }, + { + "city": "Trujillo Alto", + "state": "Puerto Rico", + "zip": "976" + }, + { + "city": "Sicklerville", + "state": "New Jersey", + "zip": "8081" + }, + { + "city": "New York", + "state": "New York", + "zip": "10010" + }, + { + "city": "Wheeling", + "state": "Illinois", + "zip": "60090" + }, + { + "city": "Claremont", + "state": "California", + "zip": "91711" + }, + { + "city": "Los Banos", + "state": "California", + "zip": "93635" + }, + { + "city": "Monterey", + "state": "California", + "zip": "93940" + }, + { + "city": "Delray Beach", + "state": "Florida", + "zip": "33484" + }, + { + "city": "Tarpon Springs", + "state": "Florida", + "zip": "34689" + }, + { + "city": "Atlanta", + "state": "Georgia", + "zip": "30341" + }, + { + "city": "Gainesville", + "state": "Florida", + "zip": "32608" + }, + { + "city": "Astoria", + "state": "New York", + "zip": "11106" + }, + { + "city": "West Bend", + "state": "Wisconsin", + "zip": "53095" + }, + { + "city": "Minneapolis", + "state": "Minnesota", + "zip": "55426" + }, + { + "city": "Galesburg", + "state": "Illinois", + "zip": "61401" + }, + { + "city": "Dallas", + "state": "Texas", + "zip": "75287" + }, + { + "city": "Brenham", + "state": "Texas", + "zip": "77833" + }, + { + "city": "Patterson", + "state": "California", + "zip": "95363" + }, + { + "city": "Carmichael", + "state": "California", + "zip": "95608" + }, + { + "city": "Voorhees", + "state": "New Jersey", + "zip": "8043" + }, + { + "city": "El Paso", + "state": "Texas", + "zip": "79938" + }, + { + "city": "Westwood", + "state": "New Jersey", + "zip": "7675" + }, + { + "city": "Waynesboro", + "state": "Pennsylvania", + "zip": "17268" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60634" + }, + { + "city": "Denver", + "state": "Colorado", + "zip": "80260" + }, + { + "city": "Cedar Rapids", + "state": "Iowa", + "zip": "52404" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78258" + }, + { + "city": "South Lake Tahoe", + "state": "California", + "zip": "96150" + }, + { + "city": "Durant", + "state": "Oklahoma", + "zip": "74701" + }, + { + "city": "Vega Alta", + "state": "Puerto Rico", + "zip": "692" + }, + { + "city": "Gastonia", + "state": "North Carolina", + "zip": "28052" + }, + { + "city": "New Orleans", + "state": "Louisiana", + "zip": "70115" + }, + { + "city": "Kalamazoo", + "state": "Michigan", + "zip": "49048" + }, + { + "city": "Fort Wayne", + "state": "Indiana", + "zip": "46804" + }, + { + "city": "Ames", + "state": "Iowa", + "zip": "50010" + }, + { + "city": "Bozeman", + "state": "Montana", + "zip": "59718" + }, + { + "city": "Little Rock", + "state": "Arkansas", + "zip": "72204" + }, + { + "city": "Ardmore", + "state": "Oklahoma", + "zip": "73401" + }, + { + "city": "Takoma Park", + "state": "Maryland", + "zip": "20912" + }, + { + "city": "Norwalk", + "state": "California", + "zip": "90650" + }, + { + "city": "San Ramon", + "state": "California", + "zip": "94583" + }, + { + "city": "Spokane", + "state": "Washington", + "zip": "99223" + }, + { + "city": "Elizabeth", + "state": "New Jersey", + "zip": "7208" + }, + { + "city": "Fairport", + "state": "New York", + "zip": "14450" + }, + { + "city": "Stafford", + "state": "Virginia", + "zip": "22554" + }, + { + "city": "Hesperia", + "state": "California", + "zip": "92345" + }, + { + "city": "Santa Maria", + "state": "California", + "zip": "93455" + }, + { + "city": "Sacramento", + "state": "California", + "zip": "95823" + }, + { + "city": "Columbus", + "state": "Ohio", + "zip": "43228" + }, + { + "city": "Holland", + "state": "Michigan", + "zip": "49424" + }, + { + "city": "Iowa City", + "state": "Iowa", + "zip": "52245" + }, + { + "city": "Woodway", + "state": "Texas", + "zip": "76712" + }, + { + "city": "Klamath Falls", + "state": "Oregon", + "zip": "97603" + }, + { + "city": "Fort Worth", + "state": "Texas", + "zip": "76114" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77066" + }, + { + "city": "Katy", + "state": "Texas", + "zip": "77449" + }, + { + "city": "Antioch", + "state": "California", + "zip": "94531" + }, + { + "city": "Revere", + "state": "Massachusetts", + "zip": "2151" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19147" + }, + { + "city": "Flower Mound", + "state": "Texas", + "zip": "75028" + }, + { + "city": "San Diego", + "state": "California", + "zip": "92110" + }, + { + "city": "Ventura", + "state": "California", + "zip": "93001" + }, + { + "city": "Orlando", + "state": "Florida", + "zip": "32809" + }, + { + "city": "Norman", + "state": "Oklahoma", + "zip": "73072" + }, + { + "city": "Rosharon", + "state": "Texas", + "zip": "77583" + }, + { + "city": "Tucson", + "state": "Arizona", + "zip": "85711" + }, + { + "city": "Dallas", + "state": "Texas", + "zip": "75227" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89107" + }, + { + "city": "Guaynabo", + "state": "Puerto Rico", + "zip": "969" + }, + { + "city": "York", + "state": "South Carolina", + "zip": "29745" + }, + { + "city": "Conyers", + "state": "Georgia", + "zip": "30013" + }, + { + "city": "Baton Rouge", + "state": "Louisiana", + "zip": "70816" + }, + { + "city": "Greeley", + "state": "Colorado", + "zip": "80631" + }, + { + "city": "Freeport", + "state": "New York", + "zip": "11520" + }, + { + "city": "Inman", + "state": "South Carolina", + "zip": "29349" + }, + { + "city": "Rowlett", + "state": "Texas", + "zip": "75088" + }, + { + "city": "Seaside", + "state": "California", + "zip": "93955" + }, + { + "city": "Springfield Gardens", + "state": "New York", + "zip": "11413" + }, + { + "city": "Frisco", + "state": "Texas", + "zip": "75035" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77014" + }, + { + "city": "Austin", + "state": "Texas", + "zip": "78753" + }, + { + "city": "Ladson", + "state": "South Carolina", + "zip": "29456" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77025" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77074" + }, + { + "city": "Amsterdam", + "state": "New York", + "zip": "12010" + }, + { + "city": "North Charleston", + "state": "South Carolina", + "zip": "29405" + }, + { + "city": "Hilton Head Island", + "state": "South Carolina", + "zip": "29926" + }, + { + "city": "Talladega", + "state": "Alabama", + "zip": "35160" + }, + { + "city": "Plainfield", + "state": "Indiana", + "zip": "46168" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90019" + }, + { + "city": "El Centro", + "state": "California", + "zip": "92243" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77057" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33127" + }, + { + "city": "Fountain Valley", + "state": "California", + "zip": "92708" + }, + { + "city": "Oxnard", + "state": "California", + "zip": "93036" + }, + { + "city": "Mentor", + "state": "Ohio", + "zip": "44060" + }, + { + "city": "Annapolis", + "state": "Maryland", + "zip": "21403" + }, + { + "city": "Sykesville", + "state": "Maryland", + "zip": "21784" + }, + { + "city": "Douglasville", + "state": "Georgia", + "zip": "30134" + }, + { + "city": "Augusta", + "state": "Georgia", + "zip": "30904" + }, + { + "city": "Ashtabula", + "state": "Ohio", + "zip": "44004" + }, + { + "city": "Haymarket", + "state": "Virginia", + "zip": "20169" + }, + { + "city": "McLean", + "state": "Virginia", + "zip": "22101" + }, + { + "city": "Lawrenceville", + "state": "Georgia", + "zip": "30046" + }, + { + "city": "Buford", + "state": "Georgia", + "zip": "30519" + }, + { + "city": "Port Arthur", + "state": "Texas", + "zip": "77642" + }, + { + "city": "Colorado Springs", + "state": "Colorado", + "zip": "80919" + }, + { + "city": "Sandy", + "state": "Utah", + "zip": "84094" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85014" + }, + { + "city": "Baldwin Park", + "state": "California", + "zip": "91706" + }, + { + "city": "Placerville", + "state": "California", + "zip": "95667" + }, + { + "city": "Salem", + "state": "Oregon", + "zip": "97304" + }, + { + "city": "Bend", + "state": "Oregon", + "zip": "97701" + }, + { + "city": "Lexington", + "state": "Kentucky", + "zip": "40508" + }, + { + "city": "Milwaukee", + "state": "Wisconsin", + "zip": "53216" + }, + { + "city": "Dublin", + "state": "California", + "zip": "94568" + }, + { + "city": "York", + "state": "Pennsylvania", + "zip": "17404" + }, + { + "city": "Snellville", + "state": "Georgia", + "zip": "30078" + }, + { + "city": "Carrollton", + "state": "Georgia", + "zip": "30117" + }, + { + "city": "High Point", + "state": "North Carolina", + "zip": "27260" + }, + { + "city": "Downers Grove", + "state": "Illinois", + "zip": "60516" + }, + { + "city": "Sapulpa", + "state": "Oklahoma", + "zip": "74066" + }, + { + "city": "Denver", + "state": "Colorado", + "zip": "80219" + }, + { + "city": "Thornton", + "state": "Colorado", + "zip": "80241" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85048" + }, + { + "city": "Mableton", + "state": "Georgia", + "zip": "30126" + }, + { + "city": "Spring", + "state": "Texas", + "zip": "77389" + }, + { + "city": "Mira Loma", + "state": "California", + "zip": "91752" + }, + { + "city": "Honolulu", + "state": "Hawaii", + "zip": "96815" + }, + { + "city": "Franklin", + "state": "North Carolina", + "zip": "28734" + }, + { + "city": "Pittsburgh", + "state": "Pennsylvania", + "zip": "15237" + }, + { + "city": "Chester", + "state": "Pennsylvania", + "zip": "19013" + }, + { + "city": "Brunswick", + "state": "Ohio", + "zip": "44212" + }, + { + "city": "Council Bluffs", + "state": "Iowa", + "zip": "51501" + }, + { + "city": "Central Islip", + "state": "New York", + "zip": "11722" + }, + { + "city": "Herndon", + "state": "Virginia", + "zip": "20171" + }, + { + "city": "Palm Beach Gardens", + "state": "Florida", + "zip": "33410" + }, + { + "city": "Murrieta", + "state": "California", + "zip": "92563" + }, + { + "city": "Woodland", + "state": "California", + "zip": "95695" + }, + { + "city": "Fall River", + "state": "Massachusetts", + "zip": "2720" + }, + { + "city": "Bensalem", + "state": "Pennsylvania", + "zip": "19020" + }, + { + "city": "Turlock", + "state": "California", + "zip": "95380" + }, + { + "city": "Sacramento", + "state": "California", + "zip": "95838" + }, + { + "city": "Greenville", + "state": "South Carolina", + "zip": "29611" + }, + { + "city": "Decatur", + "state": "Georgia", + "zip": "30032" + }, + { + "city": "Indianapolis", + "state": "Indiana", + "zip": "46235" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60655" + }, + { + "city": "Littleton", + "state": "Colorado", + "zip": "80129" + }, + { + "city": "Albuquerque", + "state": "New Mexico", + "zip": "87120" + }, + { + "city": "Alpharetta", + "state": "Georgia", + "zip": "30005" + }, + { + "city": "Seattle", + "state": "Washington", + "zip": "98102" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11232" + }, + { + "city": "Baltimore", + "state": "Maryland", + "zip": "21217" + }, + { + "city": "Newport News", + "state": "Virginia", + "zip": "23602" + }, + { + "city": "Grayslake", + "state": "Illinois", + "zip": "60030" + }, + { + "city": "Rogers", + "state": "Arkansas", + "zip": "72756" + }, + { + "city": "Bastrop", + "state": "Texas", + "zip": "78602" + }, + { + "city": "Pueblo", + "state": "Colorado", + "zip": "81007" + }, + { + "city": "Burnsville", + "state": "Minnesota", + "zip": "55337" + }, + { + "city": "Florissant", + "state": "Missouri", + "zip": "63031" + }, + { + "city": "Shreveport", + "state": "Louisiana", + "zip": "71118" + }, + { + "city": "Katy", + "state": "Texas", + "zip": "77450" + }, + { + "city": "Duncan", + "state": "Oklahoma", + "zip": "73533" + }, + { + "city": "Parker", + "state": "Colorado", + "zip": "80138" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89110" + }, + { + "city": "El Cerrito", + "state": "California", + "zip": "94530" + }, + { + "city": "Longwood", + "state": "Florida", + "zip": "32779" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90057" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90062" + }, + { + "city": "Laurel", + "state": "Maryland", + "zip": "20723" + }, + { + "city": "Atascadero", + "state": "California", + "zip": "93422" + }, + { + "city": "Tacoma", + "state": "Washington", + "zip": "98404" + }, + { + "city": "Burlington", + "state": "North Carolina", + "zip": "27215" + }, + { + "city": "Texarkana", + "state": "Texas", + "zip": "75503" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89145" + }, + { + "city": "Estero", + "state": "Florida", + "zip": "33928" + }, + { + "city": "Daphne", + "state": "Alabama", + "zip": "36526" + }, + { + "city": "Kalamazoo", + "state": "Michigan", + "zip": "49009" + }, + { + "city": "Middleton", + "state": "Wisconsin", + "zip": "53562" + }, + { + "city": "Fullerton", + "state": "California", + "zip": "92832" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77021" + }, + { + "city": "La Porte", + "state": "Texas", + "zip": "77571" + }, + { + "city": "Austin", + "state": "Texas", + "zip": "78757" + }, + { + "city": "San Diego", + "state": "California", + "zip": "92139" + }, + { + "city": "Caldwell", + "state": "New Jersey", + "zip": "7006" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77095" + }, + { + "city": "Utica", + "state": "New York", + "zip": "13501" + }, + { + "city": "Middletown", + "state": "Delaware", + "zip": "19709" + }, + { + "city": "Lynchburg", + "state": "Virginia", + "zip": "24502" + }, + { + "city": "Kansas City", + "state": "Missouri", + "zip": "64119" + }, + { + "city": "Mesquite", + "state": "Texas", + "zip": "75149" + }, + { + "city": "Fort Worth", + "state": "Texas", + "zip": "76123" + }, + { + "city": "Bronx", + "state": "New York", + "zip": "10451" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11221" + }, + { + "city": "Murfreesboro", + "state": "Tennessee", + "zip": "37130" + }, + { + "city": "Cleveland", + "state": "Ohio", + "zip": "44134" + }, + { + "city": "Ellicott City", + "state": "Maryland", + "zip": "21042" + }, + { + "city": "Douglasville", + "state": "Georgia", + "zip": "30135" + }, + { + "city": "Melbourne", + "state": "Florida", + "zip": "32904" + }, + { + "city": "Tampa", + "state": "Florida", + "zip": "33613" + }, + { + "city": "Carolina", + "state": "Puerto Rico", + "zip": "987" + }, + { + "city": "South Boston", + "state": "Massachusetts", + "zip": "2127" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19115" + }, + { + "city": "Columbia", + "state": "Maryland", + "zip": "21045" + }, + { + "city": "Charlotte", + "state": "North Carolina", + "zip": "28270" + }, + { + "city": "Frankfort", + "state": "Illinois", + "zip": "60423" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60640" + }, + { + "city": "Livingston", + "state": "Texas", + "zip": "77351" + }, + { + "city": "Ypsilanti", + "state": "Michigan", + "zip": "48197" + }, + { + "city": "Charlotte", + "state": "North Carolina", + "zip": "28278" + }, + { + "city": "Kankakee", + "state": "Illinois", + "zip": "60901" + }, + { + "city": "Lincoln", + "state": "Nebraska", + "zip": "68516" + }, + { + "city": "Garland", + "state": "Texas", + "zip": "75040" + }, + { + "city": "Cypress", + "state": "California", + "zip": "90630" + }, + { + "city": "Fairbanks", + "state": "Alaska", + "zip": "99709" + }, + { + "city": "Lake Charles", + "state": "Louisiana", + "zip": "70605" + }, + { + "city": "New Bedford", + "state": "Massachusetts", + "zip": "2740" + }, + { + "city": "Manchester", + "state": "New Hampshire", + "zip": "3103" + }, + { + "city": "Meriden", + "state": "Connecticut", + "zip": "6450" + }, + { + "city": "Canton", + "state": "Georgia", + "zip": "30115" + }, + { + "city": "Sanger", + "state": "California", + "zip": "93657" + }, + { + "city": "Bear", + "state": "Delaware", + "zip": "19701" + }, + { + "city": "Cartersville", + "state": "Georgia", + "zip": "30120" + }, + { + "city": "Mckinney", + "state": "Texas", + "zip": "75071" + }, + { + "city": "Jackson", + "state": "New Jersey", + "zip": "8527" + }, + { + "city": "Brentwood", + "state": "Tennessee", + "zip": "37027" + }, + { + "city": "Oconomowoc", + "state": "Wisconsin", + "zip": "53066" + }, + { + "city": "Rockville", + "state": "Maryland", + "zip": "20853" + }, + { + "city": "Greenwood", + "state": "South Carolina", + "zip": "29646" + }, + { + "city": "Portsmouth", + "state": "Ohio", + "zip": "45662" + }, + { + "city": "Easton", + "state": "Pennsylvania", + "zip": "18042" + }, + { + "city": "Taylors", + "state": "South Carolina", + "zip": "29687" + }, + { + "city": "Powder Springs", + "state": "Georgia", + "zip": "30127" + }, + { + "city": "Atlanta", + "state": "Georgia", + "zip": "30331" + }, + { + "city": "Moreno Valley", + "state": "California", + "zip": "92553" + }, + { + "city": "Spring Hill", + "state": "Florida", + "zip": "34609" + }, + { + "city": "Indianapolis", + "state": "Indiana", + "zip": "46221" + }, + { + "city": "Minneapolis", + "state": "Minnesota", + "zip": "55408" + }, + { + "city": "Muncie", + "state": "Indiana", + "zip": "47304" + }, + { + "city": "Ann Arbor", + "state": "Michigan", + "zip": "48103" + }, + { + "city": "Parkville", + "state": "Maryland", + "zip": "21234" + }, + { + "city": "Marietta", + "state": "Georgia", + "zip": "30062" + }, + { + "city": "Pittsburgh", + "state": "Pennsylvania", + "zip": "15236" + }, + { + "city": "Indianapolis", + "state": "Indiana", + "zip": "46268" + }, + { + "city": "Minneapolis", + "state": "Minnesota", + "zip": "55449" + }, + { + "city": "Tustin", + "state": "California", + "zip": "92780" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11210" + }, + { + "city": "Massapequa", + "state": "New York", + "zip": "11758" + }, + { + "city": "Bethel Park", + "state": "Pennsylvania", + "zip": "15102" + }, + { + "city": "Saint Cloud", + "state": "Minnesota", + "zip": "56301" + }, + { + "city": "Romeoville", + "state": "Illinois", + "zip": "60446" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60609" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77020" + }, + { + "city": "Elizabeth", + "state": "New Jersey", + "zip": "7202" + }, + { + "city": "Oakland", + "state": "California", + "zip": "94610" + }, + { + "city": "San Jose", + "state": "California", + "zip": "95134" + }, + { + "city": "Mechanicsburg", + "state": "Pennsylvania", + "zip": "17050" + }, + { + "city": "Baltimore", + "state": "Maryland", + "zip": "21229" + }, + { + "city": "Columbia", + "state": "South Carolina", + "zip": "29229" + }, + { + "city": "O'Fallon", + "state": "Illinois", + "zip": "62269" + }, + { + "city": "Bronx", + "state": "New York", + "zip": "10468" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19128" + }, + { + "city": "Ceres", + "state": "California", + "zip": "95307" + }, + { + "city": "Federal Way", + "state": "Washington", + "zip": "98023" + }, + { + "city": "Gillette", + "state": "Wyoming", + "zip": "82718" + }, + { + "city": "Milford", + "state": "Connecticut", + "zip": "6460" + }, + { + "city": "Hagerstown", + "state": "Maryland", + "zip": "21740" + }, + { + "city": "Columbia", + "state": "Missouri", + "zip": "65202" + }, + { + "city": "Atlanta", + "state": "Georgia", + "zip": "30350" + }, + { + "city": "Saint Petersburg", + "state": "Florida", + "zip": "33702" + }, + { + "city": "Provo", + "state": "Utah", + "zip": "84601" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33134" + }, + { + "city": "Broken Arrow", + "state": "Oklahoma", + "zip": "74014" + }, + { + "city": "San Luis", + "state": "Arizona", + "zip": "85349" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85018" + }, + { + "city": "Quebradillas", + "state": "Puerto Rico", + "zip": "678" + }, + { + "city": "Naugatuck", + "state": "Connecticut", + "zip": "6770" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90018" + }, + { + "city": "Walnut Creek", + "state": "California", + "zip": "94598" + }, + { + "city": "Champaign", + "state": "Illinois", + "zip": "61820" + }, + { + "city": "Albuquerque", + "state": "New Mexico", + "zip": "87111" + }, + { + "city": "El Cajon", + "state": "California", + "zip": "92019" + }, + { + "city": "Bellingham", + "state": "Washington", + "zip": "98229" + }, + { + "city": "Derby", + "state": "Kansas", + "zip": "67037" + }, + { + "city": "Flint", + "state": "Michigan", + "zip": "48506" + }, + { + "city": "North Hills", + "state": "California", + "zip": "91343" + }, + { + "city": "Jacksonville", + "state": "Florida", + "zip": "32246" + }, + { + "city": "Fort Lauderdale", + "state": "Florida", + "zip": "33331" + }, + { + "city": "Brandon", + "state": "Mississippi", + "zip": "39047" + }, + { + "city": "Powell", + "state": "Ohio", + "zip": "43065" + }, + { + "city": "Saint Louis", + "state": "Missouri", + "zip": "63136" + }, + { + "city": "Murray", + "state": "Kentucky", + "zip": "42071" + }, + { + "city": "Bloomington", + "state": "Indiana", + "zip": "47401" + }, + { + "city": "Racine", + "state": "Wisconsin", + "zip": "53402" + }, + { + "city": "Bloomington", + "state": "California", + "zip": "92316" + }, + { + "city": "Lawrence", + "state": "Massachusetts", + "zip": "1841" + }, + { + "city": "New York", + "state": "New York", + "zip": "10128" + }, + { + "city": "Seffner", + "state": "Florida", + "zip": "33584" + }, + { + "city": "Indianapolis", + "state": "Indiana", + "zip": "46236" + }, + { + "city": "Santa Fe", + "state": "New Mexico", + "zip": "87505" + }, + { + "city": "Laredo", + "state": "Texas", + "zip": "78045" + }, + { + "city": "Elk Grove", + "state": "California", + "zip": "95624" + }, + { + "city": "Elko", + "state": "Nevada", + "zip": "89801" + }, + { + "city": "New City", + "state": "New York", + "zip": "10956" + }, + { + "city": "Woodbridge", + "state": "Virginia", + "zip": "22191" + }, + { + "city": "Harrisonburg", + "state": "Virginia", + "zip": "22802" + }, + { + "city": "Evans", + "state": "Georgia", + "zip": "30809" + }, + { + "city": "Junction City", + "state": "Kansas", + "zip": "66441" + }, + { + "city": "Stephenville", + "state": "Texas", + "zip": "76401" + }, + { + "city": "Spring", + "state": "Texas", + "zip": "77388" + }, + { + "city": "Lubbock", + "state": "Texas", + "zip": "79424" + }, + { + "city": "West Palm Beach", + "state": "Florida", + "zip": "33411" + }, + { + "city": "Memphis", + "state": "Tennessee", + "zip": "38125" + }, + { + "city": "Indianapolis", + "state": "Indiana", + "zip": "46203" + }, + { + "city": "Pittsburgh", + "state": "Pennsylvania", + "zip": "15210" + }, + { + "city": "Laurel", + "state": "Maryland", + "zip": "20708" + }, + { + "city": "Livingston", + "state": "New Jersey", + "zip": "7039" + }, + { + "city": "Falls Church", + "state": "Virginia", + "zip": "22041" + }, + { + "city": "Charlottesville", + "state": "Virginia", + "zip": "22903" + }, + { + "city": "Griffin", + "state": "Georgia", + "zip": "30224" + }, + { + "city": "Enfield", + "state": "Connecticut", + "zip": "6082" + }, + { + "city": "Randolph", + "state": "New Jersey", + "zip": "7869" + }, + { + "city": "Sugar Land", + "state": "Texas", + "zip": "77498" + }, + { + "city": "Evansville", + "state": "Indiana", + "zip": "47715" + }, + { + "city": "Owensboro", + "state": "Kentucky", + "zip": "42301" + }, + { + "city": "Batavia", + "state": "Illinois", + "zip": "60510" + }, + { + "city": "Arlington", + "state": "Tennessee", + "zip": "38002" + }, + { + "city": "Eau Claire", + "state": "Wisconsin", + "zip": "54703" + }, + { + "city": "Wasilla", + "state": "Alaska", + "zip": "99654" + }, + { + "city": "Dover", + "state": "Pennsylvania", + "zip": "17315" + }, + { + "city": "Bowling Green", + "state": "Kentucky", + "zip": "42104" + }, + { + "city": "New Castle", + "state": "Indiana", + "zip": "47362" + }, + { + "city": "Blue Springs", + "state": "Missouri", + "zip": "64014" + }, + { + "city": "El Cajon", + "state": "California", + "zip": "92020" + }, + { + "city": "Kennewick", + "state": "Washington", + "zip": "99336" + }, + { + "city": "Tucson", + "state": "Arizona", + "zip": "85756" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85022" + }, + { + "city": "San Carlos", + "state": "California", + "zip": "94070" + }, + { + "city": "Santa Ana", + "state": "California", + "zip": "92706" + }, + { + "city": "Hendersonville", + "state": "North Carolina", + "zip": "28792" + }, + { + "city": "Bartow", + "state": "Florida", + "zip": "33830" + }, + { + "city": "Syracuse", + "state": "New York", + "zip": "13210" + }, + { + "city": "Palmdale", + "state": "California", + "zip": "93551" + }, + { + "city": "Dothan", + "state": "Alabama", + "zip": "36303" + }, + { + "city": "Seattle", + "state": "Washington", + "zip": "98144" + }, + { + "city": "Germantown", + "state": "Tennessee", + "zip": "38138" + }, + { + "city": "Columbus", + "state": "Ohio", + "zip": "43209" + }, + { + "city": "Oxnard", + "state": "California", + "zip": "93030" + }, + { + "city": "Seattle", + "state": "Washington", + "zip": "98133" + }, + { + "city": "Graham", + "state": "Washington", + "zip": "98338" + }, + { + "city": "Fairhope", + "state": "Alabama", + "zip": "36532" + }, + { + "city": "Mobile", + "state": "Alabama", + "zip": "36605" + }, + { + "city": "Hobart", + "state": "Indiana", + "zip": "46342" + }, + { + "city": "Peoria", + "state": "Illinois", + "zip": "61604" + }, + { + "city": "El Paso", + "state": "Texas", + "zip": "79915" + }, + { + "city": "Van Nuys", + "state": "California", + "zip": "91401" + }, + { + "city": "Hayward", + "state": "California", + "zip": "94544" + }, + { + "city": "Naranjito", + "state": "Puerto Rico", + "zip": "719" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78207" + }, + { + "city": "Draper", + "state": "Utah", + "zip": "84020" + }, + { + "city": "Glendale", + "state": "Arizona", + "zip": "85304" + }, + { + "city": "Mercedes", + "state": "Texas", + "zip": "78570" + }, + { + "city": "Avondale", + "state": "Arizona", + "zip": "85392" + }, + { + "city": "Wilmington", + "state": "North Carolina", + "zip": "28411" + }, + { + "city": "Lawrenceville", + "state": "Georgia", + "zip": "30045" + }, + { + "city": "Skokie", + "state": "Illinois", + "zip": "60076" + }, + { + "city": "Keller", + "state": "Texas", + "zip": "76244" + }, + { + "city": "Donna", + "state": "Texas", + "zip": "78537" + }, + { + "city": "North Las Vegas", + "state": "Nevada", + "zip": "89030" + }, + { + "city": "Ankeny", + "state": "Iowa", + "zip": "50021" + }, + { + "city": "Danville", + "state": "Illinois", + "zip": "61832" + }, + { + "city": "Dallas", + "state": "Texas", + "zip": "75211" + }, + { + "city": "Raleigh", + "state": "North Carolina", + "zip": "27607" + }, + { + "city": "Fort Lauderdale", + "state": "Florida", + "zip": "33314" + }, + { + "city": "Fort Lauderdale", + "state": "Florida", + "zip": "33322" + }, + { + "city": "Reynoldsburg", + "state": "Ohio", + "zip": "43068" + }, + { + "city": "Isabela", + "state": "Puerto Rico", + "zip": "662" + }, + { + "city": "Guayama", + "state": "Puerto Rico", + "zip": "784" + }, + { + "city": "East Hartford", + "state": "Connecticut", + "zip": "6118" + }, + { + "city": "Pittsburgh", + "state": "Pennsylvania", + "zip": "15213" + }, + { + "city": "Durham", + "state": "North Carolina", + "zip": "27707" + }, + { + "city": "La Mesa", + "state": "California", + "zip": "91941" + }, + { + "city": "Hemet", + "state": "California", + "zip": "92545" + }, + { + "city": "Jacksonville", + "state": "Florida", + "zip": "32218" + }, + { + "city": "Minot", + "state": "North Dakota", + "zip": "58701" + }, + { + "city": "San Diego", + "state": "California", + "zip": "92154" + }, + { + "city": "Tallahassee", + "state": "Florida", + "zip": "32301" + }, + { + "city": "North Hollywood", + "state": "California", + "zip": "91606" + }, + { + "city": "Valdosta", + "state": "Georgia", + "zip": "31602" + }, + { + "city": "Stamford", + "state": "Connecticut", + "zip": "6902" + }, + { + "city": "Chula Vista", + "state": "California", + "zip": "91910" + }, + { + "city": "Pasco", + "state": "Washington", + "zip": "99301" + }, + { + "city": "Phillipsburg", + "state": "New Jersey", + "zip": "8865" + }, + { + "city": "Milledgeville", + "state": "Georgia", + "zip": "31061" + }, + { + "city": "Glasgow", + "state": "Kentucky", + "zip": "42141" + }, + { + "city": "Everett", + "state": "Washington", + "zip": "98204" + }, + { + "city": "Princeton", + "state": "New Jersey", + "zip": "8540" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77084" + }, + { + "city": "Idaho Falls", + "state": "Idaho", + "zip": "83401" + }, + { + "city": "Henderson", + "state": "Nevada", + "zip": "89074" + }, + { + "city": "San Diego", + "state": "California", + "zip": "92131" + }, + { + "city": "Medford", + "state": "Oregon", + "zip": "97504" + }, + { + "city": "Fajardo", + "state": "Puerto Rico", + "zip": "738" + }, + { + "city": "Hyde Park", + "state": "Massachusetts", + "zip": "2136" + }, + { + "city": "Irvington", + "state": "New Jersey", + "zip": "7111" + }, + { + "city": "Kissimmee", + "state": "Florida", + "zip": "34746" + }, + { + "city": "Morgantown", + "state": "West Virginia", + "zip": "26505" + }, + { + "city": "San Francisco", + "state": "California", + "zip": "94117" + }, + { + "city": "Pikesville", + "state": "Maryland", + "zip": "21208" + }, + { + "city": "Winston Salem", + "state": "North Carolina", + "zip": "27104" + }, + { + "city": "Charleston", + "state": "South Carolina", + "zip": "29412" + }, + { + "city": "Fairburn", + "state": "Georgia", + "zip": "30213" + }, + { + "city": "Ringgold", + "state": "Georgia", + "zip": "30736" + }, + { + "city": "Fresno", + "state": "California", + "zip": "93720" + }, + { + "city": "Westport", + "state": "Connecticut", + "zip": "6880" + }, + { + "city": "Roxbury", + "state": "Massachusetts", + "zip": "2119" + }, + { + "city": "Absecon", + "state": "New Jersey", + "zip": "8205" + }, + { + "city": "Long Island City", + "state": "New York", + "zip": "11101" + }, + { + "city": "Kissimmee", + "state": "Florida", + "zip": "34759" + }, + { + "city": "Memphis", + "state": "Tennessee", + "zip": "38116" + }, + { + "city": "Buffalo Grove", + "state": "Illinois", + "zip": "60089" + }, + { + "city": "Allen", + "state": "Texas", + "zip": "75002" + }, + { + "city": "Lufkin", + "state": "Texas", + "zip": "75901" + }, + { + "city": "Salt Lake City", + "state": "Utah", + "zip": "84123" + }, + { + "city": "Tucson", + "state": "Arizona", + "zip": "85716" + }, + { + "city": "Santa Clarita", + "state": "California", + "zip": "91350" + }, + { + "city": "San Dimas", + "state": "California", + "zip": "91773" + }, + { + "city": "Concord", + "state": "California", + "zip": "94520" + }, + { + "city": "Oakley", + "state": "California", + "zip": "94561" + }, + { + "city": "Sedro Woolley", + "state": "Washington", + "zip": "98284" + }, + { + "city": "Jersey City", + "state": "New Jersey", + "zip": "7307" + }, + { + "city": "Palm Desert", + "state": "California", + "zip": "92211" + }, + { + "city": "Temple Hills", + "state": "Maryland", + "zip": "20748" + }, + { + "city": "National City", + "state": "California", + "zip": "91950" + }, + { + "city": "Dallas", + "state": "Georgia", + "zip": "30157" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33180" + }, + { + "city": "Bellefonte", + "state": "Pennsylvania", + "zip": "16823" + }, + { + "city": "Pueblo", + "state": "Colorado", + "zip": "81001" + }, + { + "city": "Chevy Chase", + "state": "Maryland", + "zip": "20815" + }, + { + "city": "Jefferson City", + "state": "Missouri", + "zip": "65101" + }, + { + "city": "Hammond", + "state": "Louisiana", + "zip": "70403" + }, + { + "city": "Azle", + "state": "Texas", + "zip": "76020" + }, + { + "city": "Newark", + "state": "New Jersey", + "zip": "7104" + }, + { + "city": "Rochester", + "state": "New York", + "zip": "14612" + }, + { + "city": "Lincolnton", + "state": "North Carolina", + "zip": "28092" + }, + { + "city": "Boca Raton", + "state": "Florida", + "zip": "33433" + }, + { + "city": "Delray Beach", + "state": "Florida", + "zip": "33445" + }, + { + "city": "Kenosha", + "state": "Wisconsin", + "zip": "53140" + }, + { + "city": "Kingston", + "state": "Pennsylvania", + "zip": "18704" + }, + { + "city": "Baton Rouge", + "state": "Louisiana", + "zip": "70806" + }, + { + "city": "Brighton", + "state": "Colorado", + "zip": "80602" + }, + { + "city": "Circle Pines", + "state": "Minnesota", + "zip": "55014" + }, + { + "city": "Glendale", + "state": "California", + "zip": "91205" + }, + { + "city": "Chatsworth", + "state": "California", + "zip": "91311" + }, + { + "city": "Hicksville", + "state": "New York", + "zip": "11801" + }, + { + "city": "Frankfort", + "state": "Kentucky", + "zip": "40601" + }, + { + "city": "Fairfax", + "state": "Virginia", + "zip": "22031" + }, + { + "city": "Metairie", + "state": "Louisiana", + "zip": "70005" + }, + { + "city": "Ponce", + "state": "Puerto Rico", + "zip": "716" + }, + { + "city": "Staten Island", + "state": "New York", + "zip": "10309" + }, + { + "city": "Pottstown", + "state": "Pennsylvania", + "zip": "19464" + }, + { + "city": "Napa", + "state": "California", + "zip": "94558" + }, + { + "city": "Portland", + "state": "Oregon", + "zip": "97267" + }, + { + "city": "Corona", + "state": "California", + "zip": "92881" + }, + { + "city": "Crosby", + "state": "Texas", + "zip": "77532" + }, + { + "city": "Bryan", + "state": "Texas", + "zip": "77803" + }, + { + "city": "New York", + "state": "New York", + "zip": "10065" + }, + { + "city": "Florence", + "state": "South Carolina", + "zip": "29501" + }, + { + "city": "Danville", + "state": "Virginia", + "zip": "24541" + }, + { + "city": "Cumming", + "state": "Georgia", + "zip": "30040" + }, + { + "city": "Jackson", + "state": "Michigan", + "zip": "49201" + }, + { + "city": "Pomona", + "state": "California", + "zip": "91768" + }, + { + "city": "Elk Grove", + "state": "California", + "zip": "95757" + }, + { + "city": "Waianae", + "state": "Hawaii", + "zip": "96792" + }, + { + "city": "Monroe", + "state": "Michigan", + "zip": "48161" + }, + { + "city": "Zeeland", + "state": "Michigan", + "zip": "49464" + }, + { + "city": "Minneapolis", + "state": "Minnesota", + "zip": "55434" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33126" + }, + { + "city": "Lehigh Acres", + "state": "Florida", + "zip": "33971" + }, + { + "city": "Milwaukee", + "state": "Wisconsin", + "zip": "53209" + }, + { + "city": "Edmonds", + "state": "Washington", + "zip": "98026" + }, + { + "city": "East Orange", + "state": "New Jersey", + "zip": "7018" + }, + { + "city": "New Brunswick", + "state": "New Jersey", + "zip": "8901" + }, + { + "city": "Flushing", + "state": "New York", + "zip": "11355" + }, + { + "city": "Washington", + "state": "District of Columbia", + "zip": "20019" + }, + { + "city": "Chandler", + "state": "Arizona", + "zip": "85286" + }, + { + "city": "Smyrna", + "state": "Tennessee", + "zip": "37167" + }, + { + "city": "Canton", + "state": "Michigan", + "zip": "48187" + }, + { + "city": "Paramus", + "state": "New Jersey", + "zip": "7652" + }, + { + "city": "Jamaica", + "state": "New York", + "zip": "11432" + }, + { + "city": "Baltimore", + "state": "Maryland", + "zip": "21206" + }, + { + "city": "Indianapolis", + "state": "Indiana", + "zip": "46260" + }, + { + "city": "Moorhead", + "state": "Minnesota", + "zip": "56560" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85020" + }, + { + "city": "Peoria", + "state": "Arizona", + "zip": "85382" + }, + { + "city": "Sparks", + "state": "Nevada", + "zip": "89434" + }, + { + "city": "Puyallup", + "state": "Washington", + "zip": "98375" + }, + { + "city": "Middle River", + "state": "Maryland", + "zip": "21220" + }, + { + "city": "Bristol", + "state": "Tennessee", + "zip": "37620" + }, + { + "city": "Nicholasville", + "state": "Kentucky", + "zip": "40356" + }, + { + "city": "Yonkers", + "state": "New York", + "zip": "10701" + }, + { + "city": "Lisle", + "state": "Illinois", + "zip": "60532" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60614" + }, + { + "city": "Vernal", + "state": "Utah", + "zip": "84078" + }, + { + "city": "Florence", + "state": "Arizona", + "zip": "85132" + }, + { + "city": "Madison", + "state": "Wisconsin", + "zip": "53719" + }, + { + "city": "Mandan", + "state": "North Dakota", + "zip": "58554" + }, + { + "city": "Pasadena", + "state": "California", + "zip": "91104" + }, + { + "city": "Panorama City", + "state": "California", + "zip": "91402" + }, + { + "city": "Escondido", + "state": "California", + "zip": "92025" + }, + { + "city": "San Marcos", + "state": "California", + "zip": "92078" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78223" + }, + { + "city": "Garden City", + "state": "Kansas", + "zip": "67846" + }, + { + "city": "Claremore", + "state": "Oklahoma", + "zip": "74017" + }, + { + "city": "Arcadia", + "state": "California", + "zip": "91007" + }, + { + "city": "Redding", + "state": "California", + "zip": "96001" + }, + { + "city": "Federal Way", + "state": "Washington", + "zip": "98003" + }, + { + "city": "Aguadilla", + "state": "Puerto Rico", + "zip": "603" + }, + { + "city": "Clifton", + "state": "New Jersey", + "zip": "7013" + }, + { + "city": "Mocksville", + "state": "North Carolina", + "zip": "27028" + }, + { + "city": "Rossville", + "state": "Georgia", + "zip": "30741" + }, + { + "city": "San Diego", + "state": "California", + "zip": "92105" + }, + { + "city": "Springfield", + "state": "Missouri", + "zip": "65807" + }, + { + "city": "Stafford", + "state": "Texas", + "zip": "77477" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78227" + }, + { + "city": "Costa Mesa", + "state": "California", + "zip": "92626" + }, + { + "city": "Mcminnville", + "state": "Oregon", + "zip": "97128" + }, + { + "city": "Durham", + "state": "North Carolina", + "zip": "27703" + }, + { + "city": "Columbia", + "state": "South Carolina", + "zip": "29223" + }, + { + "city": "Lagrange", + "state": "Georgia", + "zip": "30241" + }, + { + "city": "Long Beach", + "state": "California", + "zip": "90802" + }, + { + "city": "Orange Park", + "state": "Florida", + "zip": "32073" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33125" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33150" + }, + { + "city": "Eastpointe", + "state": "Michigan", + "zip": "48021" + }, + { + "city": "Chesapeake", + "state": "Virginia", + "zip": "23323" + }, + { + "city": "Lexington", + "state": "North Carolina", + "zip": "27292" + }, + { + "city": "Concord", + "state": "California", + "zip": "94521" + }, + { + "city": "Crowley", + "state": "Texas", + "zip": "76036" + }, + { + "city": "Ponte Vedra Beach", + "state": "Florida", + "zip": "32082" + }, + { + "city": "Memphis", + "state": "Tennessee", + "zip": "38114" + }, + { + "city": "Corona", + "state": "California", + "zip": "92879" + }, + { + "city": "Reno", + "state": "Nevada", + "zip": "89503" + }, + { + "city": "Peachtree City", + "state": "Georgia", + "zip": "30269" + }, + { + "city": "Bellevue", + "state": "Washington", + "zip": "98004" + }, + { + "city": "Garfield", + "state": "New Jersey", + "zip": "7026" + }, + { + "city": "New Rochelle", + "state": "New York", + "zip": "10801" + }, + { + "city": "Port Saint Lucie", + "state": "Florida", + "zip": "34986" + }, + { + "city": "Bryan", + "state": "Texas", + "zip": "77802" + }, + { + "city": "Matthews", + "state": "North Carolina", + "zip": "28105" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89122" + }, + { + "city": "Capitol Heights", + "state": "Maryland", + "zip": "20743" + }, + { + "city": "Greenville", + "state": "South Carolina", + "zip": "29609" + }, + { + "city": "Bloomington", + "state": "Illinois", + "zip": "61701" + }, + { + "city": "Longview", + "state": "Texas", + "zip": "75604" + }, + { + "city": "Corpus Christi", + "state": "Texas", + "zip": "78412" + }, + { + "city": "Georgetown", + "state": "Texas", + "zip": "78628" + }, + { + "city": "Kyle", + "state": "Texas", + "zip": "78640" + }, + { + "city": "Canon City", + "state": "Colorado", + "zip": "81212" + }, + { + "city": "Minneapolis", + "state": "Minnesota", + "zip": "55414" + }, + { + "city": "Inglewood", + "state": "California", + "zip": "90301" + }, + { + "city": "Fremont", + "state": "California", + "zip": "94555" + }, + { + "city": "Winona", + "state": "Minnesota", + "zip": "55987" + }, + { + "city": "San Diego", + "state": "California", + "zip": "92101" + }, + { + "city": "Bergenfield", + "state": "New Jersey", + "zip": "7621" + }, + { + "city": "Lawrence", + "state": "Kansas", + "zip": "66044" + }, + { + "city": "Hoboken", + "state": "New Jersey", + "zip": "7030" + }, + { + "city": "Alexandria", + "state": "Virginia", + "zip": "22314" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90007" + }, + { + "city": "Eureka", + "state": "California", + "zip": "95503" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77061" + }, + { + "city": "Gainesville", + "state": "Georgia", + "zip": "30507" + }, + { + "city": "Elk Grove Village", + "state": "Illinois", + "zip": "60007" + }, + { + "city": "Stroudsburg", + "state": "Pennsylvania", + "zip": "18360" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90063" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77072" + }, + { + "city": "Perris", + "state": "California", + "zip": "92571" + }, + { + "city": "San Francisco", + "state": "California", + "zip": "94132" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33142" + }, + { + "city": "Canton", + "state": "Ohio", + "zip": "44708" + }, + { + "city": "Santa Cruz", + "state": "California", + "zip": "95062" + }, + { + "city": "Detroit", + "state": "Michigan", + "zip": "48209" + }, + { + "city": "Sheboygan", + "state": "Wisconsin", + "zip": "53081" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90017" + }, + { + "city": "Chelsea", + "state": "Massachusetts", + "zip": "2150" + }, + { + "city": "Noblesville", + "state": "Indiana", + "zip": "46060" + }, + { + "city": "Grand Forks", + "state": "North Dakota", + "zip": "58201" + }, + { + "city": "Fredericksburg", + "state": "Virginia", + "zip": "22405" + }, + { + "city": "Roswell", + "state": "Georgia", + "zip": "30075" + }, + { + "city": "Edinburg", + "state": "Texas", + "zip": "78542" + }, + { + "city": "Vancouver", + "state": "Washington", + "zip": "98682" + }, + { + "city": "Mount Pleasant", + "state": "Michigan", + "zip": "48858" + }, + { + "city": "Saint Paul", + "state": "Minnesota", + "zip": "55104" + }, + { + "city": "Irwin", + "state": "Pennsylvania", + "zip": "15642" + }, + { + "city": "Elizabeth", + "state": "New Jersey", + "zip": "7201" + }, + { + "city": "Clermont", + "state": "Florida", + "zip": "34711" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77018" + }, + { + "city": "Winchester", + "state": "Kentucky", + "zip": "40391" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60653" + }, + { + "city": "Edwardsville", + "state": "Illinois", + "zip": "62025" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78259" + }, + { + "city": "Denver", + "state": "Colorado", + "zip": "80220" + }, + { + "city": "Cheyenne", + "state": "Wyoming", + "zip": "82009" + }, + { + "city": "Lehi", + "state": "Utah", + "zip": "84043" + }, + { + "city": "Danbury", + "state": "Connecticut", + "zip": "6811" + }, + { + "city": "Davenport", + "state": "Iowa", + "zip": "52806" + }, + { + "city": "Richmond", + "state": "California", + "zip": "94801" + }, + { + "city": "Conroe", + "state": "Texas", + "zip": "77301" + }, + { + "city": "Seattle", + "state": "Washington", + "zip": "98115" + }, + { + "city": "Decatur", + "state": "Alabama", + "zip": "35601" + }, + { + "city": "Hendersonville", + "state": "Tennessee", + "zip": "37075" + }, + { + "city": "Arlington Heights", + "state": "Illinois", + "zip": "60004" + }, + { + "city": "Littleton", + "state": "Colorado", + "zip": "80126" + }, + { + "city": "Woburn", + "state": "Massachusetts", + "zip": "1801" + }, + { + "city": "Centereach", + "state": "New York", + "zip": "11720" + }, + { + "city": "Locust Grove", + "state": "Georgia", + "zip": "30248" + }, + { + "city": "Hemet", + "state": "California", + "zip": "92544" + }, + { + "city": "Olympia", + "state": "Washington", + "zip": "98516" + }, + { + "city": "Springfield", + "state": "Massachusetts", + "zip": "1109" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11222" + }, + { + "city": "Lenoir", + "state": "North Carolina", + "zip": "28645" + }, + { + "city": "Orange Park", + "state": "Florida", + "zip": "32065" + }, + { + "city": "Columbus", + "state": "Ohio", + "zip": "43219" + }, + { + "city": "Monroe", + "state": "Louisiana", + "zip": "71203" + }, + { + "city": "Mount Vernon", + "state": "New York", + "zip": "10550" + }, + { + "city": "Lititz", + "state": "Pennsylvania", + "zip": "17543" + }, + { + "city": "Hazleton", + "state": "Pennsylvania", + "zip": "18201" + }, + { + "city": "Greensboro", + "state": "North Carolina", + "zip": "27405" + }, + { + "city": "Raleigh", + "state": "North Carolina", + "zip": "27606" + }, + { + "city": "Raleigh", + "state": "North Carolina", + "zip": "27610" + }, + { + "city": "Durham", + "state": "North Carolina", + "zip": "27713" + }, + { + "city": "Cumming", + "state": "Georgia", + "zip": "30041" + }, + { + "city": "Fullerton", + "state": "California", + "zip": "92831" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77073" + }, + { + "city": "Woonsocket", + "state": "Rhode Island", + "zip": "2895" + }, + { + "city": "Plant City", + "state": "Florida", + "zip": "33563" + }, + { + "city": "Ashland", + "state": "Ohio", + "zip": "44805" + }, + { + "city": "Wilmington", + "state": "Delaware", + "zip": "19805" + }, + { + "city": "Winston Salem", + "state": "North Carolina", + "zip": "27107" + }, + { + "city": "Jacksonville", + "state": "North Carolina", + "zip": "28546" + }, + { + "city": "Wheaton", + "state": "Illinois", + "zip": "60189" + }, + { + "city": "Trumbull", + "state": "Connecticut", + "zip": "6611" + }, + { + "city": "Saint Paul", + "state": "Minnesota", + "zip": "55106" + }, + { + "city": "Deerfield", + "state": "Illinois", + "zip": "60015" + }, + { + "city": "Pomona", + "state": "California", + "zip": "91766" + }, + { + "city": "Shrewsbury", + "state": "Massachusetts", + "zip": "1545" + }, + { + "city": "Baldwinsville", + "state": "New York", + "zip": "13027" + }, + { + "city": "Chapel Hill", + "state": "North Carolina", + "zip": "27516" + }, + { + "city": "New Bern", + "state": "North Carolina", + "zip": "28562" + }, + { + "city": "Arnold", + "state": "Missouri", + "zip": "63010" + }, + { + "city": "Mayaguez", + "state": "Puerto Rico", + "zip": "682" + }, + { + "city": "Cincinnati", + "state": "Ohio", + "zip": "45239" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77063" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77088" + }, + { + "city": "Denver", + "state": "Colorado", + "zip": "80233" + }, + { + "city": "Villa Park", + "state": "Illinois", + "zip": "60181" + }, + { + "city": "Norfolk", + "state": "Nebraska", + "zip": "68701" + }, + { + "city": "Boerne", + "state": "Texas", + "zip": "78006" + }, + { + "city": "Corpus Christi", + "state": "Texas", + "zip": "78413" + }, + { + "city": "Winston Salem", + "state": "North Carolina", + "zip": "27106" + }, + { + "city": "Charlotte", + "state": "North Carolina", + "zip": "28269" + }, + { + "city": "Forest Park", + "state": "Georgia", + "zip": "30297" + }, + { + "city": "Fontana", + "state": "California", + "zip": "92336" + }, + { + "city": "Sacramento", + "state": "California", + "zip": "95822" + }, + { + "city": "Roseburg", + "state": "Oregon", + "zip": "97471" + }, + { + "city": "Owensboro", + "state": "Kentucky", + "zip": "42303" + }, + { + "city": "Burlington", + "state": "Iowa", + "zip": "52601" + }, + { + "city": "Watertown", + "state": "South Dakota", + "zip": "57201" + }, + { + "city": "Hanover Park", + "state": "Illinois", + "zip": "60133" + }, + { + "city": "Gardena", + "state": "California", + "zip": "90247" + }, + { + "city": "Mercer Island", + "state": "Washington", + "zip": "98040" + }, + { + "city": "Poulsbo", + "state": "Washington", + "zip": "98370" + }, + { + "city": "Tampa", + "state": "Florida", + "zip": "33615" + }, + { + "city": "Springfield", + "state": "Illinois", + "zip": "62704" + }, + { + "city": "Olympia", + "state": "Washington", + "zip": "98502" + }, + { + "city": "Temecula", + "state": "California", + "zip": "92592" + }, + { + "city": "Wheat Ridge", + "state": "Colorado", + "zip": "80033" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77064" + }, + { + "city": "North Las Vegas", + "state": "Nevada", + "zip": "89032" + }, + { + "city": "Simi Valley", + "state": "California", + "zip": "93065" + }, + { + "city": "Mililani", + "state": "Hawaii", + "zip": "96789" + }, + { + "city": "Honolulu", + "state": "Hawaii", + "zip": "96822" + }, + { + "city": "Santa Ana", + "state": "California", + "zip": "92707" + }, + { + "city": "Johnson City", + "state": "Tennessee", + "zip": "37601" + }, + { + "city": "Salisbury", + "state": "North Carolina", + "zip": "28146" + }, + { + "city": "Randallstown", + "state": "Maryland", + "zip": "21133" + }, + { + "city": "Arvada", + "state": "Colorado", + "zip": "80005" + }, + { + "city": "Sacramento", + "state": "California", + "zip": "95824" + }, + { + "city": "Rahway", + "state": "New Jersey", + "zip": "7065" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85042" + }, + { + "city": "Lake Charles", + "state": "Louisiana", + "zip": "70607" + }, + { + "city": "Provo", + "state": "Utah", + "zip": "84604" + }, + { + "city": "Waterbury", + "state": "Connecticut", + "zip": "6708" + }, + { + "city": "Tiffin", + "state": "Ohio", + "zip": "44883" + }, + { + "city": "Glendale", + "state": "Arizona", + "zip": "85308" + }, + { + "city": "Youngstown", + "state": "Ohio", + "zip": "44515" + }, + { + "city": "Menlo Park", + "state": "California", + "zip": "94025" + }, + { + "city": "Round Lake", + "state": "Illinois", + "zip": "60073" + }, + { + "city": "Naguabo", + "state": "Puerto Rico", + "zip": "718" + }, + { + "city": "Greensboro", + "state": "North Carolina", + "zip": "27455" + }, + { + "city": "Davenport", + "state": "Iowa", + "zip": "52804" + }, + { + "city": "Riverview", + "state": "Florida", + "zip": "33578" + }, + { + "city": "Waukegan", + "state": "Illinois", + "zip": "60087" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77040" + }, + { + "city": "Albuquerque", + "state": "New Mexico", + "zip": "87112" + }, + { + "city": "Nashville", + "state": "Tennessee", + "zip": "37211" + }, + { + "city": "El Paso", + "state": "Texas", + "zip": "79934" + }, + { + "city": "Mesa", + "state": "Arizona", + "zip": "85213" + }, + { + "city": "Sachse", + "state": "Texas", + "zip": "75048" + }, + { + "city": "League City", + "state": "Texas", + "zip": "77573" + }, + { + "city": "Pickerington", + "state": "Ohio", + "zip": "43147" + }, + { + "city": "La Porte", + "state": "Indiana", + "zip": "46350" + }, + { + "city": "West Babylon", + "state": "New York", + "zip": "11704" + }, + { + "city": "Whittier", + "state": "California", + "zip": "90602" + }, + { + "city": "Indio", + "state": "California", + "zip": "92201" + }, + { + "city": "Staten Island", + "state": "New York", + "zip": "10303" + }, + { + "city": "Oswego", + "state": "New York", + "zip": "13126" + }, + { + "city": "Wilkes Barre", + "state": "Pennsylvania", + "zip": "18702" + }, + { + "city": "Silver Spring", + "state": "Maryland", + "zip": "20901" + }, + { + "city": "Roanoke", + "state": "Virginia", + "zip": "24012" + }, + { + "city": "Concord", + "state": "North Carolina", + "zip": "28025" + }, + { + "city": "Snellville", + "state": "Georgia", + "zip": "30039" + }, + { + "city": "Gilbert", + "state": "Arizona", + "zip": "85233" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60623" + }, + { + "city": "Ogden", + "state": "Utah", + "zip": "84403" + }, + { + "city": "York", + "state": "Pennsylvania", + "zip": "17402" + }, + { + "city": "Norco", + "state": "California", + "zip": "92860" + }, + { + "city": "Stockton", + "state": "California", + "zip": "95209" + }, + { + "city": "Rockford", + "state": "Illinois", + "zip": "61108" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11229" + }, + { + "city": "Oakland", + "state": "California", + "zip": "94605" + }, + { + "city": "Puyallup", + "state": "Washington", + "zip": "98374" + }, + { + "city": "Corpus Christi", + "state": "Texas", + "zip": "78414" + }, + { + "city": "Salt Lake City", + "state": "Utah", + "zip": "84121" + }, + { + "city": "Hobbs", + "state": "New Mexico", + "zip": "88240" + }, + { + "city": "Carson City", + "state": "Nevada", + "zip": "89701" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90005" + }, + { + "city": "Ontario", + "state": "California", + "zip": "91762" + }, + { + "city": "Saratoga", + "state": "California", + "zip": "95070" + }, + { + "city": "Battle Ground", + "state": "Washington", + "zip": "98604" + }, + { + "city": "Malden", + "state": "Massachusetts", + "zip": "2148" + }, + { + "city": "Carol Stream", + "state": "Illinois", + "zip": "60188" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60657" + }, + { + "city": "Harvey", + "state": "Louisiana", + "zip": "70058" + }, + { + "city": "Dallas", + "state": "Texas", + "zip": "75217" + }, + { + "city": "Dallas", + "state": "Texas", + "zip": "75240" + }, + { + "city": "Colorado Springs", + "state": "Colorado", + "zip": "80909" + }, + { + "city": "Cabo Rojo", + "state": "Puerto Rico", + "zip": "623" + }, + { + "city": "Portland", + "state": "Maine", + "zip": "4103" + }, + { + "city": "Ridgewood", + "state": "New Jersey", + "zip": "7450" + }, + { + "city": "New Castle", + "state": "Pennsylvania", + "zip": "16101" + }, + { + "city": "Washington", + "state": "District of Columbia", + "zip": "20032" + }, + { + "city": "Salisbury", + "state": "Maryland", + "zip": "21801" + }, + { + "city": "Melbourne", + "state": "Florida", + "zip": "32901" + }, + { + "city": "Westland", + "state": "Michigan", + "zip": "48185" + }, + { + "city": "Hackettstown", + "state": "New Jersey", + "zip": "7840" + }, + { + "city": "Burke", + "state": "Virginia", + "zip": "22015" + }, + { + "city": "Chesapeake", + "state": "Virginia", + "zip": "23320" + }, + { + "city": "Providence", + "state": "Rhode Island", + "zip": "2905" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60628" + }, + { + "city": "Brownwood", + "state": "Texas", + "zip": "76801" + }, + { + "city": "Garden Grove", + "state": "California", + "zip": "92841" + }, + { + "city": "Warner Robins", + "state": "Georgia", + "zip": "31093" + }, + { + "city": "Holyoke", + "state": "Massachusetts", + "zip": "1040" + }, + { + "city": "Lorain", + "state": "Ohio", + "zip": "44052" + }, + { + "city": "Perkasie", + "state": "Pennsylvania", + "zip": "18944" + }, + { + "city": "Mesa", + "state": "Arizona", + "zip": "85204" + }, + { + "city": "Albuquerque", + "state": "New Mexico", + "zip": "87105" + }, + { + "city": "Springfield", + "state": "Oregon", + "zip": "97478" + }, + { + "city": "Columbus", + "state": "Ohio", + "zip": "43220" + }, + { + "city": "Pleasanton", + "state": "California", + "zip": "94566" + }, + { + "city": "Port Saint Lucie", + "state": "Florida", + "zip": "34983" + }, + { + "city": "Columbus", + "state": "Ohio", + "zip": "43229" + }, + { + "city": "Encinitas", + "state": "California", + "zip": "92024" + }, + { + "city": "Redmond", + "state": "Washington", + "zip": "98052" + }, + { + "city": "Panama City", + "state": "Florida", + "zip": "32404" + }, + { + "city": "Cape Coral", + "state": "Florida", + "zip": "33993" + }, + { + "city": "Vincennes", + "state": "Indiana", + "zip": "47591" + }, + { + "city": "Elgin", + "state": "Illinois", + "zip": "60123" + }, + { + "city": "Columbia", + "state": "Missouri", + "zip": "65201" + }, + { + "city": "Copperas Cove", + "state": "Texas", + "zip": "76522" + }, + { + "city": "Scottsdale", + "state": "Arizona", + "zip": "85254" + }, + { + "city": "Dorchester", + "state": "Massachusetts", + "zip": "2125" + }, + { + "city": "New Britain", + "state": "Connecticut", + "zip": "6053" + }, + { + "city": "Goldsboro", + "state": "North Carolina", + "zip": "27530" + }, + { + "city": "Clovis", + "state": "California", + "zip": "93619" + }, + { + "city": "Mattapan", + "state": "Massachusetts", + "zip": "2126" + }, + { + "city": "Mount Pleasant", + "state": "South Carolina", + "zip": "29464" + }, + { + "city": "Upland", + "state": "California", + "zip": "91786" + }, + { + "city": "Honolulu", + "state": "Hawaii", + "zip": "96819" + }, + { + "city": "Middle Village", + "state": "New York", + "zip": "11379" + }, + { + "city": "Niagara Falls", + "state": "New York", + "zip": "14304" + }, + { + "city": "El Dorado", + "state": "Arkansas", + "zip": "71730" + }, + { + "city": "Yukon", + "state": "Oklahoma", + "zip": "73099" + }, + { + "city": "Garland", + "state": "Texas", + "zip": "75043" + }, + { + "city": "Virginia Beach", + "state": "Virginia", + "zip": "23451" + }, + { + "city": "Conway", + "state": "South Carolina", + "zip": "29527" + }, + { + "city": "Acworth", + "state": "Georgia", + "zip": "30102" + }, + { + "city": "Atlanta", + "state": "Georgia", + "zip": "30316" + }, + { + "city": "Bakersfield", + "state": "California", + "zip": "93311" + }, + { + "city": "San Jose", + "state": "California", + "zip": "95125" + }, + { + "city": "Maple Valley", + "state": "Washington", + "zip": "98038" + }, + { + "city": "Grand Rapids", + "state": "Michigan", + "zip": "49525" + }, + { + "city": "Neenah", + "state": "Wisconsin", + "zip": "54956" + }, + { + "city": "Huntington Beach", + "state": "California", + "zip": "92648" + }, + { + "city": "Kapolei", + "state": "Hawaii", + "zip": "96707" + }, + { + "city": "Portland", + "state": "Oregon", + "zip": "97219" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60611" + }, + { + "city": "Humble", + "state": "Texas", + "zip": "77396" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19114" + }, + { + "city": "Martinsville", + "state": "Virginia", + "zip": "24112" + }, + { + "city": "Morganton", + "state": "North Carolina", + "zip": "28655" + }, + { + "city": "College Station", + "state": "Texas", + "zip": "77840" + }, + { + "city": "Amarillo", + "state": "Texas", + "zip": "79107" + }, + { + "city": "Jonesborough", + "state": "Tennessee", + "zip": "37659" + }, + { + "city": "Cordova", + "state": "Tennessee", + "zip": "38018" + }, + { + "city": "Iowa City", + "state": "Iowa", + "zip": "52240" + }, + { + "city": "West Palm Beach", + "state": "Florida", + "zip": "33409" + }, + { + "city": "Los Lunas", + "state": "New Mexico", + "zip": "87031" + }, + { + "city": "New York", + "state": "New York", + "zip": "10024" + }, + { + "city": "Waldorf", + "state": "Maryland", + "zip": "20603" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77005" + }, + { + "city": "Avondale", + "state": "Arizona", + "zip": "85323" + }, + { + "city": "Tampa", + "state": "Florida", + "zip": "33626" + }, + { + "city": "Memphis", + "state": "Tennessee", + "zip": "38134" + }, + { + "city": "Roseville", + "state": "California", + "zip": "95747" + }, + { + "city": "Carlsbad", + "state": "California", + "zip": "92008" + }, + { + "city": "Fayetteville", + "state": "Arkansas", + "zip": "72701" + }, + { + "city": "Rocklin", + "state": "California", + "zip": "95677" + }, + { + "city": "Salem", + "state": "Virginia", + "zip": "24153" + }, + { + "city": "Lexington", + "state": "South Carolina", + "zip": "29073" + }, + { + "city": "Washington", + "state": "District of Columbia", + "zip": "20008" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77070" + }, + { + "city": "Hialeah", + "state": "Florida", + "zip": "33018" + }, + { + "city": "Kissimmee", + "state": "Florida", + "zip": "34758" + }, + { + "city": "Morristown", + "state": "Tennessee", + "zip": "37814" + }, + { + "city": "Blacklick", + "state": "Ohio", + "zip": "43004" + }, + { + "city": "Saint Paul", + "state": "Minnesota", + "zip": "55122" + }, + { + "city": "Carson", + "state": "California", + "zip": "90745" + }, + { + "city": "Fresno", + "state": "California", + "zip": "93710" + }, + { + "city": "North Richland Hills", + "state": "Texas", + "zip": "76180" + }, + { + "city": "Patchogue", + "state": "New York", + "zip": "11772" + }, + { + "city": "Lockport", + "state": "New York", + "zip": "14094" + }, + { + "city": "Atlanta", + "state": "Georgia", + "zip": "30349" + }, + { + "city": "North Olmsted", + "state": "Ohio", + "zip": "44070" + }, + { + "city": "Compton", + "state": "California", + "zip": "90222" + }, + { + "city": "Pomona", + "state": "California", + "zip": "91767" + }, + { + "city": "Union City", + "state": "California", + "zip": "94587" + }, + { + "city": "Somerville", + "state": "Massachusetts", + "zip": "2145" + }, + { + "city": "Waco", + "state": "Texas", + "zip": "76706" + }, + { + "city": "Abilene", + "state": "Texas", + "zip": "79606" + }, + { + "city": "Henderson", + "state": "Nevada", + "zip": "89014" + }, + { + "city": "Rapid City", + "state": "South Dakota", + "zip": "57701" + }, + { + "city": "Dacula", + "state": "Georgia", + "zip": "30019" + }, + { + "city": "Fayetteville", + "state": "Georgia", + "zip": "30214" + }, + { + "city": "Arlington", + "state": "Virginia", + "zip": "22207" + }, + { + "city": "Columbus", + "state": "Ohio", + "zip": "43213" + }, + { + "city": "Hamtramck", + "state": "Michigan", + "zip": "48212" + }, + { + "city": "Antioch", + "state": "Tennessee", + "zip": "37013" + }, + { + "city": "Sugar Land", + "state": "Texas", + "zip": "77478" + }, + { + "city": "El Paso", + "state": "Texas", + "zip": "79932" + }, + { + "city": "Shingle Springs", + "state": "California", + "zip": "95682" + }, + { + "city": "Allentown", + "state": "Pennsylvania", + "zip": "18103" + }, + { + "city": "Princeton", + "state": "West Virginia", + "zip": "24740" + }, + { + "city": "Minneapolis", + "state": "Minnesota", + "zip": "55404" + }, + { + "city": "Washington", + "state": "District of Columbia", + "zip": "20020" + }, + { + "city": "Stafford", + "state": "Virginia", + "zip": "22556" + }, + { + "city": "Glenview", + "state": "Illinois", + "zip": "60025" + }, + { + "city": "Key West", + "state": "Florida", + "zip": "33040" + }, + { + "city": "Carrollton", + "state": "Texas", + "zip": "75010" + }, + { + "city": "Fitchburg", + "state": "Massachusetts", + "zip": "1420" + }, + { + "city": "Atlanta", + "state": "Georgia", + "zip": "30315" + }, + { + "city": "Jacksonville", + "state": "Florida", + "zip": "32257" + }, + { + "city": "Dover", + "state": "Delaware", + "zip": "19901" + }, + { + "city": "Alexandria", + "state": "Virginia", + "zip": "22312" + }, + { + "city": "Glen Allen", + "state": "Virginia", + "zip": "23060" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78238" + }, + { + "city": "Columbus", + "state": "Ohio", + "zip": "43201" + }, + { + "city": "Oceanside", + "state": "California", + "zip": "92056" + }, + { + "city": "Mission Viejo", + "state": "California", + "zip": "92691" + }, + { + "city": "Wayne", + "state": "Pennsylvania", + "zip": "19087" + }, + { + "city": "Henrico", + "state": "Virginia", + "zip": "23233" + }, + { + "city": "Fuquay Varina", + "state": "North Carolina", + "zip": "27526" + }, + { + "city": "Meridian", + "state": "Mississippi", + "zip": "39301" + }, + { + "city": "Marietta", + "state": "Ohio", + "zip": "45750" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60645" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78228" + }, + { + "city": "Downey", + "state": "California", + "zip": "90242" + }, + { + "city": "Hercules", + "state": "California", + "zip": "94547" + }, + { + "city": "Centralia", + "state": "Washington", + "zip": "98531" + }, + { + "city": "New York", + "state": "New York", + "zip": "10029" + }, + { + "city": "Cumming", + "state": "Georgia", + "zip": "30028" + }, + { + "city": "Snohomish", + "state": "Washington", + "zip": "98290" + }, + { + "city": "Newark", + "state": "New Jersey", + "zip": "7112" + }, + { + "city": "Astoria", + "state": "New York", + "zip": "11103" + }, + { + "city": "King Of Prussia", + "state": "Pennsylvania", + "zip": "19406" + }, + { + "city": "Columbia", + "state": "Tennessee", + "zip": "38401" + }, + { + "city": "Jackson", + "state": "Michigan", + "zip": "49203" + }, + { + "city": "Bettendorf", + "state": "Iowa", + "zip": "52722" + }, + { + "city": "Milwaukee", + "state": "Wisconsin", + "zip": "53212" + }, + { + "city": "Huntley", + "state": "Illinois", + "zip": "60142" + }, + { + "city": "Hot Springs National Park", + "state": "Arkansas", + "zip": "71901" + }, + { + "city": "Lubbock", + "state": "Texas", + "zip": "79416" + }, + { + "city": "La Mesa", + "state": "California", + "zip": "91942" + }, + { + "city": "San Jose", + "state": "California", + "zip": "95122" + }, + { + "city": "Seattle", + "state": "Washington", + "zip": "98116" + }, + { + "city": "Gig Harbor", + "state": "Washington", + "zip": "98335" + }, + { + "city": "Lake Worth", + "state": "Florida", + "zip": "33461" + }, + { + "city": "Wesley Chapel", + "state": "Florida", + "zip": "33544" + }, + { + "city": "Northbrook", + "state": "Illinois", + "zip": "60062" + }, + { + "city": "Glastonbury", + "state": "Connecticut", + "zip": "6033" + }, + { + "city": "Medford", + "state": "New Jersey", + "zip": "8055" + }, + { + "city": "Spring Valley", + "state": "New York", + "zip": "10977" + }, + { + "city": "Madison", + "state": "Tennessee", + "zip": "37115" + }, + { + "city": "Crawfordsville", + "state": "Indiana", + "zip": "47933" + }, + { + "city": "San Jose", + "state": "California", + "zip": "95117" + }, + { + "city": "West Palm Beach", + "state": "Florida", + "zip": "33404" + }, + { + "city": "Jacksonville", + "state": "Florida", + "zip": "32277" + }, + { + "city": "Minneapolis", + "state": "Minnesota", + "zip": "55417" + }, + { + "city": "Fredericksburg", + "state": "Virginia", + "zip": "22407" + }, + { + "city": "Woodstock", + "state": "Illinois", + "zip": "60098" + }, + { + "city": "Denver", + "state": "Colorado", + "zip": "80205" + }, + { + "city": "Aurora", + "state": "Illinois", + "zip": "60506" + }, + { + "city": "Scottsdale", + "state": "Arizona", + "zip": "85257" + }, + { + "city": "Littleton", + "state": "Colorado", + "zip": "80128" + }, + { + "city": "Muskegon", + "state": "Michigan", + "zip": "49441" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77016" + }, + { + "city": "Tacoma", + "state": "Washington", + "zip": "98409" + }, + { + "city": "Seattle", + "state": "Washington", + "zip": "98117" + }, + { + "city": "Culpeper", + "state": "Virginia", + "zip": "22701" + }, + { + "city": "Rock Springs", + "state": "Wyoming", + "zip": "82901" + }, + { + "city": "Salinas", + "state": "California", + "zip": "93905" + }, + { + "city": "Brandon", + "state": "Florida", + "zip": "33510" + }, + { + "city": "Pewaukee", + "state": "Wisconsin", + "zip": "53072" + }, + { + "city": "Bronx", + "state": "New York", + "zip": "10465" + }, + { + "city": "Salinas", + "state": "California", + "zip": "93901" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89102" + }, + { + "city": "Raleigh", + "state": "North Carolina", + "zip": "27614" + }, + { + "city": "Tallahassee", + "state": "Florida", + "zip": "32309" + }, + { + "city": "Milwaukee", + "state": "Wisconsin", + "zip": "53210" + }, + { + "city": "Beloit", + "state": "Wisconsin", + "zip": "53511" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77041" + }, + { + "city": "Austin", + "state": "Texas", + "zip": "78717" + }, + { + "city": "Jonesboro", + "state": "Arkansas", + "zip": "72401" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78247" + }, + { + "city": "Boston", + "state": "Massachusetts", + "zip": "2118" + }, + { + "city": "Willingboro", + "state": "New Jersey", + "zip": "8046" + }, + { + "city": "Atlanta", + "state": "Georgia", + "zip": "30324" + }, + { + "city": "Pompano Beach", + "state": "Florida", + "zip": "33076" + }, + { + "city": "Lakeland", + "state": "Florida", + "zip": "33801" + }, + { + "city": "Carmel", + "state": "Indiana", + "zip": "46033" + }, + { + "city": "Leesburg", + "state": "Virginia", + "zip": "20175" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19125" + }, + { + "city": "Lebanon", + "state": "Ohio", + "zip": "45036" + }, + { + "city": "Plainview", + "state": "Texas", + "zip": "79072" + }, + { + "city": "Titusville", + "state": "Florida", + "zip": "32780" + }, + { + "city": "Buffalo", + "state": "New York", + "zip": "14221" + }, + { + "city": "District Heights", + "state": "Maryland", + "zip": "20747" + }, + { + "city": "Pasadena", + "state": "Maryland", + "zip": "21122" + }, + { + "city": "Essex", + "state": "Maryland", + "zip": "21221" + }, + { + "city": "Charlottesville", + "state": "Virginia", + "zip": "22901" + }, + { + "city": "Bradenton", + "state": "Florida", + "zip": "34205" + }, + { + "city": "Allen Park", + "state": "Michigan", + "zip": "48101" + }, + { + "city": "Gaithersburg", + "state": "Maryland", + "zip": "20878" + }, + { + "city": "Hollywood", + "state": "Florida", + "zip": "33025" + }, + { + "city": "Cocoa", + "state": "Florida", + "zip": "32927" + }, + { + "city": "Tampa", + "state": "Florida", + "zip": "33612" + }, + { + "city": "East Chicago", + "state": "Indiana", + "zip": "46312" + }, + { + "city": "Algonquin", + "state": "Illinois", + "zip": "60102" + }, + { + "city": "Woodstock", + "state": "Georgia", + "zip": "30188" + }, + { + "city": "Merrick", + "state": "New York", + "zip": "11566" + }, + { + "city": "Layton", + "state": "Utah", + "zip": "84041" + }, + { + "city": "Hialeah", + "state": "Florida", + "zip": "33012" + }, + { + "city": "Lanham", + "state": "Maryland", + "zip": "20706" + }, + { + "city": "Plano", + "state": "Texas", + "zip": "75025" + }, + { + "city": "Dallas", + "state": "Texas", + "zip": "75243" + }, + { + "city": "San Jose", + "state": "California", + "zip": "95118" + }, + { + "city": "Deerfield Beach", + "state": "Florida", + "zip": "33441" + }, + { + "city": "Memphis", + "state": "Tennessee", + "zip": "38115" + }, + { + "city": "Ft Mitchell", + "state": "Kentucky", + "zip": "41017" + }, + { + "city": "Columbus", + "state": "Ohio", + "zip": "43214" + }, + { + "city": "Plano", + "state": "Texas", + "zip": "75075" + }, + { + "city": "Long Beach", + "state": "California", + "zip": "90807" + }, + { + "city": "San Diego", + "state": "California", + "zip": "92107" + }, + { + "city": "Toms River", + "state": "New Jersey", + "zip": "8755" + }, + { + "city": "Lansing", + "state": "Illinois", + "zip": "60438" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60630" + }, + { + "city": "Saint Charles", + "state": "Missouri", + "zip": "63303" + }, + { + "city": "Tallahassee", + "state": "Florida", + "zip": "32304" + }, + { + "city": "Walla Walla", + "state": "Washington", + "zip": "99362" + }, + { + "city": "Cypress", + "state": "Texas", + "zip": "77429" + }, + { + "city": "Denver", + "state": "Colorado", + "zip": "80239" + }, + { + "city": "Hudson", + "state": "Florida", + "zip": "34667" + }, + { + "city": "Memphis", + "state": "Tennessee", + "zip": "38117" + }, + { + "city": "Peachtree Corners", + "state": "Georgia", + "zip": "30092" + }, + { + "city": "Mount Dora", + "state": "Florida", + "zip": "32757" + }, + { + "city": "Flint", + "state": "Michigan", + "zip": "48504" + }, + { + "city": "Menomonie", + "state": "Wisconsin", + "zip": "54751" + }, + { + "city": "Bakersfield", + "state": "California", + "zip": "93314" + }, + { + "city": "Antioch", + "state": "California", + "zip": "94509" + }, + { + "city": "Winter Garden", + "state": "Florida", + "zip": "34787" + }, + { + "city": "Belleville", + "state": "Illinois", + "zip": "62226" + }, + { + "city": "Hopewell", + "state": "Virginia", + "zip": "23860" + }, + { + "city": "Shreveport", + "state": "Louisiana", + "zip": "71106" + }, + { + "city": "Rockwall", + "state": "Texas", + "zip": "75032" + }, + { + "city": "Pleasant Grove", + "state": "Utah", + "zip": "84062" + }, + { + "city": "Irvine", + "state": "California", + "zip": "92612" + }, + { + "city": "Ridgewood", + "state": "New York", + "zip": "11385" + }, + { + "city": "Cape Coral", + "state": "Florida", + "zip": "33909" + }, + { + "city": "Tuscaloosa", + "state": "Alabama", + "zip": "35405" + }, + { + "city": "Harlingen", + "state": "Texas", + "zip": "78550" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11224" + }, + { + "city": "Jacksonville", + "state": "Florida", + "zip": "32221" + }, + { + "city": "Pembroke Pines", + "state": "Florida", + "zip": "33028" + }, + { + "city": "Florence", + "state": "Alabama", + "zip": "35630" + }, + { + "city": "Terre Haute", + "state": "Indiana", + "zip": "47802" + }, + { + "city": "Mesquite", + "state": "Texas", + "zip": "75181" + }, + { + "city": "Sandusky", + "state": "Ohio", + "zip": "44870" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60608" + }, + { + "city": "Reno", + "state": "Nevada", + "zip": "89521" + }, + { + "city": "Berkeley", + "state": "California", + "zip": "94704" + }, + { + "city": "Columbus", + "state": "Georgia", + "zip": "31904" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89183" + }, + { + "city": "Lakeside", + "state": "California", + "zip": "92040" + }, + { + "city": "Santa Barbara", + "state": "California", + "zip": "93105" + }, + { + "city": "Fresno", + "state": "California", + "zip": "93725" + }, + { + "city": "Merced", + "state": "California", + "zip": "95348" + }, + { + "city": "Portage", + "state": "Indiana", + "zip": "46368" + }, + { + "city": "Pasadena", + "state": "California", + "zip": "91103" + }, + { + "city": "Santa Ana", + "state": "California", + "zip": "92704" + }, + { + "city": "Grand Rapids", + "state": "Michigan", + "zip": "49508" + }, + { + "city": "Menasha", + "state": "Wisconsin", + "zip": "54952" + }, + { + "city": "Sioux Falls", + "state": "South Dakota", + "zip": "57104" + }, + { + "city": "Morovis", + "state": "Puerto Rico", + "zip": "687" + }, + { + "city": "Hopewell Junction", + "state": "New York", + "zip": "12533" + }, + { + "city": "Aurora", + "state": "Illinois", + "zip": "60505" + }, + { + "city": "Kansas City", + "state": "Kansas", + "zip": "66102" + }, + { + "city": "Jacksonville", + "state": "Arkansas", + "zip": "72076" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77076" + }, + { + "city": "Tomball", + "state": "Texas", + "zip": "77377" + }, + { + "city": "Eagle", + "state": "Idaho", + "zip": "83616" + }, + { + "city": "San Luis Obispo", + "state": "California", + "zip": "93405" + }, + { + "city": "Oakland", + "state": "California", + "zip": "94611" + }, + { + "city": "Charlotte", + "state": "North Carolina", + "zip": "28217" + }, + { + "city": "Raeford", + "state": "North Carolina", + "zip": "28376" + }, + { + "city": "Tampa", + "state": "Florida", + "zip": "33614" + }, + { + "city": "Lebanon", + "state": "Tennessee", + "zip": "37087" + }, + { + "city": "Sevierville", + "state": "Tennessee", + "zip": "37876" + }, + { + "city": "Cleveland", + "state": "Ohio", + "zip": "44111" + }, + { + "city": "Cincinnati", + "state": "Ohio", + "zip": "45230" + }, + { + "city": "Evansville", + "state": "Indiana", + "zip": "47712" + }, + { + "city": "Zionsville", + "state": "Indiana", + "zip": "46077" + }, + { + "city": "Indianapolis", + "state": "Indiana", + "zip": "46227" + }, + { + "city": "Bixby", + "state": "Oklahoma", + "zip": "74008" + }, + { + "city": "Haltom City", + "state": "Texas", + "zip": "76117" + }, + { + "city": "Bel Air", + "state": "Maryland", + "zip": "21014" + }, + { + "city": "Richmond", + "state": "Virginia", + "zip": "23227" + }, + { + "city": "Fort Mill", + "state": "South Carolina", + "zip": "29708" + }, + { + "city": "Sandy", + "state": "Utah", + "zip": "84070" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11217" + }, + { + "city": "Buffalo", + "state": "New York", + "zip": "14225" + }, + { + "city": "Manassas", + "state": "Virginia", + "zip": "20112" + }, + { + "city": "Bakersfield", + "state": "California", + "zip": "93304" + }, + { + "city": "Encino", + "state": "California", + "zip": "91316" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60620" + }, + { + "city": "Omaha", + "state": "Nebraska", + "zip": "68116" + }, + { + "city": "Houma", + "state": "Louisiana", + "zip": "70363" + }, + { + "city": "Pueblo", + "state": "Colorado", + "zip": "81005" + }, + { + "city": "Kingman", + "state": "Arizona", + "zip": "86401" + }, + { + "city": "Jacksonville", + "state": "Florida", + "zip": "32211" + }, + { + "city": "Waukesha", + "state": "Wisconsin", + "zip": "53188" + }, + { + "city": "Downingtown", + "state": "Pennsylvania", + "zip": "19335" + }, + { + "city": "Greenville", + "state": "South Carolina", + "zip": "29605" + }, + { + "city": "Atlanta", + "state": "Georgia", + "zip": "30318" + }, + { + "city": "Carpentersville", + "state": "Illinois", + "zip": "60110" + }, + { + "city": "Saint Petersburg", + "state": "Florida", + "zip": "33703" + }, + { + "city": "Athens", + "state": "Alabama", + "zip": "35613" + }, + { + "city": "Wyoming", + "state": "Michigan", + "zip": "49509" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78230" + }, + { + "city": "Denver", + "state": "Colorado", + "zip": "80214" + }, + { + "city": "Eagle Mountain", + "state": "Utah", + "zip": "84005" + }, + { + "city": "Riverside", + "state": "California", + "zip": "92504" + }, + { + "city": "San Jose", + "state": "California", + "zip": "95128" + }, + { + "city": "Gresham", + "state": "Oregon", + "zip": "97030" + }, + { + "city": "Seattle", + "state": "Washington", + "zip": "98155" + }, + { + "city": "Lexington", + "state": "Kentucky", + "zip": "40502" + }, + { + "city": "East Lansing", + "state": "Michigan", + "zip": "48823" + }, + { + "city": "Clinton", + "state": "Iowa", + "zip": "52732" + }, + { + "city": "Fond Du Lac", + "state": "Wisconsin", + "zip": "54935" + }, + { + "city": "Hartford", + "state": "Connecticut", + "zip": "6106" + }, + { + "city": "Birmingham", + "state": "Alabama", + "zip": "35216" + }, + { + "city": "Saint Paul", + "state": "Minnesota", + "zip": "55129" + }, + { + "city": "Highland Park", + "state": "Illinois", + "zip": "60035" + }, + { + "city": "Buda", + "state": "Texas", + "zip": "78610" + }, + { + "city": "Carolina", + "state": "Puerto Rico", + "zip": "983" + }, + { + "city": "Merrimack", + "state": "New Hampshire", + "zip": "3054" + }, + { + "city": "Reading", + "state": "Pennsylvania", + "zip": "19601" + }, + { + "city": "Milpitas", + "state": "California", + "zip": "95035" + }, + { + "city": "Sacramento", + "state": "California", + "zip": "95826" + }, + { + "city": "Kent", + "state": "Washington", + "zip": "98031" + }, + { + "city": "Shepherdsville", + "state": "Kentucky", + "zip": "40165" + }, + { + "city": "Las Cruces", + "state": "New Mexico", + "zip": "88012" + }, + { + "city": "Moca", + "state": "Puerto Rico", + "zip": "676" + }, + { + "city": "Brighton", + "state": "Massachusetts", + "zip": "2135" + }, + { + "city": "Lumberton", + "state": "North Carolina", + "zip": "28358" + }, + { + "city": "Odessa", + "state": "Texas", + "zip": "79762" + }, + { + "city": "Manhattan", + "state": "Kansas", + "zip": "66502" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77077" + }, + { + "city": "Detroit", + "state": "Michigan", + "zip": "48205" + }, + { + "city": "Atlantic City", + "state": "New Jersey", + "zip": "8401" + }, + { + "city": "Far Rockaway", + "state": "New York", + "zip": "11691" + }, + { + "city": "Saint Louis", + "state": "Missouri", + "zip": "63122" + }, + { + "city": "Baton Rouge", + "state": "Louisiana", + "zip": "70808" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90038" + }, + { + "city": "Gresham", + "state": "Oregon", + "zip": "97080" + }, + { + "city": "Portland", + "state": "Oregon", + "zip": "97213" + }, + { + "city": "Toa Alta", + "state": "Puerto Rico", + "zip": "953" + }, + { + "city": "Brockton", + "state": "Massachusetts", + "zip": "2302" + }, + { + "city": "Davenport", + "state": "Florida", + "zip": "33837" + }, + { + "city": "Indianapolis", + "state": "Indiana", + "zip": "46241" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78232" + }, + { + "city": "Seattle", + "state": "Washington", + "zip": "98112" + }, + { + "city": "Virginia Beach", + "state": "Virginia", + "zip": "23456" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19145" + }, + { + "city": "Statesville", + "state": "North Carolina", + "zip": "28625" + }, + { + "city": "Orlando", + "state": "Florida", + "zip": "32824" + }, + { + "city": "Troy", + "state": "New York", + "zip": "12180" + }, + { + "city": "Newark", + "state": "Delaware", + "zip": "19711" + }, + { + "city": "West Palm Beach", + "state": "Florida", + "zip": "33417" + }, + { + "city": "Bowling Green", + "state": "Kentucky", + "zip": "42101" + }, + { + "city": "Freehold", + "state": "New Jersey", + "zip": "7728" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77093" + }, + { + "city": "Laredo", + "state": "Texas", + "zip": "78043" + }, + { + "city": "Cibolo", + "state": "Texas", + "zip": "78108" + }, + { + "city": "Cedar Park", + "state": "Texas", + "zip": "78613" + }, + { + "city": "Brea", + "state": "California", + "zip": "92821" + }, + { + "city": "South Windsor", + "state": "Connecticut", + "zip": "6074" + }, + { + "city": "Newark", + "state": "Delaware", + "zip": "19713" + }, + { + "city": "Portsmouth", + "state": "Virginia", + "zip": "23703" + }, + { + "city": "New Orleans", + "state": "Louisiana", + "zip": "70122" + }, + { + "city": "Amityville", + "state": "New York", + "zip": "11701" + }, + { + "city": "Defiance", + "state": "Ohio", + "zip": "43512" + }, + { + "city": "Orem", + "state": "Utah", + "zip": "84057" + }, + { + "city": "San Marcos", + "state": "California", + "zip": "92069" + }, + { + "city": "Rogers", + "state": "Arkansas", + "zip": "72758" + }, + { + "city": "Porterville", + "state": "California", + "zip": "93257" + }, + { + "city": "Columbus", + "state": "Ohio", + "zip": "43221" + }, + { + "city": "North Hollywood", + "state": "California", + "zip": "91601" + }, + { + "city": "Coachella", + "state": "California", + "zip": "92236" + }, + { + "city": "Dickson", + "state": "Tennessee", + "zip": "37055" + }, + { + "city": "New Albany", + "state": "Ohio", + "zip": "43054" + }, + { + "city": "Toledo", + "state": "Ohio", + "zip": "43612" + }, + { + "city": "Decatur", + "state": "Illinois", + "zip": "62526" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60626" + }, + { + "city": "Moreno Valley", + "state": "California", + "zip": "92555" + }, + { + "city": "Jacksonville", + "state": "Florida", + "zip": "32207" + }, + { + "city": "Hialeah", + "state": "Florida", + "zip": "33015" + }, + { + "city": "Kenosha", + "state": "Wisconsin", + "zip": "53144" + }, + { + "city": "Saint Paul", + "state": "Minnesota", + "zip": "55113" + }, + { + "city": "Owasso", + "state": "Oklahoma", + "zip": "74055" + }, + { + "city": "Dover", + "state": "Delaware", + "zip": "19904" + }, + { + "city": "Fairfax", + "state": "Virginia", + "zip": "22033" + }, + { + "city": "Pensacola", + "state": "Florida", + "zip": "32507" + }, + { + "city": "West Chester", + "state": "Ohio", + "zip": "45069" + }, + { + "city": "Fairmont", + "state": "West Virginia", + "zip": "26554" + }, + { + "city": "Tracy", + "state": "California", + "zip": "95377" + }, + { + "city": "Woodland Hills", + "state": "California", + "zip": "91367" + }, + { + "city": "Ballwin", + "state": "Missouri", + "zip": "63011" + }, + { + "city": "Tallahassee", + "state": "Florida", + "zip": "32312" + }, + { + "city": "Louisville", + "state": "Kentucky", + "zip": "40272" + }, + { + "city": "Bay City", + "state": "Michigan", + "zip": "48706" + }, + { + "city": "Chula Vista", + "state": "California", + "zip": "91911" + }, + { + "city": "Saint Augustine", + "state": "Florida", + "zip": "32086" + }, + { + "city": "West Palm Beach", + "state": "Florida", + "zip": "33415" + }, + { + "city": "North Hollywood", + "state": "California", + "zip": "91605" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60647" + }, + { + "city": "Washington", + "state": "District of Columbia", + "zip": "20009" + }, + { + "city": "Macon", + "state": "Georgia", + "zip": "31210" + }, + { + "city": "Knoxville", + "state": "Tennessee", + "zip": "37918" + }, + { + "city": "Fremont", + "state": "Ohio", + "zip": "43420" + }, + { + "city": "Cleveland", + "state": "Ohio", + "zip": "44135" + }, + { + "city": "Tyler", + "state": "Texas", + "zip": "75703" + }, + { + "city": "College Station", + "state": "Texas", + "zip": "77845" + }, + { + "city": "Roy", + "state": "Utah", + "zip": "84067" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90006" + }, + { + "city": "Lancaster", + "state": "California", + "zip": "93536" + }, + { + "city": "Modesto", + "state": "California", + "zip": "95355" + }, + { + "city": "West Sacramento", + "state": "California", + "zip": "95691" + }, + { + "city": "Wilmington", + "state": "North Carolina", + "zip": "28412" + }, + { + "city": "Jefferson", + "state": "Georgia", + "zip": "30549" + }, + { + "city": "Perris", + "state": "California", + "zip": "92570" + }, + { + "city": "Castro Valley", + "state": "California", + "zip": "94546" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77042" + }, + { + "city": "Daytona Beach", + "state": "Florida", + "zip": "32114" + }, + { + "city": "Fort Lauderdale", + "state": "Florida", + "zip": "33325" + }, + { + "city": "Canal Winchester", + "state": "Ohio", + "zip": "43110" + }, + { + "city": "Scottsdale", + "state": "Arizona", + "zip": "85258" + }, + { + "city": "Rockford", + "state": "Illinois", + "zip": "61107" + }, + { + "city": "Plano", + "state": "Texas", + "zip": "75093" + }, + { + "city": "San Angelo", + "state": "Texas", + "zip": "76904" + }, + { + "city": "Victoria", + "state": "Texas", + "zip": "77901" + }, + { + "city": "Statesville", + "state": "North Carolina", + "zip": "28677" + }, + { + "city": "Utica", + "state": "Michigan", + "zip": "48316" + }, + { + "city": "Hastings", + "state": "Nebraska", + "zip": "68901" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85006" + }, + { + "city": "Tucson", + "state": "Arizona", + "zip": "85745" + }, + { + "city": "Rancho Cucamonga", + "state": "California", + "zip": "91737" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89147" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19124" + }, + { + "city": "Harrisonburg", + "state": "Virginia", + "zip": "22801" + }, + { + "city": "Sioux Falls", + "state": "South Dakota", + "zip": "57106" + }, + { + "city": "Orange", + "state": "California", + "zip": "92869" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85015" + }, + { + "city": "Mesa", + "state": "Arizona", + "zip": "85206" + }, + { + "city": "Irvine", + "state": "California", + "zip": "92614" + }, + { + "city": "Melrose Park", + "state": "Illinois", + "zip": "60160" + }, + { + "city": "Humble", + "state": "Texas", + "zip": "77338" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85029" + }, + { + "city": "Flushing", + "state": "New York", + "zip": "11358" + }, + { + "city": "Grand Rapids", + "state": "Michigan", + "zip": "49507" + }, + { + "city": "Riverside", + "state": "New Jersey", + "zip": "8075" + }, + { + "city": "Edison", + "state": "New Jersey", + "zip": "8817" + }, + { + "city": "San Francisco", + "state": "California", + "zip": "94116" + }, + { + "city": "Santa Clara", + "state": "California", + "zip": "95051" + }, + { + "city": "Bronx", + "state": "New York", + "zip": "10466" + }, + { + "city": "Ozone Park", + "state": "New York", + "zip": "11417" + }, + { + "city": "Springfield", + "state": "Ohio", + "zip": "45503" + }, + { + "city": "Tucson", + "state": "Arizona", + "zip": "85742" + }, + { + "city": "Clovis", + "state": "New Mexico", + "zip": "88101" + }, + { + "city": "Agoura Hills", + "state": "California", + "zip": "91301" + }, + { + "city": "Canyon Country", + "state": "California", + "zip": "91351" + }, + { + "city": "Chino Hills", + "state": "California", + "zip": "91709" + }, + { + "city": "San Juan", + "state": "Texas", + "zip": "78589" + }, + { + "city": "Torrance", + "state": "California", + "zip": "90504" + }, + { + "city": "Mansfield", + "state": "Texas", + "zip": "76063" + }, + { + "city": "Woodbury", + "state": "New Jersey", + "zip": "8096" + }, + { + "city": "West Chester", + "state": "Pennsylvania", + "zip": "19380" + }, + { + "city": "Cookeville", + "state": "Tennessee", + "zip": "38506" + }, + { + "city": "Lakewood", + "state": "New Jersey", + "zip": "8701" + }, + { + "city": "Riverhead", + "state": "New York", + "zip": "11901" + }, + { + "city": "Wheeling", + "state": "West Virginia", + "zip": "26003" + }, + { + "city": "Mooresville", + "state": "North Carolina", + "zip": "28117" + }, + { + "city": "Alpharetta", + "state": "Georgia", + "zip": "30004" + }, + { + "city": "Annandale", + "state": "Virginia", + "zip": "22003" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33179" + }, + { + "city": "Columbus", + "state": "Ohio", + "zip": "43232" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33165" + }, + { + "city": "Lewis Center", + "state": "Ohio", + "zip": "43035" + }, + { + "city": "Beltsville", + "state": "Maryland", + "zip": "20705" + }, + { + "city": "Grand Island", + "state": "Nebraska", + "zip": "68803" + }, + { + "city": "Watertown", + "state": "Massachusetts", + "zip": "2472" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78213" + }, + { + "city": "Tooele", + "state": "Utah", + "zip": "84074" + }, + { + "city": "Altadena", + "state": "California", + "zip": "91001" + }, + { + "city": "Cedar Falls", + "state": "Iowa", + "zip": "50613" + }, + { + "city": "La Crosse", + "state": "Wisconsin", + "zip": "54601" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78218" + }, + { + "city": "Albuquerque", + "state": "New Mexico", + "zip": "87114" + }, + { + "city": "San Diego", + "state": "California", + "zip": "92114" + }, + { + "city": "Rocklin", + "state": "California", + "zip": "95765" + }, + { + "city": "Hampton", + "state": "Virginia", + "zip": "23666" + }, + { + "city": "Saint Louis", + "state": "Missouri", + "zip": "63123" + }, + { + "city": "Cliffside Park", + "state": "New Jersey", + "zip": "7010" + }, + { + "city": "Spartanburg", + "state": "South Carolina", + "zip": "29303" + }, + { + "city": "Mount Vernon", + "state": "Ohio", + "zip": "43050" + }, + { + "city": "Graham", + "state": "North Carolina", + "zip": "27253" + }, + { + "city": "Athens", + "state": "Georgia", + "zip": "30606" + }, + { + "city": "Ozark", + "state": "Missouri", + "zip": "65721" + }, + { + "city": "New Orleans", + "state": "Louisiana", + "zip": "70131" + }, + { + "city": "Cape Coral", + "state": "Florida", + "zip": "33990" + }, + { + "city": "Johnson City", + "state": "Tennessee", + "zip": "37604" + }, + { + "city": "Peoria", + "state": "Arizona", + "zip": "85345" + }, + { + "city": "South Pasadena", + "state": "California", + "zip": "91030" + }, + { + "city": "Bakersfield", + "state": "California", + "zip": "93305" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33147" + }, + { + "city": "Corozal", + "state": "Puerto Rico", + "zip": "783" + }, + { + "city": "Bridgeport", + "state": "Connecticut", + "zip": "6604" + }, + { + "city": "Laurel", + "state": "Maryland", + "zip": "20707" + }, + { + "city": "Bakersfield", + "state": "California", + "zip": "93307" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90003" + }, + { + "city": "Barstow", + "state": "California", + "zip": "92311" + }, + { + "city": "Henrico", + "state": "Virginia", + "zip": "23238" + }, + { + "city": "Royse City", + "state": "Texas", + "zip": "75189" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77079" + }, + { + "city": "Rio Grande", + "state": "Puerto Rico", + "zip": "745" + }, + { + "city": "Stoughton", + "state": "Massachusetts", + "zip": "2072" + }, + { + "city": "Bridgeton", + "state": "New Jersey", + "zip": "8302" + }, + { + "city": "Perth Amboy", + "state": "New Jersey", + "zip": "8861" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11209" + }, + { + "city": "Lebanon", + "state": "Pennsylvania", + "zip": "17042" + }, + { + "city": "Salisbury", + "state": "Maryland", + "zip": "21804" + }, + { + "city": "Louisville", + "state": "Kentucky", + "zip": "40229" + }, + { + "city": "Racine", + "state": "Wisconsin", + "zip": "53405" + }, + { + "city": "Burbank", + "state": "Illinois", + "zip": "60459" + }, + { + "city": "Fountain Hills", + "state": "Arizona", + "zip": "85268" + }, + { + "city": "Palm Desert", + "state": "California", + "zip": "92260" + }, + { + "city": "Kahului", + "state": "Hawaii", + "zip": "96732" + }, + { + "city": "Portland", + "state": "Oregon", + "zip": "97225" + }, + { + "city": "Huntsville", + "state": "Alabama", + "zip": "35810" + }, + { + "city": "Austin", + "state": "Texas", + "zip": "78705" + }, + { + "city": "Delaware", + "state": "Ohio", + "zip": "43015" + }, + { + "city": "Farmington", + "state": "Michigan", + "zip": "48336" + }, + { + "city": "Hackensack", + "state": "New Jersey", + "zip": "7601" + }, + { + "city": "Nashville", + "state": "Tennessee", + "zip": "37206" + }, + { + "city": "Knoxville", + "state": "Tennessee", + "zip": "37920" + }, + { + "city": "Detroit", + "state": "Michigan", + "zip": "48224" + }, + { + "city": "Nixa", + "state": "Missouri", + "zip": "65714" + }, + { + "city": "Long Beach", + "state": "California", + "zip": "90804" + }, + { + "city": "San Jose", + "state": "California", + "zip": "95148" + }, + { + "city": "Marysville", + "state": "California", + "zip": "95901" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11203" + }, + { + "city": "Guaynabo", + "state": "Puerto Rico", + "zip": "971" + }, + { + "city": "Pasadena", + "state": "Texas", + "zip": "77506" + }, + { + "city": "Kingsville", + "state": "Texas", + "zip": "78363" + }, + { + "city": "Corona", + "state": "California", + "zip": "92883" + }, + { + "city": "Lebanon", + "state": "Pennsylvania", + "zip": "17046" + }, + { + "city": "Millsboro", + "state": "Delaware", + "zip": "19966" + }, + { + "city": "Orangeburg", + "state": "South Carolina", + "zip": "29115" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78244" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78254" + }, + { + "city": "Amarillo", + "state": "Texas", + "zip": "79109" + }, + { + "city": "Mesa", + "state": "Arizona", + "zip": "85202" + }, + { + "city": "Vicksburg", + "state": "Mississippi", + "zip": "39180" + }, + { + "city": "Redford", + "state": "Michigan", + "zip": "48239" + }, + { + "city": "Long Beach", + "state": "California", + "zip": "90813" + }, + { + "city": "Manteca", + "state": "California", + "zip": "95337" + }, + { + "city": "Victoria", + "state": "Texas", + "zip": "77904" + }, + { + "city": "Garden Grove", + "state": "California", + "zip": "92843" + }, + { + "city": "Liberty", + "state": "Missouri", + "zip": "64068" + }, + { + "city": "Baytown", + "state": "Texas", + "zip": "77520" + }, + { + "city": "Mcdonough", + "state": "Georgia", + "zip": "30252" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33162" + }, + { + "city": "Indianapolis", + "state": "Indiana", + "zip": "46205" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60632" + }, + { + "city": "Casa Grande", + "state": "Arizona", + "zip": "85122" + }, + { + "city": "Palm Coast", + "state": "Florida", + "zip": "32137" + }, + { + "city": "San Diego", + "state": "California", + "zip": "92117" + }, + { + "city": "San Francisco", + "state": "California", + "zip": "94124" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19142" + }, + { + "city": "Lexington", + "state": "Kentucky", + "zip": "40509" + }, + { + "city": "Rochester", + "state": "Minnesota", + "zip": "55904" + }, + { + "city": "Corona", + "state": "California", + "zip": "92880" + }, + { + "city": "Wilsonville", + "state": "Oregon", + "zip": "97070" + }, + { + "city": "Asbury Park", + "state": "New Jersey", + "zip": "7712" + }, + { + "city": "Ozone Park", + "state": "New York", + "zip": "11416" + }, + { + "city": "Pittsburgh", + "state": "Pennsylvania", + "zip": "15235" + }, + { + "city": "Thomasville", + "state": "North Carolina", + "zip": "27360" + }, + { + "city": "Palm Coast", + "state": "Florida", + "zip": "32164" + }, + { + "city": "Hamilton", + "state": "Ohio", + "zip": "45013" + }, + { + "city": "Milford", + "state": "Ohio", + "zip": "45150" + }, + { + "city": "Fishers", + "state": "Indiana", + "zip": "46037" + }, + { + "city": "Greenwood", + "state": "Indiana", + "zip": "46143" + }, + { + "city": "Mason City", + "state": "Iowa", + "zip": "50401" + }, + { + "city": "Webster", + "state": "Texas", + "zip": "77598" + }, + { + "city": "El Mirage", + "state": "Arizona", + "zip": "85335" + }, + { + "city": "Manhattan Beach", + "state": "California", + "zip": "90266" + }, + { + "city": "Portland", + "state": "Oregon", + "zip": "97233" + }, + { + "city": "Keene", + "state": "New Hampshire", + "zip": "3431" + }, + { + "city": "Bangor", + "state": "Maine", + "zip": "4401" + }, + { + "city": "Alexandria", + "state": "Virginia", + "zip": "22304" + }, + { + "city": "Louisburg", + "state": "North Carolina", + "zip": "27549" + }, + { + "city": "Torrance", + "state": "California", + "zip": "90503" + }, + { + "city": "Altamonte Springs", + "state": "Florida", + "zip": "32714" + }, + { + "city": "Dayton", + "state": "Ohio", + "zip": "45459" + }, + { + "city": "Brentwood", + "state": "New York", + "zip": "11717" + }, + { + "city": "Allentown", + "state": "Pennsylvania", + "zip": "18102" + }, + { + "city": "Royersford", + "state": "Pennsylvania", + "zip": "19468" + }, + { + "city": "Lynchburg", + "state": "Virginia", + "zip": "24501" + }, + { + "city": "Detroit", + "state": "Michigan", + "zip": "48235" + }, + { + "city": "San Diego", + "state": "California", + "zip": "92111" + }, + { + "city": "Yucaipa", + "state": "California", + "zip": "92399" + }, + { + "city": "Hartford", + "state": "Connecticut", + "zip": "6114" + }, + { + "city": "New London", + "state": "Connecticut", + "zip": "6320" + }, + { + "city": "Flemington", + "state": "New Jersey", + "zip": "8822" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19151" + }, + { + "city": "Raleigh", + "state": "North Carolina", + "zip": "27616" + }, + { + "city": "Bakersfield", + "state": "California", + "zip": "93306" + }, + { + "city": "Fremont", + "state": "California", + "zip": "94536" + }, + { + "city": "Atlanta", + "state": "Georgia", + "zip": "30344" + }, + { + "city": "Flowery Branch", + "state": "Georgia", + "zip": "30542" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77017" + }, + { + "city": "Anoka", + "state": "Minnesota", + "zip": "55303" + }, + { + "city": "Alexandria", + "state": "Minnesota", + "zip": "56308" + }, + { + "city": "Cary", + "state": "North Carolina", + "zip": "27511" + }, + { + "city": "Norcross", + "state": "Georgia", + "zip": "30093" + }, + { + "city": "Stow", + "state": "Ohio", + "zip": "44224" + }, + { + "city": "Portage", + "state": "Michigan", + "zip": "49024" + }, + { + "city": "Zion", + "state": "Illinois", + "zip": "60099" + }, + { + "city": "Saint Louis", + "state": "Missouri", + "zip": "63146" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85051" + }, + { + "city": "Rapid City", + "state": "South Dakota", + "zip": "57702" + }, + { + "city": "Irving", + "state": "Texas", + "zip": "75062" + }, + { + "city": "San Rafael", + "state": "California", + "zip": "94903" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33177" + }, + { + "city": "Crown Point", + "state": "Indiana", + "zip": "46307" + }, + { + "city": "Sarasota", + "state": "Florida", + "zip": "34232" + }, + { + "city": "La Verne", + "state": "California", + "zip": "91750" + }, + { + "city": "San Diego", + "state": "California", + "zip": "92122" + }, + { + "city": "Seattle", + "state": "Washington", + "zip": "98107" + }, + { + "city": "Somerset", + "state": "New Jersey", + "zip": "8873" + }, + { + "city": "Charleston", + "state": "South Carolina", + "zip": "29406" + }, + { + "city": "Lawrenceville", + "state": "Georgia", + "zip": "30043" + }, + { + "city": "Canton", + "state": "Georgia", + "zip": "30114" + }, + { + "city": "Des Plaines", + "state": "Illinois", + "zip": "60018" + }, + { + "city": "Winter Park", + "state": "Florida", + "zip": "32789" + }, + { + "city": "Alabaster", + "state": "Alabama", + "zip": "35007" + }, + { + "city": "Manchester", + "state": "Tennessee", + "zip": "37355" + }, + { + "city": "Minneapolis", + "state": "Minnesota", + "zip": "55418" + }, + { + "city": "North Las Vegas", + "state": "Nevada", + "zip": "89081" + }, + { + "city": "Danbury", + "state": "Connecticut", + "zip": "6810" + }, + { + "city": "New York", + "state": "New York", + "zip": "10027" + }, + { + "city": "Clifton Park", + "state": "New York", + "zip": "12065" + }, + { + "city": "Fayetteville", + "state": "North Carolina", + "zip": "28314" + }, + { + "city": "Atlanta", + "state": "Georgia", + "zip": "30319" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33143" + }, + { + "city": "Ocala", + "state": "Florida", + "zip": "34471" + }, + { + "city": "Wentzville", + "state": "Missouri", + "zip": "63385" + }, + { + "city": "Omaha", + "state": "Nebraska", + "zip": "68135" + }, + { + "city": "Chapel Hill", + "state": "North Carolina", + "zip": "27517" + }, + { + "city": "Opa Locka", + "state": "Florida", + "zip": "33055" + }, + { + "city": "Cleveland", + "state": "Tennessee", + "zip": "37311" + }, + { + "city": "Corinth", + "state": "Mississippi", + "zip": "38834" + }, + { + "city": "Manitowoc", + "state": "Wisconsin", + "zip": "54220" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11207" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60607" + }, + { + "city": "Mays Landing", + "state": "New Jersey", + "zip": "8330" + }, + { + "city": "Bayside", + "state": "New York", + "zip": "11361" + }, + { + "city": "Winter Springs", + "state": "Florida", + "zip": "32708" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33157" + }, + { + "city": "Decatur", + "state": "Alabama", + "zip": "35603" + }, + { + "city": "Eastlake", + "state": "Ohio", + "zip": "44095" + }, + { + "city": "Mckinney", + "state": "Texas", + "zip": "75069" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60646" + }, + { + "city": "Arvada", + "state": "Colorado", + "zip": "80004" + }, + { + "city": "Saratoga Springs", + "state": "Utah", + "zip": "84045" + }, + { + "city": "Beaverton", + "state": "Oregon", + "zip": "97007" + }, + { + "city": "Loveland", + "state": "Colorado", + "zip": "80537" + }, + { + "city": "San Jose", + "state": "California", + "zip": "95111" + }, + { + "city": "Detroit", + "state": "Michigan", + "zip": "48227" + }, + { + "city": "Derry", + "state": "New Hampshire", + "zip": "3038" + }, + { + "city": "Richardson", + "state": "Texas", + "zip": "75080" + }, + { + "city": "Lowell", + "state": "Massachusetts", + "zip": "1851" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77044" + }, + { + "city": "Cincinnati", + "state": "Ohio", + "zip": "45211" + }, + { + "city": "Denver", + "state": "Colorado", + "zip": "80221" + }, + { + "city": "Lawrence", + "state": "Massachusetts", + "zip": "1843" + }, + { + "city": "Williamsburg", + "state": "Virginia", + "zip": "23188" + }, + { + "city": "Milton", + "state": "Florida", + "zip": "32570" + }, + { + "city": "Traverse City", + "state": "Michigan", + "zip": "49684" + }, + { + "city": "Brick", + "state": "New Jersey", + "zip": "8724" + }, + { + "city": "Apopka", + "state": "Florida", + "zip": "32703" + }, + { + "city": "Gulfport", + "state": "Mississippi", + "zip": "39503" + }, + { + "city": "Xenia", + "state": "Ohio", + "zip": "45385" + }, + { + "city": "Indianapolis", + "state": "Indiana", + "zip": "46219" + }, + { + "city": "Oakland", + "state": "California", + "zip": "94601" + }, + { + "city": "Prosper", + "state": "Texas", + "zip": "75078" + }, + { + "city": "Yorktown Heights", + "state": "New York", + "zip": "10598" + }, + { + "city": "Burlington", + "state": "North Carolina", + "zip": "27217" + }, + { + "city": "Dearborn Heights", + "state": "Michigan", + "zip": "48127" + }, + { + "city": "San Diego", + "state": "California", + "zip": "92129" + }, + { + "city": "Irvine", + "state": "California", + "zip": "92606" + }, + { + "city": "Hutto", + "state": "Texas", + "zip": "78634" + }, + { + "city": "Covina", + "state": "California", + "zip": "91724" + }, + { + "city": "Orlando", + "state": "Florida", + "zip": "32808" + }, + { + "city": "Westerville", + "state": "Ohio", + "zip": "43082" + }, + { + "city": "Newport News", + "state": "Virginia", + "zip": "23606" + }, + { + "city": "Mission", + "state": "Texas", + "zip": "78572" + }, + { + "city": "Altoona", + "state": "Pennsylvania", + "zip": "16602" + }, + { + "city": "Fayetteville", + "state": "North Carolina", + "zip": "28304" + }, + { + "city": "Bowie", + "state": "Maryland", + "zip": "20721" + }, + { + "city": "Clarksville", + "state": "Tennessee", + "zip": "37040" + }, + { + "city": "Portland", + "state": "Oregon", + "zip": "97224" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33176" + }, + { + "city": "Pataskala", + "state": "Ohio", + "zip": "43062" + }, + { + "city": "Spanaway", + "state": "Washington", + "zip": "98387" + }, + { + "city": "Lakewood", + "state": "Washington", + "zip": "98499" + }, + { + "city": "Falls Church", + "state": "Virginia", + "zip": "22042" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90023" + }, + { + "city": "Fresno", + "state": "California", + "zip": "93706" + }, + { + "city": "Endicott", + "state": "New York", + "zip": "13760" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60610" + }, + { + "city": "Lake Saint Louis", + "state": "Missouri", + "zip": "63367" + }, + { + "city": "Kansas City", + "state": "Kansas", + "zip": "66104" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77071" + }, + { + "city": "Orem", + "state": "Utah", + "zip": "84058" + }, + { + "city": "Scottsdale", + "state": "Arizona", + "zip": "85260" + }, + { + "city": "Sparks", + "state": "Nevada", + "zip": "89436" + }, + { + "city": "Albany", + "state": "New York", + "zip": "12205" + }, + { + "city": "Palm City", + "state": "Florida", + "zip": "34990" + }, + { + "city": "Morgantown", + "state": "West Virginia", + "zip": "26508" + }, + { + "city": "Mankato", + "state": "Minnesota", + "zip": "56001" + }, + { + "city": "Evanston", + "state": "Illinois", + "zip": "60201" + }, + { + "city": "Albuquerque", + "state": "New Mexico", + "zip": "87110" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90012" + }, + { + "city": "Reisterstown", + "state": "Maryland", + "zip": "21136" + }, + { + "city": "Denton", + "state": "Texas", + "zip": "76210" + }, + { + "city": "San Benito", + "state": "Texas", + "zip": "78586" + }, + { + "city": "Boise", + "state": "Idaho", + "zip": "83709" + }, + { + "city": "Victorville", + "state": "California", + "zip": "92392" + }, + { + "city": "Melbourne", + "state": "Florida", + "zip": "32935" + }, + { + "city": "Colorado Springs", + "state": "Colorado", + "zip": "80920" + }, + { + "city": "West Jordan", + "state": "Utah", + "zip": "84081" + }, + { + "city": "Sparks", + "state": "Nevada", + "zip": "89431" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60629" + }, + { + "city": "Boise", + "state": "Idaho", + "zip": "83713" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78214" + }, + { + "city": "Stanton", + "state": "California", + "zip": "90680" + }, + { + "city": "Virginia Beach", + "state": "Virginia", + "zip": "23462" + }, + { + "city": "Montgomery", + "state": "Alabama", + "zip": "36116" + }, + { + "city": "Colonial Heights", + "state": "Virginia", + "zip": "23834" + }, + { + "city": "Warwick", + "state": "Rhode Island", + "zip": "2889" + }, + { + "city": "Ballston Spa", + "state": "New York", + "zip": "12020" + }, + { + "city": "Baltimore", + "state": "Maryland", + "zip": "21216" + }, + { + "city": "Peyton", + "state": "Colorado", + "zip": "80831" + }, + { + "city": "Belleville", + "state": "Illinois", + "zip": "62221" + }, + { + "city": "Camarillo", + "state": "California", + "zip": "93012" + }, + { + "city": "Huntsville", + "state": "Texas", + "zip": "77340" + }, + { + "city": "Honolulu", + "state": "Hawaii", + "zip": "96818" + }, + { + "city": "Vancouver", + "state": "Washington", + "zip": "98662" + }, + { + "city": "Elizabethport", + "state": "New Jersey", + "zip": "7206" + }, + { + "city": "Port Washington", + "state": "New York", + "zip": "11050" + }, + { + "city": "Baltimore", + "state": "Maryland", + "zip": "21239" + }, + { + "city": "Cornelius", + "state": "North Carolina", + "zip": "28031" + }, + { + "city": "Evansville", + "state": "Indiana", + "zip": "47711" + }, + { + "city": "Indianapolis", + "state": "Indiana", + "zip": "46254" + }, + { + "city": "San Pedro", + "state": "California", + "zip": "90731" + }, + { + "city": "Henrico", + "state": "Virginia", + "zip": "23229" + }, + { + "city": "Fort Mill", + "state": "South Carolina", + "zip": "29707" + }, + { + "city": "Lake Stevens", + "state": "Washington", + "zip": "98258" + }, + { + "city": "Placentia", + "state": "California", + "zip": "92870" + }, + { + "city": "Gwynn Oak", + "state": "Maryland", + "zip": "21207" + }, + { + "city": "Juana Diaz", + "state": "Puerto Rico", + "zip": "795" + }, + { + "city": "Boiling Springs", + "state": "South Carolina", + "zip": "29316" + }, + { + "city": "Des Plaines", + "state": "Illinois", + "zip": "60016" + }, + { + "city": "Normal", + "state": "Illinois", + "zip": "61761" + }, + { + "city": "Marrero", + "state": "Louisiana", + "zip": "70072" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90004" + }, + { + "city": "Oswego", + "state": "Illinois", + "zip": "60543" + }, + { + "city": "Redding", + "state": "California", + "zip": "96002" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90047" + }, + { + "city": "Fair Lawn", + "state": "New Jersey", + "zip": "7410" + }, + { + "city": "Dallas", + "state": "Texas", + "zip": "75252" + }, + { + "city": "Caldwell", + "state": "Idaho", + "zip": "83607" + }, + { + "city": "Westfield", + "state": "Indiana", + "zip": "46074" + }, + { + "city": "Euless", + "state": "Texas", + "zip": "76039" + }, + { + "city": "El Paso", + "state": "Texas", + "zip": "79928" + }, + { + "city": "Las Cruces", + "state": "New Mexico", + "zip": "88011" + }, + { + "city": "West Orange", + "state": "New Jersey", + "zip": "7052" + }, + { + "city": "Washington", + "state": "Pennsylvania", + "zip": "15301" + }, + { + "city": "New Caney", + "state": "Texas", + "zip": "77357" + }, + { + "city": "Yorba Linda", + "state": "California", + "zip": "92886" + }, + { + "city": "Las Cruces", + "state": "New Mexico", + "zip": "88005" + }, + { + "city": "West Roxbury", + "state": "Massachusetts", + "zip": "2132" + }, + { + "city": "Charleston", + "state": "South Carolina", + "zip": "29414" + }, + { + "city": "Springfield", + "state": "Virginia", + "zip": "22152" + }, + { + "city": "Woodbridge", + "state": "Virginia", + "zip": "22192" + }, + { + "city": "Corbin", + "state": "Kentucky", + "zip": "40701" + }, + { + "city": "Ambler", + "state": "Pennsylvania", + "zip": "19002" + }, + { + "city": "North Augusta", + "state": "South Carolina", + "zip": "29841" + }, + { + "city": "New Braunfels", + "state": "Texas", + "zip": "78132" + }, + { + "city": "Henderson", + "state": "Nevada", + "zip": "89012" + }, + { + "city": "Centreville", + "state": "Virginia", + "zip": "20121" + }, + { + "city": "Calhoun", + "state": "Georgia", + "zip": "30701" + }, + { + "city": "Toa Baja", + "state": "Puerto Rico", + "zip": "949" + }, + { + "city": "Gloucester", + "state": "Massachusetts", + "zip": "1930" + }, + { + "city": "Gurnee", + "state": "Illinois", + "zip": "60031" + }, + { + "city": "Sherman", + "state": "Texas", + "zip": "75090" + }, + { + "city": "Downey", + "state": "California", + "zip": "90240" + }, + { + "city": "Lake Forest", + "state": "California", + "zip": "92630" + }, + { + "city": "Seattle", + "state": "Washington", + "zip": "98118" + }, + { + "city": "Monroe", + "state": "Washington", + "zip": "98272" + }, + { + "city": "Hialeah", + "state": "Florida", + "zip": "33010" + }, + { + "city": "Saint Petersburg", + "state": "Florida", + "zip": "33705" + }, + { + "city": "Naples", + "state": "Florida", + "zip": "34120" + }, + { + "city": "Wahiawa", + "state": "Hawaii", + "zip": "96786" + }, + { + "city": "Portland", + "state": "Oregon", + "zip": "97266" + }, + { + "city": "Nashville", + "state": "Tennessee", + "zip": "37221" + }, + { + "city": "Charlotte", + "state": "North Carolina", + "zip": "28227" + }, + { + "city": "Wilmington", + "state": "North Carolina", + "zip": "28405" + }, + { + "city": "Arlington", + "state": "Texas", + "zip": "76002" + }, + { + "city": "Houma", + "state": "Louisiana", + "zip": "70364" + }, + { + "city": "Lufkin", + "state": "Texas", + "zip": "75904" + }, + { + "city": "Mcallen", + "state": "Texas", + "zip": "78501" + }, + { + "city": "Wappingers Falls", + "state": "New York", + "zip": "12590" + }, + { + "city": "Fort Mill", + "state": "South Carolina", + "zip": "29715" + }, + { + "city": "Fredericksburg", + "state": "Virginia", + "zip": "22401" + }, + { + "city": "Elizabethton", + "state": "Tennessee", + "zip": "37643" + }, + { + "city": "Malvern", + "state": "Pennsylvania", + "zip": "19355" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11237" + }, + { + "city": "Canandaigua", + "state": "New York", + "zip": "14424" + }, + { + "city": "Bronx", + "state": "New York", + "zip": "10461" + }, + { + "city": "Queens Village", + "state": "New York", + "zip": "11429" + }, + { + "city": "Washington", + "state": "District of Columbia", + "zip": "20016" + }, + { + "city": "Lake Worth", + "state": "Florida", + "zip": "33463" + }, + { + "city": "Woodridge", + "state": "Illinois", + "zip": "60517" + }, + { + "city": "Bridgeport", + "state": "Connecticut", + "zip": "6606" + }, + { + "city": "New York", + "state": "New York", + "zip": "10025" + }, + { + "city": "Media", + "state": "Pennsylvania", + "zip": "19063" + }, + { + "city": "Bend", + "state": "Oregon", + "zip": "97702" + }, + { + "city": "Wisconsin Rapids", + "state": "Wisconsin", + "zip": "54494" + }, + { + "city": "Dalton", + "state": "Georgia", + "zip": "30721" + }, + { + "city": "Midvale", + "state": "Utah", + "zip": "84047" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33186" + }, + { + "city": "Fort Lauderdale", + "state": "Florida", + "zip": "33324" + }, + { + "city": "Tampa", + "state": "Florida", + "zip": "33619" + }, + { + "city": "New York", + "state": "New York", + "zip": "10023" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33155" + }, + { + "city": "Venice", + "state": "Florida", + "zip": "34293" + }, + { + "city": "Muncie", + "state": "Indiana", + "zip": "47302" + }, + { + "city": "Minneapolis", + "state": "Minnesota", + "zip": "55446" + }, + { + "city": "Glendale Heights", + "state": "Illinois", + "zip": "60139" + }, + { + "city": "Kissimmee", + "state": "Florida", + "zip": "34743" + }, + { + "city": "Nashville", + "state": "Tennessee", + "zip": "37209" + }, + { + "city": "Westminster", + "state": "Colorado", + "zip": "80031" + }, + { + "city": "Vancouver", + "state": "Washington", + "zip": "98665" + }, + { + "city": "Tujunga", + "state": "California", + "zip": "91042" + }, + { + "city": "Calumet City", + "state": "Illinois", + "zip": "60409" + }, + { + "city": "Medford", + "state": "Massachusetts", + "zip": "2155" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11236" + }, + { + "city": "Muskegon", + "state": "Michigan", + "zip": "49444" + }, + { + "city": "Arlington", + "state": "Texas", + "zip": "76010" + }, + { + "city": "Syracuse", + "state": "Utah", + "zip": "84075" + }, + { + "city": "Columbia", + "state": "South Carolina", + "zip": "29209" + }, + { + "city": "Upland", + "state": "California", + "zip": "91784" + }, + { + "city": "Springdale", + "state": "Arkansas", + "zip": "72762" + }, + { + "city": "Round Rock", + "state": "Texas", + "zip": "78664" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85027" + }, + { + "city": "Oakland", + "state": "California", + "zip": "94606" + }, + { + "city": "Raleigh", + "state": "North Carolina", + "zip": "27612" + }, + { + "city": "Decatur", + "state": "Georgia", + "zip": "30030" + }, + { + "city": "Melbourne", + "state": "Florida", + "zip": "32940" + }, + { + "city": "South Bend", + "state": "Indiana", + "zip": "46628" + }, + { + "city": "Richmond", + "state": "California", + "zip": "94804" + }, + { + "city": "Portland", + "state": "Oregon", + "zip": "97203" + }, + { + "city": "Vancouver", + "state": "Washington", + "zip": "98683" + }, + { + "city": "Vega Baja", + "state": "Puerto Rico", + "zip": "693" + }, + { + "city": "Nashua", + "state": "New Hampshire", + "zip": "3060" + }, + { + "city": "Egg Harbor Township", + "state": "New Jersey", + "zip": "8234" + }, + { + "city": "Woodhaven", + "state": "New York", + "zip": "11421" + }, + { + "city": "Chambersburg", + "state": "Pennsylvania", + "zip": "17202" + }, + { + "city": "Charlotte", + "state": "North Carolina", + "zip": "28215" + }, + { + "city": "Cidra", + "state": "Puerto Rico", + "zip": "739" + }, + { + "city": "Reidsville", + "state": "North Carolina", + "zip": "27320" + }, + { + "city": "Lansing", + "state": "Michigan", + "zip": "48910" + }, + { + "city": "New Berlin", + "state": "Wisconsin", + "zip": "53151" + }, + { + "city": "Hightstown", + "state": "New Jersey", + "zip": "8520" + }, + { + "city": "Pittsburgh", + "state": "Pennsylvania", + "zip": "15206" + }, + { + "city": "Charlottesville", + "state": "Virginia", + "zip": "22902" + }, + { + "city": "Denver", + "state": "Colorado", + "zip": "80207" + }, + { + "city": "Bountiful", + "state": "Utah", + "zip": "84010" + }, + { + "city": "Sun City West", + "state": "Arizona", + "zip": "85375" + }, + { + "city": "Reno", + "state": "Nevada", + "zip": "89512" + }, + { + "city": "San Francisco", + "state": "California", + "zip": "94114" + }, + { + "city": "Stone Mountain", + "state": "Georgia", + "zip": "30087" + }, + { + "city": "Laguna Hills", + "state": "California", + "zip": "92653" + }, + { + "city": "San Francisco", + "state": "California", + "zip": "94122" + }, + { + "city": "Palm Bay", + "state": "Florida", + "zip": "32909" + }, + { + "city": "Riverview", + "state": "Florida", + "zip": "33569" + }, + { + "city": "Saint Paul", + "state": "Minnesota", + "zip": "55125" + }, + { + "city": "Chester", + "state": "Virginia", + "zip": "23831" + }, + { + "city": "Stuart", + "state": "Florida", + "zip": "34997" + }, + { + "city": "Texarkana", + "state": "Arkansas", + "zip": "71854" + }, + { + "city": "Austin", + "state": "Texas", + "zip": "78758" + }, + { + "city": "Bronx", + "state": "New York", + "zip": "10455" + }, + { + "city": "Tallahassee", + "state": "Florida", + "zip": "32303" + }, + { + "city": "Lansing", + "state": "Michigan", + "zip": "48911" + }, + { + "city": "San Diego", + "state": "California", + "zip": "92115" + }, + { + "city": "North Platte", + "state": "Nebraska", + "zip": "69101" + }, + { + "city": "Del Valle", + "state": "Texas", + "zip": "78617" + }, + { + "city": "Lexington", + "state": "North Carolina", + "zip": "27295" + }, + { + "city": "Columbia", + "state": "South Carolina", + "zip": "29203" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33133" + }, + { + "city": "Fort Collins", + "state": "Colorado", + "zip": "80525" + }, + { + "city": "Anaheim", + "state": "California", + "zip": "92802" + }, + { + "city": "Marysville", + "state": "Washington", + "zip": "98270" + }, + { + "city": "Grovetown", + "state": "Georgia", + "zip": "30813" + }, + { + "city": "Maineville", + "state": "Ohio", + "zip": "45039" + }, + { + "city": "Urbana", + "state": "Illinois", + "zip": "61801" + }, + { + "city": "Bakersfield", + "state": "California", + "zip": "93312" + }, + { + "city": "Savannah", + "state": "Georgia", + "zip": "31419" + }, + { + "city": "Traverse City", + "state": "Michigan", + "zip": "49686" + }, + { + "city": "Tacoma", + "state": "Washington", + "zip": "98466" + }, + { + "city": "Vallejo", + "state": "California", + "zip": "94589" + }, + { + "city": "Lynnwood", + "state": "Washington", + "zip": "98087" + }, + { + "city": "Seattle", + "state": "Washington", + "zip": "98105" + }, + { + "city": "Marysville", + "state": "Ohio", + "zip": "43040" + }, + { + "city": "Riverview", + "state": "Florida", + "zip": "33579" + }, + { + "city": "Summerfield", + "state": "Florida", + "zip": "34491" + }, + { + "city": "Louisville", + "state": "Kentucky", + "zip": "40258" + }, + { + "city": "Grosse Pointe", + "state": "Michigan", + "zip": "48236" + }, + { + "city": "Midland", + "state": "Michigan", + "zip": "48640" + }, + { + "city": "El Cajon", + "state": "California", + "zip": "92021" + }, + { + "city": "Denver", + "state": "Colorado", + "zip": "80210" + }, + { + "city": "Huntsville", + "state": "Alabama", + "zip": "35803" + }, + { + "city": "Canton", + "state": "Michigan", + "zip": "48188" + }, + { + "city": "West Palm Beach", + "state": "Florida", + "zip": "33406" + }, + { + "city": "Rochester", + "state": "New York", + "zip": "14624" + }, + { + "city": "Washington", + "state": "District of Columbia", + "zip": "20007" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11206" + }, + { + "city": "Buffalo", + "state": "New York", + "zip": "14224" + }, + { + "city": "Webster", + "state": "New York", + "zip": "14580" + }, + { + "city": "Springfield", + "state": "Virginia", + "zip": "22153" + }, + { + "city": "North Miami Beach", + "state": "Florida", + "zip": "33160" + }, + { + "city": "Bradenton", + "state": "Florida", + "zip": "34203" + }, + { + "city": "Memphis", + "state": "Tennessee", + "zip": "38135" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78251" + }, + { + "city": "Peoria", + "state": "Arizona", + "zip": "85383" + }, + { + "city": "West Chicago", + "state": "Illinois", + "zip": "60185" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11216" + }, + { + "city": "Saint Louis", + "state": "Missouri", + "zip": "63125" + }, + { + "city": "Searcy", + "state": "Arkansas", + "zip": "72143" + }, + { + "city": "Olympia", + "state": "Washington", + "zip": "98501" + }, + { + "city": "San Angelo", + "state": "Texas", + "zip": "76903" + }, + { + "city": "Little Falls", + "state": "New Jersey", + "zip": "7424" + }, + { + "city": "San Gabriel", + "state": "California", + "zip": "91776" + }, + { + "city": "Redwood City", + "state": "California", + "zip": "94061" + }, + { + "city": "Riverbank", + "state": "California", + "zip": "95367" + }, + { + "city": "Sterling", + "state": "Virginia", + "zip": "20164" + }, + { + "city": "Carrollton", + "state": "Georgia", + "zip": "30116" + }, + { + "city": "Griffin", + "state": "Georgia", + "zip": "30223" + }, + { + "city": "Salinas", + "state": "California", + "zip": "93906" + }, + { + "city": "Manchester Township", + "state": "New Jersey", + "zip": "8759" + }, + { + "city": "Binghamton", + "state": "New York", + "zip": "13905" + }, + { + "city": "Rosedale", + "state": "Maryland", + "zip": "21237" + }, + { + "city": "Dover", + "state": "New Hampshire", + "zip": "3820" + }, + { + "city": "Virginia Beach", + "state": "Virginia", + "zip": "23452" + }, + { + "city": "Greenville", + "state": "North Carolina", + "zip": "27858" + }, + { + "city": "Gilroy", + "state": "California", + "zip": "95020" + }, + { + "city": "Santa Rosa", + "state": "California", + "zip": "95401" + }, + { + "city": "Youngsville", + "state": "Louisiana", + "zip": "70592" + }, + { + "city": "Millington", + "state": "Tennessee", + "zip": "38053" + }, + { + "city": "Stillwater", + "state": "Minnesota", + "zip": "55082" + }, + { + "city": "Kannapolis", + "state": "North Carolina", + "zip": "28081" + }, + { + "city": "Charlotte", + "state": "North Carolina", + "zip": "28210" + }, + { + "city": "Bourbonnais", + "state": "Illinois", + "zip": "60914" + }, + { + "city": "Rockford", + "state": "Illinois", + "zip": "61109" + }, + { + "city": "Rochester", + "state": "Michigan", + "zip": "48306" + }, + { + "city": "Beaufort", + "state": "South Carolina", + "zip": "29906" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89135" + }, + { + "city": "Jacksonville", + "state": "Florida", + "zip": "32224" + }, + { + "city": "Oak Ridge", + "state": "Tennessee", + "zip": "37830" + }, + { + "city": "Fresno", + "state": "California", + "zip": "93704" + }, + { + "city": "Riverside", + "state": "California", + "zip": "92506" + }, + { + "city": "Newark", + "state": "Delaware", + "zip": "19702" + }, + { + "city": "Grove City", + "state": "Ohio", + "zip": "43123" + }, + { + "city": "Minneapolis", + "state": "Minnesota", + "zip": "55407" + }, + { + "city": "Cambridge", + "state": "Massachusetts", + "zip": "2138" + }, + { + "city": "Naperville", + "state": "Illinois", + "zip": "60565" + }, + { + "city": "Marysville", + "state": "Washington", + "zip": "98271" + }, + { + "city": "East Haven", + "state": "Connecticut", + "zip": "6512" + }, + { + "city": "Belleville", + "state": "New Jersey", + "zip": "7109" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90036" + }, + { + "city": "San Bernardino", + "state": "California", + "zip": "92405" + }, + { + "city": "Sylvania", + "state": "Ohio", + "zip": "43560" + }, + { + "city": "New York", + "state": "New York", + "zip": "10022" + }, + { + "city": "East Meadow", + "state": "New York", + "zip": "11554" + }, + { + "city": "Jonesboro", + "state": "Georgia", + "zip": "30236" + }, + { + "city": "Orlando", + "state": "Florida", + "zip": "32832" + }, + { + "city": "Picayune", + "state": "Mississippi", + "zip": "39466" + }, + { + "city": "Osseo", + "state": "Minnesota", + "zip": "55369" + }, + { + "city": "Dublin", + "state": "Georgia", + "zip": "31021" + }, + { + "city": "Gulf Breeze", + "state": "Florida", + "zip": "32563" + }, + { + "city": "Magna", + "state": "Utah", + "zip": "84044" + }, + { + "city": "Randolph", + "state": "Massachusetts", + "zip": "2368" + }, + { + "city": "San Ysidro", + "state": "California", + "zip": "92173" + }, + { + "city": "Everett", + "state": "Washington", + "zip": "98203" + }, + { + "city": "Chambersburg", + "state": "Pennsylvania", + "zip": "17201" + }, + { + "city": "Kingsport", + "state": "Tennessee", + "zip": "37664" + }, + { + "city": "Akron", + "state": "Ohio", + "zip": "44313" + }, + { + "city": "Dayton", + "state": "Ohio", + "zip": "45431" + }, + { + "city": "Culver City", + "state": "California", + "zip": "90230" + }, + { + "city": "Hayward", + "state": "California", + "zip": "94541" + }, + { + "city": "Schaumburg", + "state": "Illinois", + "zip": "60193" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90028" + }, + { + "city": "Belmont", + "state": "California", + "zip": "94002" + }, + { + "city": "Stockton", + "state": "California", + "zip": "95206" + }, + { + "city": "Puyallup", + "state": "Washington", + "zip": "98372" + }, + { + "city": "Winter Haven", + "state": "Florida", + "zip": "33881" + }, + { + "city": "Monroe", + "state": "Louisiana", + "zip": "71202" + }, + { + "city": "Casper", + "state": "Wyoming", + "zip": "82601" + }, + { + "city": "Uniondale", + "state": "New York", + "zip": "11553" + }, + { + "city": "Clayton", + "state": "North Carolina", + "zip": "27527" + }, + { + "city": "The Villages", + "state": "Florida", + "zip": "32162" + }, + { + "city": "Hallandale", + "state": "Florida", + "zip": "33009" + }, + { + "city": "Boynton Beach", + "state": "Florida", + "zip": "33436" + }, + { + "city": "Cincinnati", + "state": "Ohio", + "zip": "45244" + }, + { + "city": "Bronx", + "state": "New York", + "zip": "10452" + }, + { + "city": "Quincy", + "state": "Illinois", + "zip": "62301" + }, + { + "city": "Chico", + "state": "California", + "zip": "95973" + }, + { + "city": "Honolulu", + "state": "Hawaii", + "zip": "96816" + }, + { + "city": "Rochester", + "state": "New York", + "zip": "14623" + }, + { + "city": "Bonney Lake", + "state": "Washington", + "zip": "98391" + }, + { + "city": "Lakeland", + "state": "Florida", + "zip": "33810" + }, + { + "city": "Kokomo", + "state": "Indiana", + "zip": "46901" + }, + { + "city": "Racine", + "state": "Wisconsin", + "zip": "53403" + }, + { + "city": "Ramona", + "state": "California", + "zip": "92065" + }, + { + "city": "Green Bay", + "state": "Wisconsin", + "zip": "54311" + }, + { + "city": "El Paso", + "state": "Texas", + "zip": "79927" + }, + { + "city": "Montgomery", + "state": "Alabama", + "zip": "36109" + }, + { + "city": "North Attleboro", + "state": "Massachusetts", + "zip": "2760" + }, + { + "city": "Henderson", + "state": "Nevada", + "zip": "89002" + }, + { + "city": "Mulberry", + "state": "Florida", + "zip": "33860" + }, + { + "city": "Oakland", + "state": "California", + "zip": "94603" + }, + { + "city": "Lakewood", + "state": "California", + "zip": "90712" + }, + { + "city": "Salem", + "state": "Oregon", + "zip": "97305" + }, + { + "city": "Mesa", + "state": "Arizona", + "zip": "85207" + }, + { + "city": "Tucson", + "state": "Arizona", + "zip": "85704" + }, + { + "city": "Orlando", + "state": "Florida", + "zip": "32817" + }, + { + "city": "Tampa", + "state": "Florida", + "zip": "33610" + }, + { + "city": "Garden City", + "state": "Michigan", + "zip": "48135" + }, + { + "city": "New York", + "state": "New York", + "zip": "10014" + }, + { + "city": "Washington", + "state": "District of Columbia", + "zip": "20003" + }, + { + "city": "Silver Spring", + "state": "Maryland", + "zip": "20903" + }, + { + "city": "Arlington", + "state": "Virginia", + "zip": "22204" + }, + { + "city": "Kansas City", + "state": "Missouri", + "zip": "64138" + }, + { + "city": "Kingwood", + "state": "Texas", + "zip": "77345" + }, + { + "city": "Austin", + "state": "Texas", + "zip": "78727" + }, + { + "city": "Loveland", + "state": "Colorado", + "zip": "80538" + }, + { + "city": "New York", + "state": "New York", + "zip": "10039" + }, + { + "city": "Uniontown", + "state": "Pennsylvania", + "zip": "15401" + }, + { + "city": "Lompoc", + "state": "California", + "zip": "93436" + }, + { + "city": "San Jose", + "state": "California", + "zip": "95112" + }, + { + "city": "Pensacola", + "state": "Florida", + "zip": "32514" + }, + { + "city": "Westlake", + "state": "Ohio", + "zip": "44145" + }, + { + "city": "Lansing", + "state": "Michigan", + "zip": "48906" + }, + { + "city": "Hopkins", + "state": "Minnesota", + "zip": "55343" + }, + { + "city": "New Haven", + "state": "Connecticut", + "zip": "6513" + }, + { + "city": "Easton", + "state": "Pennsylvania", + "zip": "18045" + }, + { + "city": "Richmond", + "state": "Virginia", + "zip": "23224" + }, + { + "city": "Buena Park", + "state": "California", + "zip": "90620" + }, + { + "city": "New Albany", + "state": "Indiana", + "zip": "47150" + }, + { + "city": "Warren", + "state": "Michigan", + "zip": "48091" + }, + { + "city": "Windermere", + "state": "Florida", + "zip": "34786" + }, + { + "city": "Columbus", + "state": "Ohio", + "zip": "43223" + }, + { + "city": "Elyria", + "state": "Ohio", + "zip": "44035" + }, + { + "city": "Cincinnati", + "state": "Ohio", + "zip": "45238" + }, + { + "city": "Rio Rancho", + "state": "New Mexico", + "zip": "87124" + }, + { + "city": "Coventry", + "state": "Rhode Island", + "zip": "2816" + }, + { + "city": "Upper Marlboro", + "state": "Maryland", + "zip": "20772" + }, + { + "city": "Lemon Grove", + "state": "California", + "zip": "91945" + }, + { + "city": "Olney", + "state": "Maryland", + "zip": "20832" + }, + { + "city": "Hillsborough", + "state": "North Carolina", + "zip": "27278" + }, + { + "city": "Jacksonville Beach", + "state": "Florida", + "zip": "32250" + }, + { + "city": "Carbondale", + "state": "Illinois", + "zip": "62901" + }, + { + "city": "Sherwood", + "state": "Arkansas", + "zip": "72120" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77065" + }, + { + "city": "Pocatello", + "state": "Idaho", + "zip": "83201" + }, + { + "city": "Myrtle Beach", + "state": "South Carolina", + "zip": "29579" + }, + { + "city": "Commack", + "state": "New York", + "zip": "11725" + }, + { + "city": "Harrisburg", + "state": "Pennsylvania", + "zip": "17111" + }, + { + "city": "Grand Prairie", + "state": "Texas", + "zip": "75051" + }, + { + "city": "Fort Worth", + "state": "Texas", + "zip": "76131" + }, + { + "city": "Chandler", + "state": "Arizona", + "zip": "85225" + }, + { + "city": "Walnut", + "state": "California", + "zip": "91789" + }, + { + "city": "Honolulu", + "state": "Hawaii", + "zip": "96826" + }, + { + "city": "Oregon City", + "state": "Oregon", + "zip": "97045" + }, + { + "city": "Gilbert", + "state": "Arizona", + "zip": "85234" + }, + { + "city": "Camden", + "state": "New Jersey", + "zip": "8105" + }, + { + "city": "Lexington", + "state": "South Carolina", + "zip": "29072" + }, + { + "city": "Piedmont", + "state": "South Carolina", + "zip": "29673" + }, + { + "city": "Plymouth", + "state": "Massachusetts", + "zip": "2360" + }, + { + "city": "Manchester", + "state": "New Hampshire", + "zip": "3104" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11201" + }, + { + "city": "Plattsburgh", + "state": "New York", + "zip": "12901" + }, + { + "city": "Bristow", + "state": "Virginia", + "zip": "20136" + }, + { + "city": "Austell", + "state": "Georgia", + "zip": "30168" + }, + { + "city": "Santa Barbara", + "state": "California", + "zip": "93101" + }, + { + "city": "Portland", + "state": "Oregon", + "zip": "97211" + }, + { + "city": "Lake Zurich", + "state": "Illinois", + "zip": "60047" + }, + { + "city": "Rolla", + "state": "Missouri", + "zip": "65401" + }, + { + "city": "Sand Springs", + "state": "Oklahoma", + "zip": "74063" + }, + { + "city": "Katy", + "state": "Texas", + "zip": "77493" + }, + { + "city": "Henderson", + "state": "Nevada", + "zip": "89052" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89129" + }, + { + "city": "Spring Hill", + "state": "Florida", + "zip": "34608" + }, + { + "city": "Ormond Beach", + "state": "Florida", + "zip": "32174" + }, + { + "city": "Orlando", + "state": "Florida", + "zip": "32825" + }, + { + "city": "Waukee", + "state": "Iowa", + "zip": "50263" + }, + { + "city": "New Lenox", + "state": "Illinois", + "zip": "60451" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60636" + }, + { + "city": "Kennewick", + "state": "Washington", + "zip": "99337" + }, + { + "city": "Passaic", + "state": "New Jersey", + "zip": "7055" + }, + { + "city": "Ephrata", + "state": "Pennsylvania", + "zip": "17522" + }, + { + "city": "Manassas", + "state": "Virginia", + "zip": "20110" + }, + { + "city": "Howell", + "state": "Michigan", + "zip": "48843" + }, + { + "city": "Chippewa Falls", + "state": "Wisconsin", + "zip": "54729" + }, + { + "city": "Aptos", + "state": "California", + "zip": "95003" + }, + { + "city": "Midland", + "state": "Texas", + "zip": "79701" + }, + { + "city": "Murfreesboro", + "state": "Tennessee", + "zip": "37128" + }, + { + "city": "Sterling Heights", + "state": "Michigan", + "zip": "48310" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60621" + }, + { + "city": "Baton Rouge", + "state": "Louisiana", + "zip": "70809" + }, + { + "city": "Glen Ellyn", + "state": "Illinois", + "zip": "60137" + }, + { + "city": "Forney", + "state": "Texas", + "zip": "75126" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77038" + }, + { + "city": "Brownsville", + "state": "Texas", + "zip": "78520" + }, + { + "city": "Tarzana", + "state": "California", + "zip": "91356" + }, + { + "city": "Natick", + "state": "Massachusetts", + "zip": "1760" + }, + { + "city": "Trenton", + "state": "New Jersey", + "zip": "8610" + }, + { + "city": "Hope Mills", + "state": "North Carolina", + "zip": "28348" + }, + { + "city": "Morrow", + "state": "Georgia", + "zip": "30260" + }, + { + "city": "Oklahoma City", + "state": "Oklahoma", + "zip": "73159" + }, + { + "city": "Commerce City", + "state": "Colorado", + "zip": "80022" + }, + { + "city": "Denver", + "state": "Colorado", + "zip": "80226" + }, + { + "city": "Lexington", + "state": "Kentucky", + "zip": "40503" + }, + { + "city": "Andover", + "state": "Minnesota", + "zip": "55304" + }, + { + "city": "Rochester", + "state": "Minnesota", + "zip": "55901" + }, + { + "city": "Fargo", + "state": "North Dakota", + "zip": "58103" + }, + { + "city": "Rockville", + "state": "Maryland", + "zip": "20850" + }, + { + "city": "Flint", + "state": "Michigan", + "zip": "48503" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77015" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85033" + }, + { + "city": "Apple Valley", + "state": "California", + "zip": "92307" + }, + { + "city": "Cleveland", + "state": "Ohio", + "zip": "44128" + }, + { + "city": "Lebanon", + "state": "Missouri", + "zip": "65536" + }, + { + "city": "Milwaukee", + "state": "Wisconsin", + "zip": "53215" + }, + { + "city": "La Mirada", + "state": "California", + "zip": "90638" + }, + { + "city": "Hyattsville", + "state": "Maryland", + "zip": "20785" + }, + { + "city": "Cary", + "state": "North Carolina", + "zip": "27519" + }, + { + "city": "Waukesha", + "state": "Wisconsin", + "zip": "53189" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11235" + }, + { + "city": "Hollywood", + "state": "Florida", + "zip": "33026" + }, + { + "city": "Staten Island", + "state": "New York", + "zip": "10305" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90020" + }, + { + "city": "Tampa", + "state": "Florida", + "zip": "33617" + }, + { + "city": "Westerville", + "state": "Ohio", + "zip": "43081" + }, + { + "city": "New York", + "state": "New York", + "zip": "10040" + }, + { + "city": "West Palm Beach", + "state": "Florida", + "zip": "33407" + }, + { + "city": "Bonita Springs", + "state": "Florida", + "zip": "34135" + }, + { + "city": "Hartselle", + "state": "Alabama", + "zip": "35640" + }, + { + "city": "Antelope", + "state": "California", + "zip": "95843" + }, + { + "city": "Slidell", + "state": "Louisiana", + "zip": "70461" + }, + { + "city": "Dallas", + "state": "Texas", + "zip": "75234" + }, + { + "city": "Laredo", + "state": "Texas", + "zip": "78046" + }, + { + "city": "Spanish Fork", + "state": "Utah", + "zip": "84660" + }, + { + "city": "Chatsworth", + "state": "Georgia", + "zip": "30705" + }, + { + "city": "Kearny", + "state": "New Jersey", + "zip": "7032" + }, + { + "city": "Milton", + "state": "Florida", + "zip": "32571" + }, + { + "city": "Canton", + "state": "Mississippi", + "zip": "39046" + }, + { + "city": "El Paso", + "state": "Texas", + "zip": "79904" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85050" + }, + { + "city": "San Diego", + "state": "California", + "zip": "92113" + }, + { + "city": "Basking Ridge", + "state": "New Jersey", + "zip": "7920" + }, + { + "city": "Dallas", + "state": "Texas", + "zip": "75238" + }, + { + "city": "San German", + "state": "Puerto Rico", + "zip": "683" + }, + { + "city": "Yabucoa", + "state": "Puerto Rico", + "zip": "767" + }, + { + "city": "Blackwood", + "state": "New Jersey", + "zip": "8012" + }, + { + "city": "Baltimore", + "state": "Maryland", + "zip": "21230" + }, + { + "city": "Cincinnati", + "state": "Ohio", + "zip": "45248" + }, + { + "city": "Waterford", + "state": "Michigan", + "zip": "48328" + }, + { + "city": "Pharr", + "state": "Texas", + "zip": "78577" + }, + { + "city": "Washington", + "state": "Utah", + "zip": "84780" + }, + { + "city": "Covington", + "state": "Georgia", + "zip": "30014" + }, + { + "city": "Wilmington", + "state": "California", + "zip": "90744" + }, + { + "city": "Lakewood", + "state": "Washington", + "zip": "98498" + }, + { + "city": "Bloomsburg", + "state": "Pennsylvania", + "zip": "17815" + }, + { + "city": "Bethlehem", + "state": "Pennsylvania", + "zip": "18015" + }, + { + "city": "Reseda", + "state": "California", + "zip": "91335" + }, + { + "city": "Thousand Oaks", + "state": "California", + "zip": "91362" + }, + { + "city": "Fort Lauderdale", + "state": "Florida", + "zip": "33308" + }, + { + "city": "Warner Robins", + "state": "Georgia", + "zip": "31088" + }, + { + "city": "Lithia", + "state": "Florida", + "zip": "33547" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77086" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77090" + }, + { + "city": "Orchard Park", + "state": "New York", + "zip": "14127" + }, + { + "city": "Alexandria", + "state": "Virginia", + "zip": "22315" + }, + { + "city": "Columbia", + "state": "South Carolina", + "zip": "29212" + }, + { + "city": "Baltimore", + "state": "Maryland", + "zip": "21224" + }, + { + "city": "Norfolk", + "state": "Virginia", + "zip": "23503" + }, + { + "city": "Sebastian", + "state": "Florida", + "zip": "32958" + }, + { + "city": "Bartlesville", + "state": "Oklahoma", + "zip": "74006" + }, + { + "city": "Farmington", + "state": "New Mexico", + "zip": "87401" + }, + { + "city": "Apple Valley", + "state": "California", + "zip": "92308" + }, + { + "city": "Rochester", + "state": "Michigan", + "zip": "48307" + }, + { + "city": "Bismarck", + "state": "North Dakota", + "zip": "58501" + }, + { + "city": "Branson", + "state": "Missouri", + "zip": "65616" + }, + { + "city": "Kingwood", + "state": "Texas", + "zip": "77339" + }, + { + "city": "Albuquerque", + "state": "New Mexico", + "zip": "87106" + }, + { + "city": "Aurora", + "state": "Colorado", + "zip": "80016" + }, + { + "city": "Durham", + "state": "North Carolina", + "zip": "27704" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90042" + }, + { + "city": "Sacramento", + "state": "California", + "zip": "95821" + }, + { + "city": "Naples", + "state": "Florida", + "zip": "34119" + }, + { + "city": "Palmetto", + "state": "Florida", + "zip": "34221" + }, + { + "city": "Saint Paul", + "state": "Minnesota", + "zip": "55123" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85043" + }, + { + "city": "Orlando", + "state": "Florida", + "zip": "32819" + }, + { + "city": "Ooltewah", + "state": "Tennessee", + "zip": "37363" + }, + { + "city": "Cleveland", + "state": "Ohio", + "zip": "44106" + }, + { + "city": "West Warwick", + "state": "Rhode Island", + "zip": "2893" + }, + { + "city": "Levittown", + "state": "New York", + "zip": "11756" + }, + { + "city": "Harvey", + "state": "Illinois", + "zip": "60426" + }, + { + "city": "Compton", + "state": "California", + "zip": "90221" + }, + { + "city": "Newark", + "state": "New Jersey", + "zip": "7105" + }, + { + "city": "Arlington", + "state": "Massachusetts", + "zip": "2474" + }, + { + "city": "Smyrna", + "state": "Delaware", + "zip": "19977" + }, + { + "city": "Garland", + "state": "Texas", + "zip": "75042" + }, + { + "city": "Orlando", + "state": "Florida", + "zip": "32806" + }, + { + "city": "Alvin", + "state": "Texas", + "zip": "77511" + }, + { + "city": "Pawtucket", + "state": "Rhode Island", + "zip": "2861" + }, + { + "city": "Shippensburg", + "state": "Pennsylvania", + "zip": "17257" + }, + { + "city": "Montgomery Village", + "state": "Maryland", + "zip": "20886" + }, + { + "city": "Lockport", + "state": "Illinois", + "zip": "60441" + }, + { + "city": "Lemoore", + "state": "California", + "zip": "93245" + }, + { + "city": "San Ramon", + "state": "California", + "zip": "94582" + }, + { + "city": "Tampa", + "state": "Florida", + "zip": "33629" + }, + { + "city": "Hilliard", + "state": "Ohio", + "zip": "43026" + }, + { + "city": "Ames", + "state": "Iowa", + "zip": "50014" + }, + { + "city": "Omaha", + "state": "Nebraska", + "zip": "68164" + }, + { + "city": "Cypress", + "state": "Texas", + "zip": "77433" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78222" + }, + { + "city": "Corpus Christi", + "state": "Texas", + "zip": "78410" + }, + { + "city": "Palmer", + "state": "Alaska", + "zip": "99645" + }, + { + "city": "North Fort Myers", + "state": "Florida", + "zip": "33917" + }, + { + "city": "Royal Oak", + "state": "Michigan", + "zip": "48073" + }, + { + "city": "West Des Moines", + "state": "Iowa", + "zip": "50266" + }, + { + "city": "Faribault", + "state": "Minnesota", + "zip": "55021" + }, + { + "city": "Georgetown", + "state": "Texas", + "zip": "78633" + }, + { + "city": "Ogden", + "state": "Utah", + "zip": "84414" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85019" + }, + { + "city": "Bronx", + "state": "New York", + "zip": "10460" + }, + { + "city": "Wilmington", + "state": "Delaware", + "zip": "19802" + }, + { + "city": "Burbank", + "state": "California", + "zip": "91505" + }, + { + "city": "Corvallis", + "state": "Oregon", + "zip": "97330" + }, + { + "city": "Methuen", + "state": "Massachusetts", + "zip": "1844" + }, + { + "city": "Owings Mills", + "state": "Maryland", + "zip": "21117" + }, + { + "city": "Norcross", + "state": "Georgia", + "zip": "30071" + }, + { + "city": "Boynton Beach", + "state": "Florida", + "zip": "33437" + }, + { + "city": "Midland", + "state": "Michigan", + "zip": "48642" + }, + { + "city": "Loganville", + "state": "Georgia", + "zip": "30052" + }, + { + "city": "Germantown", + "state": "Maryland", + "zip": "20876" + }, + { + "city": "Mesquite", + "state": "Texas", + "zip": "75150" + }, + { + "city": "Gilbert", + "state": "Arizona", + "zip": "85296" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89148" + }, + { + "city": "Crystal Lake", + "state": "Illinois", + "zip": "60014" + }, + { + "city": "Schenectady", + "state": "New York", + "zip": "12302" + }, + { + "city": "Sedalia", + "state": "Missouri", + "zip": "65301" + }, + { + "city": "Lafayette", + "state": "Louisiana", + "zip": "70501" + }, + { + "city": "Detroit", + "state": "Michigan", + "zip": "48238" + }, + { + "city": "Tucson", + "state": "Arizona", + "zip": "85712" + }, + { + "city": "Bradenton", + "state": "Florida", + "zip": "34207" + }, + { + "city": "Birmingham", + "state": "Alabama", + "zip": "35215" + }, + { + "city": "Clinton", + "state": "Tennessee", + "zip": "37716" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89128" + }, + { + "city": "Sacramento", + "state": "California", + "zip": "95828" + }, + { + "city": "West Monroe", + "state": "Louisiana", + "zip": "71291" + }, + { + "city": "Winchester", + "state": "California", + "zip": "92596" + }, + { + "city": "Portland", + "state": "Oregon", + "zip": "97217" + }, + { + "city": "South Ozone Park", + "state": "New York", + "zip": "11420" + }, + { + "city": "King George", + "state": "Virginia", + "zip": "22485" + }, + { + "city": "Bessemer", + "state": "Alabama", + "zip": "35020" + }, + { + "city": "Saint Paul", + "state": "Minnesota", + "zip": "55105" + }, + { + "city": "Rego Park", + "state": "New York", + "zip": "11374" + }, + { + "city": "Rosedale", + "state": "New York", + "zip": "11422" + }, + { + "city": "Reno", + "state": "Nevada", + "zip": "89509" + }, + { + "city": "Ogden", + "state": "Utah", + "zip": "84404" + }, + { + "city": "Augusta", + "state": "Maine", + "zip": "4330" + }, + { + "city": "New York", + "state": "New York", + "zip": "10031" + }, + { + "city": "Fayetteville", + "state": "North Carolina", + "zip": "28311" + }, + { + "city": "Arlington", + "state": "Texas", + "zip": "76012" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89123" + }, + { + "city": "Fair Oaks", + "state": "California", + "zip": "95628" + }, + { + "city": "Seattle", + "state": "Washington", + "zip": "98109" + }, + { + "city": "Soddy Daisy", + "state": "Tennessee", + "zip": "37379" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89149" + }, + { + "city": "Lafayette", + "state": "California", + "zip": "94549" + }, + { + "city": "Mount Pleasant", + "state": "Texas", + "zip": "75455" + }, + { + "city": "Haines City", + "state": "Florida", + "zip": "33844" + }, + { + "city": "Euless", + "state": "Texas", + "zip": "76040" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77022" + }, + { + "city": "Aurora", + "state": "Colorado", + "zip": "80012" + }, + { + "city": "Mahwah", + "state": "New Jersey", + "zip": "7430" + }, + { + "city": "Covington", + "state": "Georgia", + "zip": "30016" + }, + { + "city": "Largo", + "state": "Florida", + "zip": "33770" + }, + { + "city": "Burlingame", + "state": "California", + "zip": "94010" + }, + { + "city": "Austin", + "state": "Texas", + "zip": "78759" + }, + { + "city": "West Valley City", + "state": "Utah", + "zip": "84119" + }, + { + "city": "Salt Lake City", + "state": "Utah", + "zip": "84104" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89121" + }, + { + "city": "San Diego", + "state": "California", + "zip": "92102" + }, + { + "city": "Toms River", + "state": "New Jersey", + "zip": "8753" + }, + { + "city": "Indianapolis", + "state": "Indiana", + "zip": "46229" + }, + { + "city": "Bismarck", + "state": "North Dakota", + "zip": "58503" + }, + { + "city": "Corpus Christi", + "state": "Texas", + "zip": "78411" + }, + { + "city": "Santee", + "state": "California", + "zip": "92071" + }, + { + "city": "Anaheim", + "state": "California", + "zip": "92807" + }, + { + "city": "Camas", + "state": "Washington", + "zip": "98607" + }, + { + "city": "Meridian", + "state": "Idaho", + "zip": "83646" + }, + { + "city": "Toledo", + "state": "Ohio", + "zip": "43613" + }, + { + "city": "Galt", + "state": "California", + "zip": "95632" + }, + { + "city": "Vernon Rockville", + "state": "Connecticut", + "zip": "6066" + }, + { + "city": "Cleburne", + "state": "Texas", + "zip": "76033" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77080" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78250" + }, + { + "city": "Lacey", + "state": "Washington", + "zip": "98503" + }, + { + "city": "Burlington", + "state": "Wisconsin", + "zip": "53105" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60639" + }, + { + "city": "Chesterfield", + "state": "Missouri", + "zip": "63017" + }, + { + "city": "Batesville", + "state": "Arkansas", + "zip": "72501" + }, + { + "city": "Dallas", + "state": "Texas", + "zip": "75219" + }, + { + "city": "Aliso Viejo", + "state": "California", + "zip": "92656" + }, + { + "city": "Oceanside", + "state": "New York", + "zip": "11572" + }, + { + "city": "Brentwood", + "state": "California", + "zip": "94513" + }, + { + "city": "Saint Augustine", + "state": "Florida", + "zip": "32092" + }, + { + "city": "Fort Lauderdale", + "state": "Florida", + "zip": "33321" + }, + { + "city": "Gallatin", + "state": "Tennessee", + "zip": "37066" + }, + { + "city": "Utica", + "state": "Michigan", + "zip": "48317" + }, + { + "city": "Milwaukee", + "state": "Wisconsin", + "zip": "53208" + }, + { + "city": "Minneapolis", + "state": "Minnesota", + "zip": "55429" + }, + { + "city": "Abbeville", + "state": "Louisiana", + "zip": "70510" + }, + { + "city": "Chelmsford", + "state": "Massachusetts", + "zip": "1824" + }, + { + "city": "Fort Myers", + "state": "Florida", + "zip": "33908" + }, + { + "city": "Tucson", + "state": "Arizona", + "zip": "85747" + }, + { + "city": "Paramount", + "state": "California", + "zip": "90723" + }, + { + "city": "Winter Park", + "state": "Florida", + "zip": "32792" + }, + { + "city": "South Lyon", + "state": "Michigan", + "zip": "48178" + }, + { + "city": "Dedham", + "state": "Massachusetts", + "zip": "2026" + }, + { + "city": "Texas City", + "state": "Texas", + "zip": "77590" + }, + { + "city": "Stone Mountain", + "state": "Georgia", + "zip": "30083" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85053" + }, + { + "city": "Riverside", + "state": "California", + "zip": "92505" + }, + { + "city": "San Jose", + "state": "California", + "zip": "95126" + }, + { + "city": "Lake Charles", + "state": "Louisiana", + "zip": "70601" + }, + { + "city": "Dayton", + "state": "Texas", + "zip": "77535" + }, + { + "city": "Tampa", + "state": "Florida", + "zip": "33625" + }, + { + "city": "Escondido", + "state": "California", + "zip": "92026" + }, + { + "city": "Grand Island", + "state": "Nebraska", + "zip": "68801" + }, + { + "city": "Kingman", + "state": "Arizona", + "zip": "86409" + }, + { + "city": "Glen Cove", + "state": "New York", + "zip": "11542" + }, + { + "city": "Newport News", + "state": "Virginia", + "zip": "23608" + }, + { + "city": "Knightdale", + "state": "North Carolina", + "zip": "27545" + }, + { + "city": "Kaneohe", + "state": "Hawaii", + "zip": "96744" + }, + { + "city": "Lutherville Timonium", + "state": "Maryland", + "zip": "21093" + }, + { + "city": "Lexington", + "state": "Kentucky", + "zip": "40517" + }, + { + "city": "Prattville", + "state": "Alabama", + "zip": "36067" + }, + { + "city": "Orangevale", + "state": "California", + "zip": "95662" + }, + { + "city": "Ellensburg", + "state": "Washington", + "zip": "98926" + }, + { + "city": "Bentonville", + "state": "Arkansas", + "zip": "72712" + }, + { + "city": "Dallas", + "state": "Texas", + "zip": "75231" + }, + { + "city": "Mission", + "state": "Texas", + "zip": "78573" + }, + { + "city": "Windsor Mill", + "state": "Maryland", + "zip": "21244" + }, + { + "city": "Great Falls", + "state": "Montana", + "zip": "59405" + }, + { + "city": "Mount Prospect", + "state": "Illinois", + "zip": "60056" + }, + { + "city": "Skokie", + "state": "Illinois", + "zip": "60077" + }, + { + "city": "Baton Rouge", + "state": "Louisiana", + "zip": "70805" + }, + { + "city": "Bossier City", + "state": "Louisiana", + "zip": "71112" + }, + { + "city": "Maspeth", + "state": "New York", + "zip": "11378" + }, + { + "city": "Georgetown", + "state": "South Carolina", + "zip": "29440" + }, + { + "city": "Lexington", + "state": "Kentucky", + "zip": "40504" + }, + { + "city": "Aurora", + "state": "Colorado", + "zip": "80011" + }, + { + "city": "Mill Valley", + "state": "California", + "zip": "94941" + }, + { + "city": "Andover", + "state": "Massachusetts", + "zip": "1810" + }, + { + "city": "Meadville", + "state": "Pennsylvania", + "zip": "16335" + }, + { + "city": "San Bernardino", + "state": "California", + "zip": "92407" + }, + { + "city": "Bothell", + "state": "Washington", + "zip": "98012" + }, + { + "city": "Seattle", + "state": "Washington", + "zip": "98106" + }, + { + "city": "North Ridgeville", + "state": "Ohio", + "zip": "44039" + }, + { + "city": "Jeffersonville", + "state": "Indiana", + "zip": "47130" + }, + { + "city": "Charlotte", + "state": "North Carolina", + "zip": "28226" + }, + { + "city": "Rome", + "state": "Georgia", + "zip": "30161" + }, + { + "city": "Cleveland", + "state": "Ohio", + "zip": "44102" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77023" + }, + { + "city": "Littleton", + "state": "Colorado", + "zip": "80127" + }, + { + "city": "Auburn", + "state": "Washington", + "zip": "98092" + }, + { + "city": "Staunton", + "state": "Virginia", + "zip": "24401" + }, + { + "city": "Springfield", + "state": "Illinois", + "zip": "62702" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90024" + }, + { + "city": "San Luis Obispo", + "state": "California", + "zip": "93401" + }, + { + "city": "Marlton", + "state": "New Jersey", + "zip": "8053" + }, + { + "city": "Norfolk", + "state": "Virginia", + "zip": "23518" + }, + { + "city": "Satellite Beach", + "state": "Florida", + "zip": "32937" + }, + { + "city": "Bradenton", + "state": "Florida", + "zip": "34208" + }, + { + "city": "Brandon", + "state": "Mississippi", + "zip": "39042" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89103" + }, + { + "city": "Converse", + "state": "Texas", + "zip": "78109" + }, + { + "city": "Denver", + "state": "Colorado", + "zip": "80249" + }, + { + "city": "Providence", + "state": "Rhode Island", + "zip": "2907" + }, + { + "city": "Coatesville", + "state": "Pennsylvania", + "zip": "19320" + }, + { + "city": "Columbus", + "state": "Ohio", + "zip": "43235" + }, + { + "city": "San Diego", + "state": "California", + "zip": "92120" + }, + { + "city": "Fort Worth", + "state": "Texas", + "zip": "76116" + }, + { + "city": "Porter", + "state": "Texas", + "zip": "77365" + }, + { + "city": "Castle Rock", + "state": "Colorado", + "zip": "80104" + }, + { + "city": "San Lorenzo", + "state": "California", + "zip": "94580" + }, + { + "city": "Merced", + "state": "California", + "zip": "95341" + }, + { + "city": "Clearwater", + "state": "Florida", + "zip": "33756" + }, + { + "city": "Madison", + "state": "Alabama", + "zip": "35758" + }, + { + "city": "Ridgefield", + "state": "Connecticut", + "zip": "6877" + }, + { + "city": "Glen Allen", + "state": "Virginia", + "zip": "23059" + }, + { + "city": "Marietta", + "state": "Georgia", + "zip": "30066" + }, + { + "city": "Cape Coral", + "state": "Florida", + "zip": "33914" + }, + { + "city": "Cleveland", + "state": "Ohio", + "zip": "44121" + }, + { + "city": "Akron", + "state": "Ohio", + "zip": "44312" + }, + { + "city": "Plymouth", + "state": "Michigan", + "zip": "48170" + }, + { + "city": "Marshfield", + "state": "Wisconsin", + "zip": "54449" + }, + { + "city": "Laredo", + "state": "Texas", + "zip": "78040" + }, + { + "city": "Wilmington", + "state": "Delaware", + "zip": "19810" + }, + { + "city": "Torrance", + "state": "California", + "zip": "90501" + }, + { + "city": "Canyon Country", + "state": "California", + "zip": "91387" + }, + { + "city": "Kalispell", + "state": "Montana", + "zip": "59901" + }, + { + "city": "Roslindale", + "state": "Massachusetts", + "zip": "2131" + }, + { + "city": "Fort Worth", + "state": "Texas", + "zip": "76107" + }, + { + "city": "Spring", + "state": "Texas", + "zip": "77381" + }, + { + "city": "Rockville Centre", + "state": "New York", + "zip": "11570" + }, + { + "city": "Conroe", + "state": "Texas", + "zip": "77385" + }, + { + "city": "Rohnert Park", + "state": "California", + "zip": "94928" + }, + { + "city": "Goleta", + "state": "California", + "zip": "93117" + }, + { + "city": "Panama City", + "state": "Florida", + "zip": "32405" + }, + { + "city": "New Smyrna Beach", + "state": "Florida", + "zip": "32168" + }, + { + "city": "Jacksonville", + "state": "Florida", + "zip": "32225" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33144" + }, + { + "city": "Spring Hill", + "state": "Florida", + "zip": "34606" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89141" + }, + { + "city": "Redlands", + "state": "California", + "zip": "92374" + }, + { + "city": "Kailua", + "state": "Hawaii", + "zip": "96734" + }, + { + "city": "West Linn", + "state": "Oregon", + "zip": "97068" + }, + { + "city": "Spokane", + "state": "Washington", + "zip": "99216" + }, + { + "city": "Coram", + "state": "New York", + "zip": "11727" + }, + { + "city": "Rochester", + "state": "New York", + "zip": "14621" + }, + { + "city": "Bethesda", + "state": "Maryland", + "zip": "20814" + }, + { + "city": "Montclair", + "state": "New Jersey", + "zip": "7042" + }, + { + "city": "Alexandria", + "state": "Virginia", + "zip": "22310" + }, + { + "city": "Goose Creek", + "state": "South Carolina", + "zip": "29445" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60616" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78237" + }, + { + "city": "Danvers", + "state": "Massachusetts", + "zip": "1923" + }, + { + "city": "Morristown", + "state": "New Jersey", + "zip": "7960" + }, + { + "city": "South Richmond Hill", + "state": "New York", + "zip": "11419" + }, + { + "city": "Richmond", + "state": "Virginia", + "zip": "23223" + }, + { + "city": "Raleigh", + "state": "North Carolina", + "zip": "27609" + }, + { + "city": "Conyers", + "state": "Georgia", + "zip": "30094" + }, + { + "city": "Irvine", + "state": "California", + "zip": "92620" + }, + { + "city": "Tracy", + "state": "California", + "zip": "95376" + }, + { + "city": "Columbus", + "state": "Ohio", + "zip": "43230" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60656" + }, + { + "city": "Lakeland", + "state": "Florida", + "zip": "33803" + }, + { + "city": "Gretna", + "state": "Louisiana", + "zip": "70056" + }, + { + "city": "Corpus Christi", + "state": "Texas", + "zip": "78418" + }, + { + "city": "Mesa", + "state": "Arizona", + "zip": "85203" + }, + { + "city": "Spokane", + "state": "Washington", + "zip": "99207" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78210" + }, + { + "city": "San Bruno", + "state": "California", + "zip": "94066" + }, + { + "city": "San Francisco", + "state": "California", + "zip": "94112" + }, + { + "city": "Fremont", + "state": "California", + "zip": "94539" + }, + { + "city": "El Dorado Hills", + "state": "California", + "zip": "95762" + }, + { + "city": "Renton", + "state": "Washington", + "zip": "98056" + }, + { + "city": "Spokane", + "state": "Washington", + "zip": "99208" + }, + { + "city": "Franklin", + "state": "Tennessee", + "zip": "37064" + }, + { + "city": "Cincinnati", + "state": "Ohio", + "zip": "45231" + }, + { + "city": "Southgate", + "state": "Michigan", + "zip": "48195" + }, + { + "city": "Duluth", + "state": "Minnesota", + "zip": "55811" + }, + { + "city": "New York", + "state": "New York", + "zip": "10011" + }, + { + "city": "Reno", + "state": "Nevada", + "zip": "89506" + }, + { + "city": "Valley Village", + "state": "California", + "zip": "91607" + }, + { + "city": "San Pablo", + "state": "California", + "zip": "94806" + }, + { + "city": "Beaverton", + "state": "Oregon", + "zip": "97006" + }, + { + "city": "Hattiesburg", + "state": "Mississippi", + "zip": "39401" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11238" + }, + { + "city": "Dickinson", + "state": "North Dakota", + "zip": "58601" + }, + { + "city": "Downers Grove", + "state": "Illinois", + "zip": "60515" + }, + { + "city": "Grants Pass", + "state": "Oregon", + "zip": "97526" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77060" + }, + { + "city": "Salem", + "state": "Oregon", + "zip": "97301" + }, + { + "city": "Mishawaka", + "state": "Indiana", + "zip": "46544" + }, + { + "city": "Lake Villa", + "state": "Illinois", + "zip": "60046" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60631" + }, + { + "city": "Enid", + "state": "Oklahoma", + "zip": "73703" + }, + { + "city": "Long Beach", + "state": "California", + "zip": "90808" + }, + { + "city": "Cheshire", + "state": "Connecticut", + "zip": "6410" + }, + { + "city": "Ashburn", + "state": "Virginia", + "zip": "20148" + }, + { + "city": "Midlothian", + "state": "Virginia", + "zip": "23112" + }, + { + "city": "Charlotte", + "state": "North Carolina", + "zip": "28212" + }, + { + "city": "Greenville", + "state": "South Carolina", + "zip": "29615" + }, + { + "city": "Duluth", + "state": "Georgia", + "zip": "30097" + }, + { + "city": "Dalton", + "state": "Georgia", + "zip": "30720" + }, + { + "city": "Arroyo Grande", + "state": "California", + "zip": "93420" + }, + { + "city": "Portland", + "state": "Oregon", + "zip": "97202" + }, + { + "city": "Portland", + "state": "Oregon", + "zip": "97236" + }, + { + "city": "Brawley", + "state": "California", + "zip": "92227" + }, + { + "city": "Litchfield Park", + "state": "Arizona", + "zip": "85340" + }, + { + "city": "Newington", + "state": "Connecticut", + "zip": "6111" + }, + { + "city": "Piscataway", + "state": "New Jersey", + "zip": "8854" + }, + { + "city": "Manchester", + "state": "New Hampshire", + "zip": "3102" + }, + { + "city": "Minneapolis", + "state": "Minnesota", + "zip": "55419" + }, + { + "city": "Bronx", + "state": "New York", + "zip": "10462" + }, + { + "city": "Newport News", + "state": "Virginia", + "zip": "23601" + }, + { + "city": "Albany", + "state": "Georgia", + "zip": "31707" + }, + { + "city": "Loxahatchee", + "state": "Florida", + "zip": "33470" + }, + { + "city": "Saint Paul", + "state": "Minnesota", + "zip": "55119" + }, + { + "city": "Anaheim", + "state": "California", + "zip": "92804" + }, + { + "city": "Simi Valley", + "state": "California", + "zip": "93063" + }, + { + "city": "Oak Park", + "state": "Illinois", + "zip": "60302" + }, + { + "city": "Minneapolis", + "state": "Minnesota", + "zip": "55411" + }, + { + "city": "Aliquippa", + "state": "Pennsylvania", + "zip": "15001" + }, + { + "city": "Raleigh", + "state": "North Carolina", + "zip": "27613" + }, + { + "city": "Florence", + "state": "South Carolina", + "zip": "29505" + }, + { + "city": "Wichita", + "state": "Kansas", + "zip": "67203" + }, + { + "city": "West New York", + "state": "New Jersey", + "zip": "7093" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33173" + }, + { + "city": "Montgomery", + "state": "Alabama", + "zip": "36117" + }, + { + "city": "Hixson", + "state": "Tennessee", + "zip": "37343" + }, + { + "city": "Albany", + "state": "Oregon", + "zip": "97321" + }, + { + "city": "Abingdon", + "state": "Maryland", + "zip": "21009" + }, + { + "city": "Anderson", + "state": "South Carolina", + "zip": "29625" + }, + { + "city": "Olympia", + "state": "Washington", + "zip": "98512" + }, + { + "city": "Kirkland", + "state": "Washington", + "zip": "98033" + }, + { + "city": "Minneapolis", + "state": "Minnesota", + "zip": "55421" + }, + { + "city": "Saint Charles", + "state": "Missouri", + "zip": "63301" + }, + { + "city": "Fort Worth", + "state": "Texas", + "zip": "76112" + }, + { + "city": "Astoria", + "state": "New York", + "zip": "11105" + }, + { + "city": "Glen Burnie", + "state": "Maryland", + "zip": "21061" + }, + { + "city": "Christiansburg", + "state": "Virginia", + "zip": "24073" + }, + { + "city": "Burleson", + "state": "Texas", + "zip": "76028" + }, + { + "city": "Palos Verdes Peninsula", + "state": "California", + "zip": "90274" + }, + { + "city": "Jacksonville", + "state": "Florida", + "zip": "32258" + }, + { + "city": "Middletown", + "state": "Ohio", + "zip": "45044" + }, + { + "city": "Bronx", + "state": "New York", + "zip": "10469" + }, + { + "city": "Bethlehem", + "state": "Pennsylvania", + "zip": "18017" + }, + { + "city": "Frederick", + "state": "Maryland", + "zip": "21702" + }, + { + "city": "Clemmons", + "state": "North Carolina", + "zip": "27012" + }, + { + "city": "Springfield", + "state": "Missouri", + "zip": "65804" + }, + { + "city": "Fort Worth", + "state": "Texas", + "zip": "76110" + }, + { + "city": "Eugene", + "state": "Oregon", + "zip": "97402" + }, + { + "city": "Leavenworth", + "state": "Kansas", + "zip": "66048" + }, + { + "city": "Worcester", + "state": "Massachusetts", + "zip": "1604" + }, + { + "city": "Saint Charles", + "state": "Illinois", + "zip": "60174" + }, + { + "city": "Compton", + "state": "California", + "zip": "90220" + }, + { + "city": "Medford", + "state": "Oregon", + "zip": "97501" + }, + { + "city": "Wildwood", + "state": "Florida", + "zip": "34785" + }, + { + "city": "Omaha", + "state": "Nebraska", + "zip": "68137" + }, + { + "city": "Coppell", + "state": "Texas", + "zip": "75019" + }, + { + "city": "Easley", + "state": "South Carolina", + "zip": "29640" + }, + { + "city": "Hollywood", + "state": "Florida", + "zip": "33029" + }, + { + "city": "Shelbyville", + "state": "Tennessee", + "zip": "37160" + }, + { + "city": "Sioux City", + "state": "Iowa", + "zip": "51106" + }, + { + "city": "Elk River", + "state": "Minnesota", + "zip": "55330" + }, + { + "city": "Brainerd", + "state": "Minnesota", + "zip": "56401" + }, + { + "city": "Saint Louis", + "state": "Missouri", + "zip": "63114" + }, + { + "city": "Albuquerque", + "state": "New Mexico", + "zip": "87121" + }, + { + "city": "Rancho Cucamonga", + "state": "California", + "zip": "91739" + }, + { + "city": "Charlotte", + "state": "North Carolina", + "zip": "28216" + }, + { + "city": "Macon", + "state": "Georgia", + "zip": "31204" + }, + { + "city": "Pensacola", + "state": "Florida", + "zip": "32503" + }, + { + "city": "Columbus", + "state": "Ohio", + "zip": "43085" + }, + { + "city": "Indianapolis", + "state": "Indiana", + "zip": "46237" + }, + { + "city": "Appleton", + "state": "Wisconsin", + "zip": "54915" + }, + { + "city": "Lake In The Hills", + "state": "Illinois", + "zip": "60156" + }, + { + "city": "Hot Springs National Park", + "state": "Arkansas", + "zip": "71913" + }, + { + "city": "Juncos", + "state": "Puerto Rico", + "zip": "777" + }, + { + "city": "Winter Haven", + "state": "Florida", + "zip": "33880" + }, + { + "city": "Valparaiso", + "state": "Indiana", + "zip": "46385" + }, + { + "city": "Long Beach", + "state": "California", + "zip": "90803" + }, + { + "city": "Newark", + "state": "New Jersey", + "zip": "7106" + }, + { + "city": "Vista", + "state": "California", + "zip": "92083" + }, + { + "city": "Irvine", + "state": "California", + "zip": "92604" + }, + { + "city": "Conway", + "state": "Arkansas", + "zip": "72032" + }, + { + "city": "North Brunswick", + "state": "New Jersey", + "zip": "8902" + }, + { + "city": "Upper Darby", + "state": "Pennsylvania", + "zip": "19082" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60660" + }, + { + "city": "Broken Arrow", + "state": "Oklahoma", + "zip": "74011" + }, + { + "city": "Niceville", + "state": "Florida", + "zip": "32578" + }, + { + "city": "Loveland", + "state": "Ohio", + "zip": "45140" + }, + { + "city": "Franklin", + "state": "Indiana", + "zip": "46131" + }, + { + "city": "Monterey Park", + "state": "California", + "zip": "91755" + }, + { + "city": "Maumelle", + "state": "Arkansas", + "zip": "72113" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78216" + }, + { + "city": "Weslaco", + "state": "Texas", + "zip": "78596" + }, + { + "city": "Austin", + "state": "Texas", + "zip": "78729" + }, + { + "city": "Twin Falls", + "state": "Idaho", + "zip": "83301" + }, + { + "city": "Boise", + "state": "Idaho", + "zip": "83706" + }, + { + "city": "Salt Lake City", + "state": "Utah", + "zip": "84116" + }, + { + "city": "Hampton", + "state": "Virginia", + "zip": "23669" + }, + { + "city": "San Mateo", + "state": "California", + "zip": "94401" + }, + { + "city": "Portland", + "state": "Oregon", + "zip": "97214" + }, + { + "city": "Scarsdale", + "state": "New York", + "zip": "10583" + }, + { + "city": "Wantagh", + "state": "New York", + "zip": "11793" + }, + { + "city": "Slidell", + "state": "Louisiana", + "zip": "70458" + }, + { + "city": "Longmont", + "state": "Colorado", + "zip": "80504" + }, + { + "city": "Orlando", + "state": "Florida", + "zip": "32807" + }, + { + "city": "Florence", + "state": "Kentucky", + "zip": "41042" + }, + { + "city": "Cottage Grove", + "state": "Minnesota", + "zip": "55016" + }, + { + "city": "Fairfield", + "state": "California", + "zip": "94533" + }, + { + "city": "Whittier", + "state": "California", + "zip": "90601" + }, + { + "city": "Birmingham", + "state": "Alabama", + "zip": "35242" + }, + { + "city": "Savage", + "state": "Minnesota", + "zip": "55378" + }, + { + "city": "Pearland", + "state": "Texas", + "zip": "77584" + }, + { + "city": "Bell Gardens", + "state": "California", + "zip": "90201" + }, + { + "city": "Arlington", + "state": "Washington", + "zip": "98223" + }, + { + "city": "Santa Maria", + "state": "California", + "zip": "93458" + }, + { + "city": "Santa Rosa", + "state": "California", + "zip": "95404" + }, + { + "city": "Tacoma", + "state": "Washington", + "zip": "98444" + }, + { + "city": "Norwalk", + "state": "Connecticut", + "zip": "6851" + }, + { + "city": "Acworth", + "state": "Georgia", + "zip": "30101" + }, + { + "city": "Saint Cloud", + "state": "Florida", + "zip": "34772" + }, + { + "city": "Edmond", + "state": "Oklahoma", + "zip": "73034" + }, + { + "city": "Chandler", + "state": "Arizona", + "zip": "85224" + }, + { + "city": "San Francisco", + "state": "California", + "zip": "94134" + }, + { + "city": "Olivehurst", + "state": "California", + "zip": "95961" + }, + { + "city": "Fargo", + "state": "North Dakota", + "zip": "58104" + }, + { + "city": "Poughkeepsie", + "state": "New York", + "zip": "12601" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33185" + }, + { + "city": "Lucedale", + "state": "Mississippi", + "zip": "39452" + }, + { + "city": "Milwaukee", + "state": "Wisconsin", + "zip": "53214" + }, + { + "city": "Columbia", + "state": "Maryland", + "zip": "21044" + }, + { + "city": "Crofton", + "state": "Maryland", + "zip": "21114" + }, + { + "city": "Monroe", + "state": "Georgia", + "zip": "30655" + }, + { + "city": "Fleming Island", + "state": "Florida", + "zip": "32003" + }, + { + "city": "Louisville", + "state": "Kentucky", + "zip": "40241" + }, + { + "city": "Detroit", + "state": "Michigan", + "zip": "48210" + }, + { + "city": "O'Fallon", + "state": "Missouri", + "zip": "63368" + }, + { + "city": "Lewiston", + "state": "Idaho", + "zip": "83501" + }, + { + "city": "Hermiston", + "state": "Oregon", + "zip": "97838" + }, + { + "city": "Hamden", + "state": "Connecticut", + "zip": "6514" + }, + { + "city": "Elizabethtown", + "state": "Pennsylvania", + "zip": "17022" + }, + { + "city": "Gettysburg", + "state": "Pennsylvania", + "zip": "17325" + }, + { + "city": "Orlando", + "state": "Florida", + "zip": "32835" + }, + { + "city": "Roseville", + "state": "California", + "zip": "95661" + }, + { + "city": "Virginia Beach", + "state": "Virginia", + "zip": "23453" + }, + { + "city": "Boone", + "state": "North Carolina", + "zip": "28607" + }, + { + "city": "Lithonia", + "state": "Georgia", + "zip": "30038" + }, + { + "city": "Bradenton", + "state": "Florida", + "zip": "34209" + }, + { + "city": "Festus", + "state": "Missouri", + "zip": "63028" + }, + { + "city": "Hutchinson", + "state": "Kansas", + "zip": "67501" + }, + { + "city": "Arlington", + "state": "Texas", + "zip": "76016" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85009" + }, + { + "city": "Allentown", + "state": "Pennsylvania", + "zip": "18104" + }, + { + "city": "Bellevue", + "state": "Washington", + "zip": "98008" + }, + { + "city": "Huntington Beach", + "state": "California", + "zip": "92647" + }, + { + "city": "Morrisville", + "state": "Pennsylvania", + "zip": "19067" + }, + { + "city": "Hernando", + "state": "Mississippi", + "zip": "38632" + }, + { + "city": "Lynn", + "state": "Massachusetts", + "zip": "1902" + }, + { + "city": "Beckley", + "state": "West Virginia", + "zip": "25801" + }, + { + "city": "Bluffton", + "state": "South Carolina", + "zip": "29910" + }, + { + "city": "Kenner", + "state": "Louisiana", + "zip": "70065" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77039" + }, + { + "city": "Spring", + "state": "Texas", + "zip": "77386" + }, + { + "city": "Angleton", + "state": "Texas", + "zip": "77515" + }, + { + "city": "San Diego", + "state": "California", + "zip": "92130" + }, + { + "city": "San Jose", + "state": "California", + "zip": "95132" + }, + { + "city": "Eugene", + "state": "Oregon", + "zip": "97405" + }, + { + "city": "Milton", + "state": "Florida", + "zip": "32583" + }, + { + "city": "Havertown", + "state": "Pennsylvania", + "zip": "19083" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19120" + }, + { + "city": "Homestead", + "state": "Florida", + "zip": "33032" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77045" + }, + { + "city": "Bloomington", + "state": "Indiana", + "zip": "47408" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77062" + }, + { + "city": "La Puente", + "state": "California", + "zip": "91744" + }, + { + "city": "Yuba City", + "state": "California", + "zip": "95991" + }, + { + "city": "Honolulu", + "state": "Hawaii", + "zip": "96813" + }, + { + "city": "Bremerton", + "state": "Washington", + "zip": "98312" + }, + { + "city": "Kerrville", + "state": "Texas", + "zip": "78028" + }, + { + "city": "Lake Elsinore", + "state": "California", + "zip": "92532" + }, + { + "city": "Redwood City", + "state": "California", + "zip": "94063" + }, + { + "city": "Farmingdale", + "state": "New York", + "zip": "11735" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19130" + }, + { + "city": "Ellenwood", + "state": "Georgia", + "zip": "30294" + }, + { + "city": "Fernandina Beach", + "state": "Florida", + "zip": "32034" + }, + { + "city": "Mechanicsville", + "state": "Virginia", + "zip": "23116" + }, + { + "city": "Clayton", + "state": "North Carolina", + "zip": "27520" + }, + { + "city": "New Orleans", + "state": "Louisiana", + "zip": "70123" + }, + { + "city": "Port Orchard", + "state": "Washington", + "zip": "98366" + }, + { + "city": "North Port", + "state": "Florida", + "zip": "34287" + }, + { + "city": "Hamilton", + "state": "Ohio", + "zip": "45011" + }, + { + "city": "Ottumwa", + "state": "Iowa", + "zip": "52501" + }, + { + "city": "Joliet", + "state": "Illinois", + "zip": "60435" + }, + { + "city": "Benton", + "state": "Arkansas", + "zip": "72019" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78239" + }, + { + "city": "Clearfield", + "state": "Utah", + "zip": "84015" + }, + { + "city": "Sacramento", + "state": "California", + "zip": "95834" + }, + { + "city": "Everett", + "state": "Massachusetts", + "zip": "2149" + }, + { + "city": "Staten Island", + "state": "New York", + "zip": "10301" + }, + { + "city": "Stockbridge", + "state": "Georgia", + "zip": "30281" + }, + { + "city": "Atlanta", + "state": "Georgia", + "zip": "30340" + }, + { + "city": "Great Falls", + "state": "Montana", + "zip": "59404" + }, + { + "city": "Grass Valley", + "state": "California", + "zip": "95945" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11231" + }, + { + "city": "Atlanta", + "state": "Georgia", + "zip": "30311" + }, + { + "city": "Alamogordo", + "state": "New Mexico", + "zip": "88310" + }, + { + "city": "Bremerton", + "state": "Washington", + "zip": "98311" + }, + { + "city": "Lakeville", + "state": "Minnesota", + "zip": "55044" + }, + { + "city": "Shirley", + "state": "New York", + "zip": "11967" + }, + { + "city": "Noblesville", + "state": "Indiana", + "zip": "46062" + }, + { + "city": "Seymour", + "state": "Indiana", + "zip": "47274" + }, + { + "city": "Bloomington", + "state": "Indiana", + "zip": "47403" + }, + { + "city": "New Orleans", + "state": "Louisiana", + "zip": "70119" + }, + { + "city": "Norman", + "state": "Oklahoma", + "zip": "73071" + }, + { + "city": "Dallas", + "state": "Texas", + "zip": "75248" + }, + { + "city": "Providence", + "state": "Rhode Island", + "zip": "2906" + }, + { + "city": "Bronx", + "state": "New York", + "zip": "10467" + }, + { + "city": "New Hyde Park", + "state": "New York", + "zip": "11040" + }, + { + "city": "Medford", + "state": "New York", + "zip": "11763" + }, + { + "city": "Lancaster", + "state": "New York", + "zip": "14086" + }, + { + "city": "Fredericksburg", + "state": "Virginia", + "zip": "22408" + }, + { + "city": "Richmond", + "state": "Virginia", + "zip": "23225" + }, + { + "city": "Milwaukee", + "state": "Wisconsin", + "zip": "53222" + }, + { + "city": "Van Buren", + "state": "Arkansas", + "zip": "72956" + }, + { + "city": "Calexico", + "state": "California", + "zip": "92231" + }, + { + "city": "Castle Rock", + "state": "Colorado", + "zip": "80108" + }, + { + "city": "Albuquerque", + "state": "New Mexico", + "zip": "87123" + }, + { + "city": "Granada Hills", + "state": "California", + "zip": "91344" + }, + { + "city": "Dublin", + "state": "Ohio", + "zip": "43016" + }, + { + "city": "Macomb", + "state": "Michigan", + "zip": "48042" + }, + { + "city": "Franklin", + "state": "Wisconsin", + "zip": "53132" + }, + { + "city": "Owatonna", + "state": "Minnesota", + "zip": "55060" + }, + { + "city": "Monrovia", + "state": "California", + "zip": "91016" + }, + { + "city": "Plainfield", + "state": "New Jersey", + "zip": "7060" + }, + { + "city": "Sun Valley", + "state": "California", + "zip": "91352" + }, + { + "city": "San Diego", + "state": "California", + "zip": "92126" + }, + { + "city": "San Mateo", + "state": "California", + "zip": "94402" + }, + { + "city": "San Jose", + "state": "California", + "zip": "95136" + }, + { + "city": "Huntington", + "state": "New York", + "zip": "11743" + }, + { + "city": "Denver", + "state": "Colorado", + "zip": "80234" + }, + { + "city": "Mesa", + "state": "Arizona", + "zip": "85212" + }, + { + "city": "Cleveland", + "state": "Ohio", + "zip": "44109" + }, + { + "city": "Warren", + "state": "Michigan", + "zip": "48092" + }, + { + "city": "Bemidji", + "state": "Minnesota", + "zip": "56601" + }, + { + "city": "Muscatine", + "state": "Iowa", + "zip": "52761" + }, + { + "city": "Edmond", + "state": "Oklahoma", + "zip": "73012" + }, + { + "city": "La Habra", + "state": "California", + "zip": "90631" + }, + { + "city": "Everett", + "state": "Washington", + "zip": "98208" + }, + { + "city": "Lexington", + "state": "Kentucky", + "zip": "40505" + }, + { + "city": "Belton", + "state": "Texas", + "zip": "76513" + }, + { + "city": "Campbellsville", + "state": "Kentucky", + "zip": "42718" + }, + { + "city": "Dearborn", + "state": "Michigan", + "zip": "48126" + }, + { + "city": "Oakland Gardens", + "state": "New York", + "zip": "11364" + }, + { + "city": "Salem", + "state": "Oregon", + "zip": "97317" + }, + { + "city": "Kent", + "state": "Ohio", + "zip": "44240" + }, + { + "city": "Ann Arbor", + "state": "Michigan", + "zip": "48108" + }, + { + "city": "Lowell", + "state": "Massachusetts", + "zip": "1852" + }, + { + "city": "Braintree", + "state": "Massachusetts", + "zip": "2184" + }, + { + "city": "Jacksonville", + "state": "Florida", + "zip": "32216" + }, + { + "city": "Fort Pierce", + "state": "Florida", + "zip": "34982" + }, + { + "city": "Lexington", + "state": "Kentucky", + "zip": "40515" + }, + { + "city": "Cedar Rapids", + "state": "Iowa", + "zip": "52402" + }, + { + "city": "Irvine", + "state": "California", + "zip": "92618" + }, + { + "city": "Charlotte", + "state": "North Carolina", + "zip": "28205" + }, + { + "city": "Grand Rapids", + "state": "Michigan", + "zip": "49506" + }, + { + "city": "Fort Lauderdale", + "state": "Florida", + "zip": "33319" + }, + { + "city": "Flint", + "state": "Michigan", + "zip": "48507" + }, + { + "city": "Garland", + "state": "Texas", + "zip": "75044" + }, + { + "city": "Sebastopol", + "state": "California", + "zip": "95472" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60619" + }, + { + "city": "Missouri City", + "state": "Texas", + "zip": "77459" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78201" + }, + { + "city": "Rancho Palos Verdes", + "state": "California", + "zip": "90275" + }, + { + "city": "Rialto", + "state": "California", + "zip": "92376" + }, + { + "city": "Freeport", + "state": "Illinois", + "zip": "61032" + }, + { + "city": "Modesto", + "state": "California", + "zip": "95350" + }, + { + "city": "Red Oak", + "state": "Texas", + "zip": "75154" + }, + { + "city": "Edinburg", + "state": "Texas", + "zip": "78541" + }, + { + "city": "Midland", + "state": "Texas", + "zip": "79706" + }, + { + "city": "Tacoma", + "state": "Washington", + "zip": "98445" + }, + { + "city": "Tulare", + "state": "California", + "zip": "93274" + }, + { + "city": "Redwood City", + "state": "California", + "zip": "94062" + }, + { + "city": "Crawfordville", + "state": "Florida", + "zip": "32327" + }, + { + "city": "Pawtucket", + "state": "Rhode Island", + "zip": "2860" + }, + { + "city": "Londonderry", + "state": "New Hampshire", + "zip": "3053" + }, + { + "city": "Ponchatoula", + "state": "Louisiana", + "zip": "70454" + }, + { + "city": "Belleville", + "state": "Michigan", + "zip": "48111" + }, + { + "city": "Bowling Green", + "state": "Ohio", + "zip": "43402" + }, + { + "city": "Kansas City", + "state": "Missouri", + "zip": "64152" + }, + { + "city": "Olathe", + "state": "Kansas", + "zip": "66061" + }, + { + "city": "Fresno", + "state": "Texas", + "zip": "77545" + }, + { + "city": "Salt Lake City", + "state": "Utah", + "zip": "84118" + }, + { + "city": "Riverside", + "state": "California", + "zip": "92503" + }, + { + "city": "Coos Bay", + "state": "Oregon", + "zip": "97420" + }, + { + "city": "Fort Worth", + "state": "Texas", + "zip": "76140" + }, + { + "city": "Spring", + "state": "Texas", + "zip": "77382" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85035" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11219" + }, + { + "city": "Mechanicsburg", + "state": "Pennsylvania", + "zip": "17055" + }, + { + "city": "Jonesboro", + "state": "Georgia", + "zip": "30238" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11205" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11225" + }, + { + "city": "Largo", + "state": "Florida", + "zip": "33771" + }, + { + "city": "Lake Worth Beach", + "state": "Florida", + "zip": "33460" + }, + { + "city": "Powell", + "state": "Tennessee", + "zip": "37849" + }, + { + "city": "Altoona", + "state": "Pennsylvania", + "zip": "16601" + }, + { + "city": "Mount Airy", + "state": "Maryland", + "zip": "21771" + }, + { + "city": "Fort Lauderdale", + "state": "Florida", + "zip": "33334" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78209" + }, + { + "city": "Glendora", + "state": "California", + "zip": "91740" + }, + { + "city": "Carlsbad", + "state": "California", + "zip": "92009" + }, + { + "city": "Newburgh", + "state": "Indiana", + "zip": "47630" + }, + { + "city": "Neptune", + "state": "New Jersey", + "zip": "7753" + }, + { + "city": "Austin", + "state": "Texas", + "zip": "78746" + }, + { + "city": "Aurora", + "state": "Colorado", + "zip": "80014" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85016" + }, + { + "city": "Chandler", + "state": "Arizona", + "zip": "85249" + }, + { + "city": "Bartlett", + "state": "Illinois", + "zip": "60103" + }, + { + "city": "Broken Arrow", + "state": "Oklahoma", + "zip": "74012" + }, + { + "city": "Fort Worth", + "state": "Texas", + "zip": "76134" + }, + { + "city": "Round Rock", + "state": "Texas", + "zip": "78681" + }, + { + "city": "Cathedral City", + "state": "California", + "zip": "92234" + }, + { + "city": "Madera", + "state": "California", + "zip": "93638" + }, + { + "city": "Santa Rosa", + "state": "California", + "zip": "95407" + }, + { + "city": "New Bern", + "state": "North Carolina", + "zip": "28560" + }, + { + "city": "Oklahoma City", + "state": "Oklahoma", + "zip": "73119" + }, + { + "city": "Denver", + "state": "Colorado", + "zip": "80231" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85008" + }, + { + "city": "Allen", + "state": "Texas", + "zip": "75013" + }, + { + "city": "Santa Ana", + "state": "California", + "zip": "92705" + }, + { + "city": "Greenville", + "state": "South Carolina", + "zip": "29617" + }, + { + "city": "Vero Beach", + "state": "Florida", + "zip": "32962" + }, + { + "city": "Camarillo", + "state": "California", + "zip": "93010" + }, + { + "city": "Newberg", + "state": "Oregon", + "zip": "97132" + }, + { + "city": "Covington", + "state": "Louisiana", + "zip": "70433" + }, + { + "city": "Costa Mesa", + "state": "California", + "zip": "92627" + }, + { + "city": "Stratford", + "state": "Connecticut", + "zip": "6614" + }, + { + "city": "Calabasas", + "state": "California", + "zip": "91302" + }, + { + "city": "Modesto", + "state": "California", + "zip": "95351" + }, + { + "city": "Dallas", + "state": "Texas", + "zip": "75229" + }, + { + "city": "Salt Lake City", + "state": "Utah", + "zip": "84107" + }, + { + "city": "Mesa", + "state": "Arizona", + "zip": "85209" + }, + { + "city": "Ventura", + "state": "California", + "zip": "93003" + }, + { + "city": "Gaithersburg", + "state": "Maryland", + "zip": "20877" + }, + { + "city": "Holly Springs", + "state": "North Carolina", + "zip": "27540" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89108" + }, + { + "city": "Saint Paul", + "state": "Minnesota", + "zip": "55110" + }, + { + "city": "Las Piedras", + "state": "Puerto Rico", + "zip": "771" + }, + { + "city": "Nottingham", + "state": "Maryland", + "zip": "21236" + }, + { + "city": "Frederick", + "state": "Maryland", + "zip": "21703" + }, + { + "city": "Winchester", + "state": "Virginia", + "zip": "22602" + }, + { + "city": "San Francisco", + "state": "California", + "zip": "94102" + }, + { + "city": "Ukiah", + "state": "California", + "zip": "95482" + }, + { + "city": "Red Bluff", + "state": "California", + "zip": "96080" + }, + { + "city": "Anchorage", + "state": "Alaska", + "zip": "99502" + }, + { + "city": "Anchorage", + "state": "Alaska", + "zip": "99504" + }, + { + "city": "San Gabriel", + "state": "California", + "zip": "91775" + }, + { + "city": "Lake Elsinore", + "state": "California", + "zip": "92530" + }, + { + "city": "Santa Ana", + "state": "California", + "zip": "92703" + }, + { + "city": "Kannapolis", + "state": "North Carolina", + "zip": "28083" + }, + { + "city": "Newhall", + "state": "California", + "zip": "91321" + }, + { + "city": "Oakdale", + "state": "California", + "zip": "95361" + }, + { + "city": "Valdosta", + "state": "Georgia", + "zip": "31601" + }, + { + "city": "Clarksville", + "state": "Tennessee", + "zip": "37043" + }, + { + "city": "Grand Rapids", + "state": "Michigan", + "zip": "49546" + }, + { + "city": "Jackson Heights", + "state": "New York", + "zip": "11372" + }, + { + "city": "Elgin", + "state": "Illinois", + "zip": "60120" + }, + { + "city": "Portland", + "state": "Oregon", + "zip": "97206" + }, + { + "city": "Elizabeth City", + "state": "North Carolina", + "zip": "27909" + }, + { + "city": "Lithonia", + "state": "Georgia", + "zip": "30058" + }, + { + "city": "Atlanta", + "state": "Georgia", + "zip": "30310" + }, + { + "city": "Waterville", + "state": "Maine", + "zip": "4901" + }, + { + "city": "Greensboro", + "state": "North Carolina", + "zip": "27407" + }, + { + "city": "Charlotte", + "state": "North Carolina", + "zip": "28277" + }, + { + "city": "Greenwood", + "state": "Indiana", + "zip": "46142" + }, + { + "city": "Opelousas", + "state": "Louisiana", + "zip": "70570" + }, + { + "city": "Macungie", + "state": "Pennsylvania", + "zip": "18062" + }, + { + "city": "Sterling", + "state": "Virginia", + "zip": "20165" + }, + { + "city": "Front Royal", + "state": "Virginia", + "zip": "22630" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33196" + }, + { + "city": "Whittier", + "state": "California", + "zip": "90605" + }, + { + "city": "Santa Cruz", + "state": "California", + "zip": "95060" + }, + { + "city": "Salem", + "state": "Oregon", + "zip": "97303" + }, + { + "city": "Amarillo", + "state": "Texas", + "zip": "79106" + }, + { + "city": "Bronx", + "state": "New York", + "zip": "10473" + }, + { + "city": "Leland", + "state": "North Carolina", + "zip": "28451" + }, + { + "city": "Lancaster", + "state": "South Carolina", + "zip": "29720" + }, + { + "city": "Louisville", + "state": "Kentucky", + "zip": "40214" + }, + { + "city": "Downey", + "state": "California", + "zip": "90241" + }, + { + "city": "Emeryville", + "state": "California", + "zip": "94608" + }, + { + "city": "Kihei", + "state": "Hawaii", + "zip": "96753" + }, + { + "city": "Waipahu", + "state": "Hawaii", + "zip": "96797" + }, + { + "city": "Cordova", + "state": "Tennessee", + "zip": "38016" + }, + { + "city": "Hattiesburg", + "state": "Mississippi", + "zip": "39402" + }, + { + "city": "Monroe", + "state": "Michigan", + "zip": "48162" + }, + { + "city": "Streamwood", + "state": "Illinois", + "zip": "60107" + }, + { + "city": "Grand Prairie", + "state": "Texas", + "zip": "75050" + }, + { + "city": "Warwick", + "state": "Rhode Island", + "zip": "2886" + }, + { + "city": "Emporia", + "state": "Kansas", + "zip": "66801" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77033" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89113" + }, + { + "city": "Fontana", + "state": "California", + "zip": "92337" + }, + { + "city": "Ashland", + "state": "Oregon", + "zip": "97520" + }, + { + "city": "Stockton", + "state": "California", + "zip": "95207" + }, + { + "city": "Greenwich", + "state": "Connecticut", + "zip": "6830" + }, + { + "city": "Salt Lake City", + "state": "Utah", + "zip": "84115" + }, + { + "city": "Tuscaloosa", + "state": "Alabama", + "zip": "35401" + }, + { + "city": "Detroit", + "state": "Michigan", + "zip": "48223" + }, + { + "city": "Trenton", + "state": "New Jersey", + "zip": "8611" + }, + { + "city": "Syosset", + "state": "New York", + "zip": "11791" + }, + { + "city": "Herndon", + "state": "Virginia", + "zip": "20170" + }, + { + "city": "Dundalk", + "state": "Maryland", + "zip": "21222" + }, + { + "city": "Simpsonville", + "state": "South Carolina", + "zip": "29681" + }, + { + "city": "Jonesboro", + "state": "Arkansas", + "zip": "72404" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19149" + }, + { + "city": "Orlando", + "state": "Florida", + "zip": "32811" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90031" + }, + { + "city": "Lodi", + "state": "California", + "zip": "95240" + }, + { + "city": "Metairie", + "state": "Louisiana", + "zip": "70001" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78217" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78245" + }, + { + "city": "Orange", + "state": "New Jersey", + "zip": "7050" + }, + { + "city": "Richland", + "state": "Washington", + "zip": "99352" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77055" + }, + { + "city": "North Andover", + "state": "Massachusetts", + "zip": "1845" + }, + { + "city": "Reading", + "state": "Massachusetts", + "zip": "1867" + }, + { + "city": "Silver Spring", + "state": "Maryland", + "zip": "20910" + }, + { + "city": "Utica", + "state": "New York", + "zip": "13502" + }, + { + "city": "Langhorne", + "state": "Pennsylvania", + "zip": "19047" + }, + { + "city": "Williamsburg", + "state": "Virginia", + "zip": "23185" + }, + { + "city": "Park Ridge", + "state": "Illinois", + "zip": "60068" + }, + { + "city": "Austin", + "state": "Texas", + "zip": "78745" + }, + { + "city": "Tucson", + "state": "Arizona", + "zip": "85706" + }, + { + "city": "East Elmhurst", + "state": "New York", + "zip": "11370" + }, + { + "city": "Selden", + "state": "New York", + "zip": "11784" + }, + { + "city": "Westminster", + "state": "Maryland", + "zip": "21157" + }, + { + "city": "Englewood", + "state": "New Jersey", + "zip": "7631" + }, + { + "city": "Mandeville", + "state": "Louisiana", + "zip": "70448" + }, + { + "city": "Gilbert", + "state": "Arizona", + "zip": "85298" + }, + { + "city": "Watertown", + "state": "New York", + "zip": "13601" + }, + { + "city": "Pittsford", + "state": "New York", + "zip": "14534" + }, + { + "city": "Amherst", + "state": "Massachusetts", + "zip": "1002" + }, + { + "city": "Columbia", + "state": "Missouri", + "zip": "65203" + }, + { + "city": "Abilene", + "state": "Texas", + "zip": "79601" + }, + { + "city": "Aurora", + "state": "Colorado", + "zip": "80010" + }, + { + "city": "Albuquerque", + "state": "New Mexico", + "zip": "87107" + }, + { + "city": "Albuquerque", + "state": "New Mexico", + "zip": "87109" + }, + { + "city": "Whittier", + "state": "California", + "zip": "90606" + }, + { + "city": "Wildomar", + "state": "California", + "zip": "92595" + }, + { + "city": "Bakersfield", + "state": "California", + "zip": "93308" + }, + { + "city": "Mahopac", + "state": "New York", + "zip": "10541" + }, + { + "city": "Clinton", + "state": "North Carolina", + "zip": "28328" + }, + { + "city": "Winder", + "state": "Georgia", + "zip": "30680" + }, + { + "city": "Fort Wayne", + "state": "Indiana", + "zip": "46815" + }, + { + "city": "Pontiac", + "state": "Michigan", + "zip": "48340" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77096" + }, + { + "city": "Glendale", + "state": "California", + "zip": "91206" + }, + { + "city": "San Francisco", + "state": "California", + "zip": "94118" + }, + { + "city": "Greer", + "state": "South Carolina", + "zip": "29650" + }, + { + "city": "Lubbock", + "state": "Texas", + "zip": "79423" + }, + { + "city": "Odessa", + "state": "Texas", + "zip": "79763" + }, + { + "city": "Howard Beach", + "state": "New York", + "zip": "11414" + }, + { + "city": "Temple", + "state": "Texas", + "zip": "76502" + }, + { + "city": "Middleburg", + "state": "Florida", + "zip": "32068" + }, + { + "city": "Mcminnville", + "state": "Tennessee", + "zip": "37110" + }, + { + "city": "Petaluma", + "state": "California", + "zip": "94952" + }, + { + "city": "Long Beach", + "state": "California", + "zip": "90815" + }, + { + "city": "Huntington Beach", + "state": "California", + "zip": "92646" + }, + { + "city": "Gaithersburg", + "state": "Maryland", + "zip": "20879" + }, + { + "city": "Kansas City", + "state": "Missouri", + "zip": "64133" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78242" + }, + { + "city": "Brownsville", + "state": "Texas", + "zip": "78521" + }, + { + "city": "Moscow", + "state": "Idaho", + "zip": "83843" + }, + { + "city": "Augusta", + "state": "Georgia", + "zip": "30907" + }, + { + "city": "Overland Park", + "state": "Kansas", + "zip": "66213" + }, + { + "city": "Roswell", + "state": "New Mexico", + "zip": "88201" + }, + { + "city": "San Mateo", + "state": "California", + "zip": "94403" + }, + { + "city": "Riverdale", + "state": "Illinois", + "zip": "60827" + }, + { + "city": "Wichita", + "state": "Kansas", + "zip": "67207" + }, + { + "city": "Dallas", + "state": "Texas", + "zip": "75254" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77087" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90001" + }, + { + "city": "Oceanside", + "state": "California", + "zip": "92054" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19132" + }, + { + "city": "Waukesha", + "state": "Wisconsin", + "zip": "53186" + }, + { + "city": "Appleton", + "state": "Wisconsin", + "zip": "54914" + }, + { + "city": "Golden", + "state": "Colorado", + "zip": "80401" + }, + { + "city": "San Diego", + "state": "California", + "zip": "92109" + }, + { + "city": "Puyallup", + "state": "Washington", + "zip": "98373" + }, + { + "city": "Wethersfield", + "state": "Connecticut", + "zip": "6109" + }, + { + "city": "Flushing", + "state": "New York", + "zip": "11367" + }, + { + "city": "Kent", + "state": "Washington", + "zip": "98030" + }, + { + "city": "Chandler", + "state": "Arizona", + "zip": "85226" + }, + { + "city": "Santa Maria", + "state": "California", + "zip": "93454" + }, + { + "city": "Bronx", + "state": "New York", + "zip": "10453" + }, + { + "city": "Doylestown", + "state": "Pennsylvania", + "zip": "18901" + }, + { + "city": "Mechanicsville", + "state": "Virginia", + "zip": "23111" + }, + { + "city": "Fort Collins", + "state": "Colorado", + "zip": "80521" + }, + { + "city": "Houma", + "state": "Louisiana", + "zip": "70360" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77007" + }, + { + "city": "Milton", + "state": "Massachusetts", + "zip": "2186" + }, + { + "city": "Tifton", + "state": "Georgia", + "zip": "31794" + }, + { + "city": "Tolleson", + "state": "Arizona", + "zip": "85353" + }, + { + "city": "Portland", + "state": "Oregon", + "zip": "97212" + }, + { + "city": "Cumberland", + "state": "Rhode Island", + "zip": "2864" + }, + { + "city": "Forest Hills", + "state": "New York", + "zip": "11375" + }, + { + "city": "Schenectady", + "state": "New York", + "zip": "12306" + }, + { + "city": "Oroville", + "state": "California", + "zip": "95966" + }, + { + "city": "Madison", + "state": "Wisconsin", + "zip": "53704" + }, + { + "city": "La Jolla", + "state": "California", + "zip": "92037" + }, + { + "city": "Burlington", + "state": "Massachusetts", + "zip": "1803" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19143" + }, + { + "city": "Collegeville", + "state": "Pennsylvania", + "zip": "19426" + }, + { + "city": "Arlington", + "state": "Virginia", + "zip": "22202" + }, + { + "city": "Niles", + "state": "Michigan", + "zip": "49120" + }, + { + "city": "Indianapolis", + "state": "Indiana", + "zip": "46226" + }, + { + "city": "Detroit", + "state": "Michigan", + "zip": "48234" + }, + { + "city": "Henrico", + "state": "Virginia", + "zip": "23228" + }, + { + "city": "Meridian", + "state": "Idaho", + "zip": "83642" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60605" + }, + { + "city": "Sulphur", + "state": "Louisiana", + "zip": "70663" + }, + { + "city": "Irmo", + "state": "South Carolina", + "zip": "29063" + }, + { + "city": "Danville", + "state": "California", + "zip": "94506" + }, + { + "city": "Denham Springs", + "state": "Louisiana", + "zip": "70726" + }, + { + "city": "Marion", + "state": "Ohio", + "zip": "43302" + }, + { + "city": "Fort Worth", + "state": "Texas", + "zip": "76119" + }, + { + "city": "Issaquah", + "state": "Washington", + "zip": "98029" + }, + { + "city": "Sammamish", + "state": "Washington", + "zip": "98074" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77008" + }, + { + "city": "Kissimmee", + "state": "Florida", + "zip": "34744" + }, + { + "city": "Grand Haven", + "state": "Michigan", + "zip": "49417" + }, + { + "city": "Rowland Heights", + "state": "California", + "zip": "91748" + }, + { + "city": "San Leandro", + "state": "California", + "zip": "94577" + }, + { + "city": "Auburn", + "state": "Alabama", + "zip": "36830" + }, + { + "city": "Chesterton", + "state": "Indiana", + "zip": "46304" + }, + { + "city": "La Grange", + "state": "Illinois", + "zip": "60525" + }, + { + "city": "Pasadena", + "state": "Texas", + "zip": "77502" + }, + { + "city": "Greer", + "state": "South Carolina", + "zip": "29651" + }, + { + "city": "Berwyn", + "state": "Illinois", + "zip": "60402" + }, + { + "city": "Woodburn", + "state": "Oregon", + "zip": "97071" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33172" + }, + { + "city": "Rockville", + "state": "Maryland", + "zip": "20852" + }, + { + "city": "Irving", + "state": "Texas", + "zip": "75061" + }, + { + "city": "Humble", + "state": "Texas", + "zip": "77346" + }, + { + "city": "Henderson", + "state": "Nevada", + "zip": "89015" + }, + { + "city": "Athens", + "state": "Alabama", + "zip": "35611" + }, + { + "city": "Milford", + "state": "Massachusetts", + "zip": "1757" + }, + { + "city": "Danville", + "state": "Kentucky", + "zip": "40422" + }, + { + "city": "Valley Stream", + "state": "New York", + "zip": "11580" + }, + { + "city": "Baltimore", + "state": "Maryland", + "zip": "21215" + }, + { + "city": "Middletown", + "state": "Ohio", + "zip": "45042" + }, + { + "city": "Mission Viejo", + "state": "California", + "zip": "92692" + }, + { + "city": "Mountain View", + "state": "California", + "zip": "94040" + }, + { + "city": "Homestead", + "state": "Florida", + "zip": "33030" + }, + { + "city": "Fort Lauderdale", + "state": "Florida", + "zip": "33312" + }, + { + "city": "Norwich", + "state": "Connecticut", + "zip": "6360" + }, + { + "city": "Rochester", + "state": "New York", + "zip": "14626" + }, + { + "city": "Beaver Falls", + "state": "Pennsylvania", + "zip": "15010" + }, + { + "city": "Jacksonville", + "state": "Florida", + "zip": "32209" + }, + { + "city": "Goodlettsville", + "state": "Tennessee", + "zip": "37072" + }, + { + "city": "Wyandotte", + "state": "Michigan", + "zip": "48192" + }, + { + "city": "Lawton", + "state": "Oklahoma", + "zip": "73505" + }, + { + "city": "Gastonia", + "state": "North Carolina", + "zip": "28056" + }, + { + "city": "Beachwood", + "state": "Ohio", + "zip": "44122" + }, + { + "city": "Saint Paul", + "state": "Minnesota", + "zip": "55109" + }, + { + "city": "Minneapolis", + "state": "Minnesota", + "zip": "55406" + }, + { + "city": "Dallas", + "state": "Texas", + "zip": "75230" + }, + { + "city": "Boulder", + "state": "Colorado", + "zip": "80303" + }, + { + "city": "Fullerton", + "state": "California", + "zip": "92833" + }, + { + "city": "Renton", + "state": "Washington", + "zip": "98058" + }, + { + "city": "Ferndale", + "state": "Washington", + "zip": "98248" + }, + { + "city": "Northfield", + "state": "Minnesota", + "zip": "55057" + }, + { + "city": "Waterbury", + "state": "Connecticut", + "zip": "6705" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89119" + }, + { + "city": "Aiken", + "state": "South Carolina", + "zip": "29803" + }, + { + "city": "Duarte", + "state": "California", + "zip": "91010" + }, + { + "city": "Fallbrook", + "state": "California", + "zip": "92028" + }, + { + "city": "Vista", + "state": "California", + "zip": "92084" + }, + { + "city": "Sun Prairie", + "state": "Wisconsin", + "zip": "53590" + }, + { + "city": "Beaverton", + "state": "Oregon", + "zip": "97005" + }, + { + "city": "West Haven", + "state": "Connecticut", + "zip": "6516" + }, + { + "city": "Rio Grande City", + "state": "Texas", + "zip": "78582" + }, + { + "city": "North Bergen", + "state": "New Jersey", + "zip": "7047" + }, + { + "city": "Garden City", + "state": "New York", + "zip": "11530" + }, + { + "city": "Sumter", + "state": "South Carolina", + "zip": "29150" + }, + { + "city": "Mission", + "state": "Texas", + "zip": "78574" + }, + { + "city": "Wooster", + "state": "Ohio", + "zip": "44691" + }, + { + "city": "Urbandale", + "state": "Iowa", + "zip": "50322" + }, + { + "city": "Madison", + "state": "Wisconsin", + "zip": "53711" + }, + { + "city": "Immokalee", + "state": "Florida", + "zip": "34142" + }, + { + "city": "Rockford", + "state": "Michigan", + "zip": "49341" + }, + { + "city": "New York", + "state": "New York", + "zip": "10002" + }, + { + "city": "Hempstead", + "state": "New York", + "zip": "11550" + }, + { + "city": "Forest", + "state": "Virginia", + "zip": "24551" + }, + { + "city": "Huntersville", + "state": "North Carolina", + "zip": "28078" + }, + { + "city": "Elkridge", + "state": "Maryland", + "zip": "21075" + }, + { + "city": "Augusta", + "state": "Georgia", + "zip": "30906" + }, + { + "city": "West Palm Beach", + "state": "Florida", + "zip": "33401" + }, + { + "city": "Chattanooga", + "state": "Tennessee", + "zip": "37421" + }, + { + "city": "Louisville", + "state": "Kentucky", + "zip": "40291" + }, + { + "city": "Dayton", + "state": "Ohio", + "zip": "45420" + }, + { + "city": "Boynton Beach", + "state": "Florida", + "zip": "33435" + }, + { + "city": "Franklin", + "state": "Tennessee", + "zip": "37067" + }, + { + "city": "Southaven", + "state": "Mississippi", + "zip": "38671" + }, + { + "city": "Maumee", + "state": "Ohio", + "zip": "43537" + }, + { + "city": "Dallas", + "state": "Texas", + "zip": "75224" + }, + { + "city": "Jersey City", + "state": "New Jersey", + "zip": "7304" + }, + { + "city": "Chesapeake", + "state": "Virginia", + "zip": "23322" + }, + { + "city": "Austin", + "state": "Texas", + "zip": "78704" + }, + { + "city": "Denver", + "state": "Colorado", + "zip": "80209" + }, + { + "city": "Victorville", + "state": "California", + "zip": "92394" + }, + { + "city": "Kailua Kona", + "state": "Hawaii", + "zip": "96740" + }, + { + "city": "Newnan", + "state": "Georgia", + "zip": "30265" + }, + { + "city": "Northridge", + "state": "California", + "zip": "91325" + }, + { + "city": "Rowlett", + "state": "Texas", + "zip": "75089" + }, + { + "city": "Aurora", + "state": "Colorado", + "zip": "80015" + }, + { + "city": "Colorado Springs", + "state": "Colorado", + "zip": "80907" + }, + { + "city": "Campbell", + "state": "California", + "zip": "95008" + }, + { + "city": "Westfield", + "state": "New Jersey", + "zip": "7090" + }, + { + "city": "Farmington", + "state": "Missouri", + "zip": "63640" + }, + { + "city": "Kingston", + "state": "New York", + "zip": "12401" + }, + { + "city": "Honolulu", + "state": "Hawaii", + "zip": "96817" + }, + { + "city": "Portland", + "state": "Oregon", + "zip": "97222" + }, + { + "city": "Portland", + "state": "Oregon", + "zip": "97229" + }, + { + "city": "Lawrence Township", + "state": "New Jersey", + "zip": "8648" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60651" + }, + { + "city": "Georgetown", + "state": "Texas", + "zip": "78626" + }, + { + "city": "San Tan Valley", + "state": "Arizona", + "zip": "85143" + }, + { + "city": "Tucson", + "state": "Arizona", + "zip": "85746" + }, + { + "city": "Atlanta", + "state": "Georgia", + "zip": "30345" + }, + { + "city": "Tinley Park", + "state": "Illinois", + "zip": "60487" + }, + { + "city": "Coeur D Alene", + "state": "Idaho", + "zip": "83815" + }, + { + "city": "Mesa", + "state": "Arizona", + "zip": "85201" + }, + { + "city": "Van Nuys", + "state": "California", + "zip": "91406" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19136" + }, + { + "city": "Lawrenceville", + "state": "Georgia", + "zip": "30044" + }, + { + "city": "Colton", + "state": "California", + "zip": "92324" + }, + { + "city": "Huntsville", + "state": "Alabama", + "zip": "35811" + }, + { + "city": "Fairfield", + "state": "Ohio", + "zip": "45014" + }, + { + "city": "Richardson", + "state": "Texas", + "zip": "75081" + }, + { + "city": "San Marcos", + "state": "Texas", + "zip": "78666" + }, + { + "city": "Boulder", + "state": "Colorado", + "zip": "80302" + }, + { + "city": "Mesa", + "state": "Arizona", + "zip": "85210" + }, + { + "city": "Sonoma", + "state": "California", + "zip": "95476" + }, + { + "city": "Salem", + "state": "Oregon", + "zip": "97306" + }, + { + "city": "Seattle", + "state": "Washington", + "zip": "98122" + }, + { + "city": "Manahawkin", + "state": "New Jersey", + "zip": "8050" + }, + { + "city": "Virginia Beach", + "state": "Virginia", + "zip": "23464" + }, + { + "city": "Mobile", + "state": "Alabama", + "zip": "36695" + }, + { + "city": "Shelbyville", + "state": "Kentucky", + "zip": "40065" + }, + { + "city": "Paducah", + "state": "Kentucky", + "zip": "42003" + }, + { + "city": "Massillon", + "state": "Ohio", + "zip": "44646" + }, + { + "city": "Batavia", + "state": "Ohio", + "zip": "45103" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77034" + }, + { + "city": "Utuado", + "state": "Puerto Rico", + "zip": "641" + }, + { + "city": "Pittsfield", + "state": "Massachusetts", + "zip": "1201" + }, + { + "city": "Shawnee", + "state": "Kansas", + "zip": "66216" + }, + { + "city": "Fort Worth", + "state": "Texas", + "zip": "76179" + }, + { + "city": "Leander", + "state": "Texas", + "zip": "78641" + }, + { + "city": "Birmingham", + "state": "Alabama", + "zip": "35226" + }, + { + "city": "Pittsburgh", + "state": "Pennsylvania", + "zip": "15227" + }, + { + "city": "Dawsonville", + "state": "Georgia", + "zip": "30534" + }, + { + "city": "Crestview", + "state": "Florida", + "zip": "32539" + }, + { + "city": "Tucson", + "state": "Arizona", + "zip": "85730" + }, + { + "city": "San Diego", + "state": "California", + "zip": "92127" + }, + { + "city": "Selma", + "state": "California", + "zip": "93662" + }, + { + "city": "Folsom", + "state": "California", + "zip": "95630" + }, + { + "city": "Baltimore", + "state": "Maryland", + "zip": "21213" + }, + { + "city": "Fredericksburg", + "state": "Virginia", + "zip": "22406" + }, + { + "city": "Dallas", + "state": "Texas", + "zip": "75208" + }, + { + "city": "Englewood", + "state": "Colorado", + "zip": "80111" + }, + { + "city": "New Britain", + "state": "Connecticut", + "zip": "6051" + }, + { + "city": "Arcadia", + "state": "California", + "zip": "91006" + }, + { + "city": "Banning", + "state": "California", + "zip": "92220" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19140" + }, + { + "city": "Desoto", + "state": "Texas", + "zip": "75115" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78221" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89142" + }, + { + "city": "Bakersfield", + "state": "California", + "zip": "93309" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33130" + }, + { + "city": "Saint Petersburg", + "state": "Florida", + "zip": "33710" + }, + { + "city": "Kokomo", + "state": "Indiana", + "zip": "46902" + }, + { + "city": "Middletown", + "state": "Connecticut", + "zip": "6457" + }, + { + "city": "Halethorpe", + "state": "Maryland", + "zip": "21227" + }, + { + "city": "Missoula", + "state": "Montana", + "zip": "59801" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11214" + }, + { + "city": "Palatine", + "state": "Illinois", + "zip": "60074" + }, + { + "city": "San Juan", + "state": "Puerto Rico", + "zip": "924" + }, + { + "city": "New Castle", + "state": "Delaware", + "zip": "19720" + }, + { + "city": "Enterprise", + "state": "Alabama", + "zip": "36330" + }, + { + "city": "Fresno", + "state": "California", + "zip": "93703" + }, + { + "city": "Midlothian", + "state": "Virginia", + "zip": "23113" + }, + { + "city": "Palmdale", + "state": "California", + "zip": "93552" + }, + { + "city": "West Lafayette", + "state": "Indiana", + "zip": "47906" + }, + { + "city": "Saginaw", + "state": "Michigan", + "zip": "48603" + }, + { + "city": "Ladera Ranch", + "state": "California", + "zip": "92694" + }, + { + "city": "Nashua", + "state": "New Hampshire", + "zip": "3062" + }, + { + "city": "Hillsborough", + "state": "New Jersey", + "zip": "8844" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11230" + }, + { + "city": "Hyattsville", + "state": "Maryland", + "zip": "20784" + }, + { + "city": "Long Branch", + "state": "New Jersey", + "zip": "7740" + }, + { + "city": "Union City", + "state": "New Jersey", + "zip": "7087" + }, + { + "city": "Newton", + "state": "New Jersey", + "zip": "7860" + }, + { + "city": "Hartsville", + "state": "South Carolina", + "zip": "29550" + }, + { + "city": "Billings", + "state": "Montana", + "zip": "59101" + }, + { + "city": "Mundelein", + "state": "Illinois", + "zip": "60060" + }, + { + "city": "Naples", + "state": "Florida", + "zip": "34116" + }, + { + "city": "Maryville", + "state": "Tennessee", + "zip": "37803" + }, + { + "city": "Macomb", + "state": "Michigan", + "zip": "48044" + }, + { + "city": "Lafayette", + "state": "Indiana", + "zip": "47905" + }, + { + "city": "Colorado Springs", + "state": "Colorado", + "zip": "80916" + }, + { + "city": "Sanford", + "state": "North Carolina", + "zip": "27332" + }, + { + "city": "Azusa", + "state": "California", + "zip": "91702" + }, + { + "city": "Miamisburg", + "state": "Ohio", + "zip": "45342" + }, + { + "city": "Baton Rouge", + "state": "Louisiana", + "zip": "70817" + }, + { + "city": "Stillwater", + "state": "Oklahoma", + "zip": "74075" + }, + { + "city": "Strongsville", + "state": "Ohio", + "zip": "44136" + }, + { + "city": "Fairfield", + "state": "Connecticut", + "zip": "6824" + }, + { + "city": "Norwalk", + "state": "Connecticut", + "zip": "6854" + }, + { + "city": "Jersey City", + "state": "New Jersey", + "zip": "7302" + }, + { + "city": "New York", + "state": "New York", + "zip": "10030" + }, + { + "city": "Poughkeepsie", + "state": "New York", + "zip": "12603" + }, + { + "city": "Tulsa", + "state": "Oklahoma", + "zip": "74136" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89156" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19104" + }, + { + "city": "Glen Burnie", + "state": "Maryland", + "zip": "21060" + }, + { + "city": "Livonia", + "state": "Michigan", + "zip": "48152" + }, + { + "city": "Independence", + "state": "Missouri", + "zip": "64055" + }, + { + "city": "Montgomery", + "state": "Texas", + "zip": "77356" + }, + { + "city": "Oak Harbor", + "state": "Washington", + "zip": "98277" + }, + { + "city": "Pensacola", + "state": "Florida", + "zip": "32506" + }, + { + "city": "Bismarck", + "state": "North Dakota", + "zip": "58504" + }, + { + "city": "Greensburg", + "state": "Pennsylvania", + "zip": "15601" + }, + { + "city": "Conyers", + "state": "Georgia", + "zip": "30012" + }, + { + "city": "Mcdonough", + "state": "Georgia", + "zip": "30253" + }, + { + "city": "Oviedo", + "state": "Florida", + "zip": "32765" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33174" + }, + { + "city": "Eden Prairie", + "state": "Minnesota", + "zip": "55347" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77081" + }, + { + "city": "Austin", + "state": "Texas", + "zip": "78724" + }, + { + "city": "Modesto", + "state": "California", + "zip": "95356" + }, + { + "city": "Sacramento", + "state": "California", + "zip": "95833" + }, + { + "city": "Midlothian", + "state": "Texas", + "zip": "76065" + }, + { + "city": "Seguin", + "state": "Texas", + "zip": "78155" + }, + { + "city": "Broomfield", + "state": "Colorado", + "zip": "80021" + }, + { + "city": "Sunnyvale", + "state": "California", + "zip": "94087" + }, + { + "city": "Hollister", + "state": "California", + "zip": "95023" + }, + { + "city": "Miami Beach", + "state": "Florida", + "zip": "33141" + }, + { + "city": "Vallejo", + "state": "California", + "zip": "94591" + }, + { + "city": "Anderson", + "state": "South Carolina", + "zip": "29621" + }, + { + "city": "Dahlonega", + "state": "Georgia", + "zip": "30533" + }, + { + "city": "Sanford", + "state": "Florida", + "zip": "32773" + }, + { + "city": "Brandon", + "state": "Florida", + "zip": "33511" + }, + { + "city": "Haverhill", + "state": "Massachusetts", + "zip": "1830" + }, + { + "city": "Coraopolis", + "state": "Pennsylvania", + "zip": "15108" + }, + { + "city": "Lakeland", + "state": "Florida", + "zip": "33809" + }, + { + "city": "Green Bay", + "state": "Wisconsin", + "zip": "54313" + }, + { + "city": "Longmont", + "state": "Colorado", + "zip": "80501" + }, + { + "city": "Matawan", + "state": "New Jersey", + "zip": "7747" + }, + { + "city": "Belmont", + "state": "Massachusetts", + "zip": "2478" + }, + { + "city": "Zanesville", + "state": "Ohio", + "zip": "43701" + }, + { + "city": "Greeley", + "state": "Colorado", + "zip": "80634" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85040" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19144" + }, + { + "city": "Pleasanton", + "state": "California", + "zip": "94588" + }, + { + "city": "Chino", + "state": "California", + "zip": "91710" + }, + { + "city": "Grapevine", + "state": "Texas", + "zip": "76051" + }, + { + "city": "Tucson", + "state": "Arizona", + "zip": "85713" + }, + { + "city": "Porter Ranch", + "state": "California", + "zip": "91326" + }, + { + "city": "Spokane", + "state": "Washington", + "zip": "99205" + }, + { + "city": "Lincoln", + "state": "Nebraska", + "zip": "68502" + }, + { + "city": "Lincoln", + "state": "Nebraska", + "zip": "68506" + }, + { + "city": "Denver", + "state": "Colorado", + "zip": "80247" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89139" + }, + { + "city": "Manteca", + "state": "California", + "zip": "95336" + }, + { + "city": "Bristol", + "state": "Connecticut", + "zip": "6010" + }, + { + "city": "East Brunswick", + "state": "New Jersey", + "zip": "8816" + }, + { + "city": "Waxhaw", + "state": "North Carolina", + "zip": "28173" + }, + { + "city": "Bedford", + "state": "Texas", + "zip": "76021" + }, + { + "city": "Boulder", + "state": "Colorado", + "zip": "80304" + }, + { + "city": "Apopka", + "state": "Florida", + "zip": "32712" + }, + { + "city": "Wilmington", + "state": "Delaware", + "zip": "19808" + }, + { + "city": "Burlington", + "state": "New Jersey", + "zip": "8016" + }, + { + "city": "Port Chester", + "state": "New York", + "zip": "10573" + }, + { + "city": "Apex", + "state": "North Carolina", + "zip": "27502" + }, + { + "city": "Raleigh", + "state": "North Carolina", + "zip": "27615" + }, + { + "city": "Jacksonville", + "state": "North Carolina", + "zip": "28540" + }, + { + "city": "Murrells Inlet", + "state": "South Carolina", + "zip": "29576" + }, + { + "city": "Spring", + "state": "Texas", + "zip": "77379" + }, + { + "city": "Silver Spring", + "state": "Maryland", + "zip": "20902" + }, + { + "city": "Chapel Hill", + "state": "North Carolina", + "zip": "27514" + }, + { + "city": "Findlay", + "state": "Ohio", + "zip": "45840" + }, + { + "city": "El Paso", + "state": "Texas", + "zip": "79924" + }, + { + "city": "Land O'Lakes", + "state": "Florida", + "zip": "34638" + }, + { + "city": "Fenton", + "state": "Michigan", + "zip": "48430" + }, + { + "city": "Salem", + "state": "Ohio", + "zip": "44460" + }, + { + "city": "Williston", + "state": "North Dakota", + "zip": "58801" + }, + { + "city": "Corsicana", + "state": "Texas", + "zip": "75110" + }, + { + "city": "Virginia Beach", + "state": "Virginia", + "zip": "23454" + }, + { + "city": "Statesboro", + "state": "Georgia", + "zip": "30458" + }, + { + "city": "Port Saint Lucie", + "state": "Florida", + "zip": "34953" + }, + { + "city": "Indianapolis", + "state": "Indiana", + "zip": "46218" + }, + { + "city": "Oak Creek", + "state": "Wisconsin", + "zip": "53154" + }, + { + "city": "Torrington", + "state": "Connecticut", + "zip": "6790" + }, + { + "city": "Latrobe", + "state": "Pennsylvania", + "zip": "15650" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90059" + }, + { + "city": "Spring Valley", + "state": "California", + "zip": "91977" + }, + { + "city": "Caguas", + "state": "Puerto Rico", + "zip": "725" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11220" + }, + { + "city": "Edinburg", + "state": "Texas", + "zip": "78539" + }, + { + "city": "San Francisco", + "state": "California", + "zip": "94107" + }, + { + "city": "Santa Clara", + "state": "California", + "zip": "95050" + }, + { + "city": "El Paso", + "state": "Texas", + "zip": "79930" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89130" + }, + { + "city": "Tampa", + "state": "Florida", + "zip": "33604" + }, + { + "city": "Jenison", + "state": "Michigan", + "zip": "49428" + }, + { + "city": "Cambridge", + "state": "Massachusetts", + "zip": "2139" + }, + { + "city": "Rome", + "state": "New York", + "zip": "13440" + }, + { + "city": "Oklahoma City", + "state": "Oklahoma", + "zip": "73132" + }, + { + "city": "Cleveland", + "state": "Tennessee", + "zip": "37323" + }, + { + "city": "Waxahachie", + "state": "Texas", + "zip": "75165" + }, + { + "city": "Beverly", + "state": "Massachusetts", + "zip": "1915" + }, + { + "city": "Long Beach", + "state": "New York", + "zip": "11561" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19148" + }, + { + "city": "Mebane", + "state": "North Carolina", + "zip": "27302" + }, + { + "city": "Pasadena", + "state": "California", + "zip": "91107" + }, + { + "city": "Oxnard", + "state": "California", + "zip": "93035" + }, + { + "city": "Washington", + "state": "North Carolina", + "zip": "27889" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77053" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78240" + }, + { + "city": "Lakeland", + "state": "Florida", + "zip": "33805" + }, + { + "city": "Brighton", + "state": "Michigan", + "zip": "48116" + }, + { + "city": "Bardstown", + "state": "Kentucky", + "zip": "40004" + }, + { + "city": "Westminster", + "state": "California", + "zip": "92683" + }, + { + "city": "Addison", + "state": "Illinois", + "zip": "60101" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78229" + }, + { + "city": "Surprise", + "state": "Arizona", + "zip": "85374" + }, + { + "city": "Maryville", + "state": "Tennessee", + "zip": "37804" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90037" + }, + { + "city": "San Jose", + "state": "California", + "zip": "95133" + }, + { + "city": "Merced", + "state": "California", + "zip": "95340" + }, + { + "city": "Saratoga Springs", + "state": "New York", + "zip": "12866" + }, + { + "city": "Glenside", + "state": "Pennsylvania", + "zip": "19038" + }, + { + "city": "Buffalo", + "state": "New York", + "zip": "14226" + }, + { + "city": "Davison", + "state": "Michigan", + "zip": "48423" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60625" + }, + { + "city": "Carolina", + "state": "Puerto Rico", + "zip": "985" + }, + { + "city": "Waterbury", + "state": "Connecticut", + "zip": "6704" + }, + { + "city": "Camp Hill", + "state": "Pennsylvania", + "zip": "17011" + }, + { + "city": "San Juan", + "state": "Puerto Rico", + "zip": "921" + }, + { + "city": "Middletown", + "state": "New York", + "zip": "10940" + }, + { + "city": "High Point", + "state": "North Carolina", + "zip": "27265" + }, + { + "city": "Summerville", + "state": "South Carolina", + "zip": "29483" + }, + { + "city": "Dallas", + "state": "Georgia", + "zip": "30132" + }, + { + "city": "Orlando", + "state": "Florida", + "zip": "32818" + }, + { + "city": "Mesa", + "state": "Arizona", + "zip": "85208" + }, + { + "city": "Long Beach", + "state": "California", + "zip": "90810" + }, + { + "city": "Desert Hot Springs", + "state": "California", + "zip": "92240" + }, + { + "city": "Oxnard", + "state": "California", + "zip": "93033" + }, + { + "city": "Sunnyside", + "state": "New York", + "zip": "11104" + }, + { + "city": "Redondo Beach", + "state": "California", + "zip": "90277" + }, + { + "city": "Visalia", + "state": "California", + "zip": "93291" + }, + { + "city": "Michigan City", + "state": "Indiana", + "zip": "46360" + }, + { + "city": "Lansdale", + "state": "Pennsylvania", + "zip": "19446" + }, + { + "city": "Raleigh", + "state": "North Carolina", + "zip": "27604" + }, + { + "city": "Vernon Hills", + "state": "Illinois", + "zip": "60061" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77036" + }, + { + "city": "Denver", + "state": "Colorado", + "zip": "80227" + }, + { + "city": "Kaysville", + "state": "Utah", + "zip": "84037" + }, + { + "city": "Wylie", + "state": "Texas", + "zip": "75098" + }, + { + "city": "Pueblo", + "state": "Colorado", + "zip": "81004" + }, + { + "city": "Glendale", + "state": "Arizona", + "zip": "85301" + }, + { + "city": "Conroe", + "state": "Texas", + "zip": "77304" + }, + { + "city": "Katy", + "state": "Texas", + "zip": "77494" + }, + { + "city": "Maricopa", + "state": "Arizona", + "zip": "85138" + }, + { + "city": "Escondido", + "state": "California", + "zip": "92027" + }, + { + "city": "Jacksonville", + "state": "Illinois", + "zip": "62650" + }, + { + "city": "Martinez", + "state": "California", + "zip": "94553" + }, + { + "city": "Fayetteville", + "state": "Arkansas", + "zip": "72704" + }, + { + "city": "Longwood", + "state": "Florida", + "zip": "32750" + }, + { + "city": "North Las Vegas", + "state": "Nevada", + "zip": "89084" + }, + { + "city": "Northridge", + "state": "California", + "zip": "91324" + }, + { + "city": "El Sobrante", + "state": "California", + "zip": "94803" + }, + { + "city": "Seattle", + "state": "Washington", + "zip": "98103" + }, + { + "city": "Newport Beach", + "state": "California", + "zip": "92660" + }, + { + "city": "Ennis", + "state": "Texas", + "zip": "75119" + }, + { + "city": "Frederick", + "state": "Maryland", + "zip": "21701" + }, + { + "city": "Orlando", + "state": "Florida", + "zip": "32826" + }, + { + "city": "Dinuba", + "state": "California", + "zip": "93618" + }, + { + "city": "Hudsonville", + "state": "Michigan", + "zip": "49426" + }, + { + "city": "Kansas City", + "state": "Missouri", + "zip": "64118" + }, + { + "city": "Denver", + "state": "Colorado", + "zip": "80204" + }, + { + "city": "Coeur D Alene", + "state": "Idaho", + "zip": "83814" + }, + { + "city": "Gainesville", + "state": "Florida", + "zip": "32607" + }, + { + "city": "North Royalton", + "state": "Ohio", + "zip": "44133" + }, + { + "city": "South El Monte", + "state": "California", + "zip": "91733" + }, + { + "city": "New York", + "state": "New York", + "zip": "10009" + }, + { + "city": "New Windsor", + "state": "New York", + "zip": "12553" + }, + { + "city": "Charlotte", + "state": "North Carolina", + "zip": "28211" + }, + { + "city": "Knoxville", + "state": "Tennessee", + "zip": "37921" + }, + { + "city": "New York", + "state": "New York", + "zip": "10003" + }, + { + "city": "Arlington", + "state": "Texas", + "zip": "76013" + }, + { + "city": "Magnolia", + "state": "Texas", + "zip": "77354" + }, + { + "city": "Beaumont", + "state": "Texas", + "zip": "77706" + }, + { + "city": "North Las Vegas", + "state": "Nevada", + "zip": "89031" + }, + { + "city": "Daly City", + "state": "California", + "zip": "94014" + }, + { + "city": "Summerville", + "state": "South Carolina", + "zip": "29485" + }, + { + "city": "Dallas", + "state": "Texas", + "zip": "75216" + }, + { + "city": "Pensacola", + "state": "Florida", + "zip": "32526" + }, + { + "city": "Suwanee", + "state": "Georgia", + "zip": "30024" + }, + { + "city": "Tampa", + "state": "Florida", + "zip": "33647" + }, + { + "city": "Jackson", + "state": "Mississippi", + "zip": "39209" + }, + { + "city": "Leesburg", + "state": "Virginia", + "zip": "20176" + }, + { + "city": "Saint Louis", + "state": "Missouri", + "zip": "63109" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90032" + }, + { + "city": "Huntington Park", + "state": "California", + "zip": "90255" + }, + { + "city": "Indio", + "state": "California", + "zip": "92203" + }, + { + "city": "Helena", + "state": "Montana", + "zip": "59602" + }, + { + "city": "Springfield", + "state": "Missouri", + "zip": "65803" + }, + { + "city": "Lewisville", + "state": "Texas", + "zip": "75067" + }, + { + "city": "New York", + "state": "New York", + "zip": "10026" + }, + { + "city": "Richmond Hill", + "state": "New York", + "zip": "11418" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19121" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19134" + }, + { + "city": "Wilmington", + "state": "North Carolina", + "zip": "28409" + }, + { + "city": "Adrian", + "state": "Michigan", + "zip": "49221" + }, + { + "city": "Colorado Springs", + "state": "Colorado", + "zip": "80918" + }, + { + "city": "Oklahoma City", + "state": "Oklahoma", + "zip": "73120" + }, + { + "city": "Austin", + "state": "Texas", + "zip": "78750" + }, + { + "city": "La Crescenta", + "state": "California", + "zip": "91214" + }, + { + "city": "Mount Airy", + "state": "North Carolina", + "zip": "27030" + }, + { + "city": "Gastonia", + "state": "North Carolina", + "zip": "28054" + }, + { + "city": "Saint Louis", + "state": "Missouri", + "zip": "63128" + }, + { + "city": "Boise", + "state": "Idaho", + "zip": "83704" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90039" + }, + { + "city": "Roseville", + "state": "California", + "zip": "95678" + }, + { + "city": "Germantown", + "state": "Maryland", + "zip": "20874" + }, + { + "city": "Bellingham", + "state": "Washington", + "zip": "98226" + }, + { + "city": "Salem", + "state": "Massachusetts", + "zip": "1970" + }, + { + "city": "Lewiston", + "state": "Maine", + "zip": "4240" + }, + { + "city": "West Jordan", + "state": "Utah", + "zip": "84088" + }, + { + "city": "Painesville", + "state": "Ohio", + "zip": "44077" + }, + { + "city": "Milwaukee", + "state": "Wisconsin", + "zip": "53221" + }, + { + "city": "Moorpark", + "state": "California", + "zip": "93021" + }, + { + "city": "Sunnyvale", + "state": "California", + "zip": "94086" + }, + { + "city": "Danville", + "state": "California", + "zip": "94526" + }, + { + "city": "Fall River", + "state": "Massachusetts", + "zip": "2721" + }, + { + "city": "Newtown", + "state": "Pennsylvania", + "zip": "18940" + }, + { + "city": "Victorville", + "state": "California", + "zip": "92395" + }, + { + "city": "Rochester", + "state": "Minnesota", + "zip": "55902" + }, + { + "city": "Grand Rapids", + "state": "Michigan", + "zip": "49504" + }, + { + "city": "Detroit", + "state": "Michigan", + "zip": "48221" + }, + { + "city": "Cranston", + "state": "Rhode Island", + "zip": "2920" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60641" + }, + { + "city": "Metairie", + "state": "Louisiana", + "zip": "70003" + }, + { + "city": "Morrisville", + "state": "North Carolina", + "zip": "27560" + }, + { + "city": "Aurora", + "state": "Illinois", + "zip": "60504" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60649" + }, + { + "city": "Garland", + "state": "Texas", + "zip": "75041" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77049" + }, + { + "city": "Spring", + "state": "Texas", + "zip": "77380" + }, + { + "city": "Beaumont", + "state": "California", + "zip": "92223" + }, + { + "city": "Issaquah", + "state": "Washington", + "zip": "98027" + }, + { + "city": "Santa Monica", + "state": "California", + "zip": "90405" + }, + { + "city": "Prairieville", + "state": "Louisiana", + "zip": "70769" + }, + { + "city": "Littleton", + "state": "Colorado", + "zip": "80130" + }, + { + "city": "Baltimore", + "state": "Maryland", + "zip": "21212" + }, + { + "city": "Spartanburg", + "state": "South Carolina", + "zip": "29301" + }, + { + "city": "Warren", + "state": "Michigan", + "zip": "48089" + }, + { + "city": "Wausau", + "state": "Wisconsin", + "zip": "54401" + }, + { + "city": "Sacramento", + "state": "California", + "zip": "95820" + }, + { + "city": "Central Point", + "state": "Oregon", + "zip": "97502" + }, + { + "city": "Montebello", + "state": "California", + "zip": "90640" + }, + { + "city": "Roanoke", + "state": "Texas", + "zip": "76262" + }, + { + "city": "Austin", + "state": "Texas", + "zip": "78754" + }, + { + "city": "Glendale", + "state": "Arizona", + "zip": "85303" + }, + { + "city": "Ponce", + "state": "Puerto Rico", + "zip": "730" + }, + { + "city": "Concord", + "state": "New Hampshire", + "zip": "3301" + }, + { + "city": "Yonkers", + "state": "New York", + "zip": "10704" + }, + { + "city": "Odenton", + "state": "Maryland", + "zip": "21113" + }, + { + "city": "Barrington", + "state": "Illinois", + "zip": "60010" + }, + { + "city": "Mchenry", + "state": "Illinois", + "zip": "60050" + }, + { + "city": "Plano", + "state": "Texas", + "zip": "75023" + }, + { + "city": "Nampa", + "state": "Idaho", + "zip": "83686" + }, + { + "city": "Sierra Vista", + "state": "Arizona", + "zip": "85635" + }, + { + "city": "Visalia", + "state": "California", + "zip": "93292" + }, + { + "city": "Wesley Chapel", + "state": "Florida", + "zip": "33543" + }, + { + "city": "Nutley", + "state": "New Jersey", + "zip": "7110" + }, + { + "city": "Jersey City", + "state": "New Jersey", + "zip": "7305" + }, + { + "city": "Parkersburg", + "state": "West Virginia", + "zip": "26101" + }, + { + "city": "Marlborough", + "state": "Massachusetts", + "zip": "1752" + }, + { + "city": "O'Fallon", + "state": "Missouri", + "zip": "63366" + }, + { + "city": "Aurora", + "state": "Colorado", + "zip": "80013" + }, + { + "city": "Jacksonville", + "state": "Florida", + "zip": "32256" + }, + { + "city": "Saint Paul", + "state": "Minnesota", + "zip": "55128" + }, + { + "city": "Watsonville", + "state": "California", + "zip": "95076" + }, + { + "city": "Richmond", + "state": "Virginia", + "zip": "23235" + }, + { + "city": "Oxford", + "state": "North Carolina", + "zip": "27565" + }, + { + "city": "Saint George", + "state": "Utah", + "zip": "84790" + }, + { + "city": "Winston Salem", + "state": "North Carolina", + "zip": "27103" + }, + { + "city": "Palo Alto", + "state": "California", + "zip": "94306" + }, + { + "city": "Napa", + "state": "California", + "zip": "94559" + }, + { + "city": "Breaux Bridge", + "state": "Louisiana", + "zip": "70517" + }, + { + "city": "Colleyville", + "state": "Texas", + "zip": "76034" + }, + { + "city": "Round Rock", + "state": "Texas", + "zip": "78665" + }, + { + "city": "Colorado Springs", + "state": "Colorado", + "zip": "80911" + }, + { + "city": "Cottonwood", + "state": "Arizona", + "zip": "86326" + }, + { + "city": "Fort Lauderdale", + "state": "Florida", + "zip": "33328" + }, + { + "city": "Sarasota", + "state": "Florida", + "zip": "34243" + }, + { + "city": "Appleton", + "state": "Wisconsin", + "zip": "54911" + }, + { + "city": "Washington", + "state": "District of Columbia", + "zip": "20002" + }, + { + "city": "Taylor", + "state": "Michigan", + "zip": "48180" + }, + { + "city": "Midland", + "state": "Texas", + "zip": "79705" + }, + { + "city": "Columbus", + "state": "Georgia", + "zip": "31909" + }, + { + "city": "Boston", + "state": "Massachusetts", + "zip": "2215" + }, + { + "city": "North Highlands", + "state": "California", + "zip": "95660" + }, + { + "city": "Orlando", + "state": "Florida", + "zip": "32839" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33135" + }, + { + "city": "Leesburg", + "state": "Florida", + "zip": "34748" + }, + { + "city": "Baytown", + "state": "Texas", + "zip": "77521" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78212" + }, + { + "city": "Orange", + "state": "California", + "zip": "92867" + }, + { + "city": "Mason", + "state": "Ohio", + "zip": "45040" + }, + { + "city": "Westland", + "state": "Michigan", + "zip": "48186" + }, + { + "city": "Holland", + "state": "Michigan", + "zip": "49423" + }, + { + "city": "Orlando", + "state": "Florida", + "zip": "32810" + }, + { + "city": "Erlanger", + "state": "Kentucky", + "zip": "41018" + }, + { + "city": "Racine", + "state": "Wisconsin", + "zip": "53406" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89115" + }, + { + "city": "Salem", + "state": "New Hampshire", + "zip": "3079" + }, + { + "city": "Texarkana", + "state": "Texas", + "zip": "75501" + }, + { + "city": "Santa Fe", + "state": "New Mexico", + "zip": "87507" + }, + { + "city": "Inglewood", + "state": "California", + "zip": "90304" + }, + { + "city": "Anaheim", + "state": "California", + "zip": "92801" + }, + { + "city": "Garden Grove", + "state": "California", + "zip": "92840" + }, + { + "city": "San Francisco", + "state": "California", + "zip": "94115" + }, + { + "city": "Pearl City", + "state": "Hawaii", + "zip": "96782" + }, + { + "city": "Moses Lake", + "state": "Washington", + "zip": "98837" + }, + { + "city": "Dayton", + "state": "Ohio", + "zip": "45458" + }, + { + "city": "Weatherford", + "state": "Texas", + "zip": "76087" + }, + { + "city": "East Northport", + "state": "New York", + "zip": "11731" + }, + { + "city": "West Chester", + "state": "Pennsylvania", + "zip": "19382" + }, + { + "city": "Port Huron", + "state": "Michigan", + "zip": "48060" + }, + { + "city": "Smithfield", + "state": "North Carolina", + "zip": "27577" + }, + { + "city": "New Philadelphia", + "state": "Ohio", + "zip": "44663" + }, + { + "city": "Hudson", + "state": "New Hampshire", + "zip": "3051" + }, + { + "city": "Wayne", + "state": "New Jersey", + "zip": "7470" + }, + { + "city": "Lancaster", + "state": "Pennsylvania", + "zip": "17602" + }, + { + "city": "Saint Charles", + "state": "Illinois", + "zip": "60175" + }, + { + "city": "Granbury", + "state": "Texas", + "zip": "76049" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78249" + }, + { + "city": "Gilbert", + "state": "Arizona", + "zip": "85297" + }, + { + "city": "Long Beach", + "state": "California", + "zip": "90806" + }, + { + "city": "San Jose", + "state": "California", + "zip": "95116" + }, + { + "city": "South San Francisco", + "state": "California", + "zip": "94080" + }, + { + "city": "Rexburg", + "state": "Idaho", + "zip": "83440" + }, + { + "city": "Salt Lake City", + "state": "Utah", + "zip": "84106" + }, + { + "city": "Carlsbad", + "state": "New Mexico", + "zip": "88220" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90046" + }, + { + "city": "Eugene", + "state": "Oregon", + "zip": "97401" + }, + { + "city": "Minneapolis", + "state": "Minnesota", + "zip": "55416" + }, + { + "city": "Attleboro", + "state": "Massachusetts", + "zip": "2703" + }, + { + "city": "Candler", + "state": "North Carolina", + "zip": "28715" + }, + { + "city": "Shreveport", + "state": "Louisiana", + "zip": "71107" + }, + { + "city": "Brownsville", + "state": "Texas", + "zip": "78526" + }, + { + "city": "San Rafael", + "state": "California", + "zip": "94901" + }, + { + "city": "Woodland Hills", + "state": "California", + "zip": "91364" + }, + { + "city": "Sherman Oaks", + "state": "California", + "zip": "91423" + }, + { + "city": "Palm Harbor", + "state": "Florida", + "zip": "34683" + }, + { + "city": "Farmington", + "state": "Minnesota", + "zip": "55024" + }, + { + "city": "Clifton", + "state": "New Jersey", + "zip": "7011" + }, + { + "city": "Whitehall", + "state": "Pennsylvania", + "zip": "18052" + }, + { + "city": "Minneapolis", + "state": "Minnesota", + "zip": "55448" + }, + { + "city": "Sioux Falls", + "state": "South Dakota", + "zip": "57103" + }, + { + "city": "Corona", + "state": "California", + "zip": "92882" + }, + { + "city": "Seattle", + "state": "Washington", + "zip": "98146" + }, + { + "city": "Butler", + "state": "Pennsylvania", + "zip": "16001" + }, + { + "city": "Fort Walton Beach", + "state": "Florida", + "zip": "32547" + }, + { + "city": "Monsey", + "state": "New York", + "zip": "10952" + }, + { + "city": "Green Bay", + "state": "Wisconsin", + "zip": "54303" + }, + { + "city": "Covington", + "state": "Kentucky", + "zip": "41011" + }, + { + "city": "Greensboro", + "state": "North Carolina", + "zip": "27410" + }, + { + "city": "Hemet", + "state": "California", + "zip": "92543" + }, + { + "city": "New Iberia", + "state": "Louisiana", + "zip": "70560" + }, + { + "city": "West Bloomfield", + "state": "Michigan", + "zip": "48322" + }, + { + "city": "Buena Park", + "state": "California", + "zip": "90621" + }, + { + "city": "Lancaster", + "state": "California", + "zip": "93535" + }, + { + "city": "Suisun City", + "state": "California", + "zip": "94585" + }, + { + "city": "Rosenberg", + "state": "Texas", + "zip": "77471" + }, + { + "city": "South Jordan", + "state": "Utah", + "zip": "84095" + }, + { + "city": "Yuma", + "state": "Arizona", + "zip": "85365" + }, + { + "city": "Saint Petersburg", + "state": "Florida", + "zip": "33713" + }, + { + "city": "Sarasota", + "state": "Florida", + "zip": "34231" + }, + { + "city": "Milwaukee", + "state": "Wisconsin", + "zip": "53204" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60644" + }, + { + "city": "Laredo", + "state": "Texas", + "zip": "78041" + }, + { + "city": "Logan", + "state": "Utah", + "zip": "84321" + }, + { + "city": "Montclair", + "state": "California", + "zip": "91763" + }, + { + "city": "Temple City", + "state": "California", + "zip": "91780" + }, + { + "city": "Horn Lake", + "state": "Mississippi", + "zip": "38637" + }, + { + "city": "Sterling Heights", + "state": "Michigan", + "zip": "48313" + }, + { + "city": "Des Moines", + "state": "Iowa", + "zip": "50315" + }, + { + "city": "Hollywood", + "state": "Florida", + "zip": "33024" + }, + { + "city": "Canoga Park", + "state": "California", + "zip": "91303" + }, + { + "city": "Honolulu", + "state": "Hawaii", + "zip": "96825" + }, + { + "city": "Lenexa", + "state": "Kansas", + "zip": "66215" + }, + { + "city": "Surprise", + "state": "Arizona", + "zip": "85388" + }, + { + "city": "Louisville", + "state": "Kentucky", + "zip": "40220" + }, + { + "city": "Colorado Springs", + "state": "Colorado", + "zip": "80923" + }, + { + "city": "Hollywood", + "state": "Florida", + "zip": "33021" + }, + { + "city": "Benton Harbor", + "state": "Michigan", + "zip": "49022" + }, + { + "city": "Waltham", + "state": "Massachusetts", + "zip": "2453" + }, + { + "city": "Bronx", + "state": "New York", + "zip": "10457" + }, + { + "city": "Monroe", + "state": "New York", + "zip": "10950" + }, + { + "city": "Rochester", + "state": "New York", + "zip": "14616" + }, + { + "city": "State College", + "state": "Pennsylvania", + "zip": "16801" + }, + { + "city": "Silver Spring", + "state": "Maryland", + "zip": "20906" + }, + { + "city": "Bolingbrook", + "state": "Illinois", + "zip": "60440" + }, + { + "city": "Naperville", + "state": "Illinois", + "zip": "60564" + }, + { + "city": "Laveen", + "state": "Arizona", + "zip": "85339" + }, + { + "city": "Clearwater", + "state": "Florida", + "zip": "33755" + }, + { + "city": "Bedford", + "state": "Ohio", + "zip": "44146" + }, + { + "city": "Akron", + "state": "Ohio", + "zip": "44310" + }, + { + "city": "Ballwin", + "state": "Missouri", + "zip": "63021" + }, + { + "city": "Saint Louis", + "state": "Missouri", + "zip": "63116" + }, + { + "city": "Liverpool", + "state": "New York", + "zip": "13090" + }, + { + "city": "Seattle", + "state": "Washington", + "zip": "98198" + }, + { + "city": "Ravenna", + "state": "Ohio", + "zip": "44266" + }, + { + "city": "Chicago Heights", + "state": "Illinois", + "zip": "60411" + }, + { + "city": "Clinton", + "state": "Mississippi", + "zip": "39056" + }, + { + "city": "East Boston", + "state": "Massachusetts", + "zip": "2128" + }, + { + "city": "Linden", + "state": "New Jersey", + "zip": "7036" + }, + { + "city": "Paso Robles", + "state": "California", + "zip": "93446" + }, + { + "city": "Auburn", + "state": "New York", + "zip": "13021" + }, + { + "city": "Deer Park", + "state": "New York", + "zip": "11729" + }, + { + "city": "Rochester", + "state": "New York", + "zip": "14606" + }, + { + "city": "Norfolk", + "state": "Virginia", + "zip": "23505" + }, + { + "city": "Charlotte", + "state": "North Carolina", + "zip": "28208" + }, + { + "city": "Rosemount", + "state": "Minnesota", + "zip": "55068" + }, + { + "city": "Torrance", + "state": "California", + "zip": "90505" + }, + { + "city": "Bayamon", + "state": "Puerto Rico", + "zip": "961" + }, + { + "city": "Omaha", + "state": "Nebraska", + "zip": "68107" + }, + { + "city": "New Orleans", + "state": "Louisiana", + "zip": "70126" + }, + { + "city": "Pineville", + "state": "Louisiana", + "zip": "71360" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85041" + }, + { + "city": "Fort Lauderdale", + "state": "Florida", + "zip": "33326" + }, + { + "city": "Joplin", + "state": "Missouri", + "zip": "64804" + }, + { + "city": "Palm Bay", + "state": "Florida", + "zip": "32907" + }, + { + "city": "Redmond", + "state": "Oregon", + "zip": "97756" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78260" + }, + { + "city": "Sandy", + "state": "Utah", + "zip": "84092" + }, + { + "city": "Rock Hill", + "state": "South Carolina", + "zip": "29732" + }, + { + "city": "Atlanta", + "state": "Georgia", + "zip": "30338" + }, + { + "city": "Carmel", + "state": "Indiana", + "zip": "46032" + }, + { + "city": "Staten Island", + "state": "New York", + "zip": "10312" + }, + { + "city": "Manassas", + "state": "Virginia", + "zip": "20111" + }, + { + "city": "Kernersville", + "state": "North Carolina", + "zip": "27284" + }, + { + "city": "Sanford", + "state": "North Carolina", + "zip": "27330" + }, + { + "city": "Minneapolis", + "state": "Minnesota", + "zip": "55443" + }, + { + "city": "Providence", + "state": "Rhode Island", + "zip": "2909" + }, + { + "city": "Silver Spring", + "state": "Maryland", + "zip": "20904" + }, + { + "city": "Bel Air", + "state": "Maryland", + "zip": "21015" + }, + { + "city": "Delray Beach", + "state": "Florida", + "zip": "33446" + }, + { + "city": "Madisonville", + "state": "Kentucky", + "zip": "42431" + }, + { + "city": "Galloway", + "state": "Ohio", + "zip": "43119" + }, + { + "city": "West Des Moines", + "state": "Iowa", + "zip": "50265" + }, + { + "city": "Cayey", + "state": "Puerto Rico", + "zip": "736" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11211" + }, + { + "city": "Buffalo", + "state": "New York", + "zip": "14215" + }, + { + "city": "Keller", + "state": "Texas", + "zip": "76248" + }, + { + "city": "Wheaton", + "state": "Illinois", + "zip": "60187" + }, + { + "city": "Athens", + "state": "Ohio", + "zip": "45701" + }, + { + "city": "Milwaukee", + "state": "Wisconsin", + "zip": "53211" + }, + { + "city": "Gardena", + "state": "California", + "zip": "90249" + }, + { + "city": "Tehachapi", + "state": "California", + "zip": "93561" + }, + { + "city": "Pullman", + "state": "Washington", + "zip": "99163" + }, + { + "city": "Port Richey", + "state": "Florida", + "zip": "34668" + }, + { + "city": "Memphis", + "state": "Tennessee", + "zip": "38127" + }, + { + "city": "Monterey Park", + "state": "California", + "zip": "91754" + }, + { + "city": "Port Charlotte", + "state": "Florida", + "zip": "33952" + }, + { + "city": "Cantonment", + "state": "Florida", + "zip": "32533" + }, + { + "city": "Pompano Beach", + "state": "Florida", + "zip": "33073" + }, + { + "city": "Saint Louis", + "state": "Missouri", + "zip": "63118" + }, + { + "city": "Salina", + "state": "Kansas", + "zip": "67401" + }, + { + "city": "Austin", + "state": "Texas", + "zip": "78731" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90033" + }, + { + "city": "San Francisco", + "state": "California", + "zip": "94133" + }, + { + "city": "Winston Salem", + "state": "North Carolina", + "zip": "27127" + }, + { + "city": "Gainesville", + "state": "Georgia", + "zip": "30501" + }, + { + "city": "Tulsa", + "state": "Oklahoma", + "zip": "74105" + }, + { + "city": "Greeneville", + "state": "Tennessee", + "zip": "37743" + }, + { + "city": "Tupelo", + "state": "Mississippi", + "zip": "38801" + }, + { + "city": "Clinton Township", + "state": "Michigan", + "zip": "48038" + }, + { + "city": "Maple Grove", + "state": "Minnesota", + "zip": "55311" + }, + { + "city": "Austin", + "state": "Minnesota", + "zip": "55912" + }, + { + "city": "Alhambra", + "state": "California", + "zip": "91803" + }, + { + "city": "Hillsboro", + "state": "Oregon", + "zip": "97123" + }, + { + "city": "Arlington", + "state": "Virginia", + "zip": "22201" + }, + { + "city": "Grand Junction", + "state": "Colorado", + "zip": "81504" + }, + { + "city": "Buckeye", + "state": "Arizona", + "zip": "85326" + }, + { + "city": "Erie", + "state": "Pennsylvania", + "zip": "16509" + }, + { + "city": "Homestead", + "state": "Florida", + "zip": "33033" + }, + { + "city": "Davis", + "state": "California", + "zip": "95616" + }, + { + "city": "San Francisco", + "state": "California", + "zip": "94121" + }, + { + "city": "Yonkers", + "state": "New York", + "zip": "10710" + }, + { + "city": "Ellicott City", + "state": "Maryland", + "zip": "21043" + }, + { + "city": "Villa Rica", + "state": "Georgia", + "zip": "30180" + }, + { + "city": "Pensacola", + "state": "Florida", + "zip": "32505" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33183" + }, + { + "city": "Superior", + "state": "Wisconsin", + "zip": "54880" + }, + { + "city": "Orland Park", + "state": "Illinois", + "zip": "60462" + }, + { + "city": "Oklahoma City", + "state": "Oklahoma", + "zip": "73170" + }, + { + "city": "Magnolia", + "state": "Texas", + "zip": "77355" + }, + { + "city": "Missouri City", + "state": "Texas", + "zip": "77489" + }, + { + "city": "Apache Junction", + "state": "Arizona", + "zip": "85120" + }, + { + "city": "Stockton", + "state": "California", + "zip": "95210" + }, + { + "city": "Auburn", + "state": "Washington", + "zip": "98002" + }, + { + "city": "Sequim", + "state": "Washington", + "zip": "98382" + }, + { + "city": "Goshen", + "state": "Indiana", + "zip": "46528" + }, + { + "city": "Champaign", + "state": "Illinois", + "zip": "61821" + }, + { + "city": "Collinsville", + "state": "Illinois", + "zip": "62234" + }, + { + "city": "Grand Prairie", + "state": "Texas", + "zip": "75052" + }, + { + "city": "Sacramento", + "state": "California", + "zip": "95825" + }, + { + "city": "Lilburn", + "state": "Georgia", + "zip": "30047" + }, + { + "city": "Marquette", + "state": "Michigan", + "zip": "49855" + }, + { + "city": "Rock Island", + "state": "Illinois", + "zip": "61201" + }, + { + "city": "Pflugerville", + "state": "Texas", + "zip": "78660" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85021" + }, + { + "city": "Chula Vista", + "state": "California", + "zip": "91915" + }, + { + "city": "Drexel Hill", + "state": "Pennsylvania", + "zip": "19026" + }, + { + "city": "Riverside", + "state": "California", + "zip": "92507" + }, + { + "city": "Reedley", + "state": "California", + "zip": "93654" + }, + { + "city": "Portland", + "state": "Oregon", + "zip": "97223" + }, + { + "city": "Tampa", + "state": "Florida", + "zip": "33624" + }, + { + "city": "Pearland", + "state": "Texas", + "zip": "77581" + }, + { + "city": "Tucson", + "state": "Arizona", + "zip": "85741" + }, + { + "city": "Floral Park", + "state": "New York", + "zip": "11001" + }, + { + "city": "Matthews", + "state": "North Carolina", + "zip": "28104" + }, + { + "city": "San Diego", + "state": "California", + "zip": "92116" + }, + { + "city": "Visalia", + "state": "California", + "zip": "93277" + }, + { + "city": "Louisville", + "state": "Kentucky", + "zip": "40219" + }, + { + "city": "Billerica", + "state": "Massachusetts", + "zip": "1821" + }, + { + "city": "Brookline", + "state": "Massachusetts", + "zip": "2446" + }, + { + "city": "Waldorf", + "state": "Maryland", + "zip": "20601" + }, + { + "city": "Menomonee Falls", + "state": "Wisconsin", + "zip": "53051" + }, + { + "city": "Arlington", + "state": "Texas", + "zip": "76014" + }, + { + "city": "Deltona", + "state": "Florida", + "zip": "32725" + }, + { + "city": "Springfield", + "state": "Oregon", + "zip": "97477" + }, + { + "city": "Warrensburg", + "state": "Missouri", + "zip": "64093" + }, + { + "city": "Plano", + "state": "Texas", + "zip": "75074" + }, + { + "city": "Colorado Springs", + "state": "Colorado", + "zip": "80906" + }, + { + "city": "Saint Peters", + "state": "Missouri", + "zip": "63376" + }, + { + "city": "Elgin", + "state": "South Carolina", + "zip": "29045" + }, + { + "city": "Manati", + "state": "Puerto Rico", + "zip": "674" + }, + { + "city": "Dyersburg", + "state": "Tennessee", + "zip": "38024" + }, + { + "city": "Sugar Land", + "state": "Texas", + "zip": "77479" + }, + { + "city": "San Clemente", + "state": "California", + "zip": "92672" + }, + { + "city": "Juneau", + "state": "Alaska", + "zip": "99801" + }, + { + "city": "Fresh Meadows", + "state": "New York", + "zip": "11365" + }, + { + "city": "Moline", + "state": "Illinois", + "zip": "61265" + }, + { + "city": "Queensbury", + "state": "New York", + "zip": "12804" + }, + { + "city": "Bethlehem", + "state": "Pennsylvania", + "zip": "18018" + }, + { + "city": "Petaluma", + "state": "California", + "zip": "94954" + }, + { + "city": "Englishtown", + "state": "New Jersey", + "zip": "7726" + }, + { + "city": "Palatine", + "state": "Illinois", + "zip": "60067" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77083" + }, + { + "city": "Arvada", + "state": "Colorado", + "zip": "80003" + }, + { + "city": "Cicero", + "state": "Illinois", + "zip": "60804" + }, + { + "city": "Lafayette", + "state": "Louisiana", + "zip": "70503" + }, + { + "city": "Lake Jackson", + "state": "Texas", + "zip": "77566" + }, + { + "city": "Colorado Springs", + "state": "Colorado", + "zip": "80910" + }, + { + "city": "Colorado Springs", + "state": "Colorado", + "zip": "80922" + }, + { + "city": "Barranquitas", + "state": "Puerto Rico", + "zip": "794" + }, + { + "city": "Paterson", + "state": "New Jersey", + "zip": "7501" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19146" + }, + { + "city": "Savannah", + "state": "Georgia", + "zip": "31406" + }, + { + "city": "Birmingham", + "state": "Alabama", + "zip": "35244" + }, + { + "city": "Louisville", + "state": "Kentucky", + "zip": "40216" + }, + { + "city": "Chaska", + "state": "Minnesota", + "zip": "55318" + }, + { + "city": "Ridgecrest", + "state": "California", + "zip": "93555" + }, + { + "city": "Bridgewater", + "state": "New Jersey", + "zip": "8807" + }, + { + "city": "New York", + "state": "New York", + "zip": "10034" + }, + { + "city": "Eugene", + "state": "Oregon", + "zip": "97404" + }, + { + "city": "Olympia", + "state": "Washington", + "zip": "98513" + }, + { + "city": "Ankeny", + "state": "Iowa", + "zip": "50023" + }, + { + "city": "Monroe", + "state": "North Carolina", + "zip": "28110" + }, + { + "city": "Cheyenne", + "state": "Wyoming", + "zip": "82001" + }, + { + "city": "San Francisco", + "state": "California", + "zip": "94131" + }, + { + "city": "Yonkers", + "state": "New York", + "zip": "10705" + }, + { + "city": "Cedar Hill", + "state": "Texas", + "zip": "75104" + }, + { + "city": "Vidor", + "state": "Texas", + "zip": "77662" + }, + { + "city": "San Jacinto", + "state": "California", + "zip": "92583" + }, + { + "city": "Fremont", + "state": "California", + "zip": "94538" + }, + { + "city": "Bronx", + "state": "New York", + "zip": "10458" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11212" + }, + { + "city": "Saint Paul", + "state": "Minnesota", + "zip": "55124" + }, + { + "city": "Quakertown", + "state": "Pennsylvania", + "zip": "18951" + }, + { + "city": "Lewisville", + "state": "Texas", + "zip": "75077" + }, + { + "city": "Midland", + "state": "Texas", + "zip": "79707" + }, + { + "city": "Caldwell", + "state": "Idaho", + "zip": "83605" + }, + { + "city": "Brigham City", + "state": "Utah", + "zip": "84302" + }, + { + "city": "Gainesville", + "state": "Georgia", + "zip": "30504" + }, + { + "city": "Saint Augustine", + "state": "Florida", + "zip": "32084" + }, + { + "city": "Miami Gardens", + "state": "Florida", + "zip": "33056" + }, + { + "city": "Minneapolis", + "state": "Minnesota", + "zip": "55428" + }, + { + "city": "Springfield", + "state": "Virginia", + "zip": "22150" + }, + { + "city": "Saint Charles", + "state": "Missouri", + "zip": "63304" + }, + { + "city": "Mayaguez", + "state": "Puerto Rico", + "zip": "680" + }, + { + "city": "Groton", + "state": "Connecticut", + "zip": "6340" + }, + { + "city": "Dracut", + "state": "Massachusetts", + "zip": "1826" + }, + { + "city": "Pittsburgh", + "state": "Pennsylvania", + "zip": "15217" + }, + { + "city": "Mooresville", + "state": "North Carolina", + "zip": "28115" + }, + { + "city": "Lindenhurst", + "state": "New York", + "zip": "11757" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19133" + }, + { + "city": "Centreville", + "state": "Virginia", + "zip": "20120" + }, + { + "city": "Granite City", + "state": "Illinois", + "zip": "62040" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77067" + }, + { + "city": "Bothell", + "state": "Washington", + "zip": "98011" + }, + { + "city": "Midlothian", + "state": "Illinois", + "zip": "60445" + }, + { + "city": "Spring", + "state": "Texas", + "zip": "77373" + }, + { + "city": "Payson", + "state": "Utah", + "zip": "84651" + }, + { + "city": "Hamburg", + "state": "New York", + "zip": "14075" + }, + { + "city": "Hoffman Estates", + "state": "Illinois", + "zip": "60169" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89117" + }, + { + "city": "Johnston", + "state": "Rhode Island", + "zip": "2919" + }, + { + "city": "Lancaster", + "state": "Pennsylvania", + "zip": "17603" + }, + { + "city": "Minneapolis", + "state": "Minnesota", + "zip": "55432" + }, + { + "city": "Dana Point", + "state": "California", + "zip": "92629" + }, + { + "city": "Orange", + "state": "California", + "zip": "92868" + }, + { + "city": "Van Nuys", + "state": "California", + "zip": "91411" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85017" + }, + { + "city": "Albuquerque", + "state": "New Mexico", + "zip": "87108" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90027" + }, + { + "city": "Sun City", + "state": "Arizona", + "zip": "85351" + }, + { + "city": "Tucson", + "state": "Arizona", + "zip": "85719" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89106" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90022" + }, + { + "city": "Covina", + "state": "California", + "zip": "91722" + }, + { + "city": "Santa Rosa", + "state": "California", + "zip": "95403" + }, + { + "city": "Wailuku", + "state": "Hawaii", + "zip": "96793" + }, + { + "city": "Severn", + "state": "Maryland", + "zip": "21144" + }, + { + "city": "Tucker", + "state": "Georgia", + "zip": "30084" + }, + { + "city": "Tahlequah", + "state": "Oklahoma", + "zip": "74464" + }, + { + "city": "Fort Worth", + "state": "Texas", + "zip": "76106" + }, + { + "city": "Huntsville", + "state": "Texas", + "zip": "77320" + }, + { + "city": "Thousand Oaks", + "state": "California", + "zip": "91360" + }, + { + "city": "Independence", + "state": "Kentucky", + "zip": "41051" + }, + { + "city": "Pekin", + "state": "Illinois", + "zip": "61554" + }, + { + "city": "Bayamon", + "state": "Puerto Rico", + "zip": "956" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19119" + }, + { + "city": "Roswell", + "state": "Georgia", + "zip": "30076" + }, + { + "city": "Pittsburg", + "state": "California", + "zip": "94565" + }, + { + "city": "Shelton", + "state": "Washington", + "zip": "98584" + }, + { + "city": "Hollywood", + "state": "Florida", + "zip": "33027" + }, + { + "city": "Erie", + "state": "Pennsylvania", + "zip": "16510" + }, + { + "city": "La Place", + "state": "Louisiana", + "zip": "70068" + }, + { + "city": "Windsor", + "state": "Colorado", + "zip": "80550" + }, + { + "city": "Flagstaff", + "state": "Arizona", + "zip": "86004" + }, + { + "city": "Lynwood", + "state": "California", + "zip": "90262" + }, + { + "city": "Oak Lawn", + "state": "Illinois", + "zip": "60453" + }, + { + "city": "Bowie", + "state": "Maryland", + "zip": "20715" + }, + { + "city": "Decatur", + "state": "Georgia", + "zip": "30034" + }, + { + "city": "Toledo", + "state": "Ohio", + "zip": "43615" + }, + { + "city": "Hyattsville", + "state": "Maryland", + "zip": "20783" + }, + { + "city": "Blue Springs", + "state": "Missouri", + "zip": "64015" + }, + { + "city": "Stone Mountain", + "state": "Georgia", + "zip": "30088" + }, + { + "city": "Cleveland", + "state": "Tennessee", + "zip": "37312" + }, + { + "city": "Livonia", + "state": "Michigan", + "zip": "48150" + }, + { + "city": "Moreno Valley", + "state": "California", + "zip": "92551" + }, + { + "city": "Colorado Springs", + "state": "Colorado", + "zip": "80917" + }, + { + "city": "Brick", + "state": "New Jersey", + "zip": "8723" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11204" + }, + { + "city": "Baltimore", + "state": "Maryland", + "zip": "21209" + }, + { + "city": "Happy Valley", + "state": "Oregon", + "zip": "97086" + }, + { + "city": "Whittier", + "state": "California", + "zip": "90604" + }, + { + "city": "San Mateo", + "state": "California", + "zip": "94404" + }, + { + "city": "Cleveland", + "state": "Ohio", + "zip": "44124" + }, + { + "city": "Plano", + "state": "Texas", + "zip": "75024" + }, + { + "city": "Mount Juliet", + "state": "Tennessee", + "zip": "37122" + }, + { + "city": "Lakewood", + "state": "Ohio", + "zip": "44107" + }, + { + "city": "San Francisco", + "state": "California", + "zip": "94109" + }, + { + "city": "Redding", + "state": "California", + "zip": "96003" + }, + { + "city": "Kent", + "state": "Washington", + "zip": "98042" + }, + { + "city": "Lexington", + "state": "Kentucky", + "zip": "40511" + }, + { + "city": "Fishers", + "state": "Indiana", + "zip": "46038" + }, + { + "city": "Alhambra", + "state": "California", + "zip": "91801" + }, + { + "city": "Vallejo", + "state": "California", + "zip": "94590" + }, + { + "city": "Clover", + "state": "South Carolina", + "zip": "29710" + }, + { + "city": "Saint Paul", + "state": "Minnesota", + "zip": "55118" + }, + { + "city": "Dodge City", + "state": "Kansas", + "zip": "67801" + }, + { + "city": "Little Elm", + "state": "Texas", + "zip": "75068" + }, + { + "city": "South Gate", + "state": "California", + "zip": "90280" + }, + { + "city": "Clovis", + "state": "California", + "zip": "93611" + }, + { + "city": "Columbus", + "state": "Georgia", + "zip": "31907" + }, + { + "city": "Aiea", + "state": "Hawaii", + "zip": "96701" + }, + { + "city": "Lynnwood", + "state": "Washington", + "zip": "98036" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78211" + }, + { + "city": "Redlands", + "state": "California", + "zip": "92373" + }, + { + "city": "Windsor", + "state": "California", + "zip": "95492" + }, + { + "city": "Saint Louis", + "state": "Missouri", + "zip": "63119" + }, + { + "city": "Lady Lake", + "state": "Florida", + "zip": "32159" + }, + { + "city": "Hialeah", + "state": "Florida", + "zip": "33013" + }, + { + "city": "Foley", + "state": "Alabama", + "zip": "36535" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60613" + }, + { + "city": "Springfield", + "state": "Illinois", + "zip": "62703" + }, + { + "city": "Wichita", + "state": "Kansas", + "zip": "67217" + }, + { + "city": "Mcalester", + "state": "Oklahoma", + "zip": "74501" + }, + { + "city": "Waco", + "state": "Texas", + "zip": "76705" + }, + { + "city": "Vail", + "state": "Arizona", + "zip": "85641" + }, + { + "city": "Henderson", + "state": "Nevada", + "zip": "89011" + }, + { + "city": "Wilmington", + "state": "North Carolina", + "zip": "28403" + }, + { + "city": "Marietta", + "state": "Georgia", + "zip": "30060" + }, + { + "city": "Brownsburg", + "state": "Indiana", + "zip": "46112" + }, + { + "city": "Shelbyville", + "state": "Indiana", + "zip": "46176" + }, + { + "city": "Cerritos", + "state": "California", + "zip": "90703" + }, + { + "city": "Upper Marlboro", + "state": "Maryland", + "zip": "20774" + }, + { + "city": "New York", + "state": "New York", + "zip": "10019" + }, + { + "city": "Bay Shore", + "state": "New York", + "zip": "11706" + }, + { + "city": "Casselberry", + "state": "Florida", + "zip": "32707" + }, + { + "city": "Land O'Lakes", + "state": "Florida", + "zip": "34639" + }, + { + "city": "Perrysburg", + "state": "Ohio", + "zip": "43551" + }, + { + "city": "Cleveland", + "state": "Ohio", + "zip": "44125" + }, + { + "city": "Mckinney", + "state": "Texas", + "zip": "75070" + }, + { + "city": "Huntington Beach", + "state": "California", + "zip": "92649" + }, + { + "city": "Oxford", + "state": "Mississippi", + "zip": "38655" + }, + { + "city": "Hanover", + "state": "Pennsylvania", + "zip": "17331" + }, + { + "city": "Oshkosh", + "state": "Wisconsin", + "zip": "54901" + }, + { + "city": "New Orleans", + "state": "Louisiana", + "zip": "70117" + }, + { + "city": "Austin", + "state": "Texas", + "zip": "78741" + }, + { + "city": "Vancouver", + "state": "Washington", + "zip": "98684" + }, + { + "city": "Phoenixville", + "state": "Pennsylvania", + "zip": "19460" + }, + { + "city": "Fairfax", + "state": "Virginia", + "zip": "22032" + }, + { + "city": "Richmond", + "state": "Virginia", + "zip": "23222" + }, + { + "city": "Navarre", + "state": "Florida", + "zip": "32566" + }, + { + "city": "Little Rock", + "state": "Arkansas", + "zip": "72209" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85044" + }, + { + "city": "Pleasant Hill", + "state": "California", + "zip": "94523" + }, + { + "city": "San Jose", + "state": "California", + "zip": "95123" + }, + { + "city": "Seattle", + "state": "Washington", + "zip": "98119" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11218" + }, + { + "city": "Pasadena", + "state": "California", + "zip": "91106" + }, + { + "city": "Oakland", + "state": "California", + "zip": "94607" + }, + { + "city": "Irving", + "state": "Texas", + "zip": "75038" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90035" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19139" + }, + { + "city": "Bossier City", + "state": "Louisiana", + "zip": "71111" + }, + { + "city": "Fountain", + "state": "Colorado", + "zip": "80817" + }, + { + "city": "Hayward", + "state": "California", + "zip": "94545" + }, + { + "city": "Vacaville", + "state": "California", + "zip": "95688" + }, + { + "city": "Springdale", + "state": "Arkansas", + "zip": "72764" + }, + { + "city": "Louisville", + "state": "Colorado", + "zip": "80027" + }, + { + "city": "Denver", + "state": "Colorado", + "zip": "80211" + }, + { + "city": "Palo Alto", + "state": "California", + "zip": "94303" + }, + { + "city": "Olive Branch", + "state": "Mississippi", + "zip": "38654" + }, + { + "city": "Indianapolis", + "state": "Indiana", + "zip": "46217" + }, + { + "city": "Howell", + "state": "New Jersey", + "zip": "7731" + }, + { + "city": "Flushing", + "state": "New York", + "zip": "11354" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19131" + }, + { + "city": "Knoxville", + "state": "Tennessee", + "zip": "37919" + }, + { + "city": "Saginaw", + "state": "Michigan", + "zip": "48602" + }, + { + "city": "Waukegan", + "state": "Illinois", + "zip": "60085" + }, + { + "city": "Naperville", + "state": "Illinois", + "zip": "60540" + }, + { + "city": "Wakefield", + "state": "Massachusetts", + "zip": "1880" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77082" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85037" + }, + { + "city": "Surprise", + "state": "Arizona", + "zip": "85379" + }, + { + "city": "Farragut", + "state": "Tennessee", + "zip": "37934" + }, + { + "city": "Aldie", + "state": "Virginia", + "zip": "20105" + }, + { + "city": "Hyattsville", + "state": "Maryland", + "zip": "20782" + }, + { + "city": "Fayetteville", + "state": "Arkansas", + "zip": "72703" + }, + { + "city": "Pompano Beach", + "state": "Florida", + "zip": "33069" + }, + { + "city": "McLean", + "state": "Virginia", + "zip": "22102" + }, + { + "city": "Arlington", + "state": "Texas", + "zip": "76017" + }, + { + "city": "Roswell", + "state": "New Mexico", + "zip": "88203" + }, + { + "city": "Cincinnati", + "state": "Ohio", + "zip": "45240" + }, + { + "city": "Clinton Township", + "state": "Michigan", + "zip": "48035" + }, + { + "city": "Marion", + "state": "Iowa", + "zip": "52302" + }, + { + "city": "West Fargo", + "state": "North Dakota", + "zip": "58078" + }, + { + "city": "Canovanas", + "state": "Puerto Rico", + "zip": "729" + }, + { + "city": "Vineland", + "state": "New Jersey", + "zip": "8360" + }, + { + "city": "Norman", + "state": "Oklahoma", + "zip": "73069" + }, + { + "city": "Oklahoma City", + "state": "Oklahoma", + "zip": "73160" + }, + { + "city": "Tomball", + "state": "Texas", + "zip": "77375" + }, + { + "city": "Sahuarita", + "state": "Arizona", + "zip": "85629" + }, + { + "city": "Redondo Beach", + "state": "California", + "zip": "90278" + }, + { + "city": "Moreno Valley", + "state": "California", + "zip": "92557" + }, + { + "city": "Vancouver", + "state": "Washington", + "zip": "98661" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90029" + }, + { + "city": "Fresno", + "state": "California", + "zip": "93722" + }, + { + "city": "San Francisco", + "state": "California", + "zip": "94110" + }, + { + "city": "Newark", + "state": "New Jersey", + "zip": "7103" + }, + { + "city": "Marietta", + "state": "Georgia", + "zip": "30067" + }, + { + "city": "Oakland", + "state": "California", + "zip": "94602" + }, + { + "city": "North Richland Hills", + "state": "Texas", + "zip": "76182" + }, + { + "city": "San Juan Capistrano", + "state": "California", + "zip": "92675" + }, + { + "city": "Asheboro", + "state": "North Carolina", + "zip": "27205" + }, + { + "city": "Riverdale", + "state": "Georgia", + "zip": "30274" + }, + { + "city": "Taunton", + "state": "Massachusetts", + "zip": "2780" + }, + { + "city": "Greensboro", + "state": "North Carolina", + "zip": "27406" + }, + { + "city": "Lansing", + "state": "Michigan", + "zip": "48917" + }, + { + "city": "Minneapolis", + "state": "Minnesota", + "zip": "55423" + }, + { + "city": "Jefferson City", + "state": "Missouri", + "zip": "65109" + }, + { + "city": "Washington", + "state": "District of Columbia", + "zip": "20011" + }, + { + "city": "Newton", + "state": "North Carolina", + "zip": "28658" + }, + { + "city": "Fort Wayne", + "state": "Indiana", + "zip": "46835" + }, + { + "city": "Trenton", + "state": "Michigan", + "zip": "48183" + }, + { + "city": "San Angelo", + "state": "Texas", + "zip": "76901" + }, + { + "city": "Anasco", + "state": "Puerto Rico", + "zip": "610" + }, + { + "city": "Westfield", + "state": "Massachusetts", + "zip": "1085" + }, + { + "city": "Taylorsville", + "state": "North Carolina", + "zip": "28681" + }, + { + "city": "Maywood", + "state": "California", + "zip": "90270" + }, + { + "city": "Vacaville", + "state": "California", + "zip": "95687" + }, + { + "city": "Hatillo", + "state": "Puerto Rico", + "zip": "659" + }, + { + "city": "Bronx", + "state": "New York", + "zip": "10456" + }, + { + "city": "North Wales", + "state": "Pennsylvania", + "zip": "19454" + }, + { + "city": "Kings Mountain", + "state": "North Carolina", + "zip": "28086" + }, + { + "city": "Nampa", + "state": "Idaho", + "zip": "83687" + }, + { + "city": "Hinesville", + "state": "Georgia", + "zip": "31313" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60617" + }, + { + "city": "Corpus Christi", + "state": "Texas", + "zip": "78415" + }, + { + "city": "Lake Mary", + "state": "Florida", + "zip": "32746" + }, + { + "city": "Fort Worth", + "state": "Texas", + "zip": "76137" + }, + { + "city": "El Paso", + "state": "Texas", + "zip": "79912" + }, + { + "city": "Hawthorne", + "state": "California", + "zip": "90250" + }, + { + "city": "Tulsa", + "state": "Oklahoma", + "zip": "74137" + }, + { + "city": "Jackson", + "state": "Tennessee", + "zip": "38305" + }, + { + "city": "New Milford", + "state": "Connecticut", + "zip": "6776" + }, + { + "city": "York", + "state": "Pennsylvania", + "zip": "17403" + }, + { + "city": "Asheville", + "state": "North Carolina", + "zip": "28806" + }, + { + "city": "San Clemente", + "state": "California", + "zip": "92673" + }, + { + "city": "Cape Girardeau", + "state": "Missouri", + "zip": "63701" + }, + { + "city": "Deerfield Beach", + "state": "Florida", + "zip": "33442" + }, + { + "city": "Madison Heights", + "state": "Michigan", + "zip": "48071" + }, + { + "city": "Tempe", + "state": "Arizona", + "zip": "85283" + }, + { + "city": "San Fernando", + "state": "California", + "zip": "91340" + }, + { + "city": "Fontana", + "state": "California", + "zip": "92335" + }, + { + "city": "Charleston", + "state": "South Carolina", + "zip": "29407" + }, + { + "city": "Macon", + "state": "Georgia", + "zip": "31206" + }, + { + "city": "North Canton", + "state": "Ohio", + "zip": "44720" + }, + { + "city": "Norristown", + "state": "Pennsylvania", + "zip": "19403" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60652" + }, + { + "city": "Harrison", + "state": "Arkansas", + "zip": "72601" + }, + { + "city": "San Bernardino", + "state": "California", + "zip": "92404" + }, + { + "city": "Murrieta", + "state": "California", + "zip": "92562" + }, + { + "city": "Mount Vernon", + "state": "Washington", + "zip": "98273" + }, + { + "city": "Cary", + "state": "Illinois", + "zip": "60013" + }, + { + "city": "Olathe", + "state": "Kansas", + "zip": "66062" + }, + { + "city": "Lake Worth", + "state": "Florida", + "zip": "33462" + }, + { + "city": "Minneapolis", + "state": "Minnesota", + "zip": "55433" + }, + { + "city": "Bayamon", + "state": "Puerto Rico", + "zip": "959" + }, + { + "city": "Natchitoches", + "state": "Louisiana", + "zip": "71457" + }, + { + "city": "Oklahoma City", + "state": "Oklahoma", + "zip": "73127" + }, + { + "city": "Flagstaff", + "state": "Arizona", + "zip": "86001" + }, + { + "city": "Wilmette", + "state": "Illinois", + "zip": "60091" + }, + { + "city": "Fort Lauderdale", + "state": "Florida", + "zip": "33309" + }, + { + "city": "Lake Worth", + "state": "Florida", + "zip": "33467" + }, + { + "city": "Newark", + "state": "New Jersey", + "zip": "7107" + }, + { + "city": "Lorton", + "state": "Virginia", + "zip": "22079" + }, + { + "city": "Valrico", + "state": "Florida", + "zip": "33594" + }, + { + "city": "Arcadia", + "state": "Florida", + "zip": "34266" + }, + { + "city": "Collierville", + "state": "Tennessee", + "zip": "38017" + }, + { + "city": "Dallas", + "state": "Texas", + "zip": "75220" + }, + { + "city": "Aubrey", + "state": "Texas", + "zip": "76227" + }, + { + "city": "Richmond Hill", + "state": "Georgia", + "zip": "31324" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60612" + }, + { + "city": "Wichita", + "state": "Kansas", + "zip": "67212" + }, + { + "city": "Alice", + "state": "Texas", + "zip": "78332" + }, + { + "city": "West Valley City", + "state": "Utah", + "zip": "84120" + }, + { + "city": "Clearwater", + "state": "Florida", + "zip": "33764" + }, + { + "city": "Ocala", + "state": "Florida", + "zip": "34472" + }, + { + "city": "Saint Albans", + "state": "New York", + "zip": "11412" + }, + { + "city": "Reading", + "state": "Pennsylvania", + "zip": "19604" + }, + { + "city": "Laguna Niguel", + "state": "California", + "zip": "92677" + }, + { + "city": "Davis", + "state": "California", + "zip": "95618" + }, + { + "city": "Baltimore", + "state": "Maryland", + "zip": "21218" + }, + { + "city": "Lincoln", + "state": "California", + "zip": "95648" + }, + { + "city": "Madera", + "state": "California", + "zip": "93637" + }, + { + "city": "Del Rio", + "state": "Texas", + "zip": "78840" + }, + { + "city": "Forest Grove", + "state": "Oregon", + "zip": "97116" + }, + { + "city": "Fort Washington", + "state": "Maryland", + "zip": "20744" + }, + { + "city": "Jacksonville", + "state": "Florida", + "zip": "32205" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19135" + }, + { + "city": "Fort Lee", + "state": "New Jersey", + "zip": "7024" + }, + { + "city": "Cortland", + "state": "New York", + "zip": "13045" + }, + { + "city": "Williamsport", + "state": "Pennsylvania", + "zip": "17701" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90026" + }, + { + "city": "Ventura", + "state": "California", + "zip": "93004" + }, + { + "city": "Fairfax", + "state": "Virginia", + "zip": "22030" + }, + { + "city": "Louisville", + "state": "Kentucky", + "zip": "40245" + }, + { + "city": "Indianapolis", + "state": "Indiana", + "zip": "46224" + }, + { + "city": "Lawrence", + "state": "Kansas", + "zip": "66049" + }, + { + "city": "American Fork", + "state": "Utah", + "zip": "84003" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90061" + }, + { + "city": "Winnetka", + "state": "California", + "zip": "91306" + }, + { + "city": "Coral Springs", + "state": "Florida", + "zip": "33065" + }, + { + "city": "Milwaukee", + "state": "Wisconsin", + "zip": "53219" + }, + { + "city": "Alameda", + "state": "California", + "zip": "94501" + }, + { + "city": "Los Gatos", + "state": "California", + "zip": "95032" + }, + { + "city": "Sacramento", + "state": "California", + "zip": "95829" + }, + { + "city": "Chula Vista", + "state": "California", + "zip": "91913" + }, + { + "city": "Anchorage", + "state": "Alaska", + "zip": "99508" + }, + { + "city": "Port Saint Lucie", + "state": "Florida", + "zip": "34952" + }, + { + "city": "Indianapolis", + "state": "Indiana", + "zip": "46222" + }, + { + "city": "Hampton", + "state": "Georgia", + "zip": "30228" + }, + { + "city": "Pine Bluff", + "state": "Arkansas", + "zip": "71603" + }, + { + "city": "Seattle", + "state": "Washington", + "zip": "98168" + }, + { + "city": "Canoga Park", + "state": "California", + "zip": "91304" + }, + { + "city": "Murfreesboro", + "state": "Tennessee", + "zip": "37129" + }, + { + "city": "Grand Blanc", + "state": "Michigan", + "zip": "48439" + }, + { + "city": "Trenton", + "state": "New Jersey", + "zip": "8618" + }, + { + "city": "Toms River", + "state": "New Jersey", + "zip": "8757" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19138" + }, + { + "city": "Waldorf", + "state": "Maryland", + "zip": "20602" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60624" + }, + { + "city": "Elmwood Park", + "state": "Illinois", + "zip": "60707" + }, + { + "city": "Union", + "state": "New Jersey", + "zip": "7083" + }, + { + "city": "East Elmhurst", + "state": "New York", + "zip": "11369" + }, + { + "city": "Waynesboro", + "state": "Virginia", + "zip": "22980" + }, + { + "city": "Winston Salem", + "state": "North Carolina", + "zip": "27105" + }, + { + "city": "Elk Grove", + "state": "California", + "zip": "95758" + }, + { + "city": "Saint Louis", + "state": "Missouri", + "zip": "63129" + }, + { + "city": "Englewood", + "state": "Colorado", + "zip": "80112" + }, + { + "city": "Memphis", + "state": "Tennessee", + "zip": "38109" + }, + { + "city": "Chillicothe", + "state": "Ohio", + "zip": "45601" + }, + { + "city": "Sterling Heights", + "state": "Michigan", + "zip": "48312" + }, + { + "city": "Richmond", + "state": "Virginia", + "zip": "23234" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33175" + }, + { + "city": "Carrollton", + "state": "Texas", + "zip": "75006" + }, + { + "city": "Fresno", + "state": "California", + "zip": "93702" + }, + { + "city": "Stockton", + "state": "California", + "zip": "95205" + }, + { + "city": "Staten Island", + "state": "New York", + "zip": "10306" + }, + { + "city": "East Stroudsburg", + "state": "Pennsylvania", + "zip": "18301" + }, + { + "city": "Albany", + "state": "Georgia", + "zip": "31705" + }, + { + "city": "Ocoee", + "state": "Florida", + "zip": "34761" + }, + { + "city": "Marshfield", + "state": "Massachusetts", + "zip": "2050" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90016" + }, + { + "city": "Cupertino", + "state": "California", + "zip": "95014" + }, + { + "city": "San Jose", + "state": "California", + "zip": "95120" + }, + { + "city": "Sacramento", + "state": "California", + "zip": "95831" + }, + { + "city": "Bellevue", + "state": "Washington", + "zip": "98006" + }, + { + "city": "Milwaukee", + "state": "Wisconsin", + "zip": "53223" + }, + { + "city": "Providence", + "state": "Rhode Island", + "zip": "2904" + }, + { + "city": "Mcallen", + "state": "Texas", + "zip": "78504" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89104" + }, + { + "city": "Sylmar", + "state": "California", + "zip": "91342" + }, + { + "city": "Sonora", + "state": "California", + "zip": "95370" + }, + { + "city": "Seattle", + "state": "Washington", + "zip": "98125" + }, + { + "city": "Big Spring", + "state": "Texas", + "zip": "79720" + }, + { + "city": "Littleton", + "state": "Colorado", + "zip": "80122" + }, + { + "city": "Rancho Cordova", + "state": "California", + "zip": "95670" + }, + { + "city": "Chico", + "state": "California", + "zip": "95928" + }, + { + "city": "La Quinta", + "state": "California", + "zip": "92253" + }, + { + "city": "Atlanta", + "state": "Georgia", + "zip": "30328" + }, + { + "city": "Lancaster", + "state": "Ohio", + "zip": "43130" + }, + { + "city": "New Orleans", + "state": "Louisiana", + "zip": "70118" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77089" + }, + { + "city": "Marietta", + "state": "Georgia", + "zip": "30068" + }, + { + "city": "Oklahoma City", + "state": "Oklahoma", + "zip": "73112" + }, + { + "city": "Boston", + "state": "Massachusetts", + "zip": "2115" + }, + { + "city": "Miami", + "state": "Florida", + "zip": "33178" + }, + { + "city": "Louisville", + "state": "Kentucky", + "zip": "40218" + }, + { + "city": "Delano", + "state": "California", + "zip": "93215" + }, + { + "city": "Salem", + "state": "Oregon", + "zip": "97302" + }, + { + "city": "Cape Coral", + "state": "Florida", + "zip": "33904" + }, + { + "city": "Prior Lake", + "state": "Minnesota", + "zip": "55372" + }, + { + "city": "Aberdeen", + "state": "South Dakota", + "zip": "57401" + }, + { + "city": "Arlington", + "state": "Texas", + "zip": "76018" + }, + { + "city": "Longmont", + "state": "Colorado", + "zip": "80503" + }, + { + "city": "Alpharetta", + "state": "Georgia", + "zip": "30022" + }, + { + "city": "Stockton", + "state": "California", + "zip": "95204" + }, + { + "city": "Owosso", + "state": "Michigan", + "zip": "48867" + }, + { + "city": "Richmond", + "state": "Texas", + "zip": "77469" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90045" + }, + { + "city": "Overland Park", + "state": "Kansas", + "zip": "66223" + }, + { + "city": "Edmond", + "state": "Oklahoma", + "zip": "73013" + }, + { + "city": "Carrollton", + "state": "Texas", + "zip": "75007" + }, + { + "city": "Dallas", + "state": "Texas", + "zip": "75228" + }, + { + "city": "Hurst", + "state": "Texas", + "zip": "76053" + }, + { + "city": "Boulder", + "state": "Colorado", + "zip": "80301" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85032" + }, + { + "city": "Tempe", + "state": "Arizona", + "zip": "85281" + }, + { + "city": "Merrillville", + "state": "Indiana", + "zip": "46410" + }, + { + "city": "Bronx", + "state": "New York", + "zip": "10475" + }, + { + "city": "Burlington", + "state": "Vermont", + "zip": "5401" + }, + { + "city": "Cleveland", + "state": "Ohio", + "zip": "44118" + }, + { + "city": "Valparaiso", + "state": "Indiana", + "zip": "46383" + }, + { + "city": "Des Moines", + "state": "Iowa", + "zip": "50317" + }, + { + "city": "Council Bluffs", + "state": "Iowa", + "zip": "51503" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19111" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19141" + }, + { + "city": "Buford", + "state": "Georgia", + "zip": "30518" + }, + { + "city": "Sanford", + "state": "Florida", + "zip": "32771" + }, + { + "city": "Grandville", + "state": "Michigan", + "zip": "49418" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90025" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90041" + }, + { + "city": "Valencia", + "state": "California", + "zip": "91354" + }, + { + "city": "Mountain View", + "state": "California", + "zip": "94043" + }, + { + "city": "Lynnwood", + "state": "Washington", + "zip": "98037" + }, + { + "city": "Cleveland", + "state": "Ohio", + "zip": "44130" + }, + { + "city": "Milwaukee", + "state": "Wisconsin", + "zip": "53225" + }, + { + "city": "Mountain Home", + "state": "Arkansas", + "zip": "72653" + }, + { + "city": "Grants Pass", + "state": "Oregon", + "zip": "97527" + }, + { + "city": "Dallas", + "state": "Texas", + "zip": "75206" + }, + { + "city": "Fort Worth", + "state": "Texas", + "zip": "76133" + }, + { + "city": "Argyle", + "state": "Texas", + "zip": "76226" + }, + { + "city": "Fort Collins", + "state": "Colorado", + "zip": "80526" + }, + { + "city": "San Bernardino", + "state": "California", + "zip": "92411" + }, + { + "city": "Alamo", + "state": "Texas", + "zip": "78516" + }, + { + "city": "Erie", + "state": "Colorado", + "zip": "80516" + }, + { + "city": "Cuyahoga Falls", + "state": "Ohio", + "zip": "44221" + }, + { + "city": "Clementon", + "state": "New Jersey", + "zip": "8021" + }, + { + "city": "Decatur", + "state": "Georgia", + "zip": "30033" + }, + { + "city": "Imperial", + "state": "Missouri", + "zip": "63052" + }, + { + "city": "Ontario", + "state": "California", + "zip": "91764" + }, + { + "city": "Hanford", + "state": "California", + "zip": "93230" + }, + { + "city": "Anaheim", + "state": "California", + "zip": "92805" + }, + { + "city": "Richmond", + "state": "Kentucky", + "zip": "40475" + }, + { + "city": "South Bend", + "state": "Indiana", + "zip": "46614" + }, + { + "city": "Framingham", + "state": "Massachusetts", + "zip": "1702" + }, + { + "city": "Cary", + "state": "North Carolina", + "zip": "27513" + }, + { + "city": "Lagrange", + "state": "Georgia", + "zip": "30240" + }, + { + "city": "Martinsville", + "state": "Indiana", + "zip": "46151" + }, + { + "city": "Muskegon", + "state": "Michigan", + "zip": "49442" + }, + { + "city": "Dubuque", + "state": "Iowa", + "zip": "52001" + }, + { + "city": "Soledad", + "state": "California", + "zip": "93960" + }, + { + "city": "Sherwood", + "state": "Oregon", + "zip": "97140" + }, + { + "city": "Inglewood", + "state": "California", + "zip": "90302" + }, + { + "city": "West Covina", + "state": "California", + "zip": "91792" + }, + { + "city": "Parker", + "state": "Colorado", + "zip": "80134" + }, + { + "city": "Hialeah", + "state": "Florida", + "zip": "33016" + }, + { + "city": "Hollywood", + "state": "Florida", + "zip": "33023" + }, + { + "city": "Minneapolis", + "state": "Minnesota", + "zip": "55422" + }, + { + "city": "Denver", + "state": "Colorado", + "zip": "80206" + }, + { + "city": "Oxford", + "state": "Michigan", + "zip": "48371" + }, + { + "city": "Saginaw", + "state": "Michigan", + "zip": "48601" + }, + { + "city": "Brookings", + "state": "South Dakota", + "zip": "57006" + }, + { + "city": "Coamo", + "state": "Puerto Rico", + "zip": "769" + }, + { + "city": "Ashburn", + "state": "Virginia", + "zip": "20147" + }, + { + "city": "Suitland", + "state": "Maryland", + "zip": "20746" + }, + { + "city": "Danville", + "state": "Virginia", + "zip": "24540" + }, + { + "city": "Las Vegas", + "state": "Nevada", + "zip": "89101" + }, + { + "city": "Arecibo", + "state": "Puerto Rico", + "zip": "612" + }, + { + "city": "Staten Island", + "state": "New York", + "zip": "10308" + }, + { + "city": "Newburgh", + "state": "New York", + "zip": "12550" + }, + { + "city": "Marietta", + "state": "Georgia", + "zip": "30008" + }, + { + "city": "Naples", + "state": "Florida", + "zip": "34112" + }, + { + "city": "Oxford", + "state": "Ohio", + "zip": "45056" + }, + { + "city": "Ypsilanti", + "state": "Michigan", + "zip": "48198" + }, + { + "city": "Franklin", + "state": "Massachusetts", + "zip": "2038" + }, + { + "city": "Manassas", + "state": "Virginia", + "zip": "20109" + }, + { + "city": "Blacksburg", + "state": "Virginia", + "zip": "24060" + }, + { + "city": "Springville", + "state": "Utah", + "zip": "84663" + }, + { + "city": "Goldsboro", + "state": "North Carolina", + "zip": "27534" + }, + { + "city": "Charlotte", + "state": "North Carolina", + "zip": "28273" + }, + { + "city": "Miami Beach", + "state": "Florida", + "zip": "33139" + }, + { + "city": "Battle Creek", + "state": "Michigan", + "zip": "49015" + }, + { + "city": "Schenectady", + "state": "New York", + "zip": "12303" + }, + { + "city": "Elmhurst", + "state": "Illinois", + "zip": "60126" + }, + { + "city": "Trabuco Canyon", + "state": "California", + "zip": "92679" + }, + { + "city": "Chesapeake", + "state": "Virginia", + "zip": "23321" + }, + { + "city": "Nashville", + "state": "Tennessee", + "zip": "37214" + }, + { + "city": "Renton", + "state": "Washington", + "zip": "98059" + }, + { + "city": "Fort Collins", + "state": "Colorado", + "zip": "80524" + }, + { + "city": "Reno", + "state": "Nevada", + "zip": "89523" + }, + { + "city": "Astoria", + "state": "New York", + "zip": "11102" + }, + { + "city": "Spring Hill", + "state": "Tennessee", + "zip": "37174" + }, + { + "city": "Kansas City", + "state": "Missouri", + "zip": "64151" + }, + { + "city": "Santa Rosa", + "state": "California", + "zip": "95409" + }, + { + "city": "Monroe", + "state": "North Carolina", + "zip": "28112" + }, + { + "city": "Fremont", + "state": "Nebraska", + "zip": "68025" + }, + { + "city": "Baton Rouge", + "state": "Louisiana", + "zip": "70815" + }, + { + "city": "Edison", + "state": "New Jersey", + "zip": "8820" + }, + { + "city": "Whitestone", + "state": "New York", + "zip": "11357" + }, + { + "city": "Opelika", + "state": "Alabama", + "zip": "36801" + }, + { + "city": "Jackson", + "state": "Tennessee", + "zip": "38301" + }, + { + "city": "Louisville", + "state": "Kentucky", + "zip": "40207" + }, + { + "city": "Elizabethtown", + "state": "Kentucky", + "zip": "42701" + }, + { + "city": "Cleveland", + "state": "Ohio", + "zip": "44129" + }, + { + "city": "Herriman", + "state": "Utah", + "zip": "84096" + }, + { + "city": "Kissimmee", + "state": "Florida", + "zip": "34741" + }, + { + "city": "Wallingford", + "state": "Connecticut", + "zip": "6492" + }, + { + "city": "San Francisco", + "state": "California", + "zip": "94103" + }, + { + "city": "Muncie", + "state": "Indiana", + "zip": "47303" + }, + { + "city": "Savannah", + "state": "Georgia", + "zip": "31405" + }, + { + "city": "Fort Myers", + "state": "Florida", + "zip": "33919" + }, + { + "city": "Tullahoma", + "state": "Tennessee", + "zip": "37388" + }, + { + "city": "Natchez", + "state": "Mississippi", + "zip": "39120" + }, + { + "city": "Willoughby", + "state": "Ohio", + "zip": "44094" + }, + { + "city": "Springfield", + "state": "Massachusetts", + "zip": "1108" + }, + { + "city": "Lowell", + "state": "Massachusetts", + "zip": "1854" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11215" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11228" + }, + { + "city": "Norfolk", + "state": "Virginia", + "zip": "23513" + }, + { + "city": "Sumter", + "state": "South Carolina", + "zip": "29154" + }, + { + "city": "Odessa", + "state": "Texas", + "zip": "79761" + }, + { + "city": "Gilbert", + "state": "Arizona", + "zip": "85295" + }, + { + "city": "Modesto", + "state": "California", + "zip": "95358" + }, + { + "city": "San Diego", + "state": "California", + "zip": "92124" + }, + { + "city": "Goodyear", + "state": "Arizona", + "zip": "85338" + }, + { + "city": "Billings", + "state": "Montana", + "zip": "59105" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60659" + }, + { + "city": "Pittsburgh", + "state": "Pennsylvania", + "zip": "15212" + }, + { + "city": "Fresno", + "state": "California", + "zip": "93711" + }, + { + "city": "Columbus", + "state": "Ohio", + "zip": "43224" + }, + { + "city": "Van Nuys", + "state": "California", + "zip": "91405" + }, + { + "city": "Lancaster", + "state": "California", + "zip": "93534" + }, + { + "city": "Sacramento", + "state": "California", + "zip": "95842" + }, + { + "city": "Hilo", + "state": "Hawaii", + "zip": "96720" + }, + { + "city": "Beaverton", + "state": "Oregon", + "zip": "97008" + }, + { + "city": "Greenville", + "state": "South Carolina", + "zip": "29607" + }, + { + "city": "Orlando", + "state": "Florida", + "zip": "32828" + }, + { + "city": "Boca Raton", + "state": "Florida", + "zip": "33428" + }, + { + "city": "Fort Myers", + "state": "Florida", + "zip": "33913" + }, + { + "city": "Fort Wayne", + "state": "Indiana", + "zip": "46825" + }, + { + "city": "Decatur", + "state": "Illinois", + "zip": "62521" + }, + { + "city": "Fort Worth", + "state": "Texas", + "zip": "76132" + }, + { + "city": "Chandler", + "state": "Arizona", + "zip": "85248" + }, + { + "city": "Scottsdale", + "state": "Arizona", + "zip": "85255" + }, + { + "city": "Aguas Buenas", + "state": "Puerto Rico", + "zip": "703" + }, + { + "city": "Columbus", + "state": "Indiana", + "zip": "47203" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19154" + }, + { + "city": "Denton", + "state": "Texas", + "zip": "76209" + }, + { + "city": "Pompano Beach", + "state": "Florida", + "zip": "33067" + }, + { + "city": "Monroeville", + "state": "Pennsylvania", + "zip": "15146" + }, + { + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19116" + }, + { + "city": "Vienna", + "state": "Virginia", + "zip": "22182" + }, + { + "city": "Hickory", + "state": "North Carolina", + "zip": "28602" + }, + { + "city": "Uniontown", + "state": "Ohio", + "zip": "44685" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90011" + }, + { + "city": "Bellevue", + "state": "Washington", + "zip": "98007" + }, + { + "city": "Suffolk", + "state": "Virginia", + "zip": "23434" + }, + { + "city": "Winterville", + "state": "North Carolina", + "zip": "28590" + }, + { + "city": "Louisville", + "state": "Kentucky", + "zip": "40299" + }, + { + "city": "Toledo", + "state": "Ohio", + "zip": "43614" + }, + { + "city": "Woodside", + "state": "New York", + "zip": "11377" + }, + { + "city": "Bellmore", + "state": "New York", + "zip": "11710" + }, + { + "city": "Venice", + "state": "California", + "zip": "90291" + }, + { + "city": "Rosemead", + "state": "California", + "zip": "91770" + }, + { + "city": "Topeka", + "state": "Kansas", + "zip": "66614" + }, + { + "city": "Cabot", + "state": "Arkansas", + "zip": "72023" + }, + { + "city": "Lawndale", + "state": "California", + "zip": "90260" + }, + { + "city": "Palm Springs", + "state": "California", + "zip": "92262" + }, + { + "city": "Livermore", + "state": "California", + "zip": "94550" + }, + { + "city": "Stockton", + "state": "California", + "zip": "95212" + }, + { + "city": "Grand Rapids", + "state": "Michigan", + "zip": "49503" + }, + { + "city": "Libertyville", + "state": "Illinois", + "zip": "60048" + }, + { + "city": "Fairfield", + "state": "California", + "zip": "94534" + }, + { + "city": "Dallas", + "state": "Texas", + "zip": "75204" + }, + { + "city": "Killeen", + "state": "Texas", + "zip": "76549" + }, + { + "city": "Channelview", + "state": "Texas", + "zip": "77530" + }, + { + "city": "San Antonio", + "state": "Texas", + "zip": "78233" + }, + { + "city": "Blue Island", + "state": "Illinois", + "zip": "60406" + }, + { + "city": "Champaign", + "state": "Illinois", + "zip": "61822" + }, + { + "city": "Troy", + "state": "Missouri", + "zip": "63379" + }, + { + "city": "El Paso", + "state": "Texas", + "zip": "79925" + }, + { + "city": "Provo", + "state": "Utah", + "zip": "84606" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11234" + }, + { + "city": "Harrisburg", + "state": "Pennsylvania", + "zip": "17112" + }, + { + "city": "Scottsdale", + "state": "Arizona", + "zip": "85251" + }, + { + "city": "Kingsport", + "state": "Tennessee", + "zip": "37660" + }, + { + "city": "Port Orange", + "state": "Florida", + "zip": "32127" + }, + { + "city": "El Monte", + "state": "California", + "zip": "91732" + }, + { + "city": "Austin", + "state": "Texas", + "zip": "78723" + }, + { + "city": "Menifee", + "state": "California", + "zip": "92584" + }, + { + "city": "Fort Worth", + "state": "Texas", + "zip": "76108" + }, + { + "city": "Phoenix", + "state": "Arizona", + "zip": "85031" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90043" + }, + { + "city": "Clinton", + "state": "Maryland", + "zip": "20735" + }, + { + "city": "Brooklyn", + "state": "Maryland", + "zip": "21225" + }, + { + "city": "Elkton", + "state": "Maryland", + "zip": "21921" + }, + { + "city": "Indianapolis", + "state": "Indiana", + "zip": "46234" + }, + { + "city": "Columbus", + "state": "Indiana", + "zip": "47201" + }, + { + "city": "Roseville", + "state": "Michigan", + "zip": "48066" + }, + { + "city": "Harbor City", + "state": "California", + "zip": "90710" + }, + { + "city": "Daly City", + "state": "California", + "zip": "94015" + }, + { + "city": "Concord", + "state": "North Carolina", + "zip": "28027" + }, + { + "city": "Georgetown", + "state": "Kentucky", + "zip": "40324" + }, + { + "city": "Bellevue", + "state": "Nebraska", + "zip": "68123" + }, + { + "city": "Thibodaux", + "state": "Louisiana", + "zip": "70301" + }, + { + "city": "Quincy", + "state": "Massachusetts", + "zip": "2169" + }, + { + "city": "Teaneck", + "state": "New Jersey", + "zip": "7666" + }, + { + "city": "Pittsburgh", + "state": "Pennsylvania", + "zip": "15221" + }, + { + "city": "Gainesville", + "state": "Virginia", + "zip": "20155" + }, + { + "city": "Oxon Hill", + "state": "Maryland", + "zip": "20745" + }, + { + "city": "San Jose", + "state": "California", + "zip": "95127" + }, + { + "city": "Palm Beach Gardens", + "state": "Florida", + "zip": "33418" + }, + { + "city": "Hermitage", + "state": "Tennessee", + "zip": "37076" + }, + { + "city": "Wyoming", + "state": "Michigan", + "zip": "49519" + }, + { + "city": "Mount Pleasant", + "state": "South Carolina", + "zip": "29466" + }, + { + "city": "Duluth", + "state": "Georgia", + "zip": "30096" + }, + { + "city": "Niles", + "state": "Illinois", + "zip": "60714" + }, + { + "city": "Bullhead City", + "state": "Arizona", + "zip": "86442" + }, + { + "city": "Santa Paula", + "state": "California", + "zip": "93060" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11208" + }, + { + "city": "Catonsville", + "state": "Maryland", + "zip": "21228" + }, + { + "city": "Fenton", + "state": "Missouri", + "zip": "63026" + }, + { + "city": "Deridder", + "state": "Louisiana", + "zip": "70634" + }, + { + "city": "Tyler", + "state": "Texas", + "zip": "75701" + }, + { + "city": "Henderson", + "state": "Kentucky", + "zip": "42420" + }, + { + "city": "Bronx", + "state": "New York", + "zip": "10459" + }, + { + "city": "Severna Park", + "state": "Maryland", + "zip": "21146" + }, + { + "city": "East Orange", + "state": "New Jersey", + "zip": "7017" + }, + { + "city": "Chantilly", + "state": "Virginia", + "zip": "20152" + }, + { + "city": "Belton", + "state": "Missouri", + "zip": "64012" + }, + { + "city": "Waco", + "state": "Texas", + "zip": "76708" + }, + { + "city": "Dickinson", + "state": "Texas", + "zip": "77539" + }, + { + "city": "San Diego", + "state": "California", + "zip": "92128" + }, + { + "city": "Bakersfield", + "state": "California", + "zip": "93313" + }, + { + "city": "Virginia Beach", + "state": "Virginia", + "zip": "23455" + }, + { + "city": "Gainesville", + "state": "Georgia", + "zip": "30506" + }, + { + "city": "New Kensington", + "state": "Pennsylvania", + "zip": "15068" + }, + { + "city": "Fayetteville", + "state": "North Carolina", + "zip": "28303" + }, + { + "city": "Bronx", + "state": "New York", + "zip": "10463" + }, + { + "city": "Carlisle", + "state": "Pennsylvania", + "zip": "17013" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77024" + }, + { + "city": "Aurora", + "state": "Colorado", + "zip": "80017" + }, + { + "city": "Indianapolis", + "state": "Indiana", + "zip": "46220" + }, + { + "city": "Tinley Park", + "state": "Illinois", + "zip": "60477" + }, + { + "city": "Portland", + "state": "Oregon", + "zip": "97230" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90008" + }, + { + "city": "Lodi", + "state": "California", + "zip": "95242" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11213" + }, + { + "city": "Suffolk", + "state": "Virginia", + "zip": "23435" + }, + { + "city": "Lombard", + "state": "Illinois", + "zip": "60148" + }, + { + "city": "Brighton", + "state": "Colorado", + "zip": "80601" + }, + { + "city": "Ewa Beach", + "state": "Hawaii", + "zip": "96706" + }, + { + "city": "Rancho Cucamonga", + "state": "California", + "zip": "91701" + }, + { + "city": "Richmond", + "state": "Indiana", + "zip": "47374" + }, + { + "city": "Littleton", + "state": "Colorado", + "zip": "80120" + }, + { + "city": "Albemarle", + "state": "North Carolina", + "zip": "28001" + }, + { + "city": "Yorkville", + "state": "Illinois", + "zip": "60560" + }, + { + "city": "Mount Laurel", + "state": "New Jersey", + "zip": "8054" + }, + { + "city": "Cranberry Township", + "state": "Pennsylvania", + "zip": "16066" + }, + { + "city": "Shelby", + "state": "North Carolina", + "zip": "28150" + }, + { + "city": "Rock Hill", + "state": "South Carolina", + "zip": "29730" + }, + { + "city": "Smyrna", + "state": "Georgia", + "zip": "30080" + }, + { + "city": "Newnan", + "state": "Georgia", + "zip": "30263" + }, + { + "city": "Newark", + "state": "California", + "zip": "94560" + }, + { + "city": "Bellingham", + "state": "Washington", + "zip": "98225" + }, + { + "city": "Los Angeles", + "state": "California", + "zip": "90002" + }, + { + "city": "Plainfield", + "state": "Illinois", + "zip": "60544" + }, + { + "city": "Denver", + "state": "Colorado", + "zip": "80229" + }, + { + "city": "Pompano Beach", + "state": "Florida", + "zip": "33060" + }, + { + "city": "Pompano Beach", + "state": "Florida", + "zip": "33068" + }, + { + "city": "Cincinnati", + "state": "Ohio", + "zip": "45215" + }, + { + "city": "Dayton", + "state": "Ohio", + "zip": "45429" + }, + { + "city": "Gainesville", + "state": "Florida", + "zip": "32605" + }, + { + "city": "Detroit", + "state": "Michigan", + "zip": "48219" + }, + { + "city": "Milwaukee", + "state": "Wisconsin", + "zip": "53220" + }, + { + "city": "Saint Paul", + "state": "Minnesota", + "zip": "55112" + }, + { + "city": "Gonzales", + "state": "Louisiana", + "zip": "70737" + }, + { + "city": "Houston", + "state": "Texas", + "zip": "77035" + }, + { + "city": "El Paso", + "state": "Texas", + "zip": "79936" + }, + { + "city": "Norwood", + "state": "Massachusetts", + "zip": "2062" + }, + { + "city": "Petersburg", + "state": "Virginia", + "zip": "23803" + }, + { + "city": "Athens", + "state": "Georgia", + "zip": "30605" + }, + { + "city": "Knoxville", + "state": "Tennessee", + "zip": "37931" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60637" + }, + { + "city": "Brooklyn", + "state": "New York", + "zip": "11223" + }, + { + "city": "Ronkonkoma", + "state": "New York", + "zip": "11779" + }, + { + "city": "Hagerstown", + "state": "Maryland", + "zip": "21742" + }, + { + "city": "Millville", + "state": "New Jersey", + "zip": "8332" + }, + { + "city": "Staten Island", + "state": "New York", + "zip": "10304" + }, + { + "city": "Chicago", + "state": "Illinois", + "zip": "60622" + } +] diff --git a/public/data/contacts.json b/public/data/contacts.json new file mode 100644 index 00000000..fec4fd17 --- /dev/null +++ b/public/data/contacts.json @@ -0,0 +1,4202 @@ +[ + { + "first_name": "Vladamir", + "last_name": "Adelsberg", + "email": "vadelsberg0@nhs.uk", + "gender": "Male", + "address": "9381 Drewry Plaza", + "city": "Charlotte", + "state": "North Carolina", + "zip": "28230", + "company": "Zooveo", + "job_title": "Operator", + "timezone": "America/New_York", + "title": "Dr" + }, + { + "first_name": "Fleming", + "last_name": "Liverseege", + "email": "fliverseege1@technorati.com", + "gender": "Agender", + "address": "6 Tony Circle", + "city": "Schenectady", + "state": "New York", + "zip": "12325", + "company": "Roomm", + "job_title": "Design Engineer", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Noami", + "last_name": "McLurg", + "email": "nmclurg2@unesco.org", + "gender": "Female", + "address": "974 New Castle Street", + "city": "Humble", + "state": "Texas", + "zip": "77346", + "company": "Youfeed", + "job_title": "Developer III", + "timezone": "America/Chicago", + "title": "Honorable" + }, + { + "first_name": "Georgine", + "last_name": "Armell", + "email": "garmell3@issuu.com", + "gender": "Female", + "address": "287 Jackson Center", + "city": "Midland", + "state": "Texas", + "zip": "79705", + "company": "Skimia", + "job_title": "Occupational Therapist", + "timezone": "America/Chicago", + "title": "Rev" + }, + { + "first_name": "Dun", + "last_name": "Henriquet", + "email": "dhenriquet4@pagesperso-orange.fr", + "gender": "Male", + "address": "70806 Laurel Road", + "city": "Albuquerque", + "state": "New Mexico", + "zip": "87140", + "company": "Jabbertype", + "job_title": "Graphic Designer", + "timezone": "America/Denver", + "title": "Ms" + }, + { + "first_name": "Binky", + "last_name": "Curnow", + "email": "bcurnow5@stanford.edu", + "gender": "Male", + "address": "2868 Main Way", + "city": "Scottsdale", + "state": "Arizona", + "zip": "85271", + "company": "Photojam", + "job_title": "Developer II", + "timezone": "America/Phoenix", + "title": "Dr" + }, + { + "first_name": "Rosetta", + "last_name": "Wykey", + "email": "rwykey6@netscape.com", + "gender": "Bigender", + "address": "927 5th Crossing", + "city": "Warren", + "state": "Michigan", + "zip": "48092", + "company": "Flipbug", + "job_title": "Database Administrator III", + "timezone": "America/Detroit", + "title": "Dr" + }, + { + "first_name": "Veronike", + "last_name": "MacRonald", + "email": "vmacronald7@blogger.com", + "gender": "Female", + "address": "941 Summit Road", + "city": "Long Beach", + "state": "California", + "zip": "90810", + "company": "Kwideo", + "job_title": "Research Nurse", + "timezone": "America/Los_Angeles", + "title": "Mrs" + }, + { + "first_name": "Sarene", + "last_name": "Jephcott", + "email": "sjephcott8@kickstarter.com", + "gender": "Female", + "address": "33 Hovde Center", + "city": "San Antonio", + "state": "Texas", + "zip": "78265", + "company": "Skivee", + "job_title": "Software Engineer IV", + "timezone": "America/Chicago", + "title": "Mrs" + }, + { + "first_name": "Manya", + "last_name": "Tilney", + "email": "mtilney9@mail.ru", + "gender": "Female", + "address": "64 Oxford Avenue", + "city": "Washington", + "state": "District of Columbia", + "zip": "20067", + "company": "Realcube", + "job_title": "Speech Pathologist", + "timezone": "America/New_York", + "title": "Dr" + }, + { + "first_name": "Gun", + "last_name": "Gyves", + "email": "ggyvesa@printfriendly.com", + "gender": "Male", + "address": "5 Sommers Park", + "city": "Brooklyn", + "state": "New York", + "zip": "11247", + "company": "Tekfly", + "job_title": "Senior Developer", + "timezone": "America/New_York", + "title": "Mrs" + }, + { + "first_name": "Darya", + "last_name": "Beazley", + "email": "dbeazleyb@si.edu", + "gender": "Female", + "address": "2 Superior Hill", + "city": "Erie", + "state": "Pennsylvania", + "zip": "16534", + "company": "Skyndu", + "job_title": "Senior Cost Accountant", + "timezone": "America/New_York", + "title": "Ms" + }, + { + "first_name": "Constance", + "last_name": "De Gregario", + "email": "cdegregarioc@pen.io", + "gender": "Female", + "address": "65 Cambridge Terrace", + "city": "White Plains", + "state": "New York", + "zip": "10633", + "company": "Gabcube", + "job_title": "Librarian", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Lanita", + "last_name": "Jakobssen", + "email": "ljakobssend@miibeian.gov.cn", + "gender": "Female", + "address": "88 Sundown Alley", + "city": "Lynn", + "state": "Massachusetts", + "zip": "01905", + "company": "Tagfeed", + "job_title": "VP Product Management", + "timezone": "America/New_York", + "title": "Mr" + }, + { + "first_name": "Ulrikaumeko", + "last_name": "Pharoah", + "email": "upharoahe@loc.gov", + "gender": "Female", + "address": "4087 Luster Trail", + "city": "Pasadena", + "state": "California", + "zip": "91186", + "company": "Quire", + "job_title": "Business Systems Development Analyst", + "timezone": "America/Los_Angeles", + "title": "Ms" + }, + { + "first_name": "Maritsa", + "last_name": "Gunney", + "email": "mgunneyf@toplist.cz", + "gender": "Female", + "address": "326 Bartelt Plaza", + "city": "Milwaukee", + "state": "Wisconsin", + "zip": "53263", + "company": "Ooba", + "job_title": "Dental Hygienist", + "timezone": "America/Chicago", + "title": "Mr" + }, + { + "first_name": "Hetti", + "last_name": "Hundal", + "email": "hhundalg@auda.org.au", + "gender": "Female", + "address": "04281 Lerdahl Circle", + "city": "Springfield", + "state": "Massachusetts", + "zip": "01152", + "company": "Skinder", + "job_title": "VP Product Management", + "timezone": "America/New_York", + "title": "Mr" + }, + { + "first_name": "Merwyn", + "last_name": "Coniff", + "email": "mconiffh@g.co", + "gender": "Genderqueer", + "address": "5 Pankratz Road", + "city": "Detroit", + "state": "Michigan", + "zip": "48224", + "company": "Ooba", + "job_title": "Occupational Therapist", + "timezone": "America/Detroit", + "title": "Mrs" + }, + { + "first_name": "Boris", + "last_name": "Cottu", + "email": "bcottui@bluehost.com", + "gender": "Male", + "address": "6 Elka Plaza", + "city": "Saint Petersburg", + "state": "Florida", + "zip": "33742", + "company": "Oodoo", + "job_title": "Speech Pathologist", + "timezone": "America/New_York", + "title": "Mrs" + }, + { + "first_name": "Fletch", + "last_name": "Jenny", + "email": "fjennyj@usnews.com", + "gender": "Male", + "address": "22 Sheridan Street", + "city": "Wichita Falls", + "state": "Texas", + "zip": "76310", + "company": "Livefish", + "job_title": "Research Nurse", + "timezone": "America/Chicago", + "title": "Mr" + }, + { + "first_name": "Leena", + "last_name": "Lees", + "email": "lleesk@ehow.com", + "gender": "Female", + "address": "180 Debra Court", + "city": "Macon", + "state": "Georgia", + "zip": "31205", + "company": "Ntag", + "job_title": "Chief Design Engineer", + "timezone": "America/New_York", + "title": "Dr" + }, + { + "first_name": "Merry", + "last_name": "Kears", + "email": "mkearsl@cbc.ca", + "gender": "Male", + "address": "103 Texas Center", + "city": "Bronx", + "state": "New York", + "zip": "10469", + "company": "Minyx", + "job_title": "Administrative Assistant I", + "timezone": "America/New_York", + "title": "Dr" + }, + { + "first_name": "Jaclyn", + "last_name": "Kobieriecki", + "email": "jkobierieckim@mysql.com", + "gender": "Female", + "address": "98138 South Drive", + "city": "Edmond", + "state": "Oklahoma", + "zip": "73034", + "company": "Oyondu", + "job_title": "Actuary", + "timezone": "America/Chicago", + "title": "Mr" + }, + { + "first_name": "Trey", + "last_name": "MacGauhy", + "email": "tmacgauhyn@nationalgeographic.com", + "gender": "Male", + "address": "31303 Fuller Center", + "city": "Baltimore", + "state": "Maryland", + "zip": "21275", + "company": "Vipe", + "job_title": "Software Consultant", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Dorella", + "last_name": "Bennellick", + "email": "dbennellicko@gov.uk", + "gender": "Female", + "address": "119 Hintze Court", + "city": "Chattanooga", + "state": "Tennessee", + "zip": "37410", + "company": "Trunyx", + "job_title": "Web Developer IV", + "timezone": "America/New_York", + "title": "Ms" + }, + { + "first_name": "Creighton", + "last_name": "Manhood", + "email": "cmanhoodp@ibm.com", + "gender": "Male", + "address": "7 Lerdahl Hill", + "city": "Boise", + "state": "Idaho", + "zip": "83722", + "company": "Thoughtbeat", + "job_title": "Biostatistician III", + "timezone": "America/Boise", + "title": "Mrs" + }, + { + "first_name": "Suzie", + "last_name": "Timby", + "email": "stimbyq@behance.net", + "gender": "Female", + "address": "4555 Northfield Alley", + "city": "Gulfport", + "state": "Mississippi", + "zip": "39505", + "company": "Jaxnation", + "job_title": "Structural Engineer", + "timezone": "America/Chicago", + "title": "Ms" + }, + { + "first_name": "Shirl", + "last_name": "Jakes", + "email": "sjakesr@arstechnica.com", + "gender": "Female", + "address": "40 Eliot Court", + "city": "Boston", + "state": "Massachusetts", + "zip": "02109", + "company": "Katz", + "job_title": "Geological Engineer", + "timezone": "America/New_York", + "title": "Mr" + }, + { + "first_name": "Gordon", + "last_name": "Gamble", + "email": "ggambles@twitpic.com", + "gender": "Male", + "address": "4091 Hansons Alley", + "city": "El Paso", + "state": "Texas", + "zip": "88569", + "company": "Flipstorm", + "job_title": "Research Nurse", + "timezone": "America/Denver", + "title": "Dr" + }, + { + "first_name": "Colin", + "last_name": "Jozwik", + "email": "cjozwikt@mac.com", + "gender": "Male", + "address": "07 Dexter Park", + "city": "Arlington", + "state": "Virginia", + "zip": "22212", + "company": "Tekfly", + "job_title": "Legal Assistant", + "timezone": "America/New_York", + "title": "Ms" + }, + { + "first_name": "Giustina", + "last_name": "Ferraresi", + "email": "gferraresiu@mashable.com", + "gender": "Female", + "address": "262 Knutson Court", + "city": "Salt Lake City", + "state": "Utah", + "zip": "84140", + "company": "Zoomzone", + "job_title": "Actuary", + "timezone": "America/Denver", + "title": "Honorable" + }, + { + "first_name": "Lenard", + "last_name": "Hegg", + "email": "lheggv@ycombinator.com", + "gender": "Male", + "address": "08236 Dottie Lane", + "city": "Pasadena", + "state": "California", + "zip": "91125", + "company": "Twitternation", + "job_title": "Human Resources Assistant III", + "timezone": "America/Los_Angeles", + "title": "Mrs" + }, + { + "first_name": "Deny", + "last_name": "Leftly", + "email": "dleftlyw@nba.com", + "gender": "Female", + "address": "8976 Homewood Terrace", + "city": "Bethlehem", + "state": "Pennsylvania", + "zip": "18018", + "company": "Jabberbean", + "job_title": "Senior Quality Engineer", + "timezone": "America/New_York", + "title": "Mr" + }, + { + "first_name": "Aland", + "last_name": "Stoakley", + "email": "astoakleyx@utexas.edu", + "gender": "Male", + "address": "956 Morrow Point", + "city": "Birmingham", + "state": "Alabama", + "zip": "35263", + "company": "Oodoo", + "job_title": "Business Systems Development Analyst", + "timezone": "America/Chicago", + "title": "Mr" + }, + { + "first_name": "Stacee", + "last_name": "Lemon", + "email": "slemony@arstechnica.com", + "gender": "Female", + "address": "5 Badeau Trail", + "city": "Waco", + "state": "Texas", + "zip": "76711", + "company": "Babbleblab", + "job_title": "Software Engineer II", + "timezone": "America/Chicago", + "title": "Mr" + }, + { + "first_name": "Hamnet", + "last_name": "Becks", + "email": "hbecksz@army.mil", + "gender": "Male", + "address": "064 Gulseth Road", + "city": "Birmingham", + "state": "Alabama", + "zip": "35254", + "company": "Divavu", + "job_title": "Editor", + "timezone": "America/Chicago", + "title": "Ms" + }, + { + "first_name": "Faustina", + "last_name": "Stoodley", + "email": "fstoodley10@networksolutions.com", + "gender": "Female", + "address": "30577 Derek Parkway", + "city": "Kalamazoo", + "state": "Michigan", + "zip": "49006", + "company": "Skippad", + "job_title": "GIS Technical Architect", + "timezone": "America/Detroit", + "title": "Honorable" + }, + { + "first_name": "April", + "last_name": "Biasini", + "email": "abiasini11@stanford.edu", + "gender": "Female", + "address": "793 Orin Crossing", + "city": "Louisville", + "state": "Kentucky", + "zip": "40220", + "company": "Mudo", + "job_title": "Junior Executive", + "timezone": "America/Kentucky/Louisville", + "title": "Mr" + }, + { + "first_name": "Panchito", + "last_name": "Hallworth", + "email": "phallworth12@pbs.org", + "gender": "Male", + "address": "1545 Monument Circle", + "city": "Topeka", + "state": "Kansas", + "zip": "66622", + "company": "Photobean", + "job_title": "Geologist III", + "timezone": "America/Chicago", + "title": "Dr" + }, + { + "first_name": "Fanchon", + "last_name": "Meiner", + "email": "fmeiner13@umn.edu", + "gender": "Female", + "address": "15 Veith Center", + "city": "Cleveland", + "state": "Ohio", + "zip": "44125", + "company": "Meemm", + "job_title": "Analyst Programmer", + "timezone": "America/New_York", + "title": "Mr" + }, + { + "first_name": "Marsiella", + "last_name": "Geal", + "email": "mgeal14@mozilla.org", + "gender": "Female", + "address": "82 Onsgard Lane", + "city": "Houston", + "state": "Texas", + "zip": "77065", + "company": "Browsetype", + "job_title": "Pharmacist", + "timezone": "America/Chicago", + "title": "Mrs" + }, + { + "first_name": "Nicolas", + "last_name": "Barnwille", + "email": "nbarnwille15@google.com.br", + "gender": "Male", + "address": "085 Clyde Gallagher Circle", + "city": "Chicago", + "state": "Illinois", + "zip": "60636", + "company": "Einti", + "job_title": "Staff Accountant III", + "timezone": "America/Chicago", + "title": "Dr" + }, + { + "first_name": "Tanney", + "last_name": "Cassels", + "email": "tcassels16@webmd.com", + "gender": "Male", + "address": "0218 Raven Alley", + "city": "Baton Rouge", + "state": "Louisiana", + "zip": "70826", + "company": "Voonyx", + "job_title": "Administrative Officer", + "timezone": "America/Chicago", + "title": "Dr" + }, + { + "first_name": "Marnia", + "last_name": "Ebbers", + "email": "mebbers17@ehow.com", + "gender": "Female", + "address": "74569 Laurel Place", + "city": "Kalamazoo", + "state": "Michigan", + "zip": "49006", + "company": "Myworks", + "job_title": "Senior Sales Associate", + "timezone": "America/Detroit", + "title": "Ms" + }, + { + "first_name": "Catlee", + "last_name": "Esslemont", + "email": "cesslemont18@army.mil", + "gender": "Female", + "address": "120 Muir Street", + "city": "Raleigh", + "state": "North Carolina", + "zip": "27615", + "company": "Quatz", + "job_title": "Internal Auditor", + "timezone": "America/New_York", + "title": "Dr" + }, + { + "first_name": "Bordy", + "last_name": "Whale", + "email": "bwhale19@bloglovin.com", + "gender": "Male", + "address": "76705 Bowman Junction", + "city": "Washington", + "state": "District of Columbia", + "zip": "20057", + "company": "Omba", + "job_title": "Financial Advisor", + "timezone": "America/New_York", + "title": "Dr" + }, + { + "first_name": "Rollin", + "last_name": "Jesper", + "email": "rjesper1a@shareasale.com", + "gender": "Male", + "address": "46 Meadow Ridge Place", + "city": "Richmond", + "state": "Virginia", + "zip": "23208", + "company": "Kwilith", + "job_title": "Junior Executive", + "timezone": "America/New_York", + "title": "Rev" + }, + { + "first_name": "Tiebold", + "last_name": "Dosdill", + "email": "tdosdill1b@ning.com", + "gender": "Male", + "address": "65 Division Plaza", + "city": "Los Angeles", + "state": "California", + "zip": "90025", + "company": "Photobean", + "job_title": "Account Coordinator", + "timezone": "America/Los_Angeles", + "title": "Dr" + }, + { + "first_name": "Martica", + "last_name": "Feragh", + "email": "mferagh1c@fastcompany.com", + "gender": "Female", + "address": "213 Moose Point", + "city": "Madison", + "state": "Wisconsin", + "zip": "53716", + "company": "Kare", + "job_title": "Senior Editor", + "timezone": "America/Chicago", + "title": "Ms" + }, + { + "first_name": "Sally", + "last_name": "Yeardsley", + "email": "syeardsley1d@behance.net", + "gender": "Female", + "address": "926 Merrick Center", + "city": "Nashville", + "state": "Tennessee", + "zip": "37228", + "company": "Feedfish", + "job_title": "Speech Pathologist", + "timezone": "America/Chicago", + "title": "Dr" + }, + { + "first_name": "Antonetta", + "last_name": "Gwilt", + "email": "agwilt1e@sitemeter.com", + "gender": "Female", + "address": "0 Butternut Park", + "city": "San Luis Obispo", + "state": "California", + "zip": "93407", + "company": "Babbleopia", + "job_title": "Geological Engineer", + "timezone": "America/Los_Angeles", + "title": "Dr" + }, + { + "first_name": "Vyky", + "last_name": "Morford", + "email": "vmorford1f@simplemachines.org", + "gender": "Female", + "address": "17363 Norway Maple Hill", + "city": "Newport Beach", + "state": "California", + "zip": "92662", + "company": "Yacero", + "job_title": "Database Administrator III", + "timezone": "America/Los_Angeles", + "title": "Honorable" + }, + { + "first_name": "Tamra", + "last_name": "Frowd", + "email": "tfrowd1g@amazon.co.jp", + "gender": "Female", + "address": "209 Coolidge Park", + "city": "Anniston", + "state": "Alabama", + "zip": "36205", + "company": "Mudo", + "job_title": "Research Associate", + "timezone": "America/Chicago", + "title": "Mr" + }, + { + "first_name": "Collin", + "last_name": "Merali", + "email": "cmerali1h@dedecms.com", + "gender": "Male", + "address": "4649 Corscot Street", + "city": "Milwaukee", + "state": "Wisconsin", + "zip": "53277", + "company": "Bluezoom", + "job_title": "Staff Accountant I", + "timezone": "America/Chicago", + "title": "Dr" + }, + { + "first_name": "Eamon", + "last_name": "Kleinert", + "email": "ekleinert1i@bandcamp.com", + "gender": "Male", + "address": "226 Novick Park", + "city": "Corpus Christi", + "state": "Texas", + "zip": "78410", + "company": "Wordtune", + "job_title": "Human Resources Manager", + "timezone": "America/Chicago", + "title": "Honorable" + }, + { + "first_name": "Meagan", + "last_name": "Duley", + "email": "mduley1j@cbslocal.com", + "gender": "Female", + "address": "60 Bluejay Trail", + "city": "Pittsburgh", + "state": "Pennsylvania", + "zip": "15235", + "company": "Flashset", + "job_title": "Media Manager IV", + "timezone": "America/New_York", + "title": "Ms" + }, + { + "first_name": "Dulce", + "last_name": "McGrory", + "email": "dmcgrory1k@boston.com", + "gender": "Female", + "address": "48 Twin Pines Court", + "city": "Washington", + "state": "District of Columbia", + "zip": "20599", + "company": "Babbleblab", + "job_title": "Recruiter", + "timezone": "America/New_York", + "title": "Mr" + }, + { + "first_name": "Thayne", + "last_name": "Exell", + "email": "texell1l@dagondesign.com", + "gender": "Male", + "address": "5 Norway Maple Street", + "city": "Peoria", + "state": "Illinois", + "zip": "61635", + "company": "Minyx", + "job_title": "Nurse", + "timezone": "America/Chicago", + "title": "Mr" + }, + { + "first_name": "Evvie", + "last_name": "Castellini", + "email": "ecastellini1m@moonfruit.com", + "gender": "Female", + "address": "0825 Derek Plaza", + "city": "Bowie", + "state": "Maryland", + "zip": "20719", + "company": "Muxo", + "job_title": "General Manager", + "timezone": "America/New_York", + "title": "Ms" + }, + { + "first_name": "Cory", + "last_name": "Mandell", + "email": "cmandell1n@people.com.cn", + "gender": "Male", + "address": "7 Meadow Vale Drive", + "city": "El Paso", + "state": "Texas", + "zip": "88514", + "company": "Bubblebox", + "job_title": "Information Systems Manager", + "timezone": "America/Denver", + "title": "Mrs" + }, + { + "first_name": "Lorettalorna", + "last_name": "Coulthard", + "email": "lcoulthard1o@skype.com", + "gender": "Female", + "address": "700 Towne Road", + "city": "Salt Lake City", + "state": "Utah", + "zip": "84110", + "company": "Thoughtbridge", + "job_title": "Research Associate", + "timezone": "America/Denver", + "title": "Ms" + }, + { + "first_name": "Michele", + "last_name": "Leynham", + "email": "mleynham1p@yolasite.com", + "gender": "Female", + "address": "63 Troy Drive", + "city": "San Francisco", + "state": "California", + "zip": "94177", + "company": "Tekfly", + "job_title": "Quality Control Specialist", + "timezone": "America/Los_Angeles", + "title": "Dr" + }, + { + "first_name": "Algernon", + "last_name": "Robers", + "email": "arobers1q@pinterest.com", + "gender": "Male", + "address": "16 Ludington Plaza", + "city": "San Luis Obispo", + "state": "California", + "zip": "93407", + "company": "Wordify", + "job_title": "Account Representative III", + "timezone": "America/Los_Angeles", + "title": "Dr" + }, + { + "first_name": "Ali", + "last_name": "Pywell", + "email": "apywell1r@mysql.com", + "gender": "Male", + "address": "3588 Straubel Avenue", + "city": "Birmingham", + "state": "Alabama", + "zip": "35244", + "company": "Mynte", + "job_title": "Automation Specialist IV", + "timezone": "America/Chicago", + "title": "Mrs" + }, + { + "first_name": "Rolland", + "last_name": "Lyffe", + "email": "rlyffe1s@yale.edu", + "gender": "Male", + "address": "47026 Oak Valley Trail", + "city": "San Francisco", + "state": "California", + "zip": "94105", + "company": "Roomm", + "job_title": "Data Coordinator", + "timezone": "America/Los_Angeles", + "title": "Mr" + }, + { + "first_name": "Marcel", + "last_name": "Pegg", + "email": "mpegg1t@blinklist.com", + "gender": "Male", + "address": "5935 Saint Paul Lane", + "city": "Fort Myers", + "state": "Florida", + "zip": "33913", + "company": "Trupe", + "job_title": "Senior Cost Accountant", + "timezone": "America/New_York", + "title": "Mrs" + }, + { + "first_name": "Hamnet", + "last_name": "Seth", + "email": "hseth1u@nyu.edu", + "gender": "Male", + "address": "2992 Tennyson Trail", + "city": "Roanoke", + "state": "Virginia", + "zip": "24040", + "company": "Quimm", + "job_title": "Help Desk Operator", + "timezone": "America/New_York", + "title": "Mrs" + }, + { + "first_name": "Corey", + "last_name": "Balnaves", + "email": "cbalnaves1v@goo.gl", + "gender": "Male", + "address": "6694 Columbus Road", + "city": "Sarasota", + "state": "Florida", + "zip": "34238", + "company": "Kimia", + "job_title": "Nuclear Power Engineer", + "timezone": "America/New_York", + "title": "Mrs" + }, + { + "first_name": "Artie", + "last_name": "Turfs", + "email": "aturfs1w@imgur.com", + "gender": "Male", + "address": "2 Killdeer Pass", + "city": "Monroe", + "state": "Louisiana", + "zip": "71208", + "company": "Chatterbridge", + "job_title": "Operator", + "timezone": "America/Chicago", + "title": "Honorable" + }, + { + "first_name": "Velma", + "last_name": "Nickolls", + "email": "vnickolls1x@is.gd", + "gender": "Female", + "address": "26 Bluejay Park", + "city": "Orlando", + "state": "Florida", + "zip": "32819", + "company": "Mymm", + "job_title": "Staff Scientist", + "timezone": "America/New_York", + "title": "Ms" + }, + { + "first_name": "Eden", + "last_name": "Deyenhardt", + "email": "edeyenhardt1y@uiuc.edu", + "gender": "Female", + "address": "333 Fallview Place", + "city": "New York City", + "state": "New York", + "zip": "10079", + "company": "Flashpoint", + "job_title": "Senior Sales Associate", + "timezone": "America/New_York", + "title": "Ms" + }, + { + "first_name": "Harriet", + "last_name": "Parman", + "email": "hparman1z@odnoklassniki.ru", + "gender": "Female", + "address": "7 John Wall Parkway", + "city": "Palo Alto", + "state": "California", + "zip": "94302", + "company": "Topicblab", + "job_title": "Staff Scientist", + "timezone": "America/Los_Angeles", + "title": "Dr" + }, + { + "first_name": "Janka", + "last_name": "Ortmann", + "email": "jortmann20@scribd.com", + "gender": "Female", + "address": "706 Mockingbird Drive", + "city": "Atlanta", + "state": "Georgia", + "zip": "30343", + "company": "Zazio", + "job_title": "Staff Scientist", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Nicolais", + "last_name": "Adel", + "email": "nadel21@army.mil", + "gender": "Male", + "address": "0 Ridgeview Center", + "city": "New Orleans", + "state": "Louisiana", + "zip": "70179", + "company": "Buzzdog", + "job_title": "Payment Adjustment Coordinator", + "timezone": "America/Chicago", + "title": "Ms" + }, + { + "first_name": "Kinsley", + "last_name": "Soots", + "email": "ksoots22@google.nl", + "gender": "Male", + "address": "98773 Commercial Crossing", + "city": "Clearwater", + "state": "Florida", + "zip": "34629", + "company": "Jazzy", + "job_title": "Quality Control Specialist", + "timezone": "America/New_York", + "title": "Mr" + }, + { + "first_name": "Jami", + "last_name": "Geraudy", + "email": "jgeraudy23@sciencedirect.com", + "gender": "Female", + "address": "352 Heath Junction", + "city": "Silver Spring", + "state": "Maryland", + "zip": "20918", + "company": "Yoveo", + "job_title": "Editor", + "timezone": "America/New_York", + "title": "Dr" + }, + { + "first_name": "Vlad", + "last_name": "Bucky", + "email": "vbucky24@vimeo.com", + "gender": "Male", + "address": "177 Lighthouse Bay Street", + "city": "Sacramento", + "state": "California", + "zip": "95894", + "company": "Jazzy", + "job_title": "Structural Engineer", + "timezone": "America/Los_Angeles", + "title": "Mr" + }, + { + "first_name": "Bruis", + "last_name": "Dinsdale", + "email": "bdinsdale25@flavors.me", + "gender": "Male", + "address": "225 7th Hill", + "city": "Baton Rouge", + "state": "Louisiana", + "zip": "70820", + "company": "Wikivu", + "job_title": "Paralegal", + "timezone": "America/Chicago", + "title": "Rev" + }, + { + "first_name": "Mufi", + "last_name": "Topper", + "email": "mtopper26@cdc.gov", + "gender": "Female", + "address": "21 Emmet Crossing", + "city": "Pittsburgh", + "state": "Pennsylvania", + "zip": "15286", + "company": "Flashdog", + "job_title": "Registered Nurse", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Zaccaria", + "last_name": "Roselli", + "email": "zroselli27@example.com", + "gender": "Male", + "address": "24 Fieldstone Trail", + "city": "Las Vegas", + "state": "Nevada", + "zip": "89160", + "company": "Skidoo", + "job_title": "Professor", + "timezone": "America/Los_Angeles", + "title": "Mrs" + }, + { + "first_name": "Janith", + "last_name": "Wolvey", + "email": "jwolvey28@xing.com", + "gender": "Female", + "address": "0452 Maywood Point", + "city": "Albuquerque", + "state": "New Mexico", + "zip": "87190", + "company": "Dabshots", + "job_title": "Compensation Analyst", + "timezone": "America/Denver", + "title": "Dr" + }, + { + "first_name": "Randy", + "last_name": "Inde", + "email": "rinde29@umn.edu", + "gender": "Male", + "address": "21 Blaine Street", + "city": "Abilene", + "state": "Texas", + "zip": "79699", + "company": "Tavu", + "job_title": "Physical Therapy Assistant", + "timezone": "America/Chicago", + "title": "Dr" + }, + { + "first_name": "Ripley", + "last_name": "Chapelhow", + "email": "rchapelhow2a@wikipedia.org", + "gender": "Male", + "address": "41774 Warbler Junction", + "city": "Washington", + "state": "District of Columbia", + "zip": "20260", + "company": "Muxo", + "job_title": "Senior Financial Analyst", + "timezone": "America/New_York", + "title": "Mr" + }, + { + "first_name": "Cob", + "last_name": "Stansell", + "email": "cstansell2b@independent.co.uk", + "gender": "Male", + "address": "61884 Cody Terrace", + "city": "Glendale", + "state": "Arizona", + "zip": "85305", + "company": "Yozio", + "job_title": "GIS Technical Architect", + "timezone": "America/Phoenix", + "title": "Mr" + }, + { + "first_name": "Livia", + "last_name": "Arkin", + "email": "larkin2c@smugmug.com", + "gender": "Female", + "address": "9 Mandrake Junction", + "city": "Syracuse", + "state": "New York", + "zip": "13205", + "company": "Abatz", + "job_title": "Nurse Practicioner", + "timezone": "America/New_York", + "title": "Mrs" + }, + { + "first_name": "Jarad", + "last_name": "Langford", + "email": "jlangford2d@npr.org", + "gender": "Male", + "address": "0 Maywood Place", + "city": "Scranton", + "state": "Pennsylvania", + "zip": "18505", + "company": "Oyoba", + "job_title": "Legal Assistant", + "timezone": "America/New_York", + "title": "Mrs" + }, + { + "first_name": "Karl", + "last_name": "Roglieri", + "email": "kroglieri2e@bloglines.com", + "gender": "Male", + "address": "66 Clyde Gallagher Crossing", + "city": "Rochester", + "state": "Minnesota", + "zip": "55905", + "company": "Youopia", + "job_title": "Mechanical Systems Engineer", + "timezone": "America/Chicago", + "title": "Mr" + }, + { + "first_name": "Cletis", + "last_name": "Shutte", + "email": "cshutte2f@hexun.com", + "gender": "Male", + "address": "7 5th Crossing", + "city": "Saint Paul", + "state": "Minnesota", + "zip": "55166", + "company": "Brainverse", + "job_title": "Software Test Engineer IV", + "timezone": "America/Chicago", + "title": "Rev" + }, + { + "first_name": "Dyanne", + "last_name": "Nind", + "email": "dnind2g@e-recht24.de", + "gender": "Female", + "address": "0 Eastwood Lane", + "city": "Fort Lauderdale", + "state": "Florida", + "zip": "33315", + "company": "Tagopia", + "job_title": "Data Coordinator", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Bern", + "last_name": "Skyrm", + "email": "bskyrm2h@microsoft.com", + "gender": "Male", + "address": "8 Rutledge Street", + "city": "Dallas", + "state": "Texas", + "zip": "75287", + "company": "Skyvu", + "job_title": "Occupational Therapist", + "timezone": "America/Chicago", + "title": "Mrs" + }, + { + "first_name": "Sargent", + "last_name": "Dowey", + "email": "sdowey2i@hibu.com", + "gender": "Polygender", + "address": "63191 Lotheville Avenue", + "city": "Los Angeles", + "state": "California", + "zip": "90101", + "company": "Tambee", + "job_title": "GIS Technical Architect", + "timezone": "America/Los_Angeles", + "title": "Honorable" + }, + { + "first_name": "Ingaberg", + "last_name": "Daybell", + "email": "idaybell2j@opensource.org", + "gender": "Female", + "address": "9973 Corben Junction", + "city": "Irvine", + "state": "California", + "zip": "92710", + "company": "Lazz", + "job_title": "Computer Systems Analyst I", + "timezone": "America/Los_Angeles", + "title": "Mr" + }, + { + "first_name": "Belle", + "last_name": "Mudle", + "email": "bmudle2k@technorati.com", + "gender": "Female", + "address": "40387 Superior Drive", + "city": "Alpharetta", + "state": "Georgia", + "zip": "30022", + "company": "Oyoloo", + "job_title": "Director of Sales", + "timezone": "America/New_York", + "title": "Rev" + }, + { + "first_name": "Loree", + "last_name": "McLice", + "email": "lmclice2l@cdc.gov", + "gender": "Female", + "address": "2 American Crossing", + "city": "Anniston", + "state": "Alabama", + "zip": "36205", + "company": "Rooxo", + "job_title": "Administrative Officer", + "timezone": "America/Chicago", + "title": "Dr" + }, + { + "first_name": "Jonathan", + "last_name": "Piegrome", + "email": "jpiegrome2m@google.com.hk", + "gender": "Male", + "address": "5797 Morningstar Parkway", + "city": "Trenton", + "state": "New Jersey", + "zip": "08638", + "company": "Fivechat", + "job_title": "Nurse", + "timezone": "America/New_York", + "title": "Mrs" + }, + { + "first_name": "Richie", + "last_name": "Brough", + "email": "rbrough2n@eepurl.com", + "gender": "Male", + "address": "80295 East Lane", + "city": "El Paso", + "state": "Texas", + "zip": "88519", + "company": "Digitube", + "job_title": "Web Designer III", + "timezone": "America/Denver", + "title": "Honorable" + }, + { + "first_name": "Tucky", + "last_name": "Morriarty", + "email": "tmorriarty2o@wired.com", + "gender": "Male", + "address": "128 Manufacturers Terrace", + "city": "Bakersfield", + "state": "California", + "zip": "93381", + "company": "Jabbersphere", + "job_title": "Director of Sales", + "timezone": "America/Los_Angeles", + "title": "Honorable" + }, + { + "first_name": "Onfre", + "last_name": "Sproule", + "email": "osproule2p@blogger.com", + "gender": "Male", + "address": "1569 Arrowood Parkway", + "city": "Atlanta", + "state": "Georgia", + "zip": "30358", + "company": "Bubblemix", + "job_title": "Senior Editor", + "timezone": "America/New_York", + "title": "Mrs" + }, + { + "first_name": "Grover", + "last_name": "Bonar", + "email": "gbonar2q@mlb.com", + "gender": "Male", + "address": "235 Summit Place", + "city": "Bloomington", + "state": "Indiana", + "zip": "47405", + "company": "Gabtype", + "job_title": "Actuary", + "timezone": "America/Indiana/Indianapolis", + "title": "Rev" + }, + { + "first_name": "Etti", + "last_name": "Garland", + "email": "egarland2r@barnesandnoble.com", + "gender": "Female", + "address": "52 Hooker Court", + "city": "San Bernardino", + "state": "California", + "zip": "92405", + "company": "Myworks", + "job_title": "Operator", + "timezone": "America/Los_Angeles", + "title": "Mr" + }, + { + "first_name": "Reece", + "last_name": "Aylesbury", + "email": "raylesbury2s@china.com.cn", + "gender": "Male", + "address": "2 Golf Road", + "city": "Syracuse", + "state": "New York", + "zip": "13224", + "company": "Wikido", + "job_title": "Compensation Analyst", + "timezone": "America/New_York", + "title": "Mr" + }, + { + "first_name": "Joela", + "last_name": "Fidler", + "email": "jfidler2t@tinypic.com", + "gender": "Female", + "address": "9 Hoard Place", + "city": "Jefferson City", + "state": "Missouri", + "zip": "65105", + "company": "Zooxo", + "job_title": "Geologist II", + "timezone": "America/Chicago", + "title": "Honorable" + }, + { + "first_name": "Emmalynn", + "last_name": "Dishmon", + "email": "edishmon2u@yandex.ru", + "gender": "Female", + "address": "1413 Birchwood Parkway", + "city": "Lincoln", + "state": "Nebraska", + "zip": "68583", + "company": "Brightbean", + "job_title": "Software Engineer III", + "timezone": "America/Chicago", + "title": "Honorable" + }, + { + "first_name": "Waring", + "last_name": "Stirling", + "email": "wstirling2v@cloudflare.com", + "gender": "Genderqueer", + "address": "02403 Fair Oaks Circle", + "city": "Dallas", + "state": "Texas", + "zip": "75392", + "company": "Thoughtbeat", + "job_title": "Structural Engineer", + "timezone": "America/Chicago", + "title": "Honorable" + }, + { + "first_name": "Olvan", + "last_name": "Blase", + "email": "oblase2w@dedecms.com", + "gender": "Male", + "address": "1322 Schurz Drive", + "city": "Houston", + "state": "Texas", + "zip": "77080", + "company": "Youbridge", + "job_title": "Quality Engineer", + "timezone": "America/Chicago", + "title": "Mr" + }, + { + "first_name": "Roberta", + "last_name": "Pimlock", + "email": "rpimlock2x@google.co.jp", + "gender": "Female", + "address": "17 Bartillon Place", + "city": "Riverside", + "state": "California", + "zip": "92519", + "company": "Mydo", + "job_title": "Senior Cost Accountant", + "timezone": "America/Los_Angeles", + "title": "Rev" + }, + { + "first_name": "Herbie", + "last_name": "Freckleton", + "email": "hfreckleton2y@theatlantic.com", + "gender": "Male", + "address": "26689 Columbus Terrace", + "city": "Cincinnati", + "state": "Ohio", + "zip": "45208", + "company": "Realfire", + "job_title": "Nurse Practicioner", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Elicia", + "last_name": "BURWIN", + "email": "eburwin2z@wikispaces.com", + "gender": "Polygender", + "address": "6 Pepper Wood Lane", + "city": "San Antonio", + "state": "Texas", + "zip": "78215", + "company": "Reallinks", + "job_title": "Librarian", + "timezone": "America/Chicago", + "title": "Ms" + }, + { + "first_name": "Katheryn", + "last_name": "Hawket", + "email": "khawket30@google.ca", + "gender": "Female", + "address": "7928 Delaware Court", + "city": "Los Angeles", + "state": "California", + "zip": "90101", + "company": "Thoughtmix", + "job_title": "Database Administrator III", + "timezone": "America/Los_Angeles", + "title": "Ms" + }, + { + "first_name": "Laurice", + "last_name": "Shinn", + "email": "lshinn31@liveinternet.ru", + "gender": "Genderqueer", + "address": "458 Annamark Avenue", + "city": "Seattle", + "state": "Washington", + "zip": "98115", + "company": "Leexo", + "job_title": "Social Worker", + "timezone": "America/Los_Angeles", + "title": "Honorable" + }, + { + "first_name": "Pebrook", + "last_name": "Taffley", + "email": "ptaffley32@typepad.com", + "gender": "Male", + "address": "52 Anniversary Street", + "city": "Birmingham", + "state": "Alabama", + "zip": "35285", + "company": "Tagopia", + "job_title": "Project Manager", + "timezone": "America/Chicago", + "title": "Ms" + }, + { + "first_name": "Hayward", + "last_name": "Ansley", + "email": "hansley33@example.com", + "gender": "Bigender", + "address": "693 Mockingbird Terrace", + "city": "New York City", + "state": "New York", + "zip": "10280", + "company": "Wordtune", + "job_title": "Accounting Assistant IV", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Rupert", + "last_name": "Perfitt", + "email": "rperfitt34@addtoany.com", + "gender": "Male", + "address": "4 Mcbride Drive", + "city": "Hampton", + "state": "Virginia", + "zip": "23668", + "company": "Skaboo", + "job_title": "Compensation Analyst", + "timezone": "America/New_York", + "title": "Rev" + }, + { + "first_name": "Shirl", + "last_name": "Stobbart", + "email": "sstobbart35@usda.gov", + "gender": "Female", + "address": "8255 Westport Hill", + "city": "Clearwater", + "state": "Florida", + "zip": "34620", + "company": "Skilith", + "job_title": "Senior Editor", + "timezone": "America/New_York", + "title": "Ms" + }, + { + "first_name": "Cordy", + "last_name": "Jeffrey", + "email": "cjeffrey36@dagondesign.com", + "gender": "Male", + "address": "2459 Menomonie Circle", + "city": "Hampton", + "state": "Virginia", + "zip": "23663", + "company": "Devshare", + "job_title": "Social Worker", + "timezone": "America/New_York", + "title": "Dr" + }, + { + "first_name": "Noami", + "last_name": "Kearn", + "email": "nkearn37@hibu.com", + "gender": "Female", + "address": "3 Maryland Alley", + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19120", + "company": "Skippad", + "job_title": "Office Assistant IV", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Chelsae", + "last_name": "Goreway", + "email": "cgoreway38@ovh.net", + "gender": "Female", + "address": "51 Lerdahl Point", + "city": "Murfreesboro", + "state": "Tennessee", + "zip": "37131", + "company": "Devcast", + "job_title": "Human Resources Assistant IV", + "timezone": "America/Chicago", + "title": "Rev" + }, + { + "first_name": "Rollie", + "last_name": "Scripture", + "email": "rscripture39@webeden.co.uk", + "gender": "Bigender", + "address": "255 Stang Road", + "city": "Sioux Falls", + "state": "South Dakota", + "zip": "57193", + "company": "Edgewire", + "job_title": "GIS Technical Architect", + "timezone": "America/Chicago", + "title": "Mrs" + }, + { + "first_name": "Nolana", + "last_name": "Orts", + "email": "norts3a@forbes.com", + "gender": "Polygender", + "address": "0234 Ridgeway Parkway", + "city": "Morgantown", + "state": "West Virginia", + "zip": "26505", + "company": "Mymm", + "job_title": "Data Coordinator", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Yves", + "last_name": "Labadini", + "email": "ylabadini3b@princeton.edu", + "gender": "Male", + "address": "860 La Follette Trail", + "city": "Washington", + "state": "District of Columbia", + "zip": "56944", + "company": "Quinu", + "job_title": "Executive Secretary", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Filbert", + "last_name": "Pamplin", + "email": "fpamplin3c@parallels.com", + "gender": "Male", + "address": "9561 Manitowish Circle", + "city": "Spokane", + "state": "Washington", + "zip": "99260", + "company": "Devify", + "job_title": "Executive Secretary", + "timezone": "America/Los_Angeles", + "title": "Dr" + }, + { + "first_name": "D'arcy", + "last_name": "Girauld", + "email": "dgirauld3d@youtube.com", + "gender": "Non-binary", + "address": "7 Moose Road", + "city": "Houston", + "state": "Texas", + "zip": "77025", + "company": "Mita", + "job_title": "Senior Cost Accountant", + "timezone": "America/Chicago", + "title": "Mr" + }, + { + "first_name": "Woody", + "last_name": "O'Neal", + "email": "woneal3e@mail.ru", + "gender": "Male", + "address": "571 Melby Lane", + "city": "Las Vegas", + "state": "Nevada", + "zip": "89178", + "company": "Edgeify", + "job_title": "Statistician I", + "timezone": "America/Los_Angeles", + "title": "Mr" + }, + { + "first_name": "Mag", + "last_name": "Peaseman", + "email": "mpeaseman3f@gizmodo.com", + "gender": "Female", + "address": "99 Chive Circle", + "city": "Dallas", + "state": "Texas", + "zip": "75367", + "company": "Topicware", + "job_title": "Account Executive", + "timezone": "America/Chicago", + "title": "Mrs" + }, + { + "first_name": "Odilia", + "last_name": "Roe", + "email": "oroe3g@cam.ac.uk", + "gender": "Female", + "address": "29 Helena Alley", + "city": "Lakeland", + "state": "Florida", + "zip": "33805", + "company": "Gigaclub", + "job_title": "Account Representative III", + "timezone": "America/New_York", + "title": "Mr" + }, + { + "first_name": "Evelina", + "last_name": "Hallgalley", + "email": "ehallgalley3h@360.cn", + "gender": "Female", + "address": "76 Golden Leaf Way", + "city": "Shreveport", + "state": "Louisiana", + "zip": "71105", + "company": "Quaxo", + "job_title": "Mechanical Systems Engineer", + "timezone": "America/Chicago", + "title": "Honorable" + }, + { + "first_name": "Guillaume", + "last_name": "Tocque", + "email": "gtocque3i@aboutads.info", + "gender": "Male", + "address": "27 Gerald Alley", + "city": "Tucson", + "state": "Arizona", + "zip": "85737", + "company": "Skilith", + "job_title": "Internal Auditor", + "timezone": "America/Phoenix", + "title": "Mr" + }, + { + "first_name": "Gaile", + "last_name": "Vaud", + "email": "gvaud3j@51.la", + "gender": "Male", + "address": "0 Pond Pass", + "city": "Boulder", + "state": "Colorado", + "zip": "80328", + "company": "Nlounge", + "job_title": "Accountant I", + "timezone": "America/Denver", + "title": "Mrs" + }, + { + "first_name": "Berget", + "last_name": "Divall", + "email": "bdivall3k@storify.com", + "gender": "Female", + "address": "739 American Street", + "city": "Tacoma", + "state": "Washington", + "zip": "98424", + "company": "Centimia", + "job_title": "General Manager", + "timezone": "America/Los_Angeles", + "title": "Ms" + }, + { + "first_name": "Geordie", + "last_name": "Atkin", + "email": "gatkin3l@flickr.com", + "gender": "Male", + "address": "43547 Warner Point", + "city": "Rochester", + "state": "New York", + "zip": "14609", + "company": "Bubbletube", + "job_title": "Physical Therapy Assistant", + "timezone": "America/New_York", + "title": "Mr" + }, + { + "first_name": "Simon", + "last_name": "Tumini", + "email": "stumini3m@dedecms.com", + "gender": "Male", + "address": "5 Garrison Way", + "city": "Charleston", + "state": "West Virginia", + "zip": "25321", + "company": "Zoovu", + "job_title": "Senior Cost Accountant", + "timezone": "America/New_York", + "title": "Dr" + }, + { + "first_name": "Boris", + "last_name": "Devonald", + "email": "bdevonald3n@reuters.com", + "gender": "Male", + "address": "83 Buena Vista Point", + "city": "Flushing", + "state": "New York", + "zip": "11388", + "company": "Devbug", + "job_title": "VP Quality Control", + "timezone": "America/New_York", + "title": "Rev" + }, + { + "first_name": "Puff", + "last_name": "Guiu", + "email": "pguiu3o@edublogs.org", + "gender": "Male", + "address": "07337 Northview Way", + "city": "Port Charlotte", + "state": "Florida", + "zip": "33954", + "company": "Avamm", + "job_title": "Senior Cost Accountant", + "timezone": "America/New_York", + "title": "Dr" + }, + { + "first_name": "Malynda", + "last_name": "Mateos", + "email": "mmateos3p@nyu.edu", + "gender": "Female", + "address": "6 Bartillon Hill", + "city": "Shawnee Mission", + "state": "Kansas", + "zip": "66215", + "company": "Feedmix", + "job_title": "Systems Administrator IV", + "timezone": "America/Chicago", + "title": "Dr" + }, + { + "first_name": "Howey", + "last_name": "Witul", + "email": "hwitul3q@acquirethisname.com", + "gender": "Male", + "address": "88 Golf View Circle", + "city": "Greeley", + "state": "Colorado", + "zip": "80638", + "company": "Yabox", + "job_title": "Help Desk Technician", + "timezone": "America/Denver", + "title": "Rev" + }, + { + "first_name": "Den", + "last_name": "Lyenyng", + "email": "dlyenyng3r@upenn.edu", + "gender": "Male", + "address": "49323 Killdeer Trail", + "city": "Stamford", + "state": "Connecticut", + "zip": "06912", + "company": "Skilith", + "job_title": "Executive Secretary", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Kenny", + "last_name": "Sealy", + "email": "ksealy3s@google.ru", + "gender": "Agender", + "address": "03736 Manitowish Hill", + "city": "Aurora", + "state": "Colorado", + "zip": "80045", + "company": "Tanoodle", + "job_title": "Help Desk Technician", + "timezone": "America/Denver", + "title": "Rev" + }, + { + "first_name": "Mozelle", + "last_name": "Meneghelli", + "email": "mmeneghelli3t@ebay.co.uk", + "gender": "Female", + "address": "128 Dovetail Court", + "city": "Hartford", + "state": "Connecticut", + "zip": "06140", + "company": "Oyoloo", + "job_title": "Internal Auditor", + "timezone": "America/New_York", + "title": "Mr" + }, + { + "first_name": "Guillermo", + "last_name": "Wallwork", + "email": "gwallwork3u@amazonaws.com", + "gender": "Male", + "address": "3 Shoshone Terrace", + "city": "Dallas", + "state": "Texas", + "zip": "75372", + "company": "Jaxworks", + "job_title": "Engineer II", + "timezone": "America/Chicago", + "title": "Rev" + }, + { + "first_name": "Rhianon", + "last_name": "Shah", + "email": "rshah3v@cocolog-nifty.com", + "gender": "Female", + "address": "29448 Miller Plaza", + "city": "Washington", + "state": "District of Columbia", + "zip": "20409", + "company": "Dynazzy", + "job_title": "Data Coordinator", + "timezone": "America/New_York", + "title": "Dr" + }, + { + "first_name": "Bendick", + "last_name": "Rannie", + "email": "brannie3w@statcounter.com", + "gender": "Genderfluid", + "address": "9922 Mesta Parkway", + "city": "Louisville", + "state": "Kentucky", + "zip": "40250", + "company": "Mita", + "job_title": "Marketing Manager", + "timezone": "America/Kentucky/Louisville", + "title": "Ms" + }, + { + "first_name": "Greta", + "last_name": "Flanaghan", + "email": "gflanaghan3x@marriott.com", + "gender": "Female", + "address": "1 Northland Pass", + "city": "Garden Grove", + "state": "California", + "zip": "92844", + "company": "Quaxo", + "job_title": "Junior Executive", + "timezone": "America/Los_Angeles", + "title": "Ms" + }, + { + "first_name": "Meade", + "last_name": "Sholl", + "email": "msholl3y@slideshare.net", + "gender": "Male", + "address": "4053 Memorial Park", + "city": "Jacksonville", + "state": "Florida", + "zip": "32244", + "company": "Zoovu", + "job_title": "Account Executive", + "timezone": "America/New_York", + "title": "Ms" + }, + { + "first_name": "North", + "last_name": "Schankel", + "email": "nschankel3z@fema.gov", + "gender": "Male", + "address": "1911 Fisk Plaza", + "city": "Tulsa", + "state": "Oklahoma", + "zip": "74149", + "company": "Eabox", + "job_title": "Structural Engineer", + "timezone": "America/Chicago", + "title": "Mr" + }, + { + "first_name": "Darci", + "last_name": "Van den Hof", + "email": "dvandenhof40@princeton.edu", + "gender": "Female", + "address": "62975 Nancy Center", + "city": "Anderson", + "state": "Indiana", + "zip": "46015", + "company": "Jaxbean", + "job_title": "Product Engineer", + "timezone": "America/Indiana/Indianapolis", + "title": "Mr" + }, + { + "first_name": "Ely", + "last_name": "Malcolm", + "email": "emalcolm41@liveinternet.ru", + "gender": "Male", + "address": "5916 Oakridge Trail", + "city": "Springfield", + "state": "Massachusetts", + "zip": "01152", + "company": "Edgeify", + "job_title": "Environmental Specialist", + "timezone": "America/New_York", + "title": "Ms" + }, + { + "first_name": "Feodor", + "last_name": "Pegden", + "email": "fpegden42@privacy.gov.au", + "gender": "Male", + "address": "5 Kedzie Lane", + "city": "Austin", + "state": "Texas", + "zip": "78744", + "company": "Mybuzz", + "job_title": "Biostatistician II", + "timezone": "America/Chicago", + "title": "Honorable" + }, + { + "first_name": "Ileane", + "last_name": "Wathell", + "email": "iwathell43@csmonitor.com", + "gender": "Female", + "address": "525 Oak Junction", + "city": "New York City", + "state": "New York", + "zip": "10110", + "company": "Tekfly", + "job_title": "Chemical Engineer", + "timezone": "America/New_York", + "title": "Ms" + }, + { + "first_name": "Solomon", + "last_name": "Jell", + "email": "sjell44@webeden.co.uk", + "gender": "Male", + "address": "04 Fulton Crossing", + "city": "Jacksonville", + "state": "Florida", + "zip": "32236", + "company": "Dynabox", + "job_title": "Environmental Specialist", + "timezone": "America/New_York", + "title": "Ms" + }, + { + "first_name": "Isidoro", + "last_name": "Greenland", + "email": "igreenland45@over-blog.com", + "gender": "Bigender", + "address": "5 Columbus Parkway", + "city": "Dallas", + "state": "Texas", + "zip": "75265", + "company": "Eimbee", + "job_title": "Internal Auditor", + "timezone": "America/Chicago", + "title": "Honorable" + }, + { + "first_name": "Obed", + "last_name": "de Mullett", + "email": "odemullett46@over-blog.com", + "gender": "Male", + "address": "8346 Lake View Trail", + "city": "Long Beach", + "state": "California", + "zip": "90805", + "company": "Tagpad", + "job_title": "Software Engineer I", + "timezone": "America/Los_Angeles", + "title": "Mrs" + }, + { + "first_name": "Gleda", + "last_name": "Braithwaite", + "email": "gbraithwaite47@dailymotion.com", + "gender": "Female", + "address": "2686 Fairview Center", + "city": "Colorado Springs", + "state": "Colorado", + "zip": "80940", + "company": "Gigashots", + "job_title": "Account Executive", + "timezone": "America/Denver", + "title": "Dr" + }, + { + "first_name": "Candace", + "last_name": "Harcombe", + "email": "charcombe48@mysql.com", + "gender": "Female", + "address": "68926 Northland Drive", + "city": "San Antonio", + "state": "Texas", + "zip": "78225", + "company": "Realmix", + "job_title": "Analyst Programmer", + "timezone": "America/Chicago", + "title": "Mr" + }, + { + "first_name": "Melissa", + "last_name": "Henker", + "email": "mhenker49@ask.com", + "gender": "Female", + "address": "8 Coolidge Alley", + "city": "Syracuse", + "state": "New York", + "zip": "13210", + "company": "LiveZ", + "job_title": "Sales Representative", + "timezone": "America/New_York", + "title": "Mr" + }, + { + "first_name": "Shandeigh", + "last_name": "Branscombe", + "email": "sbranscombe4a@ocn.ne.jp", + "gender": "Female", + "address": "271 Cody Avenue", + "city": "Rockville", + "state": "Maryland", + "zip": "20851", + "company": "Babbleblab", + "job_title": "Accounting Assistant I", + "timezone": "America/New_York", + "title": "Dr" + }, + { + "first_name": "Gauthier", + "last_name": "Nizet", + "email": "gnizet4b@comcast.net", + "gender": "Male", + "address": "67397 1st Terrace", + "city": "El Paso", + "state": "Texas", + "zip": "88530", + "company": "Tagcat", + "job_title": "Product Engineer", + "timezone": "America/Denver", + "title": "Honorable" + }, + { + "first_name": "Lyell", + "last_name": "Spiaggia", + "email": "lspiaggia4c@scribd.com", + "gender": "Male", + "address": "67 Walton Terrace", + "city": "Omaha", + "state": "Nebraska", + "zip": "68144", + "company": "Browsetype", + "job_title": "Junior Executive", + "timezone": "America/Chicago", + "title": "Dr" + }, + { + "first_name": "Nerta", + "last_name": "Seago", + "email": "nseago4d@4shared.com", + "gender": "Female", + "address": "88 Browning Court", + "city": "Rochester", + "state": "New York", + "zip": "14604", + "company": "Edgewire", + "job_title": "Librarian", + "timezone": "America/New_York", + "title": "Dr" + }, + { + "first_name": "Christin", + "last_name": "Cornau", + "email": "ccornau4e@booking.com", + "gender": "Female", + "address": "853 Drewry Avenue", + "city": "Chicago", + "state": "Illinois", + "zip": "60630", + "company": "Edgepulse", + "job_title": "Dental Hygienist", + "timezone": "America/Chicago", + "title": "Mr" + }, + { + "first_name": "Junina", + "last_name": "McCreedy", + "email": "jmccreedy4f@tinyurl.com", + "gender": "Female", + "address": "2591 Oriole Road", + "city": "Alexandria", + "state": "Louisiana", + "zip": "71307", + "company": "Linkbuzz", + "job_title": "Database Administrator IV", + "timezone": "America/Chicago", + "title": "Mr" + }, + { + "first_name": "Emelen", + "last_name": "Boardman", + "email": "eboardman4g@usnews.com", + "gender": "Male", + "address": "5 Red Cloud Junction", + "city": "Houston", + "state": "Texas", + "zip": "77299", + "company": "Dabjam", + "job_title": "Electrical Engineer", + "timezone": "America/Chicago", + "title": "Rev" + }, + { + "first_name": "Mindy", + "last_name": "Kimmitt", + "email": "mkimmitt4h@so-net.ne.jp", + "gender": "Female", + "address": "86 Karstens Pass", + "city": "Atlanta", + "state": "Georgia", + "zip": "30323", + "company": "Jaxworks", + "job_title": "Biostatistician III", + "timezone": "America/New_York", + "title": "Ms" + }, + { + "first_name": "Sharleen", + "last_name": "Neilands", + "email": "sneilands4i@imdb.com", + "gender": "Female", + "address": "41 Roxbury Hill", + "city": "Minneapolis", + "state": "Minnesota", + "zip": "55436", + "company": "Quinu", + "job_title": "Registered Nurse", + "timezone": "America/Chicago", + "title": "Rev" + }, + { + "first_name": "Hasheem", + "last_name": "Fortie", + "email": "hfortie4j@shutterfly.com", + "gender": "Male", + "address": "680 Sycamore Road", + "city": "Miami", + "state": "Florida", + "zip": "33129", + "company": "Zazio", + "job_title": "Legal Assistant", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Dennison", + "last_name": "Dargue", + "email": "ddargue4k@oracle.com", + "gender": "Male", + "address": "5 Upham Junction", + "city": "Torrance", + "state": "California", + "zip": "90510", + "company": "Babblestorm", + "job_title": "Automation Specialist III", + "timezone": "America/Los_Angeles", + "title": "Ms" + }, + { + "first_name": "Conan", + "last_name": "Shoebotham", + "email": "cshoebotham4l@sogou.com", + "gender": "Male", + "address": "4 Hudson Junction", + "city": "Hartford", + "state": "Connecticut", + "zip": "06183", + "company": "Zoovu", + "job_title": "Chief Design Engineer", + "timezone": "America/New_York", + "title": "Rev" + }, + { + "first_name": "Rosabelle", + "last_name": "Wharmby", + "email": "rwharmby4m@ucoz.ru", + "gender": "Female", + "address": "747 Northfield Lane", + "city": "New York City", + "state": "New York", + "zip": "10175", + "company": "Tazzy", + "job_title": "Internal Auditor", + "timezone": "America/New_York", + "title": "Mr" + }, + { + "first_name": "Ivar", + "last_name": "Mackrell", + "email": "imackrell4n@seattletimes.com", + "gender": "Male", + "address": "396 Clove Junction", + "city": "Washington", + "state": "District of Columbia", + "zip": "20088", + "company": "Edgepulse", + "job_title": "VP Quality Control", + "timezone": "America/New_York", + "title": "Ms" + }, + { + "first_name": "Ciel", + "last_name": "Parsonage", + "email": "cparsonage4o@altervista.org", + "gender": "Female", + "address": "053 Aberg Parkway", + "city": "Phoenix", + "state": "Arizona", + "zip": "85035", + "company": "Mynte", + "job_title": "Librarian", + "timezone": "America/Phoenix", + "title": "Honorable" + }, + { + "first_name": "Gun", + "last_name": "Perl", + "email": "gperl4p@wordpress.org", + "gender": "Male", + "address": "35 Sage Drive", + "city": "Springfield", + "state": "Illinois", + "zip": "62711", + "company": "Skimia", + "job_title": "Dental Hygienist", + "timezone": "America/Chicago", + "title": "Ms" + }, + { + "first_name": "Cristabel", + "last_name": "Habble", + "email": "chabble4q@google.es", + "gender": "Female", + "address": "6 Straubel Point", + "city": "Washington", + "state": "District of Columbia", + "zip": "20520", + "company": "BlogXS", + "job_title": "Systems Administrator III", + "timezone": "America/New_York", + "title": "Mrs" + }, + { + "first_name": "Mirabel", + "last_name": "Rodnight", + "email": "mrodnight4r@un.org", + "gender": "Female", + "address": "5217 Cordelia Junction", + "city": "Tucson", + "state": "Arizona", + "zip": "85705", + "company": "Cogidoo", + "job_title": "Senior Cost Accountant", + "timezone": "America/Phoenix", + "title": "Honorable" + }, + { + "first_name": "Andee", + "last_name": "Tuftin", + "email": "atuftin4s@cnet.com", + "gender": "Female", + "address": "6 Claremont Center", + "city": "Trenton", + "state": "New Jersey", + "zip": "08650", + "company": "Zazio", + "job_title": "Software Engineer IV", + "timezone": "America/New_York", + "title": "Mr" + }, + { + "first_name": "Keene", + "last_name": "Brombell", + "email": "kbrombell4t@upenn.edu", + "gender": "Genderfluid", + "address": "55 Cody Trail", + "city": "Akron", + "state": "Ohio", + "zip": "44329", + "company": "Demivee", + "job_title": "Staff Accountant II", + "timezone": "America/New_York", + "title": "Rev" + }, + { + "first_name": "Leonora", + "last_name": "Eppson", + "email": "leppson4u@dropbox.com", + "gender": "Female", + "address": "41 Moland Road", + "city": "Rochester", + "state": "Minnesota", + "zip": "55905", + "company": "Zoonoodle", + "job_title": "Chemical Engineer", + "timezone": "America/Chicago", + "title": "Dr" + }, + { + "first_name": "Nilson", + "last_name": "Geerling", + "email": "ngeerling4v@unc.edu", + "gender": "Male", + "address": "08 Springs Hill", + "city": "Lubbock", + "state": "Texas", + "zip": "79415", + "company": "Riffwire", + "job_title": "Research Nurse", + "timezone": "America/Chicago", + "title": "Honorable" + }, + { + "first_name": "Eduino", + "last_name": "Romeril", + "email": "eromeril4w@boston.com", + "gender": "Male", + "address": "8 1st Point", + "city": "San Antonio", + "state": "Texas", + "zip": "78285", + "company": "Quinu", + "job_title": "Actuary", + "timezone": "America/Chicago", + "title": "Honorable" + }, + { + "first_name": "Kora", + "last_name": "Beckey", + "email": "kbeckey4x@google.com", + "gender": "Female", + "address": "90864 Luster Circle", + "city": "Johnstown", + "state": "Pennsylvania", + "zip": "15906", + "company": "Kanoodle", + "job_title": "Database Administrator III", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Whitman", + "last_name": "Graves", + "email": "wgraves4y@uiuc.edu", + "gender": "Male", + "address": "793 Pleasure Drive", + "city": "Montgomery", + "state": "Alabama", + "zip": "36104", + "company": "Tagtune", + "job_title": "Chemical Engineer", + "timezone": "America/Chicago", + "title": "Ms" + }, + { + "first_name": "Kelly", + "last_name": "Goodie", + "email": "kgoodie4z@weibo.com", + "gender": "Female", + "address": "93 Oriole Road", + "city": "Denton", + "state": "Texas", + "zip": "76210", + "company": "Chatterbridge", + "job_title": "VP Product Management", + "timezone": "America/Chicago", + "title": "Dr" + }, + { + "first_name": "Xaviera", + "last_name": "Venables", + "email": "xvenables50@flavors.me", + "gender": "Female", + "address": "60 Fordem Court", + "city": "Miami", + "state": "Florida", + "zip": "33175", + "company": "Quamba", + "job_title": "Computer Systems Analyst I", + "timezone": "America/New_York", + "title": "Mr" + }, + { + "first_name": "Leah", + "last_name": "Ducker", + "email": "lducker51@alibaba.com", + "gender": "Female", + "address": "2963 Karstens Parkway", + "city": "Arlington", + "state": "Virginia", + "zip": "22244", + "company": "Flipstorm", + "job_title": "Librarian", + "timezone": "America/New_York", + "title": "Mr" + }, + { + "first_name": "Averill", + "last_name": "Bacon", + "email": "abacon52@time.com", + "gender": "Male", + "address": "765 Becker Avenue", + "city": "Oklahoma City", + "state": "Oklahoma", + "zip": "73114", + "company": "Blogpad", + "job_title": "Mechanical Systems Engineer", + "timezone": "America/Chicago", + "title": "Mrs" + }, + { + "first_name": "Flossy", + "last_name": "Marler", + "email": "fmarler53@ning.com", + "gender": "Female", + "address": "94 Charing Cross Park", + "city": "Augusta", + "state": "Georgia", + "zip": "30905", + "company": "Wordware", + "job_title": "Senior Editor", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Euphemia", + "last_name": "Garfirth", + "email": "egarfirth54@accuweather.com", + "gender": "Female", + "address": "351 Esker Plaza", + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19120", + "company": "Realmix", + "job_title": "Teacher", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Kent", + "last_name": "Moresby", + "email": "kmoresby55@senate.gov", + "gender": "Male", + "address": "3780 Independence Avenue", + "city": "Dayton", + "state": "Ohio", + "zip": "45432", + "company": "Yadel", + "job_title": "Community Outreach Specialist", + "timezone": "America/New_York", + "title": "Mr" + }, + { + "first_name": "Crissie", + "last_name": "Bowsher", + "email": "cbowsher56@rediff.com", + "gender": "Female", + "address": "5347 Pankratz Alley", + "city": "Lancaster", + "state": "Pennsylvania", + "zip": "17605", + "company": "Yozio", + "job_title": "Legal Assistant", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Gayler", + "last_name": "Joselovitch", + "email": "gjoselovitch57@chron.com", + "gender": "Male", + "address": "87556 Oxford Alley", + "city": "Ocala", + "state": "Florida", + "zip": "34479", + "company": "Skipstorm", + "job_title": "Data Coordinator", + "timezone": "America/New_York", + "title": "Ms" + }, + { + "first_name": "Aldric", + "last_name": "Jekel", + "email": "ajekel58@unblog.fr", + "gender": "Polygender", + "address": "30705 Waywood Park", + "city": "Indianapolis", + "state": "Indiana", + "zip": "46278", + "company": "Roodel", + "job_title": "Database Administrator III", + "timezone": "America/Indiana/Indianapolis", + "title": "Rev" + }, + { + "first_name": "Clotilda", + "last_name": "Gilfoy", + "email": "cgilfoy59@blogger.com", + "gender": "Female", + "address": "033 Myrtle Junction", + "city": "Wilkes Barre", + "state": "Pennsylvania", + "zip": "18768", + "company": "Minyx", + "job_title": "Analyst Programmer", + "timezone": "America/New_York", + "title": "Mr" + }, + { + "first_name": "Vlad", + "last_name": "Soles", + "email": "vsoles5a@google.com", + "gender": "Male", + "address": "52211 Sloan Avenue", + "city": "Las Vegas", + "state": "Nevada", + "zip": "89115", + "company": "Katz", + "job_title": "Programmer Analyst III", + "timezone": "America/Los_Angeles", + "title": "Mr" + }, + { + "first_name": "Gael", + "last_name": "Carnalan", + "email": "gcarnalan5b@instagram.com", + "gender": "Male", + "address": "12572 Gateway Trail", + "city": "Wilmington", + "state": "North Carolina", + "zip": "28405", + "company": "Kwimbee", + "job_title": "Programmer II", + "timezone": "America/New_York", + "title": "Mr" + }, + { + "first_name": "Shaun", + "last_name": "Binnell", + "email": "sbinnell5c@fda.gov", + "gender": "Non-binary", + "address": "3 Graedel Plaza", + "city": "Orlando", + "state": "Florida", + "zip": "32891", + "company": "Kare", + "job_title": "Staff Accountant III", + "timezone": "America/New_York", + "title": "Mr" + }, + { + "first_name": "Modesty", + "last_name": "Vern", + "email": "mvern5d@istockphoto.com", + "gender": "Female", + "address": "2 Ramsey Center", + "city": "Alexandria", + "state": "Virginia", + "zip": "22301", + "company": "Einti", + "job_title": "Environmental Specialist", + "timezone": "America/New_York", + "title": "Ms" + }, + { + "first_name": "Leandra", + "last_name": "Lightbody", + "email": "llightbody5e@ycombinator.com", + "gender": "Female", + "address": "701 Mesta Junction", + "city": "Hartford", + "state": "Connecticut", + "zip": "06160", + "company": "Photolist", + "job_title": "Marketing Manager", + "timezone": "America/New_York", + "title": "Dr" + }, + { + "first_name": "Alecia", + "last_name": "Newbigging", + "email": "anewbigging5f@prnewswire.com", + "gender": "Female", + "address": "578 Quincy Trail", + "city": "Los Angeles", + "state": "California", + "zip": "90020", + "company": "Blogtag", + "job_title": "Media Manager IV", + "timezone": "America/Los_Angeles", + "title": "Mr" + }, + { + "first_name": "Beniamino", + "last_name": "Worlidge", + "email": "bworlidge5g@google.pl", + "gender": "Male", + "address": "7620 Hovde Street", + "city": "Seattle", + "state": "Washington", + "zip": "98175", + "company": "Jabbercube", + "job_title": "Safety Technician III", + "timezone": "America/Los_Angeles", + "title": "Dr" + }, + { + "first_name": "Josias", + "last_name": "Pressman", + "email": "jpressman5h@edublogs.org", + "gender": "Male", + "address": "34520 Duke Road", + "city": "Lexington", + "state": "Kentucky", + "zip": "40581", + "company": "Vipe", + "job_title": "Accountant IV", + "timezone": "America/New_York", + "title": "Ms" + }, + { + "first_name": "Kerrin", + "last_name": "Sworn", + "email": "ksworn5i@examiner.com", + "gender": "Female", + "address": "80714 Columbus Avenue", + "city": "Chicago", + "state": "Illinois", + "zip": "60641", + "company": "Rhyzio", + "job_title": "Computer Systems Analyst I", + "timezone": "America/Chicago", + "title": "Dr" + }, + { + "first_name": "Davidson", + "last_name": "Lyness", + "email": "dlyness5j@ucsd.edu", + "gender": "Male", + "address": "01840 Rigney Point", + "city": "Albany", + "state": "New York", + "zip": "12237", + "company": "Quinu", + "job_title": "Help Desk Operator", + "timezone": "America/New_York", + "title": "Dr" + }, + { + "first_name": "Betteanne", + "last_name": "Dive", + "email": "bdive5k@myspace.com", + "gender": "Female", + "address": "42 Bunker Hill Park", + "city": "Evansville", + "state": "Indiana", + "zip": "47705", + "company": "DabZ", + "job_title": "VP Marketing", + "timezone": "America/Chicago", + "title": "Rev" + }, + { + "first_name": "Hamlen", + "last_name": "MacKissack", + "email": "hmackissack5l@lycos.com", + "gender": "Male", + "address": "99981 3rd Road", + "city": "Roanoke", + "state": "Virginia", + "zip": "24004", + "company": "Blogspan", + "job_title": "Human Resources Assistant I", + "timezone": "America/New_York", + "title": "Rev" + }, + { + "first_name": "Bianka", + "last_name": "Dagleas", + "email": "bdagleas5m@howstuffworks.com", + "gender": "Genderqueer", + "address": "37577 Macpherson Plaza", + "city": "San Antonio", + "state": "Texas", + "zip": "78230", + "company": "Skilith", + "job_title": "Help Desk Operator", + "timezone": "America/Chicago", + "title": "Rev" + }, + { + "first_name": "Storm", + "last_name": "Dainter", + "email": "sdainter5n@virginia.edu", + "gender": "Female", + "address": "26 Mesta Crossing", + "city": "Roanoke", + "state": "Virginia", + "zip": "24020", + "company": "Shufflebeat", + "job_title": "Software Test Engineer I", + "timezone": "America/New_York", + "title": "Ms" + }, + { + "first_name": "Albertine", + "last_name": "Torrisi", + "email": "atorrisi5o@nasa.gov", + "gender": "Female", + "address": "79 Derek Point", + "city": "Trenton", + "state": "New Jersey", + "zip": "08650", + "company": "Camimbo", + "job_title": "Software Test Engineer I", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Salomon", + "last_name": "Livingstone", + "email": "slivingstone5p@europa.eu", + "gender": "Male", + "address": "40756 Sunnyside Alley", + "city": "Austin", + "state": "Texas", + "zip": "78783", + "company": "Midel", + "job_title": "Budget/Accounting Analyst III", + "timezone": "America/Chicago", + "title": "Rev" + }, + { + "first_name": "Dick", + "last_name": "Windas", + "email": "dwindas5q@cdc.gov", + "gender": "Male", + "address": "448 Comanche Plaza", + "city": "Wichita", + "state": "Kansas", + "zip": "67260", + "company": "Divanoodle", + "job_title": "Recruiter", + "timezone": "America/Chicago", + "title": "Ms" + }, + { + "first_name": "Sascha", + "last_name": "Goldin", + "email": "sgoldin5r@irs.gov", + "gender": "Male", + "address": "4841 Forest Road", + "city": "El Paso", + "state": "Texas", + "zip": "79968", + "company": "Photobug", + "job_title": "Analyst Programmer", + "timezone": "America/Denver", + "title": "Dr" + }, + { + "first_name": "Jorie", + "last_name": "Ferby", + "email": "jferby5s@yellowbook.com", + "gender": "Female", + "address": "48205 Pepper Wood Crossing", + "city": "Edmond", + "state": "Oklahoma", + "zip": "73034", + "company": "Katz", + "job_title": "Health Coach II", + "timezone": "America/Chicago", + "title": "Ms" + }, + { + "first_name": "Dillie", + "last_name": "Dibling", + "email": "ddibling5t@sun.com", + "gender": "Male", + "address": "8 Grayhawk Junction", + "city": "Washington", + "state": "District of Columbia", + "zip": "20067", + "company": "Jaloo", + "job_title": "GIS Technical Architect", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Lesli", + "last_name": "Hards", + "email": "lhards5u@businessweek.com", + "gender": "Female", + "address": "70621 Bunting Way", + "city": "Anchorage", + "state": "Alaska", + "zip": "99599", + "company": "Oyoba", + "job_title": "Product Engineer", + "timezone": "America/Anchorage", + "title": "Ms" + }, + { + "first_name": "Tybi", + "last_name": "Newland", + "email": "tnewland5v@wordpress.org", + "gender": "Female", + "address": "3406 Fairfield Point", + "city": "Lima", + "state": "Ohio", + "zip": "45807", + "company": "Kazio", + "job_title": "Safety Technician IV", + "timezone": "America/New_York", + "title": "Mrs" + }, + { + "first_name": "Pierson", + "last_name": "Burnyate", + "email": "pburnyate5w@smh.com.au", + "gender": "Male", + "address": "9 Del Sol Pass", + "city": "Pittsburgh", + "state": "Pennsylvania", + "zip": "15261", + "company": "Tazz", + "job_title": "Speech Pathologist", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Thelma", + "last_name": "Swettenham", + "email": "tswettenham5x@berkeley.edu", + "gender": "Female", + "address": "02 Canary Terrace", + "city": "Hialeah", + "state": "Florida", + "zip": "33018", + "company": "Tagchat", + "job_title": "Nuclear Power Engineer", + "timezone": "America/New_York", + "title": "Mr" + }, + { + "first_name": "Aindrea", + "last_name": "Pietrusiak", + "email": "apietrusiak5y@cnet.com", + "gender": "Female", + "address": "2 Crescent Oaks Circle", + "city": "Memphis", + "state": "Tennessee", + "zip": "38168", + "company": "Quatz", + "job_title": "Product Engineer", + "timezone": "America/Chicago", + "title": "Mr" + }, + { + "first_name": "Leanna", + "last_name": "Foulstone", + "email": "lfoulstone5z@phpbb.com", + "gender": "Female", + "address": "15 Ridgeview Lane", + "city": "Louisville", + "state": "Kentucky", + "zip": "40205", + "company": "Blognation", + "job_title": "Help Desk Operator", + "timezone": "America/Kentucky/Louisville", + "title": "Dr" + }, + { + "first_name": "Margarette", + "last_name": "Attewell", + "email": "mattewell60@etsy.com", + "gender": "Female", + "address": "0319 Springview Pass", + "city": "Austin", + "state": "Texas", + "zip": "78764", + "company": "Youtags", + "job_title": "Accountant IV", + "timezone": "America/Chicago", + "title": "Dr" + }, + { + "first_name": "Tobiah", + "last_name": "Anstis", + "email": "tanstis61@walmart.com", + "gender": "Male", + "address": "8364 Doe Crossing Court", + "city": "Washington", + "state": "District of Columbia", + "zip": "20310", + "company": "Fivespan", + "job_title": "Operator", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Nicol", + "last_name": "Fydoe", + "email": "nfydoe62@devhub.com", + "gender": "Male", + "address": "9 Randy Park", + "city": "Cleveland", + "state": "Ohio", + "zip": "44197", + "company": "Flipstorm", + "job_title": "General Manager", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Mart", + "last_name": "Selly", + "email": "mselly63@eepurl.com", + "gender": "Male", + "address": "4649 Prentice Point", + "city": "Atlanta", + "state": "Georgia", + "zip": "31132", + "company": "Feedbug", + "job_title": "Pharmacist", + "timezone": "America/New_York", + "title": "Ms" + }, + { + "first_name": "Nerissa", + "last_name": "Bothbie", + "email": "nbothbie64@devhub.com", + "gender": "Female", + "address": "58 Dovetail Drive", + "city": "Charlotte", + "state": "North Carolina", + "zip": "28263", + "company": "Plambee", + "job_title": "Budget/Accounting Analyst III", + "timezone": "America/New_York", + "title": "Rev" + }, + { + "first_name": "Luciana", + "last_name": "Haslam", + "email": "lhaslam65@mediafire.com", + "gender": "Female", + "address": "70852 Haas Crossing", + "city": "Memphis", + "state": "Tennessee", + "zip": "38197", + "company": "Photojam", + "job_title": "Assistant Manager", + "timezone": "America/Chicago", + "title": "Rev" + }, + { + "first_name": "Gizela", + "last_name": "Brabban", + "email": "gbrabban66@acquirethisname.com", + "gender": "Female", + "address": "9 Clemons Pass", + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19109", + "company": "Wordpedia", + "job_title": "Senior Sales Associate", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Nick", + "last_name": "Sutherns", + "email": "nsutherns67@abc.net.au", + "gender": "Male", + "address": "1 Mandrake Point", + "city": "Yakima", + "state": "Washington", + "zip": "98907", + "company": "Jazzy", + "job_title": "Programmer Analyst II", + "timezone": "America/Los_Angeles", + "title": "Honorable" + }, + { + "first_name": "Aubrie", + "last_name": "Stothard", + "email": "astothard68@seattletimes.com", + "gender": "Female", + "address": "19444 Ronald Regan Parkway", + "city": "Dallas", + "state": "Texas", + "zip": "75379", + "company": "Oyonder", + "job_title": "Project Manager", + "timezone": "America/Chicago", + "title": "Ms" + }, + { + "first_name": "Jerome", + "last_name": "Grayston", + "email": "jgrayston69@homestead.com", + "gender": "Male", + "address": "4 Schurz Park", + "city": "Garland", + "state": "Texas", + "zip": "75049", + "company": "Buzzbean", + "job_title": "Account Representative III", + "timezone": "America/Chicago", + "title": "Dr" + }, + { + "first_name": "Liam", + "last_name": "Meredith", + "email": "lmeredith6a@umn.edu", + "gender": "Male", + "address": "46 Meadow Ridge Park", + "city": "Arlington", + "state": "Virginia", + "zip": "22234", + "company": "BlogXS", + "job_title": "Account Coordinator", + "timezone": "America/New_York", + "title": "Ms" + }, + { + "first_name": "Kiley", + "last_name": "Piscopiello", + "email": "kpiscopiello6b@mail.ru", + "gender": "Genderqueer", + "address": "2 Lerdahl Plaza", + "city": "Ann Arbor", + "state": "Michigan", + "zip": "48107", + "company": "Dynazzy", + "job_title": "Accounting Assistant II", + "timezone": "America/Detroit", + "title": "Dr" + }, + { + "first_name": "Patrica", + "last_name": "Gobbett", + "email": "pgobbett6c@istockphoto.com", + "gender": "Female", + "address": "05 Gateway Pass", + "city": "Anniston", + "state": "Alabama", + "zip": "36205", + "company": "Voolia", + "job_title": "Senior Cost Accountant", + "timezone": "America/Chicago", + "title": "Mr" + }, + { + "first_name": "Bobbye", + "last_name": "Dobel", + "email": "bdobel6d@twitter.com", + "gender": "Female", + "address": "4 Chinook Terrace", + "city": "Buffalo", + "state": "New York", + "zip": "14233", + "company": "Jazzy", + "job_title": "Occupational Therapist", + "timezone": "America/New_York", + "title": "Dr" + }, + { + "first_name": "Adrien", + "last_name": "Garett", + "email": "agarett6e@unblog.fr", + "gender": "Male", + "address": "105 Truax Junction", + "city": "Saint Paul", + "state": "Minnesota", + "zip": "55172", + "company": "Shuffletag", + "job_title": "Software Test Engineer I", + "timezone": "America/Chicago", + "title": "Mrs" + }, + { + "first_name": "Fabien", + "last_name": "Seago", + "email": "fseago6f@yelp.com", + "gender": "Male", + "address": "35183 Pearson Street", + "city": "Lancaster", + "state": "Pennsylvania", + "zip": "17605", + "company": "Zazio", + "job_title": "Data Coordinator", + "timezone": "America/New_York", + "title": "Mrs" + }, + { + "first_name": "Valina", + "last_name": "Freezer", + "email": "vfreezer6g@hubpages.com", + "gender": "Female", + "address": "35 La Follette Hill", + "city": "Corona", + "state": "California", + "zip": "92878", + "company": "Skyble", + "job_title": "Marketing Manager", + "timezone": "America/Los_Angeles", + "title": "Rev" + }, + { + "first_name": "Nickolai", + "last_name": "Kerwin", + "email": "nkerwin6h@gov.uk", + "gender": "Male", + "address": "2 Ruskin Circle", + "city": "Raleigh", + "state": "North Carolina", + "zip": "27635", + "company": "Skinix", + "job_title": "Help Desk Technician", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Dodi", + "last_name": "Jeremaes", + "email": "djeremaes6i@reuters.com", + "gender": "Female", + "address": "1 Golden Leaf Hill", + "city": "Baltimore", + "state": "Maryland", + "zip": "21282", + "company": "Rooxo", + "job_title": "Geologist III", + "timezone": "America/New_York", + "title": "Ms" + }, + { + "first_name": "Wainwright", + "last_name": "Jealous", + "email": "wjealous6j@deviantart.com", + "gender": "Male", + "address": "4638 Oriole Parkway", + "city": "Lexington", + "state": "Kentucky", + "zip": "40591", + "company": "Janyx", + "job_title": "Desktop Support Technician", + "timezone": "America/New_York", + "title": "Ms" + }, + { + "first_name": "Timmy", + "last_name": "Simony", + "email": "tsimony6k@hp.com", + "gender": "Female", + "address": "6384 Kensington Center", + "city": "New York City", + "state": "New York", + "zip": "10160", + "company": "Kare", + "job_title": "Design Engineer", + "timezone": "America/New_York", + "title": "Rev" + }, + { + "first_name": "Gibb", + "last_name": "Swalwel", + "email": "gswalwel6l@qq.com", + "gender": "Male", + "address": "48492 Badeau Parkway", + "city": "Columbia", + "state": "South Carolina", + "zip": "29208", + "company": "Snaptags", + "job_title": "Account Coordinator", + "timezone": "America/New_York", + "title": "Dr" + }, + { + "first_name": "Celeste", + "last_name": "Warkup", + "email": "cwarkup6m@tmall.com", + "gender": "Genderqueer", + "address": "59649 Vahlen Avenue", + "city": "Albany", + "state": "New York", + "zip": "12242", + "company": "Yambee", + "job_title": "Recruiter", + "timezone": "America/New_York", + "title": "Ms" + }, + { + "first_name": "Tobi", + "last_name": "Sydenham", + "email": "tsydenham6n@google.cn", + "gender": "Female", + "address": "78 Blue Bill Park Park", + "city": "Washington", + "state": "District of Columbia", + "zip": "20041", + "company": "Yamia", + "job_title": "Compensation Analyst", + "timezone": "America/New_York", + "title": "Mrs" + }, + { + "first_name": "Ivor", + "last_name": "Kubicki", + "email": "ikubicki6o@moonfruit.com", + "gender": "Male", + "address": "97906 Maple Drive", + "city": "Shawnee Mission", + "state": "Kansas", + "zip": "66276", + "company": "Twinte", + "job_title": "Accounting Assistant II", + "timezone": "America/Chicago", + "title": "Honorable" + }, + { + "first_name": "Silas", + "last_name": "Cuppitt", + "email": "scuppitt6p@1688.com", + "gender": "Male", + "address": "27 Hauk Plaza", + "city": "Scottsdale", + "state": "Arizona", + "zip": "85255", + "company": "Quatz", + "job_title": "Account Coordinator", + "timezone": "America/Phoenix", + "title": "Dr" + }, + { + "first_name": "Wilek", + "last_name": "Weblin", + "email": "wweblin6q@photobucket.com", + "gender": "Male", + "address": "11770 Merrick Drive", + "city": "Lafayette", + "state": "Louisiana", + "zip": "70593", + "company": "Dynabox", + "job_title": "Computer Systems Analyst IV", + "timezone": "America/Chicago", + "title": "Mrs" + }, + { + "first_name": "Chicky", + "last_name": "Lanon", + "email": "clanon6r@seattletimes.com", + "gender": "Female", + "address": "32 Fairfield Trail", + "city": "Washington", + "state": "District of Columbia", + "zip": "20041", + "company": "Jaxworks", + "job_title": "Occupational Therapist", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Maribel", + "last_name": "Shildrake", + "email": "mshildrake6s@w3.org", + "gender": "Female", + "address": "2535 Artisan Junction", + "city": "Jackson", + "state": "Mississippi", + "zip": "39282", + "company": "Buzzdog", + "job_title": "Analog Circuit Design manager", + "timezone": "America/Chicago", + "title": "Honorable" + }, + { + "first_name": "Georg", + "last_name": "Narbett", + "email": "gnarbett6t@nydailynews.com", + "gender": "Male", + "address": "603 Westerfield Crossing", + "city": "Provo", + "state": "Utah", + "zip": "84605", + "company": "Yombu", + "job_title": "Marketing Manager", + "timezone": "America/Denver", + "title": "Honorable" + }, + { + "first_name": "Doug", + "last_name": "Watt", + "email": "dwatt6u@slashdot.org", + "gender": "Male", + "address": "26 Lien Parkway", + "city": "San Francisco", + "state": "California", + "zip": "94110", + "company": "Voonyx", + "job_title": "Cost Accountant", + "timezone": "America/Los_Angeles", + "title": "Ms" + }, + { + "first_name": "Sena", + "last_name": "Joanic", + "email": "sjoanic6v@jigsy.com", + "gender": "Female", + "address": "6659 Hanover Hill", + "city": "Daytona Beach", + "state": "Florida", + "zip": "32123", + "company": "Cogidoo", + "job_title": "Electrical Engineer", + "timezone": "America/New_York", + "title": "Dr" + }, + { + "first_name": "Ginnifer", + "last_name": "Putton", + "email": "gputton6w@state.gov", + "gender": "Female", + "address": "5 Jay Circle", + "city": "Philadelphia", + "state": "Pennsylvania", + "zip": "19191", + "company": "Meejo", + "job_title": "Administrative Assistant II", + "timezone": "America/New_York", + "title": "Mr" + }, + { + "first_name": "Sully", + "last_name": "Janosevic", + "email": "sjanosevic6x@meetup.com", + "gender": "Male", + "address": "1465 Paget Circle", + "city": "Decatur", + "state": "Illinois", + "zip": "62525", + "company": "Myworks", + "job_title": "Senior Financial Analyst", + "timezone": "America/Chicago", + "title": "Rev" + }, + { + "first_name": "Udell", + "last_name": "Scamadine", + "email": "uscamadine6y@instagram.com", + "gender": "Genderqueer", + "address": "200 Gale Road", + "city": "Phoenix", + "state": "Arizona", + "zip": "85020", + "company": "Eamia", + "job_title": "Statistician II", + "timezone": "America/Phoenix", + "title": "Honorable" + }, + { + "first_name": "Ryan", + "last_name": "Inman", + "email": "rinman6z@google.it", + "gender": "Male", + "address": "6885 7th Park", + "city": "Bridgeport", + "state": "Connecticut", + "zip": "06673", + "company": "Plajo", + "job_title": "Information Systems Manager", + "timezone": "America/New_York", + "title": "Ms" + }, + { + "first_name": "Glenda", + "last_name": "Sans", + "email": "gsans70@businessinsider.com", + "gender": "Female", + "address": "1079 Blackbird Way", + "city": "Minneapolis", + "state": "Minnesota", + "zip": "55487", + "company": "Zoonder", + "job_title": "Budget/Accounting Analyst II", + "timezone": "America/Chicago", + "title": "Mr" + }, + { + "first_name": "Pauly", + "last_name": "Rochewell", + "email": "prochewell71@google.co.jp", + "gender": "Male", + "address": "01 Oak Point", + "city": "Concord", + "state": "California", + "zip": "94522", + "company": "Mybuzz", + "job_title": "Sales Associate", + "timezone": "America/Los_Angeles", + "title": "Ms" + }, + { + "first_name": "Jacinda", + "last_name": "Fishpond", + "email": "jfishpond72@fc2.com", + "gender": "Female", + "address": "877 Redwing Way", + "city": "Hattiesburg", + "state": "Mississippi", + "zip": "39404", + "company": "Jazzy", + "job_title": "Junior Executive", + "timezone": "America/Chicago", + "title": "Dr" + }, + { + "first_name": "Sim", + "last_name": "Frankcom", + "email": "sfrankcom73@cisco.com", + "gender": "Male", + "address": "75 Gerald Street", + "city": "Reading", + "state": "Pennsylvania", + "zip": "19610", + "company": "Geba", + "job_title": "Software Test Engineer III", + "timezone": "America/New_York", + "title": "Rev" + }, + { + "first_name": "Stevy", + "last_name": "Withnall", + "email": "swithnall74@wufoo.com", + "gender": "Male", + "address": "52 Emmet Plaza", + "city": "Daytona Beach", + "state": "Florida", + "zip": "32123", + "company": "Twitterbeat", + "job_title": "Budget/Accounting Analyst III", + "timezone": "America/New_York", + "title": "Rev" + }, + { + "first_name": "Virginie", + "last_name": "Kiraly", + "email": "vkiraly75@unesco.org", + "gender": "Female", + "address": "4 High Crossing Terrace", + "city": "Palm Bay", + "state": "Florida", + "zip": "32909", + "company": "Flashpoint", + "job_title": "Product Engineer", + "timezone": "America/New_York", + "title": "Rev" + }, + { + "first_name": "Rockwell", + "last_name": "Pitkaithly", + "email": "rpitkaithly76@ucla.edu", + "gender": "Male", + "address": "8417 Monica Pass", + "city": "Albuquerque", + "state": "New Mexico", + "zip": "87190", + "company": "Twitterbridge", + "job_title": "VP Marketing", + "timezone": "America/Denver", + "title": "Ms" + }, + { + "first_name": "Maud", + "last_name": "Doogue", + "email": "mdoogue77@mac.com", + "gender": "Female", + "address": "93084 Hudson Plaza", + "city": "Jamaica", + "state": "New York", + "zip": "11499", + "company": "Dabjam", + "job_title": "Geologist IV", + "timezone": "America/New_York", + "title": "Rev" + }, + { + "first_name": "Rene", + "last_name": "Lindeman", + "email": "rlindeman78@tinypic.com", + "gender": "Agender", + "address": "58334 Cherokee Road", + "city": "Washington", + "state": "District of Columbia", + "zip": "20016", + "company": "Digitube", + "job_title": "Accountant III", + "timezone": "America/New_York", + "title": "Dr" + }, + { + "first_name": "Marjory", + "last_name": "Redmond", + "email": "mredmond79@sciencedaily.com", + "gender": "Female", + "address": "24928 Gateway Road", + "city": "Salt Lake City", + "state": "Utah", + "zip": "84120", + "company": "Myworks", + "job_title": "Senior Developer", + "timezone": "America/Denver", + "title": "Honorable" + }, + { + "first_name": "Laural", + "last_name": "Brader", + "email": "lbrader7a@myspace.com", + "gender": "Female", + "address": "2182 Everett Circle", + "city": "Hartford", + "state": "Connecticut", + "zip": "06160", + "company": "Quinu", + "job_title": "Staff Accountant II", + "timezone": "America/New_York", + "title": "Mr" + }, + { + "first_name": "Wilfrid", + "last_name": "Twigger", + "email": "wtwigger7b@joomla.org", + "gender": "Male", + "address": "9 Ronald Regan Court", + "city": "Scottsdale", + "state": "Arizona", + "zip": "85260", + "company": "Thoughtstorm", + "job_title": "Accounting Assistant III", + "timezone": "America/Phoenix", + "title": "Dr" + }, + { + "first_name": "Broddy", + "last_name": "Bilsford", + "email": "bbilsford7c@toplist.cz", + "gender": "Polygender", + "address": "0042 Swallow Court", + "city": "Portland", + "state": "Oregon", + "zip": "97255", + "company": "Skidoo", + "job_title": "Senior Cost Accountant", + "timezone": "America/Los_Angeles", + "title": "Dr" + }, + { + "first_name": "Yulma", + "last_name": "Happel", + "email": "yhappel7d@symantec.com", + "gender": "Male", + "address": "44519 Grim Plaza", + "city": "San Diego", + "state": "California", + "zip": "92105", + "company": "Yotz", + "job_title": "Structural Analysis Engineer", + "timezone": "America/Los_Angeles", + "title": "Dr" + }, + { + "first_name": "Zachary", + "last_name": "Fenby", + "email": "zfenby7e@pagesperso-orange.fr", + "gender": "Genderqueer", + "address": "18 Granby Plaza", + "city": "Boca Raton", + "state": "Florida", + "zip": "33432", + "company": "Linktype", + "job_title": "Marketing Manager", + "timezone": "America/New_York", + "title": "Dr" + }, + { + "first_name": "Elisa", + "last_name": "Stubbley", + "email": "estubbley7f@dot.gov", + "gender": "Female", + "address": "6 Coleman Avenue", + "city": "Columbus", + "state": "Ohio", + "zip": "43231", + "company": "Kare", + "job_title": "Teacher", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Josefa", + "last_name": "Dimitriades", + "email": "jdimitriades7g@csmonitor.com", + "gender": "Female", + "address": "8330 Kinsman Parkway", + "city": "Pittsburgh", + "state": "Pennsylvania", + "zip": "15235", + "company": "Mudo", + "job_title": "Analog Circuit Design manager", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Melisenda", + "last_name": "Simao", + "email": "msimao7h@pbs.org", + "gender": "Female", + "address": "4 Miller Place", + "city": "El Paso", + "state": "Texas", + "zip": "79940", + "company": "Kimia", + "job_title": "Quality Control Specialist", + "timezone": "America/Denver", + "title": "Rev" + }, + { + "first_name": "Jewelle", + "last_name": "Minchenton", + "email": "jminchenton7i@e-recht24.de", + "gender": "Female", + "address": "171 Twin Pines Hill", + "city": "Glendale", + "state": "Arizona", + "zip": "85311", + "company": "Twitterbeat", + "job_title": "Assistant Professor", + "timezone": "America/Phoenix", + "title": "Rev" + }, + { + "first_name": "Anderson", + "last_name": "Peters", + "email": "apeters7j@amazon.com", + "gender": "Male", + "address": "72 American Ash Pass", + "city": "Rochester", + "state": "New York", + "zip": "14639", + "company": "Digitube", + "job_title": "Operator", + "timezone": "America/New_York", + "title": "Mrs" + }, + { + "first_name": "Wynn", + "last_name": "Melluish", + "email": "wmelluish7k@pinterest.com", + "gender": "Female", + "address": "30969 Menomonie Terrace", + "city": "Wichita Falls", + "state": "Texas", + "zip": "76310", + "company": "Skivee", + "job_title": "Project Manager", + "timezone": "America/Chicago", + "title": "Mr" + }, + { + "first_name": "Cleveland", + "last_name": "Plevey", + "email": "cplevey7l@army.mil", + "gender": "Male", + "address": "45 Caliangt Pass", + "city": "Lake Worth", + "state": "Florida", + "zip": "33462", + "company": "Kanoodle", + "job_title": "Operator", + "timezone": "America/New_York", + "title": "Ms" + }, + { + "first_name": "Raynell", + "last_name": "Strudwick", + "email": "rstrudwick7m@psu.edu", + "gender": "Female", + "address": "880 Onsgard Point", + "city": "Santa Rosa", + "state": "California", + "zip": "95405", + "company": "Browseblab", + "job_title": "Dental Hygienist", + "timezone": "America/Los_Angeles", + "title": "Rev" + }, + { + "first_name": "Sheryl", + "last_name": "Fitchew", + "email": "sfitchew7n@nhs.uk", + "gender": "Female", + "address": "25 Golf View Hill", + "city": "Erie", + "state": "Pennsylvania", + "zip": "16505", + "company": "Tanoodle", + "job_title": "Media Manager IV", + "timezone": "America/New_York", + "title": "Ms" + }, + { + "first_name": "Lydie", + "last_name": "Cobson", + "email": "lcobson7o@moonfruit.com", + "gender": "Female", + "address": "4138 Northfield Hill", + "city": "Saint Paul", + "state": "Minnesota", + "zip": "55166", + "company": "Skimia", + "job_title": "VP Marketing", + "timezone": "America/Chicago", + "title": "Rev" + }, + { + "first_name": "Teena", + "last_name": "Palley", + "email": "tpalley7p@discovery.com", + "gender": "Female", + "address": "230 Del Sol Circle", + "city": "Colorado Springs", + "state": "Colorado", + "zip": "80915", + "company": "Youtags", + "job_title": "Health Coach I", + "timezone": "America/Denver", + "title": "Honorable" + }, + { + "first_name": "Bonni", + "last_name": "Canland", + "email": "bcanland7q@goodreads.com", + "gender": "Female", + "address": "7782 Oriole Point", + "city": "Wichita Falls", + "state": "Texas", + "zip": "76310", + "company": "Kaymbo", + "job_title": "Executive Secretary", + "timezone": "America/Chicago", + "title": "Rev" + }, + { + "first_name": "Jillana", + "last_name": "Caillou", + "email": "jcaillou7r@salon.com", + "gender": "Female", + "address": "431 Tennessee Crossing", + "city": "Memphis", + "state": "Tennessee", + "zip": "38197", + "company": "Topdrive", + "job_title": "Clinical Specialist", + "timezone": "America/Chicago", + "title": "Honorable" + }, + { + "first_name": "Ingemar", + "last_name": "Walklott", + "email": "iwalklott7s@comcast.net", + "gender": "Male", + "address": "089 Russell Lane", + "city": "Tucson", + "state": "Arizona", + "zip": "85710", + "company": "Dynava", + "job_title": "Account Executive", + "timezone": "America/Phoenix", + "title": "Mr" + }, + { + "first_name": "Vinnie", + "last_name": "Barnfield", + "email": "vbarnfield7t@people.com.cn", + "gender": "Female", + "address": "991 Cherokee Way", + "city": "Minneapolis", + "state": "Minnesota", + "zip": "55470", + "company": "Devify", + "job_title": "Tax Accountant", + "timezone": "America/Chicago", + "title": "Dr" + }, + { + "first_name": "Evy", + "last_name": "Nother", + "email": "enother7u@angelfire.com", + "gender": "Female", + "address": "87544 Colorado Street", + "city": "Lansing", + "state": "Michigan", + "zip": "48956", + "company": "Eare", + "job_title": "Technical Writer", + "timezone": "America/Detroit", + "title": "Ms" + }, + { + "first_name": "Sergent", + "last_name": "Vear", + "email": "svear7v@photobucket.com", + "gender": "Male", + "address": "65112 Burning Wood Park", + "city": "San Jose", + "state": "California", + "zip": "95173", + "company": "Roombo", + "job_title": "Data Coordinator", + "timezone": "America/Los_Angeles", + "title": "Dr" + }, + { + "first_name": "Lin", + "last_name": "Loan", + "email": "lloan7w@squarespace.com", + "gender": "Genderfluid", + "address": "418 Dixon Junction", + "city": "Houston", + "state": "Texas", + "zip": "77035", + "company": "Yamia", + "job_title": "Statistician IV", + "timezone": "America/Chicago", + "title": "Mrs" + }, + { + "first_name": "Katuscha", + "last_name": "Neill", + "email": "kneill7x@disqus.com", + "gender": "Female", + "address": "4 Myrtle Terrace", + "city": "Oakland", + "state": "California", + "zip": "94622", + "company": "Zoomzone", + "job_title": "Geologist IV", + "timezone": "America/Los_Angeles", + "title": "Honorable" + }, + { + "first_name": "Rancell", + "last_name": "Glavis", + "email": "rglavis7y@printfriendly.com", + "gender": "Male", + "address": "536 Division Crossing", + "city": "Fresno", + "state": "California", + "zip": "93786", + "company": "Meemm", + "job_title": "Analog Circuit Design manager", + "timezone": "America/Los_Angeles", + "title": "Mrs" + }, + { + "first_name": "Torrin", + "last_name": "McIlharga", + "email": "tmcilharga7z@ca.gov", + "gender": "Male", + "address": "996 Northwestern Junction", + "city": "Warren", + "state": "Ohio", + "zip": "44485", + "company": "Devpulse", + "job_title": "Structural Engineer", + "timezone": "America/New_York", + "title": "Mr" + }, + { + "first_name": "Grace", + "last_name": "Hirtz", + "email": "ghirtz80@blogger.com", + "gender": "Male", + "address": "7 Daystar Way", + "city": "Honolulu", + "state": "Hawaii", + "zip": "96835", + "company": "Topicshots", + "job_title": "Desktop Support Technician", + "timezone": "Pacific/Honolulu", + "title": "Dr" + }, + { + "first_name": "Latisha", + "last_name": "Heinish", + "email": "lheinish81@wufoo.com", + "gender": "Female", + "address": "2 Dawn Crossing", + "city": "Aurora", + "state": "Colorado", + "zip": "80045", + "company": "Feedmix", + "job_title": "Civil Engineer", + "timezone": "America/Denver", + "title": "Rev" + }, + { + "first_name": "Rania", + "last_name": "Posen", + "email": "rposen82@barnesandnoble.com", + "gender": "Female", + "address": "53 Upham Lane", + "city": "Jacksonville", + "state": "Florida", + "zip": "32215", + "company": "Teklist", + "job_title": "Community Outreach Specialist", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Marika", + "last_name": "Blaik", + "email": "mblaik83@nationalgeographic.com", + "gender": "Female", + "address": "200 Wayridge Way", + "city": "North Hollywood", + "state": "California", + "zip": "91606", + "company": "Centizu", + "job_title": "Assistant Professor", + "timezone": "America/Los_Angeles", + "title": "Dr" + }, + { + "first_name": "Sarena", + "last_name": "Kubiczek", + "email": "skubiczek84@hexun.com", + "gender": "Female", + "address": "2 Springs Avenue", + "city": "Fairbanks", + "state": "Alaska", + "zip": "99790", + "company": "Mybuzz", + "job_title": "GIS Technical Architect", + "timezone": "America/Anchorage", + "title": "Honorable" + }, + { + "first_name": "Rafe", + "last_name": "Idell", + "email": "ridell85@rambler.ru", + "gender": "Male", + "address": "05238 Maple Parkway", + "city": "Davenport", + "state": "Iowa", + "zip": "52809", + "company": "Zoomzone", + "job_title": "Human Resources Manager", + "timezone": "America/Chicago", + "title": "Dr" + }, + { + "first_name": "Clifford", + "last_name": "MacNeish", + "email": "cmacneish86@facebook.com", + "gender": "Male", + "address": "9 Lakewood Point", + "city": "Mesa", + "state": "Arizona", + "zip": "85205", + "company": "Bubblemix", + "job_title": "Web Developer I", + "timezone": "America/Phoenix", + "title": "Mrs" + }, + { + "first_name": "Laird", + "last_name": "Smurthwaite", + "email": "lsmurthwaite87@vkontakte.ru", + "gender": "Male", + "address": "1415 Elgar Court", + "city": "San Diego", + "state": "California", + "zip": "92145", + "company": "Thoughtmix", + "job_title": "Marketing Assistant", + "timezone": "America/Los_Angeles", + "title": "Mrs" + }, + { + "first_name": "Ramon", + "last_name": "Mattedi", + "email": "rmattedi88@slashdot.org", + "gender": "Bigender", + "address": "1796 Oneill Circle", + "city": "Young America", + "state": "Minnesota", + "zip": "55557", + "company": "Oyope", + "job_title": "Media Manager I", + "timezone": "America/Chicago", + "title": "Rev" + }, + { + "first_name": "Leslie", + "last_name": "Menichelli", + "email": "lmenichelli89@mlb.com", + "gender": "Male", + "address": "48088 Oakridge Park", + "city": "Cleveland", + "state": "Ohio", + "zip": "44197", + "company": "Realblab", + "job_title": "Operator", + "timezone": "America/New_York", + "title": "Mrs" + }, + { + "first_name": "Krisha", + "last_name": "Crockford", + "email": "kcrockford8a@eventbrite.com", + "gender": "Male", + "address": "698 Mariners Cove Pass", + "city": "Dayton", + "state": "Ohio", + "zip": "45408", + "company": "Gabtype", + "job_title": "Professor", + "timezone": "America/New_York", + "title": "Honorable" + }, + { + "first_name": "Sarine", + "last_name": "Gaitone", + "email": "sgaitone8b@weather.com", + "gender": "Female", + "address": "22 Bayside Pass", + "city": "Memphis", + "state": "Tennessee", + "zip": "38114", + "company": "Thoughtstorm", + "job_title": "Developer III", + "timezone": "America/Chicago", + "title": "Honorable" + } +] diff --git a/public/data/names.json b/public/data/names.json new file mode 100644 index 00000000..9e516c4e --- /dev/null +++ b/public/data/names.json @@ -0,0 +1,1002 @@ +[ + "Aaden", + "Aarav", + "Aaron", + "Abdiel", + "Abdullah", + "Abel", + "Abraham", + "Abram", + "Ace", + "Achilles", + "Adam", + "Adan", + "Aden", + "Adonis", + "Adrian", + "Adriel", + "Adrien", + "Agustin", + "Ahmad", + "Ahmed", + "Aidan", + "Aiden", + "Alan", + "Alaric", + "Albert", + "Alberto", + "Alden", + "Aldo", + "Alec", + "Alejandro", + "Alessandro", + "Alex", + "Alexander", + "Alexis", + "Alexzander", + "Alfonso", + "Alfred", + "Alfredo", + "Ali", + "Alijah", + "Alistair", + "Allan", + "Allen", + "Alonso", + "Alonzo", + "Alvaro", + "Alvin", + "Amari", + "Ameer", + "Amir", + "Amos", + "Anakin", + "Anders", + "Anderson", + "Andre", + "Andres", + "Andrew", + "Andy", + "Angel", + "Angelo", + "Anson", + "Anthony", + "Anton", + "Antonio", + "Apollo", + "Archer", + "Ares", + "Ari", + "Arian", + "Ariel", + "Arjun", + "Arlo", + "Armando", + "Armani", + "Aron", + "Arthur", + "Arturo", + "Aryan", + "Asa", + "Asher", + "Ashton", + "Atlas", + "Atticus", + "August", + "Augustine", + "Augustus", + "Austin", + "Avery", + "Avi", + "Axel", + "Axl", + "Axton", + "Ayaan", + "Ayan", + "Ayden", + "Aydin", + "Azariah", + "Barrett", + "Baylor", + "Beau", + "Beckett", + "Beckham", + "Ben", + "Benjamin", + "Bennett", + "Benson", + "Bentlee", + "Bentley", + "Benton", + "Billy", + "Bishop", + "Bjorn", + "Blaine", + "Blaise", + "Blake", + "Blaze", + "Bo", + "Bobby", + "Bodhi", + "Bodie", + "Boone", + "Boston", + "Bowen", + "Braden", + "Bradley", + "Brady", + "Brandon", + "Branson", + "Brantley", + "Braxton", + "Brayan", + "Brayden", + "Braydon", + "Braylen", + "Braylon", + "Brayson", + "Brecken", + "Brendan", + "Brenden", + "Brennan", + "Brentley", + "Brett", + "Brian", + "Briar", + "Bridger", + "Briggs", + "Brixton", + "Brock", + "Brodie", + "Brody", + "Bronson", + "Brooks", + "Bruce", + "Bruno", + "Bryan", + "Bryant", + "Bryce", + "Brycen", + "Brysen", + "Bryson", + "Byron", + "Cade", + "Caden", + "Caiden", + "Cain", + "Cairo", + "Caleb", + "Callan", + "Callen", + "Callum", + "Calvin", + "Camden", + "Camdyn", + "Cameron", + "Camilo", + "Camron", + "Canaan", + "Cannon", + "Carl", + "Carlos", + "Carmelo", + "Carson", + "Carter", + "Case", + "Casen", + "Casey", + "Cash", + "Cason", + "Caspian", + "Cassius", + "Castiel", + "Cayden", + "Cayson", + "Cedric", + "Cesar", + "Chad", + "Chaim", + "Chance", + "Chandler", + "Channing", + "Charles", + "Charlie", + "Chase", + "Chris", + "Christian", + "Christopher", + "Clark", + "Clay", + "Clayton", + "Clyde", + "Cody", + "Coen", + "Cohen", + "Colby", + "Cole", + "Coleman", + "Colin", + "Collin", + "Colson", + "Colt", + "Colten", + "Colton", + "Conner", + "Connor", + "Conor", + "Conrad", + "Cooper", + "Corbin", + "Corey", + "Cory", + "Craig", + "Crew", + "Cristian", + "Cristiano", + "Crosby", + "Cruz", + "Cullen", + "Curtis", + "Cyrus", + "Dakota", + "Dallas", + "Dalton", + "Damari", + "Damian", + "Damien", + "Damon", + "Dane", + "Dangelo", + "Daniel", + "Danny", + "Dante", + "Darian", + "Dariel", + "Dario", + "Darius", + "Darrell", + "Darren", + "Darwin", + "Dash", + "Davian", + "David", + "Davion", + "Davis", + "Dawson", + "Dax", + "Daxton", + "Dayton", + "Deacon", + "Dean", + "Deandre", + "Decker", + "Declan", + "Demetrius", + "Dennis", + "Denver", + "Derek", + "Derrick", + "Desmond", + "Devin", + "Devon", + "Dexter", + "Diego", + "Dilan", + "Dillon", + "Dimitri", + "Dominic", + "Dominick", + "Dominik", + "Dominique", + "Donald", + "Donovan", + "Dorian", + "Douglas", + "Drake", + "Draven", + "Drew", + "Duke", + "Duncan", + "Dustin", + "Dwayne", + "Dylan", + "Eason", + "Easton", + "Eddie", + "Eden", + "Edgar", + "Edison", + "Eduardo", + "Edward", + "Edwin", + "Eli", + "Elian", + "Elias", + "Eliezer", + "Elijah", + "Eliseo", + "Elisha", + "Elliot", + "Elliott", + "Ellis", + "Emanuel", + "Emerson", + "Emery", + "Emiliano", + "Emilio", + "Emmanuel", + "Emmet", + "Emmett", + "Emmitt", + "Emory", + "Enoch", + "Enrique", + "Enzo", + "Ephraim", + "Eric", + "Erick", + "Erik", + "Ernest", + "Ernesto", + "Esteban", + "Ethan", + "Eugene", + "Evan", + "Everett", + "Ezekiel", + "Ezequiel", + "Ezra", + "Fabian", + "Felipe", + "Felix", + "Fernando", + "Finley", + "Finn", + "Finnegan", + "Finnley", + "Fisher", + "Fletcher", + "Flynn", + "Ford", + "Forrest", + "Foster", + "Fox", + "Francis", + "Francisco", + "Franco", + "Frank", + "Frankie", + "Franklin", + "Frederick", + "Gabriel", + "Gael", + "Gage", + "Gannon", + "Garrett", + "Gary", + "Gatlin", + "Gavin", + "George", + "Gerald", + "Gerardo", + "Giancarlo", + "Gianluca", + "Gianni", + "Gibson", + "Gideon", + "Giovanni", + "Gordon", + "Grady", + "Graham", + "Grant", + "Graysen", + "Grayson", + "Gregory", + "Grey", + "Greysen", + "Greyson", + "Griffin", + "Guillermo", + "Gunnar", + "Gunner", + "Gustavo", + "Hamza", + "Hank", + "Harlan", + "Harley", + "Harold", + "Harper", + "Harrison", + "Harry", + "Harvey", + "Hassan", + "Hayden", + "Hayes", + "Heath", + "Hector", + "Hendrix", + "Henrik", + "Henry", + "Hezekiah", + "Holden", + "Houston", + "Howard", + "Hudson", + "Hugh", + "Hugo", + "Hunter", + "Huxley", + "Ian", + "Ibrahim", + "Ignacio", + "Iker", + "Ira", + "Isaac", + "Isaiah", + "Isaias", + "Ishaan", + "Ismael", + "Israel", + "Issac", + "Ivan", + "Izaiah", + "Jabari", + "Jace", + "Jack", + "Jackson", + "Jacob", + "Jacoby", + "Jad", + "Jaden", + "Jadiel", + "Jagger", + "Jaiden", + "Jaime", + "Jairo", + "Jake", + "Jakob", + "Jalen", + "Jamal", + "Jamari", + "James", + "Jameson", + "Jamie", + "Jamir", + "Jamison", + "Jared", + "Jase", + "Jasiah", + "Jason", + "Jasper", + "Javier", + "Javion", + "Javon", + "Jax", + "Jaxen", + "Jaxon", + "Jaxson", + "Jaxton", + "Jaxx", + "Jaxxon", + "Jay", + "Jayce", + "Jayceon", + "Jayden", + "Jaylen", + "Jayson", + "Jaziel", + "Jedidiah", + "Jefferson", + "Jeffery", + "Jeffrey", + "Jensen", + "Jeremiah", + "Jeremias", + "Jeremy", + "Jermaine", + "Jerome", + "Jerry", + "Jesse", + "Jessie", + "Jesus", + "Jett", + "Jimmy", + "Joaquin", + "Joe", + "Joel", + "Joey", + "Johan", + "John", + "Johnathan", + "Johnny", + "Jon", + "Jonah", + "Jonas", + "Jonathan", + "Jordan", + "Jordy", + "Jorge", + "Jose", + "Joseph", + "Joshua", + "Josiah", + "Josue", + "Jovanni", + "Joziah", + "Juan", + "Judah", + "Jude", + "Judson", + "Juelz", + "Julian", + "Julien", + "Julio", + "Julius", + "Junior", + "Justice", + "Justin", + "Kace", + "Kade", + "Kaden", + "Kai", + "Kaiden", + "Kairo", + "Kaiser", + "Kaison", + "Kaleb", + "Kalel", + "Kamari", + "Kamden", + "Kameron", + "Kamryn", + "Kane", + "Kannon", + "Kareem", + "Karson", + "Karter", + "Kase", + "Kasen", + "Kash", + "Kashton", + "Kason", + "Kayden", + "Kaysen", + "Kayson", + "Keagan", + "Keanu", + "Keaton", + "Keegan", + "Keenan", + "Keith", + "Kellan", + "Kellen", + "Kelvin", + "Kendall", + "Kendrick", + "Kenneth", + "Kenny", + "Kevin", + "Khalid", + "Khalil", + "Khari", + "Kian", + "Kieran", + "Killian", + "King", + "Kingsley", + "Kingston", + "Knox", + "Koa", + "Kobe", + "Koda", + "Kody", + "Kohen", + "Kole", + "Kolten", + "Kolton", + "Konnor", + "Korbin", + "Kristian", + "Kristopher", + "Kye", + "Kylan", + "Kyle", + "Kylen", + "Kyler", + "Kyng", + "Kyree", + "Kyrie", + "Kyson", + "Lachlan", + "Lamar", + "Lance", + "Landen", + "Landon", + "Landry", + "Landyn", + "Lane", + "Langston", + "Larry", + "Lawrence", + "Lawson", + "Layne", + "Layton", + "Leandro", + "Ledger", + "Lee", + "Legend", + "Leif", + "Leighton", + "Leland", + "Lennon", + "Lennox", + "Leo", + "Leon", + "Leonard", + "Leonardo", + "Leonel", + "Leonidas", + "Leroy", + "Levi", + "Lewis", + "Liam", + "Lincoln", + "Lionel", + "Lochlan", + "Logan", + "London", + "Lorenzo", + "Louie", + "Louis", + "Luca", + "Lucas", + "Lucca", + "Lucian", + "Luciano", + "Luis", + "Luka", + "Lukas", + "Luke", + "Lyle", + "Lyric", + "Mack", + "Madden", + "Maddox", + "Maddux", + "Magnus", + "Maison", + "Major", + "Makai", + "Malachi", + "Malakai", + "Malcolm", + "Malik", + "Manuel", + "Marc", + "Marcel", + "Marcelo", + "Marco", + "Marcos", + "Marcus", + "Mario", + "Mark", + "Markus", + "Marley", + "Marlon", + "Marshall", + "Martin", + "Marvin", + "Mason", + "Mateo", + "Mathew", + "Mathias", + "Matias", + "Matteo", + "Matthew", + "Matthias", + "Maurice", + "Mauricio", + "Maverick", + "Max", + "Maxim", + "Maximilian", + "Maximiliano", + "Maximo", + "Maximus", + "Maxton", + "Maxwell", + "Mayson", + "Mekhi", + "Melvin", + "Memphis", + "Merrick", + "Messiah", + "Micah", + "Michael", + "Micheal", + "Miguel", + "Mike", + "Milan", + "Miles", + "Miller", + "Milo", + "Misael", + "Mitchell", + "Mohamed", + "Mohammad", + "Mohammed", + "Moises", + "Morgan", + "Moses", + "Moshe", + "Muhammad", + "Musa", + "Mustafa", + "Myles", + "Nash", + "Nasir", + "Nathan", + "Nathanael", + "Nathaniel", + "Nehemiah", + "Neil", + "Nelson", + "Nicholas", + "Nickolas", + "Nico", + "Nicolas", + "Niko", + "Nikolai", + "Nikolas", + "Nixon", + "Noah", + "Noe", + "Noel", + "Nolan", + "Nova", + "Oakley", + "Odin", + "Oliver", + "Omar", + "Omari", + "Orion", + "Orlando", + "Oscar", + "Otis", + "Otto", + "Owen", + "Pablo", + "Parker", + "Patrick", + "Paul", + "Paxton", + "Payton", + "Pedro", + "Peter", + "Peyton", + "Philip", + "Phillip", + "Phoenix", + "Pierce", + "Porter", + "Preston", + "Prince", + "Princeton", + "Quentin", + "Quincy", + "Quinn", + "Quinton", + "Rafael", + "Raiden", + "Ramiro", + "Ramon", + "Randall", + "Randy", + "Raphael", + "Rashad", + "Raul", + "Ray", + "Rayan", + "Rayden", + "Raylan", + "Raymond", + "Reagan", + "Reece", + "Reed", + "Reese", + "Reginald", + "Reid", + "Reign", + "Remington", + "Remy", + "Rene", + "Reuben", + "Rex", + "Rey", + "Reyansh", + "Rhett", + "Rhys", + "Ricardo", + "Richard", + "Ricky", + "Ridge", + "Riley", + "River", + "Robert", + "Roberto", + "Rocco", + "Rocky", + "Rodney", + "Rodrigo", + "Rogelio", + "Roger", + "Rohan", + "Roland", + "Roman", + "Romeo", + "Ronald", + "Ronan", + "Ronin", + "Ronnie", + "Rory", + "Rowan", + "Rowen", + "Roy", + "Royal", + "Royce", + "Ruben", + "Rudy", + "Russell", + "Ryan", + "Ryder", + "Ryker", + "Rylan", + "Ryland", + "Sage", + "Salvador", + "Salvatore", + "Sam", + "Samir", + "Samson", + "Samuel", + "Santana", + "Santiago", + "Santino", + "Santos", + "Saul", + "Sawyer", + "Scott", + "Seamus", + "Sean", + "Sebastian", + "Sergio", + "Seth", + "Shane", + "Shaun", + "Shawn", + "Shepard", + "Shepherd", + "Shiloh", + "Shmuel", + "Silas", + "Simeon", + "Simon", + "Sincere", + "Skylar", + "Skyler", + "Solomon", + "Sonny", + "Soren", + "Spencer", + "Stanley", + "Stefan", + "Stephen", + "Sterling", + "Stetson", + "Steven", + "Sullivan", + "Sutton", + "Sylas", + "Tadeo", + "Talon", + "Tanner", + "Tate", + "Tatum", + "Taylor", + "Terrance", + "Terrell", + "Terrence", + "Terry", + "Thaddeus", + "Thatcher", + "Theo", + "Theodore", + "Thiago", + "Thomas", + "Timothy", + "Titan", + "Titus", + "Tobias", + "Toby", + "Tomas", + "Tommy", + "Tony", + "Trace", + "Travis", + "Trent", + "Trenton", + "Trevor", + "Trey", + "Tripp", + "Tristan", + "Tristen", + "Tristian", + "Troy", + "Tucker", + "Ty", + "Tyler", + "Tyson", + "Ulises", + "Uriah", + "Uriel", + "Valentin", + "Valentino", + "Van", + "Vance", + "Vaughn", + "Vicente", + "Victor", + "Vihaan", + "Vincent", + "Vincenzo", + "Vivaan", + "Wade", + "Walker", + "Walter", + "Warren", + "Waylon", + "Wayne", + "Wells", + "Wesley", + "Wesson", + "Westin", + "Westley", + "Weston", + "Wilder", + "Will", + "William", + "Willie", + "Wilson", + "Winston", + "Wyatt", + "Xander", + "Xavier", + "Xzavier", + "Yadiel", + "Yahir", + "Yahya", + "Yehuda", + "Yisroel", + "Yosef", + "Yousef", + "Yusuf", + "Zachariah", + "Zachary", + "Zackary", + "Zahir", + "Zaid", + "Zaiden", + "Zain", + "Zaire", + "Zander", + "Zane", + "Zavier", + "Zayd", + "Zayden", + "Zayn", + "Zayne", + "Zechariah", + "Zeke", + "Zion", + "Zyaire" +] diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 00000000..b21ed2c9 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/public/generated/README.md b/public/generated/README.md new file mode 100644 index 00000000..c8936681 --- /dev/null +++ b/public/generated/README.md @@ -0,0 +1 @@ +The contents of this folder are generated automatically. Do not edit them by hand. \ No newline at end of file diff --git a/public/generated/code-snippets/CellComponent.json b/public/generated/code-snippets/CellComponent.json new file mode 100644 index 00000000..931bd1af --- /dev/null +++ b/public/generated/code-snippets/CellComponent.json @@ -0,0 +1,4 @@ +{ + "javaScript": "
import {} from \"react-window\";
\n
\n
function CellComponent({ contacts, columnIndex, rowIndex, style }) {
\n
const address = contacts[rowIndex];
\n
const content = address[indexToColumn(columnIndex)];
\n
\n
return (
\n
<div className=\"truncate\" style={style}>
\n
{content}
\n
</div>
\n
);
\n
}
", + "typeScript": "
import { type CellComponentProps } from \"react-window\";
\n
\n
function CellComponent({
\n
contacts,
\n
columnIndex,
\n
rowIndex,
\n
style
\n
}: CellComponentProps<{
\n
contacts: Contact[];
\n
}>) {
\n
const address = contacts[rowIndex];
\n
const content = address[indexToColumn(columnIndex)];
\n
\n
return (
\n
<div className=\"truncate\" style={style}>
\n
{content}
\n
</div>
\n
);
\n
}
" +} \ No newline at end of file diff --git a/public/generated/code-snippets/FixedHeightList.json b/public/generated/code-snippets/FixedHeightList.json new file mode 100644 index 00000000..9db447f8 --- /dev/null +++ b/public/generated/code-snippets/FixedHeightList.json @@ -0,0 +1,4 @@ +{ + "javaScript": "
import { List } from \"react-window\";
\n
\n
function Example({ names }) {
\n
return (
\n
<List
\n
rowComponent={RowComponent}
\n
rowCount={names.length}
\n
rowHeight={25}
\n
rowProps={{ names }}
\n
/>
\n
);
\n
}
", + "typeScript": "
import { List } from \"react-window\";
\n
\n
function Example({ names }: { names: string[] }) {
\n
return (
\n
<List
\n
rowComponent={RowComponent}
\n
rowCount={names.length}
\n
rowHeight={25}
\n
rowProps={{ names }}
\n
/>
\n
);
\n
}
" +} \ No newline at end of file diff --git a/public/generated/code-snippets/FixedHeightRowComponent.json b/public/generated/code-snippets/FixedHeightRowComponent.json new file mode 100644 index 00000000..efa54a6a --- /dev/null +++ b/public/generated/code-snippets/FixedHeightRowComponent.json @@ -0,0 +1,4 @@ +{ + "javaScript": "
import {} from \"react-window\";
\n
\n
function RowComponent({ index, names, style }) {
\n
return (
\n
<div className=\"flex items-center justify-between\" style={style}>
\n
{names[index]}
\n
<div className=\"text-slate-500 text-xs\">{`${index + 1} of ${names.length}`}</div>
\n
</div>
\n
);
\n
}
", + "typeScript": "
import { type RowComponentProps } from \"react-window\";
\n
\n
function RowComponent({
\n
index,
\n
names,
\n
style
\n
}: RowComponentProps<{
\n
names: string[];
\n
}>) {
\n
return (
\n
<div className=\"flex items-center justify-between\" style={style}>
\n
{names[index]}
\n
<div className=\"text-slate-500 text-xs\">{`${index + 1} of ${names.length}`}</div>
\n
</div>
\n
);
\n
}
" +} \ No newline at end of file diff --git a/public/generated/code-snippets/FlexboxLayout.json b/public/generated/code-snippets/FlexboxLayout.json new file mode 100644 index 00000000..1979c599 --- /dev/null +++ b/public/generated/code-snippets/FlexboxLayout.json @@ -0,0 +1,4 @@ +{ + "javaScript": "
import { getScrollbarSize, List } from \"react-window\";
\n
\n
function Example({ addresses }) {
\n
const [size] = useState(getScrollbarSize);
\n
\n
return (
\n
<div className=\"h-55 flex flex-col\">
\n
<div className=\"flex flex-row bg-teal-600 p-1 px-2\">
\n
<div className=\"grow flex flex-row items-center gap-2 font-bold\">
\n
<div className=\"flex-1\">City</div>
\n
<div className=\"flex-1\">State</div>
\n
<div className=\"w-10\">Zip</div>
\n
</div>
\n
<div className=\"shrink\" style={{ width: size }} />
\n
</div>
\n
<div className=\"overflow-hidden\">
\n
<List
\n
rowComponent={RowComponent}
\n
rowCount={addresses.length}
\n
rowHeight={25}
\n
rowProps={{ addresses }}
\n
/>
\n
</div>
\n
</div>
\n
);
\n
}
\n
\n
function RowComponent({ index, addresses, style }) {
\n
const address = addresses[index];
\n
\n
return (
\n
<div className=\"flex flex-row items-center gap-2 px-2\" style={style}>
\n
<div className=\"flex-1\">{address.city}</div>
\n
<div className=\"flex-1\">{address.state}</div>
\n
<div className=\"w-10 text-xs\">{address.zip}</div>
\n
</div>
\n
);
\n
}
", + "typeScript": "
import { getScrollbarSize, List, type RowComponentProps } from \"react-window\";
\n
\n
function Example({ addresses }: { addresses: Address[] }) {
\n
const [size] = useState(getScrollbarSize);
\n
\n
return (
\n
<div className=\"h-55 flex flex-col\">
\n
<div className=\"flex flex-row bg-teal-600 p-1 px-2\">
\n
<div className=\"grow flex flex-row items-center gap-2 font-bold\">
\n
<div className=\"flex-1\">City</div>
\n
<div className=\"flex-1\">State</div>
\n
<div className=\"w-10\">Zip</div>
\n
</div>
\n
<div className=\"shrink\" style={{ width: size }} />
\n
</div>
\n
<div className=\"overflow-hidden\">
\n
<List
\n
rowComponent={RowComponent}
\n
rowCount={addresses.length}
\n
rowHeight={25}
\n
rowProps={{ addresses }}
\n
/>
\n
</div>
\n
</div>
\n
);
\n
}
\n
\n
function RowComponent({
\n
index,
\n
addresses,
\n
style
\n
}: RowComponentProps<{
\n
addresses: Address[];
\n
}>) {
\n
const address = addresses[index];
\n
\n
return (
\n
<div className=\"flex flex-row items-center gap-2 px-2\" style={style}>
\n
<div className=\"flex-1\">{address.city}</div>
\n
<div className=\"flex-1\">{address.state}</div>
\n
<div className=\"w-10 text-xs\">{address.zip}</div>
\n
</div>
\n
);
\n
}
" +} \ No newline at end of file diff --git a/public/generated/code-snippets/Grid.json b/public/generated/code-snippets/Grid.json new file mode 100644 index 00000000..22fb93c9 --- /dev/null +++ b/public/generated/code-snippets/Grid.json @@ -0,0 +1,4 @@ +{ + "javaScript": "
import { Grid } from \"react-window\";
\n
\n
function Example({ contacts }) {
\n
return (
\n
<Grid
\n
cellComponent={CellComponent}
\n
cellProps={{ contacts }}
\n
columnCount={10}
\n
columnWidth={columnWidth}
\n
rowCount={contacts.length}
\n
rowHeight={25}
\n
/>
\n
);
\n
}
", + "typeScript": "
import { Grid } from \"react-window\";
\n
\n
function Example({ contacts }: { contacts: Contact[] }) {
\n
return (
\n
<Grid
\n
cellComponent={CellComponent}
\n
cellProps={{ contacts }}
\n
columnCount={10}
\n
columnWidth={columnWidth}
\n
rowCount={contacts.length}
\n
rowHeight={25}
\n
/>
\n
);
\n
}
" +} \ No newline at end of file diff --git a/public/generated/code-snippets/HorizontalList.json b/public/generated/code-snippets/HorizontalList.json new file mode 100644 index 00000000..532692a6 --- /dev/null +++ b/public/generated/code-snippets/HorizontalList.json @@ -0,0 +1,4 @@ +{ + "javaScript": "
import { Grid } from \"react-window\";
\n
\n
function HorizontalList({ emails }) {
\n
return (
\n
<Grid
\n
cellComponent={CellComponent}
\n
cellProps={{ emails }}
\n
columnCount={emails.length}
\n
columnWidth={150}
\n
rowCount={1}
\n
rowHeight=\"100%\"
\n
/>
\n
);
\n
}
", + "typeScript": "
import { Grid } from \"react-window\";
\n
\n
function HorizontalList({ emails }: { emails: string[] }) {
\n
return (
\n
<Grid
\n
cellComponent={CellComponent}
\n
cellProps={{ emails }}
\n
columnCount={emails.length}
\n
columnWidth={150}
\n
rowCount={1}
\n
rowHeight=\"100%\"
\n
/>
\n
);
\n
}
" +} \ No newline at end of file diff --git a/public/generated/code-snippets/HorizontalListCellRenderer.json b/public/generated/code-snippets/HorizontalListCellRenderer.json new file mode 100644 index 00000000..b78cf2a1 --- /dev/null +++ b/public/generated/code-snippets/HorizontalListCellRenderer.json @@ -0,0 +1,4 @@ +{ + "javaScript": "
import {} from \"react-window\";
\n
\n
function CellComponent({ columnIndex, emails, style }) {
\n
return (
\n
<div
\n
className={cn(\"px-2 truncate text-center leading-[2.5]\", {
\n
\"bg-white/10 rounded\": columnIndex % 2 === 0,
\n
})}
\n
style={style}
\n
>
\n
{emails[columnIndex]}
\n
</div>
\n
);
\n
}
", + "typeScript": "
import { type CellComponentProps } from \"react-window\";
\n
\n
function CellComponent({
\n
columnIndex,
\n
emails,
\n
style
\n
}: CellComponentProps<{ emails: string[] }>) {
\n
return (
\n
<div
\n
className={cn(\"px-2 truncate text-center leading-[2.5]\", {
\n
\"bg-white/10 rounded\": columnIndex % 2 === 0
\n
})}
\n
style={style}
\n
>
\n
{emails[columnIndex]}
\n
</div>
\n
);
\n
}
" +} \ No newline at end of file diff --git a/public/generated/code-snippets/ListVariableRowHeights.json b/public/generated/code-snippets/ListVariableRowHeights.json new file mode 100644 index 00000000..816404e6 --- /dev/null +++ b/public/generated/code-snippets/ListVariableRowHeights.json @@ -0,0 +1,4 @@ +{ + "javaScript": "
import { List } from \"react-window\";
\n
\n
function Example({ items }) {
\n
return (
\n
<List
\n
rowComponent={RowComponent}
\n
rowCount={items.length}
\n
rowHeight={rowHeight}
\n
rowProps={{ items }}
\n
/>
\n
);
\n
}
\n
\n
function rowHeight(index, { items }) {
\n
switch (items[index].type) {
\n
case \"state\": {
\n
return 30;
\n
}
\n
case \"zip\": {
\n
return 25;
\n
}
\n
}
\n
}
\n
\n
function RowComponent({ index, items, style }) {
\n
const item = items[index];
\n
\n
const className = getClassName(item);
\n
\n
return (
\n
<div className={className} style={style}>
\n
{item.type === \"state\" ? (
\n
<span>{item.state}</span>
\n
) : (
\n
<span>
\n
{item.city}, {item.zip}
\n
</span>
\n
)}
\n
<div className=\"text-slate-500 text-xs\">{`${index + 1} of ${items.length}`}</div>
\n
</div>
\n
);
\n
}
", + "typeScript": "
import {
\n
List,
\n
type ListImperativeAPI,
\n
type RowComponentProps
\n
} from \"react-window\";
\n
\n
type Item =
\n
| { type: \"state\"; state: string }
\n
| { type: \"zip\"; city: string; zip: string };
\n
\n
type RowProps = {
\n
items: Item[];
\n
};
\n
\n
function Example({ items }: { items: Item[] }) {
\n
return (
\n
<List<RowProps>
\n
rowComponent={RowComponent}
\n
rowCount={items.length}
\n
rowHeight={rowHeight}
\n
rowProps={{ items }}
\n
/>
\n
);
\n
}
\n
\n
function rowHeight(index: number, { items }: RowProps) {
\n
switch (items[index].type) {
\n
case \"state\": {
\n
return 30;
\n
}
\n
case \"zip\": {
\n
return 25;
\n
}
\n
}
\n
}
\n
\n
function RowComponent({ index, items, style }: RowComponentProps<RowProps>) {
\n
const item = items[index];
\n
\n
const className = getClassName(item);
\n
\n
return (
\n
<div className={className} style={style}>
\n
{item.type === \"state\" ? (
\n
<span>{item.state}</span>
\n
) : (
\n
<span>
\n
{item.city}, {item.zip}
\n
</span>
\n
)}
\n
<div className=\"text-slate-500 text-xs\">{`${index + 1} of ${items.length}`}</div>
\n
</div>
\n
);
\n
}
" +} \ No newline at end of file diff --git a/public/generated/code-snippets/RtlGrid.json b/public/generated/code-snippets/RtlGrid.json new file mode 100644 index 00000000..6a0af507 --- /dev/null +++ b/public/generated/code-snippets/RtlGrid.json @@ -0,0 +1,4 @@ +{ + "javaScript": "
import { Grid } from \"react-window\";
\n
\n
function RtlExample({ contacts }) {
\n
return (
\n
<Grid
\n
cellComponent={CellComponent}
\n
cellProps={{ contacts }}
\n
columnCount={10}
\n
columnWidth={columnWidth}
\n
dir=\"rtl\"
\n
rowCount={contacts.length}
\n
rowHeight={35}
\n
/>
\n
);
\n
}
", + "typeScript": "
import { Grid } from \"react-window\";
\n
\n
function RtlExample({ contacts }: { contacts: Contact[] }) {
\n
return (
\n
<Grid
\n
cellComponent={CellComponent}
\n
cellProps={{ contacts }}
\n
columnCount={10}
\n
columnWidth={columnWidth}
\n
dir=\"rtl\"
\n
rowCount={contacts.length}
\n
rowHeight={35}
\n
/>
\n
);
\n
}
" +} \ No newline at end of file diff --git a/public/generated/code-snippets/columnWidth.json b/public/generated/code-snippets/columnWidth.json new file mode 100644 index 00000000..916b6d7e --- /dev/null +++ b/public/generated/code-snippets/columnWidth.json @@ -0,0 +1,4 @@ +{ + "javaScript": "
function columnWidth(index) {
\n
switch (indexToColumn(index)) {
\n
case \"address\": {
\n
return 250;
\n
}
\n
case \"email\": {
\n
return 300;
\n
}
\n
case \"job_title\": {
\n
return 150;
\n
}
\n
case \"timezone\": {
\n
return 200;
\n
}
\n
case \"zip\": {
\n
return 75;
\n
}
\n
default: {
\n
return 100;
\n
}
\n
}
\n
}
", + "typeScript": "
function columnWidth(index: number) {
\n
switch (indexToColumn(index)) {
\n
case \"address\": {
\n
return 250;
\n
}
\n
case \"email\": {
\n
return 300;
\n
}
\n
case \"job_title\": {
\n
return 150;
\n
}
\n
case \"timezone\": {
\n
return 200;
\n
}
\n
case \"zip\": {
\n
return 75;
\n
}
\n
default: {
\n
return 100;
\n
}
\n
}
\n
}
" +} \ No newline at end of file diff --git a/public/generated/code-snippets/gridRefClickEventHandler.json b/public/generated/code-snippets/gridRefClickEventHandler.json new file mode 100644 index 00000000..be49e103 --- /dev/null +++ b/public/generated/code-snippets/gridRefClickEventHandler.json @@ -0,0 +1,4 @@ +{ + "javaScript": "
const onClick = () => {
\n
const grid = gridRef.current;
\n
grid?.scrollToCell({
\n
behavior: \"auto\", // optional
\n
columnAlign: \"auto\", // optional
\n
columnIndex: 10,
\n
rowAlign: \"auto\", // optional
\n
rowIndex: 250,
\n
});
\n
};
", + "typeScript": "
const onClick = () => {
\n
const grid = gridRef.current;
\n
grid?.scrollToCell({
\n
behavior: \"auto\", // optional
\n
columnAlign: \"auto\", // optional
\n
columnIndex: 10,
\n
rowAlign: \"auto\", // optional
\n
rowIndex: 250
\n
});
\n
};
" +} \ No newline at end of file diff --git a/public/generated/code-snippets/listRefClickEventHandler.json b/public/generated/code-snippets/listRefClickEventHandler.json new file mode 100644 index 00000000..ebd60de2 --- /dev/null +++ b/public/generated/code-snippets/listRefClickEventHandler.json @@ -0,0 +1,4 @@ +{ + "javaScript": "
const onClick = () => {
\n
const list = listRef.current;
\n
list?.scrollToRow({
\n
align: \"auto\", // optional
\n
behavior: \"auto\", // optional
\n
index: 250,
\n
});
\n
};
", + "typeScript": "
const onClick = () => {
\n
const list = listRef.current;
\n
list?.scrollToRow({
\n
align: \"auto\", // optional
\n
behavior: \"auto\", // optional
\n
index: 250
\n
});
\n
};
" +} \ No newline at end of file diff --git a/public/generated/code-snippets/rowHeight.json b/public/generated/code-snippets/rowHeight.json new file mode 100644 index 00000000..7ac5260c --- /dev/null +++ b/public/generated/code-snippets/rowHeight.json @@ -0,0 +1,4 @@ +{ + "javaScript": "
function rowHeight(index, { items }) {
\n
return items[index].type === \"state\" ? 30 : 25;
\n
}
", + "typeScript": "
function rowHeight(index: number, { items }: RowProps) {
\n
return items[index].type === \"state\" ? 30 : 25;
\n
}
" +} \ No newline at end of file diff --git a/public/generated/code-snippets/useGridCallbackRef.json b/public/generated/code-snippets/useGridCallbackRef.json new file mode 100644 index 00000000..161bd543 --- /dev/null +++ b/public/generated/code-snippets/useGridCallbackRef.json @@ -0,0 +1,4 @@ +{ + "javaScript": "
import { useGridCallbackRef } from \"react-window\";
\n
\n
function Example(props) {
\n
const [grid, setGrid] = useGridCallbackRef(null);
\n
\n
useCustomHook(grid);
\n
\n
return <Grid gridRef={setGrid} {...props} />;
\n
}
", + "typeScript": "
import { useGridCallbackRef } from \"react-window\";
\n
\n
function Example(props: Props) {
\n
const [grid, setGrid] = useGridCallbackRef(null);
\n
\n
useCustomHook(grid);
\n
\n
return <Grid gridRef={setGrid} {...props} />;
\n
}
" +} \ No newline at end of file diff --git a/public/generated/code-snippets/useGridRef.json b/public/generated/code-snippets/useGridRef.json new file mode 100644 index 00000000..75a980c1 --- /dev/null +++ b/public/generated/code-snippets/useGridRef.json @@ -0,0 +1,4 @@ +{ + "javaScript": "
function Example(props) {
\n
const gridRef = useGridRef(null);
\n
\n
return <Grid gridRef={gridRef} {...props} />;
\n
}
", + "typeScript": "
function Example(props: Props) {
\n
const gridRef = useGridRef(null);
\n
\n
return <Grid gridRef={gridRef} {...props} />;
\n
}
" +} \ No newline at end of file diff --git a/public/generated/code-snippets/useGridRefImport.json b/public/generated/code-snippets/useGridRefImport.json new file mode 100644 index 00000000..8f7f5ba2 --- /dev/null +++ b/public/generated/code-snippets/useGridRefImport.json @@ -0,0 +1,3 @@ +{ + "javaScript": "
import { useGridRef } from \"react-window\";
" +} \ No newline at end of file diff --git a/public/generated/code-snippets/useListCallbackRef.json b/public/generated/code-snippets/useListCallbackRef.json new file mode 100644 index 00000000..3a226672 --- /dev/null +++ b/public/generated/code-snippets/useListCallbackRef.json @@ -0,0 +1,4 @@ +{ + "javaScript": "
import { useListCallbackRef } from \"react-window\";
\n
\n
function Example(props) {
\n
const [list, setList] = useListCallbackRef(null);
\n
\n
useCustomHook(list);
\n
\n
return <List listRef={setList} {...props} />;
\n
}
", + "typeScript": "
import { useListCallbackRef } from \"react-window\";
\n
\n
function Example(props: Props) {
\n
const [list, setList] = useListCallbackRef(null);
\n
\n
useCustomHook(list);
\n
\n
return <List listRef={setList} {...props} />;
\n
}
" +} \ No newline at end of file diff --git a/public/generated/code-snippets/useListRef.json b/public/generated/code-snippets/useListRef.json new file mode 100644 index 00000000..b8d5c3e7 --- /dev/null +++ b/public/generated/code-snippets/useListRef.json @@ -0,0 +1,4 @@ +{ + "javaScript": "
function Example(props) {
\n
const listRef = useListRef(null);
\n
\n
return <List listRef={listRef} {...props} />;
\n
}
", + "typeScript": "
function Example(props: Props) {
\n
const listRef = useListRef(null);
\n
\n
return <List listRef={listRef} {...props} />;
\n
}
" +} \ No newline at end of file diff --git a/public/generated/code-snippets/useListRefImport.json b/public/generated/code-snippets/useListRefImport.json new file mode 100644 index 00000000..8533c7ba --- /dev/null +++ b/public/generated/code-snippets/useListRefImport.json @@ -0,0 +1,3 @@ +{ + "javaScript": "
import { useListRef } from \"react-window\";
" +} \ No newline at end of file diff --git a/public/generated/js-docs/Grid.json b/public/generated/js-docs/Grid.json new file mode 100644 index 00000000..ec2e4091 --- /dev/null +++ b/public/generated/js-docs/Grid.json @@ -0,0 +1,399 @@ +{ + "tags": {}, + "filePath": "lib/components/grid/Grid.tsx", + "description": "", + "displayName": "Grid", + "methods": [], + "props": { + "className": { + "defaultValue": null, + "description": "CSS class name.", + "name": "className", + "parent": { + "fileName": "react-window/node_modules/.pnpm/@types+react@19.1.8/node_modules/@types/react/index.d.ts", + "name": "HTMLAttributes" + }, + "declarations": [ + { + "fileName": "react-window/node_modules/.pnpm/@types+react@19.1.8/node_modules/@types/react/index.d.ts", + "name": "HTMLAttributes" + }, + { + "fileName": "react-window/lib/components/grid/types.ts", + "name": "TypeLiteral" + } + ], + "required": false, + "type": { + "name": "enum", + "raw": "string | undefined", + "value": [ + { + "value": "undefined" + }, + { + "value": "string" + } + ] + } + }, + "dir": { + "defaultValue": null, + "description": "Corresponds to the HTML dir attribute:\nhttps://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/dir", + "name": "dir", + "parent": { + "fileName": "react-window/node_modules/.pnpm/@types+react@19.1.8/node_modules/@types/react/index.d.ts", + "name": "HTMLAttributes" + }, + "declarations": [ + { + "fileName": "react-window/node_modules/.pnpm/@types+react@19.1.8/node_modules/@types/react/index.d.ts", + "name": "HTMLAttributes" + }, + { + "fileName": "react-window/lib/components/grid/types.ts", + "name": "TypeLiteral" + } + ], + "required": false, + "type": { + "name": "enum", + "raw": "string | undefined", + "value": [ + { + "value": "undefined" + }, + { + "value": "string" + } + ] + } + }, + "style": { + "defaultValue": null, + "description": "Optional CSS properties.\nThe grid of cells will fill the height and width defined by this style.", + "name": "style", + "parent": { + "fileName": "react-window/node_modules/.pnpm/@types+react@19.1.8/node_modules/@types/react/index.d.ts", + "name": "HTMLAttributes" + }, + "declarations": [ + { + "fileName": "react-window/node_modules/.pnpm/@types+react@19.1.8/node_modules/@types/react/index.d.ts", + "name": "HTMLAttributes" + }, + { + "fileName": "react-window/lib/components/grid/types.ts", + "name": "TypeLiteral" + } + ], + "required": false, + "type": { + "name": "enum", + "raw": "CSSProperties | undefined", + "value": [ + { + "value": "undefined" + }, + { + "value": "CSSProperties", + "description": "", + "fullComment": "", + "tags": {} + } + ] + } + }, + "cellComponent": { + "defaultValue": null, + "description": "React component responsible for rendering a cell.\n\nThis component will receive an `index` and `style` prop by default.\nAdditionally it will receive prop values passed to `cellProps`.\n\n⚠️ The prop types for this component are exported as `CellComponentProps`", + "name": "cellComponent", + "declarations": [ + { + "fileName": "react-window/lib/components/grid/types.ts", + "name": "TypeLiteral" + } + ], + "required": true, + "type": { + "name": "(props: { columnIndex: number; rowIndex: number; style: CSSProperties; } & CellProps) => ReactNode" + } + }, + "cellProps": { + "defaultValue": null, + "description": "Additional props to be passed to the cell-rendering component.\nGrid will automatically re-render cells when values in this object change.\n\n⚠️ This object must not contain either an `index` or `style` prop.", + "name": "cellProps", + "declarations": [ + { + "fileName": "react-window/lib/components/grid/types.ts", + "name": "TypeLiteral" + } + ], + "required": true, + "type": { + "name": "ExcludeForbiddenKeys" + } + }, + "columnCount": { + "defaultValue": null, + "description": "Number of columns to be rendered in the grid.", + "name": "columnCount", + "declarations": [ + { + "fileName": "react-window/lib/components/grid/types.ts", + "name": "TypeLiteral" + } + ], + "required": true, + "type": { + "name": "number" + } + }, + "columnWidth": { + "defaultValue": null, + "description": "Column width; the following formats are supported:\n- number of pixels (number)\n- percentage of the grid's current width (string)\n- function that returns the row width (in pixels) given an index and `cellProps`", + "name": "columnWidth", + "declarations": [ + { + "fileName": "react-window/lib/components/grid/types.ts", + "name": "TypeLiteral" + } + ], + "required": true, + "type": { + "name": "enum", + "raw": "string | number | ((index: number, cellProps: CellProps) => number)", + "value": [ + { + "value": "string" + }, + { + "value": "number" + }, + { + "value": "(index: number, cellProps: CellProps) => number", + "description": "", + "fullComment": "", + "tags": {} + } + ] + } + }, + "defaultHeight": { + "defaultValue": { + "value": "0" + }, + "description": "Default height of grid for initial render.\nThis value is important for server rendering.", + "name": "defaultHeight", + "declarations": [ + { + "fileName": "react-window/lib/components/grid/types.ts", + "name": "TypeLiteral" + } + ], + "required": false, + "type": { + "name": "enum", + "raw": "number | undefined", + "value": [ + { + "value": "undefined" + }, + { + "value": "number" + } + ] + } + }, + "defaultWidth": { + "defaultValue": { + "value": "0" + }, + "description": "Default width of grid for initial render.\nThis value is important for server rendering.", + "name": "defaultWidth", + "declarations": [ + { + "fileName": "react-window/lib/components/grid/types.ts", + "name": "TypeLiteral" + } + ], + "required": false, + "type": { + "name": "enum", + "raw": "number | undefined", + "value": [ + { + "value": "undefined" + }, + { + "value": "number" + } + ] + } + }, + "gridRef": { + "defaultValue": null, + "description": "Ref used to interact with this component's imperative API.\n\nThis API has imperative methods for scrolling and a getter for the outermost DOM element.\n\n⚠️ The `useGridRef` and `useGridCallbackRef` hooks are exported for convenience use in TypeScript projects.", + "name": "gridRef", + "declarations": [ + { + "fileName": "react-window/lib/components/grid/types.ts", + "name": "TypeLiteral" + } + ], + "required": false, + "type": { + "name": "enum", + "raw": "Ref | undefined", + "value": [ + { + "value": "undefined" + }, + { + "value": "null" + }, + { + "value": "(instance: GridImperativeAPI | null) => void | (() => VoidOrUndefinedOnly)", + "description": "", + "fullComment": "", + "tags": {} + }, + { + "value": "RefObject", + "description": "Created by {@link createRef}, or {@link useRef} when passed `null`.", + "fullComment": "Created by {@link createRef}, or {@link useRef} when passed `null`.\n@template T The type of the ref's value.\n@example ```tsx\nconst ref = createRef();\n\nref.current = document.createElement('div'); // Error\n```", + "tags": { + "template": "T The type of the ref's value.", + "example": "```tsx\nconst ref = createRef();\n\nref.current = document.createElement('div'); // Error\n```" + } + } + ] + } + }, + "onCellsRendered": { + "defaultValue": null, + "description": "Callback notified when the range of rendered cells changes.", + "name": "onCellsRendered", + "declarations": [ + { + "fileName": "react-window/lib/components/grid/types.ts", + "name": "TypeLiteral" + } + ], + "required": false, + "type": { + "name": "enum", + "raw": "((args: { columnStartIndex: number; columnStopIndex: number; rowStartIndex: number; rowStopIndex: number; }) => void) | undefined", + "value": [ + { + "value": "undefined" + }, + { + "value": "(args: { columnStartIndex: number; columnStopIndex: number; rowStartIndex: number; rowStopIndex: number; }) => void", + "description": "", + "fullComment": "", + "tags": {} + } + ] + } + }, + "onResize": { + "defaultValue": null, + "description": "Callback notified when the Grid's outermost HTMLElement resizes.\nThis may be used to (re)scroll a cell into view.", + "name": "onResize", + "declarations": [ + { + "fileName": "react-window/lib/components/grid/types.ts", + "name": "TypeLiteral" + } + ], + "required": false, + "type": { + "name": "enum", + "raw": "((size: { height: number; width: number; }, prevSize: { height: number; width: number; }) => void) | undefined", + "value": [ + { + "value": "undefined" + }, + { + "value": "(size: { height: number; width: number; }, prevSize: { height: number; width: number; }) => void", + "description": "", + "fullComment": "", + "tags": {} + } + ] + } + }, + "overscanCount": { + "defaultValue": { + "value": "3" + }, + "description": "How many additional rows/columns to render outside of the visible area.\nThis can reduce visual flickering near the edges of a grid when scrolling.", + "name": "overscanCount", + "declarations": [ + { + "fileName": "react-window/lib/components/grid/types.ts", + "name": "TypeLiteral" + } + ], + "required": false, + "type": { + "name": "enum", + "raw": "number | undefined", + "value": [ + { + "value": "undefined" + }, + { + "value": "number" + } + ] + } + }, + "rowCount": { + "defaultValue": null, + "description": "Number of rows to be rendered in the grid.", + "name": "rowCount", + "declarations": [ + { + "fileName": "react-window/lib/components/grid/types.ts", + "name": "TypeLiteral" + } + ], + "required": true, + "type": { + "name": "number" + } + }, + "rowHeight": { + "defaultValue": null, + "description": "Row height; the following formats are supported:\n- number of pixels (number)\n- percentage of the grid's current height (string)\n- function that returns the row height (in pixels) given an index and `cellProps`", + "name": "rowHeight", + "declarations": [ + { + "fileName": "react-window/lib/components/grid/types.ts", + "name": "TypeLiteral" + } + ], + "required": true, + "type": { + "name": "enum", + "raw": "string | number | ((index: number, cellProps: CellProps) => number)", + "value": [ + { + "value": "string" + }, + { + "value": "number" + }, + { + "value": "(index: number, cellProps: CellProps) => number", + "description": "", + "fullComment": "", + "tags": {} + } + ] + } + } + } +} \ No newline at end of file diff --git a/public/generated/js-docs/List.json b/public/generated/js-docs/List.json new file mode 100644 index 00000000..7b0415ff --- /dev/null +++ b/public/generated/js-docs/List.json @@ -0,0 +1,296 @@ +{ + "tags": {}, + "filePath": "lib/components/list/List.tsx", + "description": "", + "displayName": "List", + "methods": [], + "props": { + "className": { + "defaultValue": null, + "description": "CSS class name.", + "name": "className", + "parent": { + "fileName": "react-window/node_modules/.pnpm/@types+react@19.1.8/node_modules/@types/react/index.d.ts", + "name": "HTMLAttributes" + }, + "declarations": [ + { + "fileName": "react-window/node_modules/.pnpm/@types+react@19.1.8/node_modules/@types/react/index.d.ts", + "name": "HTMLAttributes" + }, + { + "fileName": "react-window/lib/components/list/types.ts", + "name": "TypeLiteral" + } + ], + "required": false, + "type": { + "name": "enum", + "raw": "string | undefined", + "value": [ + { + "value": "undefined" + }, + { + "value": "string" + } + ] + } + }, + "style": { + "defaultValue": null, + "description": "Optional CSS properties.\nThe list of rows will fill the height defined by this style.", + "name": "style", + "parent": { + "fileName": "react-window/node_modules/.pnpm/@types+react@19.1.8/node_modules/@types/react/index.d.ts", + "name": "HTMLAttributes" + }, + "declarations": [ + { + "fileName": "react-window/node_modules/.pnpm/@types+react@19.1.8/node_modules/@types/react/index.d.ts", + "name": "HTMLAttributes" + }, + { + "fileName": "react-window/lib/components/list/types.ts", + "name": "TypeLiteral" + } + ], + "required": false, + "type": { + "name": "enum", + "raw": "CSSProperties | undefined", + "value": [ + { + "value": "undefined" + }, + { + "value": "CSSProperties", + "description": "", + "fullComment": "", + "tags": {} + } + ] + } + }, + "defaultHeight": { + "defaultValue": { + "value": "0" + }, + "description": "Default height of list for initial render.\nThis value is important for server rendering.", + "name": "defaultHeight", + "declarations": [ + { + "fileName": "react-window/lib/components/list/types.ts", + "name": "TypeLiteral" + } + ], + "required": false, + "type": { + "name": "enum", + "raw": "number | undefined", + "value": [ + { + "value": "undefined" + }, + { + "value": "number" + } + ] + } + }, + "listRef": { + "defaultValue": null, + "description": "Ref used to interact with this component's imperative API.\n\nThis API has imperative methods for scrolling and a getter for the outermost DOM element.\n\n⚠️ The `useListRef` and `useListCallbackRef` hooks are exported for convenience use in TypeScript projects.", + "name": "listRef", + "declarations": [ + { + "fileName": "react-window/lib/components/list/types.ts", + "name": "TypeLiteral" + } + ], + "required": false, + "type": { + "name": "enum", + "raw": "Ref | undefined", + "value": [ + { + "value": "undefined" + }, + { + "value": "null" + }, + { + "value": "(instance: ListImperativeAPI | null) => void | (() => VoidOrUndefinedOnly)", + "description": "", + "fullComment": "", + "tags": {} + }, + { + "value": "RefObject", + "description": "Created by {@link createRef}, or {@link useRef} when passed `null`.", + "fullComment": "Created by {@link createRef}, or {@link useRef} when passed `null`.\n@template T The type of the ref's value.\n@example ```tsx\nconst ref = createRef();\n\nref.current = document.createElement('div'); // Error\n```", + "tags": { + "template": "T The type of the ref's value.", + "example": "```tsx\nconst ref = createRef();\n\nref.current = document.createElement('div'); // Error\n```" + } + } + ] + } + }, + "onResize": { + "defaultValue": null, + "description": "Callback notified when the List's outermost HTMLElement resizes.\nThis may be used to (re)scroll a row into view.", + "name": "onResize", + "declarations": [ + { + "fileName": "react-window/lib/components/list/types.ts", + "name": "TypeLiteral" + } + ], + "required": false, + "type": { + "name": "enum", + "raw": "((size: { height: number; width: number; }, prevSize: { height: number; width: number; }) => void) | undefined", + "value": [ + { + "value": "undefined" + }, + { + "value": "(size: { height: number; width: number; }, prevSize: { height: number; width: number; }) => void", + "description": "", + "fullComment": "", + "tags": {} + } + ] + } + }, + "onRowsRendered": { + "defaultValue": null, + "description": "Callback notified when the range of visible rows changes.", + "name": "onRowsRendered", + "declarations": [ + { + "fileName": "react-window/lib/components/list/types.ts", + "name": "TypeLiteral" + } + ], + "required": false, + "type": { + "name": "enum", + "raw": "((args: { startIndex: number; stopIndex: number; }) => void) | undefined", + "value": [ + { + "value": "undefined" + }, + { + "value": "(args: { startIndex: number; stopIndex: number; }) => void", + "description": "", + "fullComment": "", + "tags": {} + } + ] + } + }, + "overscanCount": { + "defaultValue": { + "value": "3" + }, + "description": "How many additional rows to render outside of the visible area.\nThis can reduce visual flickering near the edges of a list when scrolling.", + "name": "overscanCount", + "declarations": [ + { + "fileName": "react-window/lib/components/list/types.ts", + "name": "TypeLiteral" + } + ], + "required": false, + "type": { + "name": "enum", + "raw": "number | undefined", + "value": [ + { + "value": "undefined" + }, + { + "value": "number" + } + ] + } + }, + "rowComponent": { + "defaultValue": null, + "description": "React component responsible for rendering a row.\n\nThis component will receive an `index` and `style` prop by default.\nAdditionally it will receive prop values passed to `rowProps`.\n\n⚠️ The prop types for this component are exported as `RowComponentProps`", + "name": "rowComponent", + "declarations": [ + { + "fileName": "react-window/lib/components/list/types.ts", + "name": "TypeLiteral" + } + ], + "required": true, + "type": { + "name": "(props: { index: number; style: CSSProperties; } & RowProps) => ReactNode" + } + }, + "rowCount": { + "defaultValue": null, + "description": "Number of items to be rendered in the list.", + "name": "rowCount", + "declarations": [ + { + "fileName": "react-window/lib/components/list/types.ts", + "name": "TypeLiteral" + } + ], + "required": true, + "type": { + "name": "number" + } + }, + "rowHeight": { + "defaultValue": null, + "description": "Row height; the following formats are supported:\n- number of pixels (number)\n- percentage of the grid's current height (string)\n- function that returns the row height (in pixels) given an index and `cellProps`", + "name": "rowHeight", + "declarations": [ + { + "fileName": "react-window/lib/components/list/types.ts", + "name": "TypeLiteral" + } + ], + "required": true, + "type": { + "name": "enum", + "raw": "string | number | ((index: number, cellProps: RowProps) => number)", + "value": [ + { + "value": "string" + }, + { + "value": "number" + }, + { + "value": "(index: number, cellProps: RowProps) => number", + "description": "", + "fullComment": "", + "tags": {} + } + ] + } + }, + "rowProps": { + "defaultValue": null, + "description": "Additional props to be passed to the row-rendering component.\nList will automatically re-render rows when values in this object change.\n\n⚠️ This object must not contain either an `index` or `style` prop.", + "name": "rowProps", + "declarations": [ + { + "fileName": "react-window/lib/components/list/types.ts", + "name": "TypeLiteral" + } + ], + "required": true, + "type": { + "name": "ExcludeForbiddenKeys" + } + } + } +} \ No newline at end of file diff --git a/public/og.html b/public/og.html new file mode 100644 index 00000000..532ebcad --- /dev/null +++ b/public/og.html @@ -0,0 +1,42 @@ + + + + + + + + + + + +
+
react-window
+
render everything
+
+ + diff --git a/public/og.png b/public/og.png new file mode 100644 index 00000000..181fccec Binary files /dev/null and b/public/og.png differ diff --git a/public/svgs/checkbox-checked.svg b/public/svgs/checkbox-checked.svg new file mode 100644 index 00000000..57c13852 --- /dev/null +++ b/public/svgs/checkbox-checked.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svgs/checkbox-indeterminate.svg b/public/svgs/checkbox-indeterminate.svg new file mode 100644 index 00000000..b09c34a6 --- /dev/null +++ b/public/svgs/checkbox-indeterminate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svgs/checkbox-unchecked.svg b/public/svgs/checkbox-unchecked.svg new file mode 100644 index 00000000..c5ef6358 --- /dev/null +++ b/public/svgs/checkbox-unchecked.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svgs/github.svg b/public/svgs/github.svg new file mode 100644 index 00000000..45e75bfb --- /dev/null +++ b/public/svgs/github.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/public/svgs/npm.svg b/public/svgs/npm.svg new file mode 100644 index 00000000..0cb019b9 --- /dev/null +++ b/public/svgs/npm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svgs/radio-checked.svg b/public/svgs/radio-checked.svg new file mode 100644 index 00000000..f9072381 --- /dev/null +++ b/public/svgs/radio-checked.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svgs/radio-unchecked.svg b/public/svgs/radio-unchecked.svg new file mode 100644 index 00000000..6ada5bb1 --- /dev/null +++ b/public/svgs/radio-unchecked.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/rollup.config.js b/rollup.config.js deleted file mode 100644 index b9421aa9..00000000 --- a/rollup.config.js +++ /dev/null @@ -1,96 +0,0 @@ -import path from 'path'; -import babel from 'rollup-plugin-babel'; -import replace from 'rollup-plugin-replace'; -import { terser } from 'rollup-plugin-terser'; -import commonjs from 'rollup-plugin-commonjs'; -import nodeResolve from 'rollup-plugin-node-resolve'; - -import pkg from './package.json'; - -const input = './src/index.js'; - -const external = id => !id.startsWith('.') && !path.isAbsolute(id); - -const babelConfigEsModules = babel({ - runtimeHelpers: true, - plugins: [['@babel/transform-runtime', { useESModules: true }]], - sourceMaps: true, -}); - -const umdGlobals = { - react: 'React', -}; - -export default [ - { - input, - output: { - file: pkg.main, - format: 'cjs', - sourcemap: true, - }, - external, - plugins: [ - babel({ - runtimeHelpers: true, - plugins: ['@babel/transform-runtime'], - sourceMaps: true, - }), - nodeResolve(), - commonjs(), - ], - }, - - { - input, - output: { - file: pkg.module, - format: 'esm', - sourcemap: true, - }, - external, - plugins: [babelConfigEsModules, nodeResolve(), commonjs()], - }, - - { - input, - output: { - file: 'dist/index-dev.umd.js', - format: 'umd', - sourcemap: true, - name: 'ReactWindow', - globals: umdGlobals, - }, - external: Object.keys(umdGlobals), - plugins: [ - babelConfigEsModules, - nodeResolve(), - commonjs(), - replace({ - 'process.env.NODE_ENV': JSON.stringify('development'), - }), - terser(), - ], - }, - - { - input, - output: { - file: 'dist/index-prod.umd.js', - format: 'umd', - sourcemap: true, - name: 'ReactWindow', - globals: umdGlobals, - }, - external: Object.keys(umdGlobals), - plugins: [ - babelConfigEsModules, - nodeResolve(), - commonjs(), - replace({ - 'process.env.NODE_ENV': JSON.stringify('production'), - }), - terser(), - ], - }, -]; diff --git a/scripts/code-snippets/run.mjs b/scripts/code-snippets/run.mjs new file mode 100644 index 00000000..17dcef2a --- /dev/null +++ b/scripts/code-snippets/run.mjs @@ -0,0 +1,62 @@ +import { mkdir, readFile, writeFile } from "node:fs/promises"; +import { basename, join } from "node:path"; +import { cwd } from "node:process"; +import { getFilesWithExtensions, rmFilesWithExtensions } from "../utils.mjs"; +import { syntaxHighlight } from "./syntax-highlight.mjs"; +import { toToJs } from "./ts-to-js.mjs"; + +async function run() { + const inputDir = join(cwd(), "src", "routes"); + const outputDir = join(cwd(), "public", "generated", "code-snippets"); + + await mkdir(outputDir, { recursive: true }); + + await rmFilesWithExtensions(outputDir, [".json"]); + + const tsFiles = await getFilesWithExtensions(inputDir, [".ts", ".tsx"]); + const exampleFiles = tsFiles.filter((file) => file.includes("example.ts")); + + for (let file of exampleFiles) { + console.debug("Extracting", file); + + const buffer = await readFile(file); + + let rawText = buffer.toString(); + + { + const pieces = rawText.split("// "); + rawText = pieces[pieces.length - 1].trim(); + } + { + const pieces = rawText.split("// "); + rawText = pieces[0].trim(); + } + + const typeScript = rawText; + const javaScript = (await toToJs(typeScript)).trim(); + + const fileName = basename(file); + + const json = { + javaScript: await syntaxHighlight(javaScript, "JSX"), + typeScript: + typeScript !== javaScript + ? await syntaxHighlight( + typeScript, + file.endsWith("tsx") ? "TSX" : "TS" + ) + : undefined + }; + + const outputFile = join( + outputDir, + fileName.replace(/\.example\.ts(x*)$/, ".json") + ); + + console.debug("Writing to", outputFile); + + await writeFile(outputFile, JSON.stringify(json, null, 2)); + } +} + +run(); diff --git a/scripts/code-snippets/syntax-highlight.mjs b/scripts/code-snippets/syntax-highlight.mjs new file mode 100644 index 00000000..b495a771 --- /dev/null +++ b/scripts/code-snippets/syntax-highlight.mjs @@ -0,0 +1,205 @@ +import { + jsxLanguage, + tsxLanguage, + typescriptLanguage +} from "@codemirror/lang-javascript"; +import { ensureSyntaxTree } from "@codemirror/language"; +import { EditorState } from "@codemirror/state"; +import { classHighlighter, highlightTree } from "@lezer/highlight"; + +export const DEFAULT_MAX_CHARACTERS = 500000; +export const DEFAULT_MAX_TIME = 5000; + +export async function syntaxHighlight(code, language) { + let extension; + switch (language) { + case "JSX": { + extension = jsxLanguage; + break; + } + case "TS": { + extension = typescriptLanguage; + break; + } + case "TSX": { + extension = tsxLanguage; + break; + } + } + if (!extension) { + console.error("Unsupported language %o", language); + } + + const tokens = await parser(code, extension); + + return tokens.map(parsedTokensToHtml).join("\n"); +} + +async function parser(code, languageExtension = jsxLanguage) { + const parsedTokens = []; + const currentLineState = { + parsedTokens: [], + rawString: "" + }; + + // The logic below to trim code sections only works with "\n" + code = code.replace(/\r\n?|\n|\u2028|\u2029/g, "\n"); + if (code.length > DEFAULT_MAX_CHARACTERS) { + let index = DEFAULT_MAX_CHARACTERS - 1; + while (index > 0 && code.charAt(index) !== "\n") { + index--; + } + if (index === 0) { + while (index < code.length && code.charAt(index) !== "\n") { + index++; + } + } + code = code.slice(0, index + 1); + } + + const state = EditorState.create({ + doc: code, + extensions: [languageExtension] + }); + + const tree = ensureSyntaxTree( + state, + DEFAULT_MAX_CHARACTERS, + DEFAULT_MAX_TIME + ); + + if (tree === null) { + return []; + } + + let characterIndex = 0; + let parsedCharacterIndex = 0; + highlightTree(tree, classHighlighter, (from, to, className) => { + if (from > characterIndex) { + // No style applied to the token between position and from. + // This typically indicates white space or newline characters. + processSection( + currentLineState, + parsedTokens, + code.slice(characterIndex, from), + "" + ); + } + processSection( + currentLineState, + parsedTokens, + code.slice(from, to), + className + ); + characterIndex = to; + }); + + const maxPosition = code.length - 1; + + if (characterIndex < maxPosition) { + // No style applied on the trailing text. + // This typically indicates white space or newline characters. + processSection( + currentLineState, + parsedTokens, + code.slice(characterIndex, maxPosition), + "" + ); + } + if (currentLineState.parsedTokens.length) { + parsedTokens.push(currentLineState.parsedTokens); + } + + parsedCharacterIndex += characterIndex + 1; + + // Anything that's left should de-opt to plain text. + if (parsedCharacterIndex < code.length) { + let nextIndex = code.indexOf("\n", parsedCharacterIndex); + let parsedLineTokens = []; + while (true) { + const line = + nextIndex >= 0 + ? code.substring(parsedCharacterIndex, nextIndex) + : code.substring(parsedCharacterIndex); + parsedLineTokens.push({ + columnIndex: 0, + type: null, + value: line + }); + if (nextIndex >= 0) { + parsedTokens.push(parsedLineTokens); + parsedLineTokens = []; + } else if (nextIndex === -1) { + break; + } + parsedCharacterIndex = nextIndex + 1; + nextIndex = code.indexOf("\n", parsedCharacterIndex); + } + if (parsedLineTokens.length) { + parsedTokens.push(parsedLineTokens); + } + } + + return parsedTokens; +} + +function processSection(currentLineState, parsedTokens, section, className) { + var _a; + const tokenType = + (_a = + className === null || className === void 0 + ? void 0 + : className.substring(4)) !== null && _a !== void 0 + ? _a + : null; // Remove "tok-" prefix; + let index = 0; + let nextIndex = section.indexOf("\n"); + while (true) { + const substring = + nextIndex >= 0 + ? section.substring(index, nextIndex) + : section.substring(index); + const token = { + columnIndex: currentLineState.rawString.length, + type: tokenType, + value: substring + }; + currentLineState.parsedTokens.push(token); + currentLineState.rawString += substring; + if (nextIndex === -1) { + break; + } + if (nextIndex >= 0) { + parsedTokens.push(currentLineState.parsedTokens); + currentLineState.parsedTokens = []; + currentLineState.rawString = ""; + } + index = nextIndex + 1; + nextIndex = section.indexOf("\n", index); + } +} + +function parsedTokensToHtml(tokens) { + let indent = 0; + + tokens = tokens.map((token, index) => { + const className = token.type ? `tok-${token.type}` : ""; + + if (index === 0 && !token.type) { + indent = token.value.length; + token.value = ""; + } + + const escapedValue = escapeHtmlEntities(token.value); + return `${escapedValue}`; + }); + + return `
${tokens.join("")}
`; +} + +function escapeHtmlEntities(rawString) { + return rawString.replace( + /[\u00A0-\u9999<>&]/g, + (substring) => "&#" + substring.charCodeAt(0) + ";" + ); +} diff --git a/scripts/code-snippets/ts-to-js.mjs b/scripts/code-snippets/ts-to-js.mjs new file mode 100644 index 00000000..238ae503 --- /dev/null +++ b/scripts/code-snippets/ts-to-js.mjs @@ -0,0 +1,13 @@ +import prettier from "prettier"; +import tsBlankSpace from "ts-blank-space"; + +export async function toToJs(source) { + source = tsBlankSpace(source); + source = source.replace(/]+>/g, "]+>/g, " file.endsWith("/List.tsx") || file.endsWith("/Grid.tsx") + ); + + for (let file of files) { + console.debug("Parsing", file); + + const components = parser.parse(file); + for (let component of components) { + // Convert to local paths + component.filePath = relative(cwd(), file); + + // Filter inherited HTML attributes + for (let key in component.props) { + const prop = component.props[key]; + if ( + prop.declarations.filter( + (declaration) => !declaration.fileName.includes("node_modules") + ).length === 0 + ) { + delete component.props[key]; + } + } + + const outputFile = join(outputDir, `${component.displayName}.json`); + + console.debug("Writing to", outputFile); + + await writeFile(outputFile, JSON.stringify(component, null, 2)); + } + } +} + +run(); diff --git a/scripts/utils.mjs b/scripts/utils.mjs new file mode 100644 index 00000000..3c80bb12 --- /dev/null +++ b/scripts/utils.mjs @@ -0,0 +1,35 @@ +import { readdir, rm } from "fs/promises"; +import { extname, join } from "node:path"; + +export async function getFilesWithExtensions(directory, extensions, filter) { + const files = []; + + const entries = await readdir(directory, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = join(directory, entry.name); + + if (entry.isDirectory()) { + files.push( + ...(await getFilesWithExtensions(fullPath, extensions, filter)) + ); + } else if (entry.isFile()) { + const fileExtension = extname(entry.name); + if (extensions.includes(fileExtension)) { + if (typeof filter !== "function" || filter(fullPath)) { + files.push(fullPath); + } + } + } + } + + return files; +} + +export async function rmFilesWithExtensions(directory, extensions) { + const files = await getFilesWithExtensions(directory, extensions); + + for (let file of files) { + await rm(file); + } +} diff --git a/src/.eslintrc b/src/.eslintrc deleted file mode 100644 index faa023bd..00000000 --- a/src/.eslintrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": ["react-app", "plugin:prettier/recommended"], - "env": { - "mocha": true - }, - "globals": { - "expect": true, - "spyOn": true - } -} diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 00000000..bffa05a9 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,101 @@ +import { Bars4Icon, XMarkIcon } from "@heroicons/react/20/solid"; +import { BrowserRouter, Route, Routes } from "react-router-dom"; +import GitHubIcon from "../public/svgs/github.svg?react"; +import NpmHubIcon from "../public/svgs/npm.svg?react"; +import { Box } from "./components/Box"; +import { ErrorBoundary } from "./components/ErrorBoundary"; +import { ExternalLink } from "./components/ExternalLink"; +import { Link } from "./components/Link"; +import { RouteChangeHandler } from "./components/RouteChangeHandler"; +import { useNavStore } from "./hooks/useNavStore"; +import { Nav } from "./nav/Nav"; +import { routeMap } from "./routes"; +import { cn } from "./utils/cn"; + +export default function App() { + const { toggle, visible } = useNavStore(); + + return ( + + + +
+ + + +
+ render everything +
+
+ + + + + + + + + +
+
+
+
+
+
+ + + {Object.entries(routeMap).map(([path, Component]) => ( + + ))} + + +
+
+
+
+
+ ); +} diff --git a/src/FixedSizeGrid.js b/src/FixedSizeGrid.js deleted file mode 100644 index 685ad334..00000000 --- a/src/FixedSizeGrid.js +++ /dev/null @@ -1,244 +0,0 @@ -// @flow - -import createGridComponent from './createGridComponent'; - -import type { Props, ScrollToAlign } from './createGridComponent'; - -const FixedSizeGrid = createGridComponent({ - getColumnOffset: ({ columnWidth }: Props, index: number): number => - index * ((columnWidth: any): number), - - getColumnWidth: ({ columnWidth }: Props, index: number): number => - ((columnWidth: any): number), - - getRowOffset: ({ rowHeight }: Props, index: number): number => - index * ((rowHeight: any): number), - - getRowHeight: ({ rowHeight }: Props, index: number): number => - ((rowHeight: any): number), - - getEstimatedTotalHeight: ({ rowCount, rowHeight }: Props) => - ((rowHeight: any): number) * rowCount, - - getEstimatedTotalWidth: ({ columnCount, columnWidth }: Props) => - ((columnWidth: any): number) * columnCount, - - getOffsetForColumnAndAlignment: ( - { columnCount, columnWidth, width }: Props, - columnIndex: number, - align: ScrollToAlign, - scrollLeft: number, - instanceProps: typeof undefined, - scrollbarSize: number - ): number => { - const lastColumnOffset = Math.max( - 0, - columnCount * ((columnWidth: any): number) - width - ); - const maxOffset = Math.min( - lastColumnOffset, - columnIndex * ((columnWidth: any): number) - ); - const minOffset = Math.max( - 0, - columnIndex * ((columnWidth: any): number) - - width + - scrollbarSize + - ((columnWidth: any): number) - ); - - if (align === 'smart') { - if (scrollLeft >= minOffset - width && scrollLeft <= maxOffset + width) { - align = 'auto'; - } else { - align = 'center'; - } - } - - switch (align) { - case 'start': - return maxOffset; - case 'end': - return minOffset; - case 'center': - // "Centered" offset is usually the average of the min and max. - // But near the edges of the list, this doesn't hold true. - const middleOffset = Math.round( - minOffset + (maxOffset - minOffset) / 2 - ); - if (middleOffset < Math.ceil(width / 2)) { - return 0; // near the beginning - } else if (middleOffset > lastColumnOffset + Math.floor(width / 2)) { - return lastColumnOffset; // near the end - } else { - return middleOffset; - } - case 'auto': - default: - if (scrollLeft >= minOffset && scrollLeft <= maxOffset) { - return scrollLeft; - } else if (minOffset > maxOffset) { - // Because we only take into account the scrollbar size when calculating minOffset - // this value can be larger than maxOffset when at the end of the list - return minOffset; - } else if (scrollLeft < minOffset) { - return minOffset; - } else { - return maxOffset; - } - } - }, - - getOffsetForRowAndAlignment: ( - { rowHeight, height, rowCount }: Props, - rowIndex: number, - align: ScrollToAlign, - scrollTop: number, - instanceProps: typeof undefined, - scrollbarSize: number - ): number => { - const lastRowOffset = Math.max( - 0, - rowCount * ((rowHeight: any): number) - height - ); - const maxOffset = Math.min( - lastRowOffset, - rowIndex * ((rowHeight: any): number) - ); - const minOffset = Math.max( - 0, - rowIndex * ((rowHeight: any): number) - - height + - scrollbarSize + - ((rowHeight: any): number) - ); - - if (align === 'smart') { - if (scrollTop >= minOffset - height && scrollTop <= maxOffset + height) { - align = 'auto'; - } else { - align = 'center'; - } - } - - switch (align) { - case 'start': - return maxOffset; - case 'end': - return minOffset; - case 'center': - // "Centered" offset is usually the average of the min and max. - // But near the edges of the list, this doesn't hold true. - const middleOffset = Math.round( - minOffset + (maxOffset - minOffset) / 2 - ); - if (middleOffset < Math.ceil(height / 2)) { - return 0; // near the beginning - } else if (middleOffset > lastRowOffset + Math.floor(height / 2)) { - return lastRowOffset; // near the end - } else { - return middleOffset; - } - case 'auto': - default: - if (scrollTop >= minOffset && scrollTop <= maxOffset) { - return scrollTop; - } else if (minOffset > maxOffset) { - // Because we only take into account the scrollbar size when calculating minOffset - // this value can be larger than maxOffset when at the end of the list - return minOffset; - } else if (scrollTop < minOffset) { - return minOffset; - } else { - return maxOffset; - } - } - }, - - getColumnStartIndexForOffset: ( - { columnWidth, columnCount }: Props, - scrollLeft: number - ): number => - Math.max( - 0, - Math.min( - columnCount - 1, - Math.floor(scrollLeft / ((columnWidth: any): number)) - ) - ), - - getColumnStopIndexForStartIndex: ( - { columnWidth, columnCount, width }: Props, - startIndex: number, - scrollLeft: number - ): number => { - const left = startIndex * ((columnWidth: any): number); - const numVisibleColumns = Math.ceil( - (width + scrollLeft - left) / ((columnWidth: any): number) - ); - return Math.max( - 0, - Math.min( - columnCount - 1, - startIndex + numVisibleColumns - 1 // -1 is because stop index is inclusive - ) - ); - }, - - getRowStartIndexForOffset: ( - { rowHeight, rowCount }: Props, - scrollTop: number - ): number => - Math.max( - 0, - Math.min(rowCount - 1, Math.floor(scrollTop / ((rowHeight: any): number))) - ), - - getRowStopIndexForStartIndex: ( - { rowHeight, rowCount, height }: Props, - startIndex: number, - scrollTop: number - ): number => { - const top = startIndex * ((rowHeight: any): number); - const numVisibleRows = Math.ceil( - (height + scrollTop - top) / ((rowHeight: any): number) - ); - return Math.max( - 0, - Math.min( - rowCount - 1, - startIndex + numVisibleRows - 1 // -1 is because stop index is inclusive - ) - ); - }, - - initInstanceProps(props: Props): any { - // Noop - }, - - shouldResetStyleCacheOnItemSizeChange: true, - - validateProps: ({ columnWidth, rowHeight }: Props): void => { - if (process.env.NODE_ENV !== 'production') { - if (typeof columnWidth !== 'number') { - throw Error( - 'An invalid "columnWidth" prop has been specified. ' + - 'Value should be a number. ' + - `"${ - columnWidth === null ? 'null' : typeof columnWidth - }" was specified.` - ); - } - - if (typeof rowHeight !== 'number') { - throw Error( - 'An invalid "rowHeight" prop has been specified. ' + - 'Value should be a number. ' + - `"${rowHeight === null ? 'null' : typeof rowHeight}" was specified.` - ); - } - } - }, -}); - -export default FixedSizeGrid; diff --git a/src/FixedSizeList.js b/src/FixedSizeList.js deleted file mode 100644 index 05d4039d..00000000 --- a/src/FixedSizeList.js +++ /dev/null @@ -1,137 +0,0 @@ -// @flow - -import createListComponent from './createListComponent'; - -import type { Props, ScrollToAlign } from './createListComponent'; - -type InstanceProps = any; - -const FixedSizeList = createListComponent({ - getItemOffset: ({ itemSize }: Props, index: number): number => - index * ((itemSize: any): number), - - getItemSize: ({ itemSize }: Props, index: number): number => - ((itemSize: any): number), - - getEstimatedTotalSize: ({ itemCount, itemSize }: Props) => - ((itemSize: any): number) * itemCount, - - getOffsetForIndexAndAlignment: ( - { direction, height, itemCount, itemSize, layout, width }: Props, - index: number, - align: ScrollToAlign, - scrollOffset: number, - instanceProps: InstanceProps, - scrollbarSize: number - ): number => { - // TODO Deprecate direction "horizontal" - const isHorizontal = direction === 'horizontal' || layout === 'horizontal'; - const size = (((isHorizontal ? width : height): any): number); - const lastItemOffset = Math.max( - 0, - itemCount * ((itemSize: any): number) - size - ); - const maxOffset = Math.min( - lastItemOffset, - index * ((itemSize: any): number) - ); - const minOffset = Math.max( - 0, - index * ((itemSize: any): number) - - size + - ((itemSize: any): number) + - scrollbarSize - ); - - if (align === 'smart') { - if ( - scrollOffset >= minOffset - size && - scrollOffset <= maxOffset + size - ) { - align = 'auto'; - } else { - align = 'center'; - } - } - - switch (align) { - case 'start': - return maxOffset; - case 'end': - return minOffset; - case 'center': { - // "Centered" offset is usually the average of the min and max. - // But near the edges of the list, this doesn't hold true. - const middleOffset = Math.round( - minOffset + (maxOffset - minOffset) / 2 - ); - if (middleOffset < Math.ceil(size / 2)) { - return 0; // near the beginning - } else if (middleOffset > lastItemOffset + Math.floor(size / 2)) { - return lastItemOffset; // near the end - } else { - return middleOffset; - } - } - case 'auto': - default: - if (scrollOffset >= minOffset && scrollOffset <= maxOffset) { - return scrollOffset; - } else if (scrollOffset < minOffset) { - return minOffset; - } else { - return maxOffset; - } - } - }, - - getStartIndexForOffset: ( - { itemCount, itemSize }: Props, - offset: number - ): number => - Math.max( - 0, - Math.min(itemCount - 1, Math.floor(offset / ((itemSize: any): number))) - ), - - getStopIndexForStartIndex: ( - { direction, height, itemCount, itemSize, layout, width }: Props, - startIndex: number, - scrollOffset: number - ): number => { - // TODO Deprecate direction "horizontal" - const isHorizontal = direction === 'horizontal' || layout === 'horizontal'; - const offset = startIndex * ((itemSize: any): number); - const size = (((isHorizontal ? width : height): any): number); - const numVisibleItems = Math.ceil( - (size + scrollOffset - offset) / ((itemSize: any): number) - ); - return Math.max( - 0, - Math.min( - itemCount - 1, - startIndex + numVisibleItems - 1 // -1 is because stop index is inclusive - ) - ); - }, - - initInstanceProps(props: Props): any { - // Noop - }, - - shouldResetStyleCacheOnItemSizeChange: true, - - validateProps: ({ itemSize }: Props): void => { - if (process.env.NODE_ENV !== 'production') { - if (typeof itemSize !== 'number') { - throw Error( - 'An invalid "itemSize" prop has been specified. ' + - 'Value should be a number. ' + - `"${itemSize === null ? 'null' : typeof itemSize}" was specified.` - ); - } - } - }, -}); - -export default FixedSizeList; diff --git a/src/VariableSizeGrid.js b/src/VariableSizeGrid.js deleted file mode 100644 index 46d59521..00000000 --- a/src/VariableSizeGrid.js +++ /dev/null @@ -1,507 +0,0 @@ -// @flow - -import createGridComponent from './createGridComponent'; - -import type { Props, ScrollToAlign } from './createGridComponent'; - -const DEFAULT_ESTIMATED_ITEM_SIZE = 50; - -type VariableSizeProps = {| - estimatedColumnWidth: number, - estimatedRowHeight: number, - ...Props, -|}; - -type itemSizeGetter = (index: number) => number; -type ItemType = 'column' | 'row'; - -type ItemMetadata = {| - offset: number, - size: number, -|}; -type ItemMetadataMap = { [index: number]: ItemMetadata }; -type InstanceProps = {| - columnMetadataMap: ItemMetadataMap, - estimatedColumnWidth: number, - estimatedRowHeight: number, - lastMeasuredColumnIndex: number, - lastMeasuredRowIndex: number, - rowMetadataMap: ItemMetadataMap, -|}; - -const getEstimatedTotalHeight = ( - { rowCount }: Props, - { rowMetadataMap, estimatedRowHeight, lastMeasuredRowIndex }: InstanceProps -) => { - let totalSizeOfMeasuredRows = 0; - - // Edge case check for when the number of items decreases while a scroll is in progress. - // https://github.com/bvaughn/react-window/pull/138 - if (lastMeasuredRowIndex >= rowCount) { - lastMeasuredRowIndex = rowCount - 1; - } - - if (lastMeasuredRowIndex >= 0) { - const itemMetadata = rowMetadataMap[lastMeasuredRowIndex]; - totalSizeOfMeasuredRows = itemMetadata.offset + itemMetadata.size; - } - - const numUnmeasuredItems = rowCount - lastMeasuredRowIndex - 1; - const totalSizeOfUnmeasuredItems = numUnmeasuredItems * estimatedRowHeight; - - return totalSizeOfMeasuredRows + totalSizeOfUnmeasuredItems; -}; - -const getEstimatedTotalWidth = ( - { columnCount }: Props, - { - columnMetadataMap, - estimatedColumnWidth, - lastMeasuredColumnIndex, - }: InstanceProps -) => { - let totalSizeOfMeasuredRows = 0; - - // Edge case check for when the number of items decreases while a scroll is in progress. - // https://github.com/bvaughn/react-window/pull/138 - if (lastMeasuredColumnIndex >= columnCount) { - lastMeasuredColumnIndex = columnCount - 1; - } - - if (lastMeasuredColumnIndex >= 0) { - const itemMetadata = columnMetadataMap[lastMeasuredColumnIndex]; - totalSizeOfMeasuredRows = itemMetadata.offset + itemMetadata.size; - } - - const numUnmeasuredItems = columnCount - lastMeasuredColumnIndex - 1; - const totalSizeOfUnmeasuredItems = numUnmeasuredItems * estimatedColumnWidth; - - return totalSizeOfMeasuredRows + totalSizeOfUnmeasuredItems; -}; - -const getItemMetadata = ( - itemType: ItemType, - props: Props, - index: number, - instanceProps: InstanceProps -): ItemMetadata => { - let itemMetadataMap, itemSize, lastMeasuredIndex; - if (itemType === 'column') { - itemMetadataMap = instanceProps.columnMetadataMap; - itemSize = ((props.columnWidth: any): itemSizeGetter); - lastMeasuredIndex = instanceProps.lastMeasuredColumnIndex; - } else { - itemMetadataMap = instanceProps.rowMetadataMap; - itemSize = ((props.rowHeight: any): itemSizeGetter); - lastMeasuredIndex = instanceProps.lastMeasuredRowIndex; - } - - if (index > lastMeasuredIndex) { - let offset = 0; - if (lastMeasuredIndex >= 0) { - const itemMetadata = itemMetadataMap[lastMeasuredIndex]; - offset = itemMetadata.offset + itemMetadata.size; - } - - for (let i = lastMeasuredIndex + 1; i <= index; i++) { - let size = itemSize(i); - - itemMetadataMap[i] = { - offset, - size, - }; - - offset += size; - } - - if (itemType === 'column') { - instanceProps.lastMeasuredColumnIndex = index; - } else { - instanceProps.lastMeasuredRowIndex = index; - } - } - - return itemMetadataMap[index]; -}; - -const findNearestItem = ( - itemType: ItemType, - props: Props, - instanceProps: InstanceProps, - offset: number -) => { - let itemMetadataMap, lastMeasuredIndex; - if (itemType === 'column') { - itemMetadataMap = instanceProps.columnMetadataMap; - lastMeasuredIndex = instanceProps.lastMeasuredColumnIndex; - } else { - itemMetadataMap = instanceProps.rowMetadataMap; - lastMeasuredIndex = instanceProps.lastMeasuredRowIndex; - } - - const lastMeasuredItemOffset = - lastMeasuredIndex > 0 ? itemMetadataMap[lastMeasuredIndex].offset : 0; - - if (lastMeasuredItemOffset >= offset) { - // If we've already measured items within this range just use a binary search as it's faster. - return findNearestItemBinarySearch( - itemType, - props, - instanceProps, - lastMeasuredIndex, - 0, - offset - ); - } else { - // If we haven't yet measured this high, fallback to an exponential search with an inner binary search. - // The exponential search avoids pre-computing sizes for the full set of items as a binary search would. - // The overall complexity for this approach is O(log n). - return findNearestItemExponentialSearch( - itemType, - props, - instanceProps, - Math.max(0, lastMeasuredIndex), - offset - ); - } -}; - -const findNearestItemBinarySearch = ( - itemType: ItemType, - props: Props, - instanceProps: InstanceProps, - high: number, - low: number, - offset: number -): number => { - while (low <= high) { - const middle = low + Math.floor((high - low) / 2); - const currentOffset = getItemMetadata( - itemType, - props, - middle, - instanceProps - ).offset; - - if (currentOffset === offset) { - return middle; - } else if (currentOffset < offset) { - low = middle + 1; - } else if (currentOffset > offset) { - high = middle - 1; - } - } - - if (low > 0) { - return low - 1; - } else { - return 0; - } -}; - -const findNearestItemExponentialSearch = ( - itemType: ItemType, - props: Props, - instanceProps: InstanceProps, - index: number, - offset: number -): number => { - const itemCount = itemType === 'column' ? props.columnCount : props.rowCount; - let interval = 1; - - while ( - index < itemCount && - getItemMetadata(itemType, props, index, instanceProps).offset < offset - ) { - index += interval; - interval *= 2; - } - - return findNearestItemBinarySearch( - itemType, - props, - instanceProps, - Math.min(index, itemCount - 1), - Math.floor(index / 2), - offset - ); -}; - -const getOffsetForIndexAndAlignment = ( - itemType: ItemType, - props: Props, - index: number, - align: ScrollToAlign, - scrollOffset: number, - instanceProps: InstanceProps, - scrollbarSize: number -): number => { - const size = itemType === 'column' ? props.width : props.height; - const itemMetadata = getItemMetadata(itemType, props, index, instanceProps); - - // Get estimated total size after ItemMetadata is computed, - // To ensure it reflects actual measurements instead of just estimates. - const estimatedTotalSize = - itemType === 'column' - ? getEstimatedTotalWidth(props, instanceProps) - : getEstimatedTotalHeight(props, instanceProps); - - const maxOffset = Math.max( - 0, - Math.min(estimatedTotalSize - size, itemMetadata.offset) - ); - const minOffset = Math.max( - 0, - itemMetadata.offset - size + scrollbarSize + itemMetadata.size - ); - - if (align === 'smart') { - if (scrollOffset >= minOffset - size && scrollOffset <= maxOffset + size) { - align = 'auto'; - } else { - align = 'center'; - } - } - - switch (align) { - case 'start': - return maxOffset; - case 'end': - return minOffset; - case 'center': - return Math.round(minOffset + (maxOffset - minOffset) / 2); - case 'auto': - default: - if (scrollOffset >= minOffset && scrollOffset <= maxOffset) { - return scrollOffset; - } else if (minOffset > maxOffset) { - // Because we only take into account the scrollbar size when calculating minOffset - // this value can be larger than maxOffset when at the end of the list - return minOffset; - } else if (scrollOffset < minOffset) { - return minOffset; - } else { - return maxOffset; - } - } -}; - -const VariableSizeGrid = createGridComponent({ - getColumnOffset: ( - props: Props, - index: number, - instanceProps: InstanceProps - ): number => getItemMetadata('column', props, index, instanceProps).offset, - - getColumnStartIndexForOffset: ( - props: Props, - scrollLeft: number, - instanceProps: InstanceProps - ): number => findNearestItem('column', props, instanceProps, scrollLeft), - - getColumnStopIndexForStartIndex: ( - props: Props, - startIndex: number, - scrollLeft: number, - instanceProps: InstanceProps - ): number => { - const { columnCount, width } = props; - - const itemMetadata = getItemMetadata( - 'column', - props, - startIndex, - instanceProps - ); - const maxOffset = scrollLeft + width; - - let offset = itemMetadata.offset + itemMetadata.size; - let stopIndex = startIndex; - - while (stopIndex < columnCount - 1 && offset < maxOffset) { - stopIndex++; - offset += getItemMetadata('column', props, stopIndex, instanceProps).size; - } - - return stopIndex; - }, - - getColumnWidth: ( - props: Props, - index: number, - instanceProps: InstanceProps - ): number => instanceProps.columnMetadataMap[index].size, - - getEstimatedTotalHeight, - getEstimatedTotalWidth, - - getOffsetForColumnAndAlignment: ( - props: Props, - index: number, - align: ScrollToAlign, - scrollOffset: number, - instanceProps: InstanceProps, - scrollbarSize: number - ): number => - getOffsetForIndexAndAlignment( - 'column', - props, - index, - align, - scrollOffset, - instanceProps, - scrollbarSize - ), - - getOffsetForRowAndAlignment: ( - props: Props, - index: number, - align: ScrollToAlign, - scrollOffset: number, - instanceProps: InstanceProps, - scrollbarSize: number - ): number => - getOffsetForIndexAndAlignment( - 'row', - props, - index, - align, - scrollOffset, - instanceProps, - scrollbarSize - ), - - getRowOffset: ( - props: Props, - index: number, - instanceProps: InstanceProps - ): number => getItemMetadata('row', props, index, instanceProps).offset, - - getRowHeight: ( - props: Props, - index: number, - instanceProps: InstanceProps - ): number => instanceProps.rowMetadataMap[index].size, - - getRowStartIndexForOffset: ( - props: Props, - scrollTop: number, - instanceProps: InstanceProps - ): number => findNearestItem('row', props, instanceProps, scrollTop), - - getRowStopIndexForStartIndex: ( - props: Props, - startIndex: number, - scrollTop: number, - instanceProps: InstanceProps - ): number => { - const { rowCount, height } = props; - - const itemMetadata = getItemMetadata( - 'row', - props, - startIndex, - instanceProps - ); - const maxOffset = scrollTop + height; - - let offset = itemMetadata.offset + itemMetadata.size; - let stopIndex = startIndex; - - while (stopIndex < rowCount - 1 && offset < maxOffset) { - stopIndex++; - offset += getItemMetadata('row', props, stopIndex, instanceProps).size; - } - - return stopIndex; - }, - - initInstanceProps(props: Props, instance: any): InstanceProps { - const { - estimatedColumnWidth, - estimatedRowHeight, - } = ((props: any): VariableSizeProps); - - const instanceProps = { - columnMetadataMap: {}, - estimatedColumnWidth: estimatedColumnWidth || DEFAULT_ESTIMATED_ITEM_SIZE, - estimatedRowHeight: estimatedRowHeight || DEFAULT_ESTIMATED_ITEM_SIZE, - lastMeasuredColumnIndex: -1, - lastMeasuredRowIndex: -1, - rowMetadataMap: {}, - }; - - instance.resetAfterColumnIndex = ( - columnIndex: number, - shouldForceUpdate?: boolean = true - ) => { - instance.resetAfterIndices({ columnIndex, shouldForceUpdate }); - }; - - instance.resetAfterRowIndex = ( - rowIndex: number, - shouldForceUpdate?: boolean = true - ) => { - instance.resetAfterIndices({ rowIndex, shouldForceUpdate }); - }; - - instance.resetAfterIndices = ({ - columnIndex, - rowIndex, - shouldForceUpdate = true, - }: { - columnIndex?: number, - rowIndex?: number, - shouldForceUpdate: boolean, - }) => { - if (typeof columnIndex === 'number') { - instanceProps.lastMeasuredColumnIndex = Math.min( - instanceProps.lastMeasuredColumnIndex, - columnIndex - 1 - ); - } - if (typeof rowIndex === 'number') { - instanceProps.lastMeasuredRowIndex = Math.min( - instanceProps.lastMeasuredRowIndex, - rowIndex - 1 - ); - } - - // We could potentially optimize further by only evicting styles after this index, - // But since styles are only cached while scrolling is in progress- - // It seems an unnecessary optimization. - // It's unlikely that resetAfterIndex() will be called while a user is scrolling. - instance._getItemStyleCache(-1); - - if (shouldForceUpdate) { - instance.forceUpdate(); - } - }; - - return instanceProps; - }, - - shouldResetStyleCacheOnItemSizeChange: false, - - validateProps: ({ columnWidth, rowHeight }: Props): void => { - if (process.env.NODE_ENV !== 'production') { - if (typeof columnWidth !== 'function') { - throw Error( - 'An invalid "columnWidth" prop has been specified. ' + - 'Value should be a function. ' + - `"${ - columnWidth === null ? 'null' : typeof columnWidth - }" was specified.` - ); - } else if (typeof rowHeight !== 'function') { - throw Error( - 'An invalid "rowHeight" prop has been specified. ' + - 'Value should be a function. ' + - `"${rowHeight === null ? 'null' : typeof rowHeight}" was specified.` - ); - } - } - }, -}); - -export default VariableSizeGrid; diff --git a/src/VariableSizeList.js b/src/VariableSizeList.js deleted file mode 100644 index 9a5b21ec..00000000 --- a/src/VariableSizeList.js +++ /dev/null @@ -1,317 +0,0 @@ -// @flow - -import createListComponent from './createListComponent'; - -import type { Props, ScrollToAlign } from './createListComponent'; - -const DEFAULT_ESTIMATED_ITEM_SIZE = 50; - -type VariableSizeProps = {| - estimatedItemSize: number, - ...Props, -|}; - -type itemSizeGetter = (index: number) => number; - -type ItemMetadata = {| - offset: number, - size: number, -|}; -type InstanceProps = {| - itemMetadataMap: { [index: number]: ItemMetadata }, - estimatedItemSize: number, - lastMeasuredIndex: number, -|}; - -const getItemMetadata = ( - props: Props, - index: number, - instanceProps: InstanceProps -): ItemMetadata => { - const { itemSize } = ((props: any): VariableSizeProps); - const { itemMetadataMap, lastMeasuredIndex } = instanceProps; - - if (index > lastMeasuredIndex) { - let offset = 0; - if (lastMeasuredIndex >= 0) { - const itemMetadata = itemMetadataMap[lastMeasuredIndex]; - offset = itemMetadata.offset + itemMetadata.size; - } - - for (let i = lastMeasuredIndex + 1; i <= index; i++) { - let size = ((itemSize: any): itemSizeGetter)(i); - - itemMetadataMap[i] = { - offset, - size, - }; - - offset += size; - } - - instanceProps.lastMeasuredIndex = index; - } - - return itemMetadataMap[index]; -}; - -const findNearestItem = ( - props: Props, - instanceProps: InstanceProps, - offset: number -) => { - const { itemMetadataMap, lastMeasuredIndex } = instanceProps; - - const lastMeasuredItemOffset = - lastMeasuredIndex > 0 ? itemMetadataMap[lastMeasuredIndex].offset : 0; - - if (lastMeasuredItemOffset >= offset) { - // If we've already measured items within this range just use a binary search as it's faster. - return findNearestItemBinarySearch( - props, - instanceProps, - lastMeasuredIndex, - 0, - offset - ); - } else { - // If we haven't yet measured this high, fallback to an exponential search with an inner binary search. - // The exponential search avoids pre-computing sizes for the full set of items as a binary search would. - // The overall complexity for this approach is O(log n). - return findNearestItemExponentialSearch( - props, - instanceProps, - Math.max(0, lastMeasuredIndex), - offset - ); - } -}; - -const findNearestItemBinarySearch = ( - props: Props, - instanceProps: InstanceProps, - high: number, - low: number, - offset: number -): number => { - while (low <= high) { - const middle = low + Math.floor((high - low) / 2); - const currentOffset = getItemMetadata(props, middle, instanceProps).offset; - - if (currentOffset === offset) { - return middle; - } else if (currentOffset < offset) { - low = middle + 1; - } else if (currentOffset > offset) { - high = middle - 1; - } - } - - if (low > 0) { - return low - 1; - } else { - return 0; - } -}; - -const findNearestItemExponentialSearch = ( - props: Props, - instanceProps: InstanceProps, - index: number, - offset: number -): number => { - const { itemCount } = props; - let interval = 1; - - while ( - index < itemCount && - getItemMetadata(props, index, instanceProps).offset < offset - ) { - index += interval; - interval *= 2; - } - - return findNearestItemBinarySearch( - props, - instanceProps, - Math.min(index, itemCount - 1), - Math.floor(index / 2), - offset - ); -}; - -const getEstimatedTotalSize = ( - { itemCount }: Props, - { itemMetadataMap, estimatedItemSize, lastMeasuredIndex }: InstanceProps -) => { - let totalSizeOfMeasuredItems = 0; - - // Edge case check for when the number of items decreases while a scroll is in progress. - // https://github.com/bvaughn/react-window/pull/138 - if (lastMeasuredIndex >= itemCount) { - lastMeasuredIndex = itemCount - 1; - } - - if (lastMeasuredIndex >= 0) { - const itemMetadata = itemMetadataMap[lastMeasuredIndex]; - totalSizeOfMeasuredItems = itemMetadata.offset + itemMetadata.size; - } - - const numUnmeasuredItems = itemCount - lastMeasuredIndex - 1; - const totalSizeOfUnmeasuredItems = numUnmeasuredItems * estimatedItemSize; - - return totalSizeOfMeasuredItems + totalSizeOfUnmeasuredItems; -}; - -const VariableSizeList = createListComponent({ - getItemOffset: ( - props: Props, - index: number, - instanceProps: InstanceProps - ): number => getItemMetadata(props, index, instanceProps).offset, - - getItemSize: ( - props: Props, - index: number, - instanceProps: InstanceProps - ): number => instanceProps.itemMetadataMap[index].size, - - getEstimatedTotalSize, - - getOffsetForIndexAndAlignment: ( - props: Props, - index: number, - align: ScrollToAlign, - scrollOffset: number, - instanceProps: InstanceProps, - scrollbarSize: number - ): number => { - const { direction, height, layout, width } = props; - - // TODO Deprecate direction "horizontal" - const isHorizontal = direction === 'horizontal' || layout === 'horizontal'; - const size = (((isHorizontal ? width : height): any): number); - const itemMetadata = getItemMetadata(props, index, instanceProps); - - // Get estimated total size after ItemMetadata is computed, - // To ensure it reflects actual measurements instead of just estimates. - const estimatedTotalSize = getEstimatedTotalSize(props, instanceProps); - - const maxOffset = Math.max( - 0, - Math.min(estimatedTotalSize - size, itemMetadata.offset) - ); - const minOffset = Math.max( - 0, - itemMetadata.offset - size + itemMetadata.size + scrollbarSize - ); - - if (align === 'smart') { - if ( - scrollOffset >= minOffset - size && - scrollOffset <= maxOffset + size - ) { - align = 'auto'; - } else { - align = 'center'; - } - } - - switch (align) { - case 'start': - return maxOffset; - case 'end': - return minOffset; - case 'center': - return Math.round(minOffset + (maxOffset - minOffset) / 2); - case 'auto': - default: - if (scrollOffset >= minOffset && scrollOffset <= maxOffset) { - return scrollOffset; - } else if (scrollOffset < minOffset) { - return minOffset; - } else { - return maxOffset; - } - } - }, - - getStartIndexForOffset: ( - props: Props, - offset: number, - instanceProps: InstanceProps - ): number => findNearestItem(props, instanceProps, offset), - - getStopIndexForStartIndex: ( - props: Props, - startIndex: number, - scrollOffset: number, - instanceProps: InstanceProps - ): number => { - const { direction, height, itemCount, layout, width } = props; - - // TODO Deprecate direction "horizontal" - const isHorizontal = direction === 'horizontal' || layout === 'horizontal'; - const size = (((isHorizontal ? width : height): any): number); - const itemMetadata = getItemMetadata(props, startIndex, instanceProps); - const maxOffset = scrollOffset + size; - - let offset = itemMetadata.offset + itemMetadata.size; - let stopIndex = startIndex; - - while (stopIndex < itemCount - 1 && offset < maxOffset) { - stopIndex++; - offset += getItemMetadata(props, stopIndex, instanceProps).size; - } - - return stopIndex; - }, - - initInstanceProps(props: Props, instance: any): InstanceProps { - const { estimatedItemSize } = ((props: any): VariableSizeProps); - - const instanceProps = { - itemMetadataMap: {}, - estimatedItemSize: estimatedItemSize || DEFAULT_ESTIMATED_ITEM_SIZE, - lastMeasuredIndex: -1, - }; - - instance.resetAfterIndex = ( - index: number, - shouldForceUpdate?: boolean = true - ) => { - instanceProps.lastMeasuredIndex = Math.min( - instanceProps.lastMeasuredIndex, - index - 1 - ); - - // We could potentially optimize further by only evicting styles after this index, - // But since styles are only cached while scrolling is in progress- - // It seems an unnecessary optimization. - // It's unlikely that resetAfterIndex() will be called while a user is scrolling. - instance._getItemStyleCache(-1); - - if (shouldForceUpdate) { - instance.forceUpdate(); - } - }; - - return instanceProps; - }, - - shouldResetStyleCacheOnItemSizeChange: false, - - validateProps: ({ itemSize }: Props): void => { - if (process.env.NODE_ENV !== 'production') { - if (typeof itemSize !== 'function') { - throw Error( - 'An invalid "itemSize" prop has been specified. ' + - 'Value should be a function. ' + - `"${itemSize === null ? 'null' : typeof itemSize}" was specified.` - ); - } - } - }, -}); - -export default VariableSizeList; diff --git a/src/__tests__/FixedSizeGrid.js b/src/__tests__/FixedSizeGrid.js deleted file mode 100644 index 2f836e5e..00000000 --- a/src/__tests__/FixedSizeGrid.js +++ /dev/null @@ -1,1339 +0,0 @@ -import React, { createRef, forwardRef, PureComponent } from 'react'; -import ReactDOM from 'react-dom'; -import ReactTestRenderer from 'react-test-renderer'; -import ReactTestUtils from 'react-dom/test-utils'; -import { FixedSizeGrid } from '..'; -import * as domHelpers from '../domHelpers'; - -const findScrollContainer = rendered => rendered.root.children[0].children[0]; - -const simulateScroll = (instance, { scrollLeft, scrollTop }) => { - instance._outerRef.scrollLeft = scrollLeft; - instance._outerRef.scrollTop = scrollTop; - ReactTestUtils.Simulate.scroll(instance._outerRef); -}; - -describe('FixedSizeGrid', () => { - let defaultProps, getScrollbarSize, itemRenderer, onItemsRendered; - - // Use PureComponent to test memoization. - // Pass through to itemRenderer mock for easier test assertions. - class PureItemRenderer extends PureComponent { - render() { - return itemRenderer(this.props); - } - } - - beforeEach(() => { - jest.useFakeTimers(); - - // JSdom does not do actual layout and so doesn't return meaningful values here. - // For the purposes of our tests though, we can mock out semi-meaningful values. - // This mock is required for e.g. "onScroll" tests to work properly. - Object.defineProperties(HTMLElement.prototype, { - clientWidth: { - configurable: true, - get: function() { - return parseInt(this.style.width, 10) || 0; - }, - }, - clientHeight: { - configurable: true, - get: function() { - return parseInt(this.style.height, 10) || 0; - }, - }, - scrollHeight: { - configurable: true, - get: () => Number.MAX_SAFE_INTEGER, - }, - scrollWidth: { - configurable: true, - get: () => Number.MAX_SAFE_INTEGER, - }, - }); - - // Mock the DOM helper util for testing purposes. - getScrollbarSize = domHelpers.getScrollbarSize = jest.fn(() => 0); - - onItemsRendered = jest.fn(); - - itemRenderer = jest.fn(({ style, ...rest }) => ( -
{JSON.stringify(rest, null, 2)}
- )); - defaultProps = { - children: PureItemRenderer, - columnCount: 100, - columnWidth: 100, - height: 100, - onItemsRendered, - rowCount: 100, - rowHeight: 25, - width: 200, - }; - }); - - it('should render an empty grid', () => { - ReactTestRenderer.create( - - ); - ReactTestRenderer.create( - - ); - ReactTestRenderer.create(); - expect(itemRenderer).not.toHaveBeenCalled(); - expect(onItemsRendered).not.toHaveBeenCalled(); - }); - - it('should render a grid of items', () => { - ReactTestRenderer.create(); - expect(itemRenderer).toHaveBeenCalledTimes(15); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - describe('style caching', () => { - it('should cache styles while scrolling to avoid breaking pure sCU for items', () => { - const rendered = ReactTestRenderer.create( - - ); - // Scroll a few times. - // Each time, make sure to render row 3, column 1. - rendered - .getInstance() - .scrollToItem({ columnIndex: 1, rowIndex: 1, align: 'start' }); - rendered - .getInstance() - .scrollToItem({ columnIndex: 1, rowIndex: 2, align: 'start' }); - rendered - .getInstance() - .scrollToItem({ columnIndex: 1, rowIndex: 3, align: 'start' }); - // Find all of the times row 3, column 1 was rendered. - // If we are caching props correctly, it should only be once. - expect( - itemRenderer.mock.calls.filter( - ([params]) => params.rowIndex === 3 && params.columnIndex === 1 - ) - ).toHaveLength(1); - }); - - it('should reset cached styles when scrolling stops', () => { - // Use ReactDOM renderer so the container ref and "onScroll" event work correctly. - const instance = ReactDOM.render( - , - document.createElement('div') - ); - // Scroll, then capture the rendered style for item 1, - // Then let the debounce timer clear the cached styles. - simulateScroll(instance, { scrollLeft: 100, scrollTop: 25 }); - expect(itemRenderer).toHaveBeenCalled(); - const itemOneArgsA = itemRenderer.mock.calls.find( - ([params]) => params.columnIndex === 1 && params.rowIndex === 1 - ); - jest.runAllTimers(); - itemRenderer.mockClear(); - // Scroll again, then capture the rendered style for item 1, - // And confirm that the style was recreated. - simulateScroll(instance, { scrollLeft: 0, scrollTop: 0 }); - expect(itemRenderer).toHaveBeenCalled(); - const itemOneArgsB = itemRenderer.mock.calls.find( - ([params]) => params.columnIndex === 1 && params.rowIndex === 1 - ); - expect(itemOneArgsA[0].style).not.toBe(itemOneArgsB[0].style); - }); - }); - - it('changing item size updates the rendered items', () => { - const rendered = ReactTestRenderer.create( - - ); - rendered.update(); - rendered.update( - - ); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('changing itemSize updates the rendered items and busts the style cache', () => { - const rendered = ReactTestRenderer.create( - - ); - const styleOne = itemRenderer.mock.calls[0][0].style; - itemRenderer.mockClear(); - rendered.update(); - expect(itemRenderer).toHaveBeenCalled(); - const styleTwo = itemRenderer.mock.calls[0][0].style; - expect(styleOne).not.toBe(styleTwo); - itemRenderer.mockClear(); - rendered.update( - - ); - const styleThree = itemRenderer.mock.calls[0][0].style; - expect(styleTwo).not.toBe(styleThree); - }); - - it('should support momentum scrolling on iOS devices', () => { - const rendered = ReactTestRenderer.create( - - ); - expect(rendered.toJSON().props.style.WebkitOverflowScrolling).toBe('touch'); - }); - - it('should disable pointer events while scrolling', () => { - const rendered = ReactTestRenderer.create( - - ); - const scrollContainer = findScrollContainer(rendered); - expect(scrollContainer.props.style.pointerEvents).toBe(undefined); - rendered.getInstance().setState({ isScrolling: true }); - expect(scrollContainer.props.style.pointerEvents).toBe('none'); - }); - - describe('style overrides', () => { - it('should support className prop', () => { - const rendered = ReactTestRenderer.create( - - ); - expect(rendered.toJSON().props.className).toBe('custom'); - }); - - it('should support style prop', () => { - const rendered = ReactTestRenderer.create( - - ); - expect(rendered.toJSON().props.style.backgroundColor).toBe('red'); - }); - }); - - describe('direction', () => { - it('should set the appropriate CSS direction style', () => { - const renderer = ReactTestRenderer.create( - - ); - expect(renderer.toJSON().props.style.direction).toBe('ltr'); - renderer.update(); - expect(renderer.toJSON().props.style.direction).toBe('rtl'); - }); - - it('should position items correctly', () => { - const renderer = ReactTestRenderer.create( - - ); - - let params = itemRenderer.mock.calls[0][0]; - expect(params.columnIndex).toBe(0); - expect(params.rowIndex).toBe(0); - let style = params.style; - expect(style.left).toBe(0); - expect(style.right).toBeUndefined(); - - itemRenderer.mockClear(); - - renderer.update(); - - params = itemRenderer.mock.calls[0][0]; - expect(params.columnIndex).toBe(0); - expect(params.rowIndex).toBe(0); - style = params.style; - expect(style.left).toBeUndefined(); - expect(style.right).toBe(0); - }); - }); - - describe('overscanColumnCount and overscanRowCount', () => { - it('should require a minimum of 1 overscan to support tabbing', () => { - ReactTestRenderer.create( - - ); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should overscan in the direction being scrolled', () => { - const rendered = ReactTestRenderer.create( - - ); - rendered.getInstance().scrollTo({ scrollLeft: 1000, scrollTop: 1000 }); - rendered.getInstance().scrollTo({ scrollLeft: 500, scrollTop: 500 }); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should overscan in both directions when not scrolling', () => { - ReactTestRenderer.create( - - ); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should accommodate a custom overscan', () => { - ReactTestRenderer.create( - - ); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should not scan past the beginning of the grid', () => { - ReactTestRenderer.create(); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should not scan past the end of the grid', () => { - ReactTestRenderer.create( - - ); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - describe('overscanCount', () => { - it('should warn about deprecated overscanCount prop', () => { - spyOn(console, 'warn'); - - const renderer = ReactTestRenderer.create( - - ); - expect(console.warn).toHaveBeenCalledTimes(1); - expect(console.warn).toHaveBeenLastCalledWith( - 'The overscanCount prop has been deprecated. ' + - 'Please use the overscanColumnCount and overscanRowCount props instead.' - ); - - renderer.update(); - - // But it should only warn once. - expect(console.warn).toHaveBeenCalledTimes(1); - }); - - it('should warn about deprecated overscanRowsCount or overscanColumnsCount prop', () => { - spyOn(console, 'warn'); - - const renderer = ReactTestRenderer.create( - - ); - expect(console.warn).toHaveBeenCalledTimes(1); - expect(console.warn).toHaveBeenLastCalledWith( - 'The overscanColumnsCount and overscanRowsCount props have been deprecated. ' + - 'Please use the overscanColumnCount and overscanRowCount props instead.' - ); - - renderer.update( - - ); - - // But it should only warn once. - expect(console.warn).toHaveBeenCalledTimes(1); - }); - - it('should use overscanColumnsCount if both it and overscanCount are provided', () => { - spyOn(console, 'warn'); - - ReactTestRenderer.create( - - ); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should use overscanRowCount if both it and overscanCount are provided', () => { - spyOn(console, 'warn'); - - ReactTestRenderer.create( - - ); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should use overscanColumnCount and overscanRowCount if both them and deprecated props are provided', () => { - spyOn(console, 'warn'); - - ReactTestRenderer.create( - - ); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should support deprecated overscanCount', () => { - spyOn(console, 'warn'); - - ReactTestRenderer.create( - - ); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should support deprecated overscanColumnsCount and overscanRowsCount', () => { - spyOn(console, 'warn'); - - ReactTestRenderer.create( - - ); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - }); - }); - - describe('useIsScrolling', () => { - it('should not pass an isScrolling param to children unless requested', () => { - ReactTestRenderer.create(); - expect(itemRenderer.mock.calls[0][0].isScrolling).toBe(undefined); - }); - - it('should pass an isScrolling param to children if requested', () => { - // Use ReactDOM renderer so the container ref and "onScroll" event work correctly. - const instance = ReactDOM.render( - , - document.createElement('div') - ); - expect(itemRenderer.mock.calls[0][0].isScrolling).toBe(false); - itemRenderer.mockClear(); - simulateScroll(instance, { scrollLeft: 300, scrollTop: 400 }); - expect(itemRenderer.mock.calls[0][0].isScrolling).toBe(true); - itemRenderer.mockClear(); - jest.runAllTimers(); - expect(itemRenderer.mock.calls[0][0].isScrolling).toBe(false); - }); - - it('should not re-render children unnecessarily if isScrolling param is not used', () => { - // Use ReactDOM renderer so the container ref and "onScroll" event work correctly. - const instance = ReactDOM.render( - , - document.createElement('div') - ); - simulateScroll(instance, { scrollLeft: 300, scrollTop: 400 }); - itemRenderer.mockClear(); - jest.runAllTimers(); - expect(itemRenderer).not.toHaveBeenCalled(); - }); - }); - - describe('scrollTo method', () => { - it('should not report isScrolling', () => { - // Use ReactDOM renderer so the container ref and "onScroll" event work correctly. - const instance = ReactDOM.render( - , - document.createElement('div') - ); - itemRenderer.mockClear(); - instance.scrollTo({ scrollLeft: 100, scrollTop: 100 }); - expect(itemRenderer.mock.calls[0][0].isScrolling).toBe(false); - }); - - it('should allow only scrollLeft or scrollTop values to be specified', () => { - const instance = ReactDOM.render( - , - document.createElement('div') - ); - - instance.scrollTo({ scrollLeft: 100, scrollTop: 100 }); - expect(onItemsRendered).toHaveBeenLastCalledWith( - expect.objectContaining({ - visibleColumnStartIndex: 1, - visibleColumnStopIndex: 2, - visibleRowStartIndex: 4, - visibleRowStopIndex: 7, - }) - ); - - itemRenderer.mockClear(); - instance.scrollTo({ scrollTop: 200 }); - expect(onItemsRendered).toHaveBeenCalled(); - expect(onItemsRendered).toHaveBeenLastCalledWith( - expect.objectContaining({ - visibleColumnStartIndex: 1, - visibleColumnStopIndex: 2, - }) - ); - - itemRenderer.mockClear(); - instance.scrollTo({ scrollLeft: 150 }); - expect(onItemsRendered).toHaveBeenCalled(); - expect(onItemsRendered).toHaveBeenLastCalledWith( - expect.objectContaining({ - visibleRowStartIndex: 8, - visibleRowStopIndex: 11, - }) - ); - }); - - it('should ignore offsets less than zero', () => { - const onScroll = jest.fn(); - const instance = ReactDOM.render( - , - document.createElement('div') - ); - instance.scrollTo({ scrollLeft: 100, scrollTop: 100 }); - onScroll.mockClear(); - instance.scrollTo({ scrollLeft: -1, scrollTop: -1 }); - expect(onScroll.mock.calls[0][0].scrollLeft).toBe(0); - expect(onScroll.mock.calls[0][0].scrollTop).toBe(0); - }); - }); - - describe('scrollToItem method', () => { - it('should not set invalid offsets when the list contains few items', () => { - const rendered = ReactTestRenderer.create( - - ); - expect(onItemsRendered).toMatchSnapshot(); - onItemsRendered.mockClear(); - rendered.getInstance().scrollToItem(0); - expect(onItemsRendered).not.toHaveBeenCalled(); - }); - - it('should scroll to the correct item for align = "auto"', () => { - const rendered = ReactTestRenderer.create( - - ); - // Scroll down enough to show item 10 at the bottom. - rendered - .getInstance() - .scrollToItem({ columnIndex: 10, rowIndex: 10, align: 'auto' }); - // No need to scroll again; item 9 is already visible. - // Overscan indices will change though, since direction changes. - rendered - .getInstance() - .scrollToItem({ columnIndex: 9, rowIndex: 9, align: 'auto' }); - // Scroll up enough to show item 2 at the top. - rendered - .getInstance() - .scrollToItem({ columnIndex: 2, rowIndex: 2, align: 'auto' }); - // Scroll down to row 10, without changing scrollLeft - rendered.getInstance().scrollToItem({ rowIndex: 10, align: 'auto' }); - // Scroll left to column 0, without changing scrollTop - rendered.getInstance().scrollToItem({ columnIndex: 0, align: 'auto' }); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('scroll with align = "auto" should work with partially-visible items', () => { - const rendered = ReactTestRenderer.create( - // Create list where items don't fit exactly into container. - // The container has space for 3 1/3 items. - - ); - // Scroll down enough to show row 10 at the bottom a nd column 10 at the right. - // Should show 4 rows: 3 full and one partial at the beginning - // Should show 3 columns: 2 full and one partial at the beginning - rendered - .getInstance() - .scrollToItem({ columnIndex: 10, rowIndex: 10, align: 'auto' }); - // No need to scroll again; row and column 9 are already visible. - // Because there's no scrolling, it won't call onItemsRendered. - rendered - .getInstance() - .scrollToItem({ columnIndex: 9, rowIndex: 9, align: 'auto' }); - // Scroll to near the end. row 96 and column 97 will be partly visible. - rendered - .getInstance() - .scrollToItem({ columnIndex: 99, rowIndex: 99, align: 'auto' }); - // Scroll back to row 91 and column 97. - // This will cause row 99 and column 99 to be partly viisble - // Even though a scroll happened, none of the items rendered have changed. - rendered - .getInstance() - .scrollToItem({ columnIndex: 97, rowIndex: 96, align: 'auto' }); - // Scroll forward again. Because row and column #99 were already partly visible, - // all props of the onItemsRendered will be the same. - rendered - .getInstance() - .scrollToItem({ columnIndex: 99, rowIndex: 99, align: 'auto' }); - // Scroll to the second row and column. - // This should leave row 4 and column 3 partly visible. - rendered - .getInstance() - .scrollToItem({ columnIndex: 1, rowIndex: 1, align: 'auto' }); - // Scroll to the first row and column. - // This should leave row 3 and column 2 partly visible. - rendered - .getInstance() - .scrollToItem({ columnIndex: 0, rowIndex: 0, align: 'auto' }); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should scroll to the correct item for align = "auto" at the bottom of the grid', () => { - getScrollbarSize.mockImplementation(() => 20); - - const rendered = ReactTestRenderer.create( - - ); - onItemsRendered.mockClear(); - - // Scroll down to the last row in the list. - rendered - .getInstance() - .scrollToItem({ columnIndex: 5, rowIndex: 19, align: 'auto' }); - - expect(onItemsRendered).toHaveBeenCalledTimes(1); - expect(onItemsRendered).toHaveBeenLastCalledWith( - expect.objectContaining({ - visibleRowStartIndex: 17, - visibleRowStopIndex: 19, - }) - ); - // Repeat the previous scrollToItem call. - rendered - .getInstance() - .scrollToItem({ columnIndex: 5, rowIndex: 19, align: 'auto' }); - - // Shouldn't have been called again - expect(onItemsRendered).toHaveBeenCalledTimes(1); - expect(onItemsRendered).toHaveBeenLastCalledWith( - expect.objectContaining({ - visibleRowStartIndex: 17, - visibleRowStopIndex: 19, - }) - ); - }); - - it('should scroll to the correct item for align = "auto" at the right hand side of the grid', () => { - getScrollbarSize.mockImplementation(() => 20); - - const rendered = ReactTestRenderer.create( - - ); - onItemsRendered.mockClear(); - - // Scroll across to the last row in the list. - rendered - .getInstance() - .scrollToItem({ columnIndex: 19, rowIndex: 19, align: 'auto' }); - - expect(onItemsRendered).toHaveBeenCalledTimes(1); - expect(onItemsRendered).toHaveBeenLastCalledWith( - expect.objectContaining({ - visibleColumnStartIndex: 18, - visibleColumnStopIndex: 19, - }) - ); - // Repeat the previous scrollToItem call. - rendered - .getInstance() - .scrollToItem({ columnIndex: 19, rowIndex: 19, align: 'auto' }); - - // Shouldn't have been called again - expect(onItemsRendered).toHaveBeenCalledTimes(1); - expect(onItemsRendered).toHaveBeenLastCalledWith( - expect.objectContaining({ - visibleColumnStartIndex: 18, - visibleColumnStopIndex: 19, - }) - ); - }); - - it('should scroll to the correct item for align = "start"', () => { - const rendered = ReactTestRenderer.create( - - ); - // Scroll down enough to show item 10 at the top. - rendered - .getInstance() - .scrollToItem({ columnIndex: 10, rowIndex: 10, align: 'start' }); - // Scroll back up so that item 9 is at the top. - // Overscroll direction wil change too. - rendered - .getInstance() - .scrollToItem({ columnIndex: 9, rowIndex: 9, align: 'start' }); - // Item 99 can't align at the top because there aren't enough items. - // Scroll down as far as possible though. - // Overscroll direction wil change again. - rendered - .getInstance() - .scrollToItem({ columnIndex: 99, rowIndex: 99, align: 'start' }); - // Scroll up to row 10, without changing scrollLeft - rendered.getInstance().scrollToItem({ rowIndex: 10, align: 'start' }); - // Scroll left to column 0, without changing scrollTop - rendered.getInstance().scrollToItem({ columnIndex: 0, align: 'start' }); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should scroll to the correct item for align = "end"', () => { - const rendered = ReactTestRenderer.create( - - ); - // Scroll down enough to show item 10 at the bottom. - rendered - .getInstance() - .scrollToItem({ columnIndex: 10, rowIndex: 10, align: 'end' }); - // Scroll back up so that item 9 is at the bottom. - // Overscroll direction wil change too. - rendered - .getInstance() - .scrollToItem({ columnIndex: 9, rowIndex: 9, align: 'end' }); - // Item 1 can't align at the bottom because it's too close to the beginning. - // Scroll up as far as possible though. - // Overscroll direction wil change again. - rendered - .getInstance() - .scrollToItem({ columnIndex: 1, rowIndex: 1, align: 'end' }); - // Scroll down to row 10, without changing scrollLeft - rendered.getInstance().scrollToItem({ rowIndex: 10, align: 'end' }); - // Scroll right to column 9, without changing scrollTop - rendered.getInstance().scrollToItem({ columnIndex: 9, align: 'end' }); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should scroll to the correct item for align = "center"', () => { - const rendered = ReactTestRenderer.create( - - ); - // Scroll down enough to show item 10 in the middle. - rendered - .getInstance() - .scrollToItem({ columnIndex: 10, rowIndex: 10, align: 'center' }); - // Scroll back up so that item 9 is in the middle. - // Overscroll direction wil change too. - rendered - .getInstance() - .scrollToItem({ columnIndex: 9, rowIndex: 9, align: 'center' }); - // Item 1 can't align in the middle because it's too close to the beginning. - // Scroll up as far as possible though. - // Overscroll direction wil change again. - rendered - .getInstance() - .scrollToItem({ columnIndex: 1, rowIndex: 1, align: 'center' }); - // Item 99 can't align in the middle because it's too close to the end. - // Scroll down as far as possible though. - // Overscroll direction wil change again. - rendered - .getInstance() - .scrollToItem({ columnIndex: 99, rowIndex: 99, align: 'center' }); - // Scroll up to row 10, without changing scrollLeft - rendered.getInstance().scrollToItem({ rowIndex: 10, align: 'center' }); - // Scroll left to column 3, without changing scrollTop - rendered.getInstance().scrollToItem({ columnIndex: 3, align: 'center' }); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should scroll to the correct item for align = "smart"', () => { - const rendered = ReactTestRenderer.create( - - ); - - // Scroll down enough to show item 10 at the center. - // It was further than one screen away, so it gets centered. - rendered - .getInstance() - .scrollToItem({ columnIndex: 10, rowIndex: 10, align: 'smart' }); - // No need to scroll again; item 9 is already visible. - // Overscan indices will change though, since direction changes. - rendered - .getInstance() - .scrollToItem({ columnIndex: 9, rowIndex: 9, align: 'smart' }); - // Scroll up enough to show item 2 as close to the center as we can. - rendered - .getInstance() - .scrollToItem({ columnIndex: 2, rowIndex: 2, align: 'smart' }); - // Scroll down to row 10, without changing scrollLeft - rendered.getInstance().scrollToItem({ rowIndex: 10, align: 'smart' }); - // Scroll left to column 0, without changing scrollTop - rendered.getInstance().scrollToItem({ columnIndex: 0, align: 'smart' }); - - // Scrolling within a distance of a single screen from viewport - // should have the 'auto' behavior of scrolling as little as possible. - rendered - .getInstance() - .scrollToItem({ columnIndex: 5, rowIndex: 5, align: 'smart' }); - rendered - .getInstance() - .scrollToItem({ columnIndex: 10, rowIndex: 10, align: 'smart' }); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should not report isScrolling', () => { - // Use ReactDOM renderer so the container ref and "onScroll" event work correctly. - const instance = ReactDOM.render( - , - document.createElement('div') - ); - itemRenderer.mockClear(); - instance.scrollToItem({ columnIndex: 15, rowIndex: 20 }); - expect(itemRenderer.mock.calls[0][0].isScrolling).toBe(false); - }); - - it('should account for scrollbar size', () => { - const onScroll = jest.fn(); - const rendered = ReactTestRenderer.create( - - ); - - onScroll.mockClear(); - rendered - .getInstance() - .scrollToItem({ columnIndex: 15, rowIndex: 10, align: 'end' }); - - // With hidden scrollbars (size === 0) we would expect... - expect(onScroll).toHaveBeenCalledWith({ - horizontalScrollDirection: 'forward', - scrollLeft: 1300, - scrollTop: 125, - scrollUpdateWasRequested: true, - verticalScrollDirection: 'forward', - }); - - getScrollbarSize.mockImplementation(() => 20); - - onScroll.mockClear(); - rendered - .getInstance() - .scrollToItem({ columnIndex: 15, rowIndex: 10, align: 'end' }); - - // With scrollbars of size 20 we would expect those values ot increase by 20px - expect(onScroll).toHaveBeenCalledWith({ - horizontalScrollDirection: 'forward', - scrollLeft: 1320, - scrollTop: 145, - scrollUpdateWasRequested: true, - verticalScrollDirection: 'forward', - }); - }); - - it('should not account for scrollbar size when no scrollbar is visible for a particular direction', () => { - getScrollbarSize.mockImplementation(() => 20); - - const onScroll = jest.fn(); - const rendered = ReactTestRenderer.create( - - ); - - onScroll.mockClear(); - rendered - .getInstance() - .scrollToItem({ columnIndex: 0, rowIndex: 10, align: 'end' }); - - // Since there aren't enough columns to require horizontal scrolling, - // the additional 20px for the scrollbar should not be taken into consideration. - expect(onScroll).toHaveBeenCalledWith({ - horizontalScrollDirection: 'backward', - scrollLeft: 0, - scrollTop: 125, - scrollUpdateWasRequested: true, - verticalScrollDirection: 'forward', - }); - - rendered.update( - - ); - - onScroll.mockClear(); - rendered - .getInstance() - .scrollToItem({ columnIndex: 15, rowIndex: 0, align: 'end' }); - - // Since there aren't enough rows to require vertical scrolling, - // the additional 20px for the scrollbar should not be taken into consideration. - expect(onScroll).toHaveBeenCalledWith({ - horizontalScrollDirection: 'forward', - scrollLeft: 1300, - scrollTop: 0, - scrollUpdateWasRequested: true, - verticalScrollDirection: 'backward', - }); - }); - - it('should ignore indexes less than zero', () => { - const instance = ReactDOM.render( - , - document.createElement('div') - ); - instance.scrollToItem({ columnIndex: 20, rowIndex: 20 }); - onItemsRendered.mockClear(); - instance.scrollToItem({ columnIndex: -1, rowIndex: -1 }); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should ignore indexes greater than itemCount', () => { - const instance = ReactDOM.render( - , - document.createElement('div') - ); - onItemsRendered.mockClear(); - instance.scrollToItem({ - columnIndex: defaultProps.columnCount * 2, - rowIndex: defaultProps.rowCount * 2, - }); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - }); - - // onItemsRendered is pretty well covered by other snapshot tests - describe('onScroll', () => { - it('should call onScroll after mount', () => { - const onScroll = jest.fn(); - ReactTestRenderer.create( - - ); - expect(onScroll.mock.calls).toMatchSnapshot(); - }); - - it('should call onScroll when scroll position changes', () => { - const onScroll = jest.fn(); - const rendered = ReactTestRenderer.create( - - ); - rendered.getInstance().scrollTo({ - scrollLeft: 100, - scrollTop: 50, - }); - rendered.getInstance().scrollTo({ - scrollLeft: 0, - scrollTop: 150, - }); - rendered.getInstance().scrollTo({ - scrollLeft: 150, - scrollTop: 0, - }); - expect(onScroll.mock.calls).toMatchSnapshot(); - }); - - it('should distinguish between "onScroll" events and scrollTo() calls', () => { - const onScroll = jest.fn(); - // Use ReactDOM renderer so the container ref and "onScroll" event work correctly. - const instance = ReactDOM.render( - , - document.createElement('div') - ); - - onScroll.mockClear(); - instance.scrollTo({ scrollLeft: 100, scrollTop: 100 }); - expect(onScroll.mock.calls[0][0].scrollUpdateWasRequested).toBe(true); - - onScroll.mockClear(); - simulateScroll(instance, { scrollLeft: 200, scrollTop: 200 }); - expect(onScroll.mock.calls[0][0].scrollUpdateWasRequested).toBe(false); - }); - - it('scrolling should report partial items correctly in onItemsRendered', () => { - // Use ReactDOM renderer so the container ref works correctly. - const instance = ReactDOM.render( - , - document.createElement('div') - ); - // grid 200w x 100h - // columnWidth: 100, rowHeight: 25, - // columnCount: 100, rowCount: 100 - // Scroll 2 items fwd, but thanks to the initialScrollOffset, we should - // still be showing partials on both ends. - instance.scrollTo({ scrollLeft: 150, scrollTop: 40 }); - // Scroll a little fwd to cause partials to be hidden - instance.scrollTo({ scrollLeft: 200, scrollTop: 50 }); - // Scroll backwards to show partials again - instance.scrollTo({ scrollLeft: 150, scrollTop: 40 }); - // Scroll near the end so that the last item is shown - // as a partial. - instance.scrollTo({ - scrollLeft: 98 * 100 - 5, - scrollTop: 96 * 25 - 5, - }); - // Scroll to the end. No partials. - instance.scrollTo({ - scrollLeft: 98 * 100, - scrollTop: 96 * 25, - }); - // Verify that backwards scrolling near the end works OK. - instance.scrollTo({ - scrollLeft: 98 * 100 - 5, - scrollTop: 96 * 25 - 5, - }); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - }); - - describe('itemKey', () => { - it('should be used', () => { - const itemKey = jest.fn( - ({ columnIndex, rowIndex }) => `${rowIndex}:${columnIndex}` - ); - ReactTestRenderer.create( - - ); - expect(itemKey).toHaveBeenCalledTimes(6); - expect(itemKey.mock.calls[0][0]).toEqual({ columnIndex: 0, rowIndex: 0 }); - expect(itemKey.mock.calls[1][0]).toEqual({ columnIndex: 1, rowIndex: 0 }); - expect(itemKey.mock.calls[2][0]).toEqual({ columnIndex: 2, rowIndex: 0 }); - expect(itemKey.mock.calls[3][0]).toEqual({ columnIndex: 0, rowIndex: 1 }); - expect(itemKey.mock.calls[4][0]).toEqual({ columnIndex: 1, rowIndex: 1 }); - expect(itemKey.mock.calls[5][0]).toEqual({ columnIndex: 2, rowIndex: 1 }); - }); - - it('should allow items to be moved within the collection without causing caching problems', () => { - const keyMap = [ - ['0:0', '0:1:', '0:2'], - ['1:0', '1:1:', '1:2'], - ]; - const keyMapItemRenderer = jest.fn(({ index, style }) => ( -
{keyMap[index]}
- )); - class ItemRenderer extends PureComponent { - render() { - return keyMapItemRenderer(this.props); - } - } - const itemKey = jest.fn( - ({ columnIndex, rowIndex }) => keyMap[rowIndex][columnIndex] - ); - const rendered = ReactTestRenderer.create( - - {ItemRenderer} - - ); - expect(itemKey).toHaveBeenCalledTimes(6); - itemKey.mockClear(); - - expect(keyMapItemRenderer).toHaveBeenCalledTimes(6); - keyMapItemRenderer.mockClear(); - - // Simulate swapping the first and last items. - keyMap[0][0] = '1:2'; - keyMap[1][2] = '0:0'; - - rendered.getInstance().forceUpdate(); - - // Our key getter should be called again for each key. - // Since we've modified the map, the first and last key will swap. - expect(itemKey).toHaveBeenCalledTimes(6); - - // The first and third item have swapped place, - // So they should have been re-rendered, - // But the second item should not. - expect(keyMapItemRenderer).toHaveBeenCalledTimes(2); - expect(keyMapItemRenderer.mock.calls[0][0].columnIndex).toBe(0); - expect(keyMapItemRenderer.mock.calls[0][0].rowIndex).toBe(0); - expect(keyMapItemRenderer.mock.calls[1][0].columnIndex).toBe(2); - expect(keyMapItemRenderer.mock.calls[1][0].rowIndex).toBe(1); - }); - - it('should receive a data value if itemData is provided', () => { - const itemKey = jest.fn( - ({ columnIndex, data, rowIndex }) => `${columnIndex}-${rowIndex}` - ); - const itemData = {}; - ReactTestRenderer.create( - - ); - expect(itemKey).toHaveBeenCalled(); - expect( - itemKey.mock.calls.filter(([params]) => params.data === itemData) - ).toHaveLength(itemKey.mock.calls.length); - }); - }); - - describe('refs', () => { - it('should pass through innerRef and outerRef ref functions', () => { - const innerRef = jest.fn(); - const outerRef = jest.fn(); - ReactDOM.render( - , - document.createElement('div') - ); - expect(innerRef).toHaveBeenCalled(); - expect(innerRef.mock.calls[0][0]).toBeInstanceOf(HTMLDivElement); - expect(outerRef).toHaveBeenCalled(); - expect(outerRef.mock.calls[0][0]).toBeInstanceOf(HTMLDivElement); - }); - - it('should pass through innerRef and outerRef createRef objects', () => { - const innerRef = createRef(); - const outerRef = createRef(); - ReactDOM.render( - , - document.createElement('div') - ); - expect(innerRef.current).toBeInstanceOf(HTMLDivElement); - expect(outerRef.current).toBeInstanceOf(HTMLDivElement); - }); - }); - - describe('custom element types', () => { - it('should use a custom innerElementType if specified', () => { - const rendered = ReactTestRenderer.create( - - ); - expect(rendered.root.findByType('section')).toBeDefined(); - }); - - it('should use a custom outerElementType if specified', () => { - const rendered = ReactTestRenderer.create( - - ); - expect(rendered.root.findByType('section')).toBeDefined(); - }); - - it('should support spreading additional, arbitrary props, e.g. id', () => { - const container = document.createElement('div'); - ReactDOM.render( - ( -
- ))} - outerElementType={forwardRef((props, ref) => ( -
- ))} - />, - container - ); - expect(container.firstChild.id).toBe('outer'); - expect(container.firstChild.firstChild.id).toBe('inner'); - }); - - it('should warn if legacy innerTagName or outerTagName props are used', () => { - spyOn(console, 'warn'); - const renderer = ReactTestRenderer.create( - - ); - expect(console.warn).toHaveBeenCalledTimes(1); - expect(console.warn).toHaveBeenLastCalledWith( - 'The innerTagName and outerTagName props have been deprecated. ' + - 'Please use the innerElementType and outerElementType props instead.' - ); - - renderer.update( - - ); - - // But it should only warn once. - expect(console.warn).toHaveBeenCalledTimes(1); - }); - }); - - describe('itemData', () => { - it('should pass itemData to item renderers as a "data" prop', () => { - const itemData = {}; - ReactTestRenderer.create( - - ); - expect(itemRenderer).toHaveBeenCalled(); - expect( - itemRenderer.mock.calls.filter(([params]) => params.data === itemData) - ).toHaveLength(itemRenderer.mock.calls.length); - }); - - it('should re-render items if itemData changes', () => { - const itemData = {}; - const rendered = ReactTestRenderer.create( - - ); - expect(itemRenderer).toHaveBeenCalled(); - itemRenderer.mockClear(); - - // Re-rendering should not affect pure sCU children: - rendered.update(); - expect(itemRenderer).not.toHaveBeenCalled(); - - // Re-rendering with new itemData should re-render children: - const newItemData = {}; - rendered.update( - - ); - expect(itemRenderer).toHaveBeenCalled(); - expect( - itemRenderer.mock.calls.filter( - ([params]) => params.data === newItemData - ) - ).toHaveLength(itemRenderer.mock.calls.length); - }); - }); - - describe('props validation', () => { - beforeEach(() => spyOn(console, 'error')); - - it('should fail if non-numeric columnWidth is provided', () => { - expect(() => - ReactTestRenderer.create( - - ) - ).toThrow( - 'An invalid "columnWidth" prop has been specified. ' + - 'Value should be a number. ' + - '"string" was specified.' - ); - }); - - it('should fail if non-numeric rowHeight is provided', () => { - expect(() => - ReactTestRenderer.create( - - ) - ).toThrow( - 'An invalid "rowHeight" prop has been specified. ' + - 'Value should be a number. ' + - '"string" was specified.' - ); - }); - - it('should fail if no children value is provided', () => { - expect(() => - ReactTestRenderer.create( - - ) - ).toThrow( - 'An invalid "children" prop has been specified. ' + - 'Value should be a React component. ' + - '"undefined" was specified.' - ); - }); - - it('should fail if an invalid direction is provided', () => { - expect(() => - ReactTestRenderer.create( - - ) - ).toThrow( - 'An invalid "direction" prop has been specified. ' + - 'Value should be either "ltr" or "rtl". ' + - '"null" was specified.' - ); - }); - - it('should fail if a string height is provided', () => { - expect(() => - ReactTestRenderer.create( - - ) - ).toThrow( - 'An invalid "height" prop has been specified. ' + - 'Grids must specify a number for height. ' + - '"string" was specified.' - ); - }); - - it('should fail if a string width is provided', () => { - expect(() => - ReactTestRenderer.create( - - ) - ).toThrow( - 'An invalid "width" prop has been specified. ' + - 'Grids must specify a number for width. ' + - '"string" was specified.' - ); - }); - }); -}); diff --git a/src/__tests__/FixedSizeList.js b/src/__tests__/FixedSizeList.js deleted file mode 100644 index cbd2b64d..00000000 --- a/src/__tests__/FixedSizeList.js +++ /dev/null @@ -1,1000 +0,0 @@ -import React, { createRef, forwardRef, PureComponent } from 'react'; -import ReactDOM from 'react-dom'; -import ReactTestRenderer from 'react-test-renderer'; -import { Simulate } from 'react-dom/test-utils'; -import { FixedSizeList } from '..'; -import * as domHelpers from '../domHelpers'; - -const simulateScroll = (instance, scrollOffset, direction = 'vertical') => { - if (direction === 'horizontal') { - instance._outerRef.scrollLeft = scrollOffset; - } else { - instance._outerRef.scrollTop = scrollOffset; - } - Simulate.scroll(instance._outerRef); -}; - -const findScrollContainer = rendered => rendered.root.children[0].children[0]; - -describe('FixedSizeList', () => { - let itemRenderer, defaultProps, getScrollbarSize, onItemsRendered; - - let mockedScrollHeight = Number.MAX_SAFE_INTEGER; - let mockedScrollWidth = Number.MAX_SAFE_INTEGER; - - // Use PureComponent to test memoization. - // Pass through to itemRenderer mock for easier test assertions. - class PureItemRenderer extends PureComponent { - render() { - return itemRenderer(this.props); - } - } - - beforeEach(() => { - jest.useFakeTimers(); - - mockedScrollHeight = Number.MAX_SAFE_INTEGER; - mockedScrollWidth = Number.MAX_SAFE_INTEGER; - - // JSdom does not do actual layout and so doesn't return meaningful values here. - // For the purposes of our tests though, we can mock out semi-meaningful values. - // This mock is required for e.g. "onScroll" tests to work properly. - Object.defineProperties(HTMLElement.prototype, { - clientWidth: { - configurable: true, - get: function() { - return parseInt(this.style.width, 10) || 0; - }, - }, - clientHeight: { - configurable: true, - get: function() { - return parseInt(this.style.height, 10) || 0; - }, - }, - scrollHeight: { - configurable: true, - get: () => mockedScrollHeight, - }, - scrollWidth: { - configurable: true, - get: () => mockedScrollWidth, - }, - }); - - // Mock the DOM helper util for testing purposes. - getScrollbarSize = domHelpers.getScrollbarSize = jest.fn(() => 0); - - onItemsRendered = jest.fn(); - - itemRenderer = jest.fn(({ style, ...rest }) => ( -
{JSON.stringify(rest, null, 2)}
- )); - defaultProps = { - children: PureItemRenderer, - height: 100, - itemCount: 100, - itemSize: 25, - onItemsRendered, - width: 50, - }; - }); - - it('should render an empty list', () => { - ReactTestRenderer.create(); - expect(itemRenderer).not.toHaveBeenCalled(); - expect(onItemsRendered).not.toHaveBeenCalled(); - }); - - it('should render a list of rows', () => { - ReactTestRenderer.create(); - expect(itemRenderer).toHaveBeenCalledTimes(6); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should render a list of columns', () => { - ReactTestRenderer.create( - - ); - expect(itemRenderer).toHaveBeenCalledTimes(4); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should re-render items if layout changes', () => { - const rendered = ReactTestRenderer.create( - - ); - expect(itemRenderer).toHaveBeenCalled(); - itemRenderer.mockClear(); - - // Re-rendering should not affect pure sCU children: - rendered.update(); - expect(itemRenderer).not.toHaveBeenCalled(); - - // Re-rendering with new layout should re-render children: - rendered.update(); - expect(itemRenderer).toHaveBeenCalled(); - }); - - // TODO Deprecate direction "horizontal" - it('should re-render items if direction changes', () => { - spyOn(console, 'warn'); // Ingore legacy prop warning - - const rendered = ReactTestRenderer.create( - - ); - expect(itemRenderer).toHaveBeenCalled(); - itemRenderer.mockClear(); - - // Re-rendering should not affect pure sCU children: - rendered.update(); - expect(itemRenderer).not.toHaveBeenCalled(); - - // Re-rendering with new layout should re-render children: - rendered.update(); - expect(itemRenderer).toHaveBeenCalled(); - }); - - describe('scrollbar handling', () => { - it('should set width to "100%" for vertical lists to avoid unnecessary horizontal scrollbar', () => { - const innerRef = createRef(); - ReactDOM.render( - , - document.createElement('div') - ); - const style = innerRef.current.style; - expect(style.width).toBe('100%'); - expect(style.height).toBe('2500px'); - }); - - it('should set height to "100%" for horizontal lists to avoid unnecessary vertical scrollbar', () => { - const innerRef = createRef(); - ReactDOM.render( - , - document.createElement('div') - ); - const style = innerRef.current.style; - expect(style.width).toBe('2500px'); - expect(style.height).toBe('100%'); - }); - }); - - describe('style caching', () => { - it('should cache styles while scrolling to avoid breaking pure sCU for items', () => { - const rendered = ReactTestRenderer.create( - - ); - // Scroll a few times. - // Each time, make sure to render item 3. - rendered.getInstance().scrollToItem(1, 'start'); - rendered.getInstance().scrollToItem(2, 'start'); - rendered.getInstance().scrollToItem(3, 'start'); - // Find all of the times item 3 was rendered. - // If we are caching props correctly, it should only be once. - expect( - itemRenderer.mock.calls.filter(([params]) => params.index === 3) - ).toHaveLength(1); - }); - - it('should reset cached styles when scrolling stops', () => { - // Use ReactDOM renderer so the container ref and "onScroll" work correctly. - const instance = ReactDOM.render( - , - document.createElement('div') - ); - // Scroll, then capture the rendered style for item 1, - // Then let the debounce timer clear the cached styles. - simulateScroll(instance, 251); - const itemOneArgsA = itemRenderer.mock.calls.find( - ([params]) => params.index === 1 - ); - jest.runAllTimers(); - itemRenderer.mockClear(); - // Scroll again, then capture the rendered style for item 1, - // And confirm that the style was recreated. - simulateScroll(instance, 0); - const itemOneArgsB = itemRenderer.mock.calls.find( - ([params]) => params.index === 1 - ); - expect(itemOneArgsA[0].style).not.toBe(itemOneArgsB[0].style); - }); - }); - - it('changing itemSize updates the rendered items', () => { - const rendered = ReactTestRenderer.create( - - ); - rendered.update(); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('changing itemSize updates the rendered items and busts the style cache', () => { - const rendered = ReactTestRenderer.create( - - ); - const oldStyle = itemRenderer.mock.calls[0][0].style; - itemRenderer.mockClear(); - rendered.update(); - expect(itemRenderer).toHaveBeenCalled(); - const newStyle = itemRenderer.mock.calls[0][0].style; - expect(oldStyle).not.toBe(newStyle); - }); - - it('should support momentum scrolling on iOS devices', () => { - const rendered = ReactTestRenderer.create( - - ); - expect(rendered.toJSON().props.style.WebkitOverflowScrolling).toBe('touch'); - }); - - it('should disable pointer events while scrolling', () => { - const rendered = ReactTestRenderer.create( - - ); - const scrollContainer = findScrollContainer(rendered); - expect(scrollContainer.props.style.pointerEvents).toBe(undefined); - rendered.getInstance().setState({ isScrolling: true }); - expect(scrollContainer.props.style.pointerEvents).toBe('none'); - }); - - describe('style overrides', () => { - it('should support className prop', () => { - const rendered = ReactTestRenderer.create( - - ); - expect(rendered.toJSON().props.className).toBe('custom'); - }); - - it('should support style prop', () => { - const rendered = ReactTestRenderer.create( - - ); - expect(rendered.toJSON().props.style.backgroundColor).toBe('red'); - }); - }); - - describe('direction', () => { - it('should set the appropriate CSS direction style', () => { - const renderer = ReactTestRenderer.create( - - ); - expect(renderer.toJSON().props.style.direction).toBe('ltr'); - renderer.update(); - expect(renderer.toJSON().props.style.direction).toBe('rtl'); - }); - - it('should position items correctly', () => { - const renderer = ReactTestRenderer.create( - - ); - - let params = itemRenderer.mock.calls[0][0]; - expect(params.index).toBe(0); - let style = params.style; - expect(style.left).toBe(0); - expect(style.right).toBeUndefined(); - - itemRenderer.mockClear(); - - renderer.update(); - - params = itemRenderer.mock.calls[0][0]; - expect(params.index).toBe(0); - style = params.style; - expect(style.left).toBeUndefined(); - expect(style.right).toBe(0); - }); - }); - - describe('overscanCount', () => { - it('should require a minimum of 1 overscan to support tabbing', () => { - ReactTestRenderer.create( - - ); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should overscan in the direction being scrolled', () => { - const instance = ReactDOM.render( - , - document.createElement('div') - ); - // Simulate scrolling (rather than using scrollTo) to test isScrolling state. - simulateScroll(instance, 100); - simulateScroll(instance, 50); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should overscan in both directions when not scrolling', () => { - ReactTestRenderer.create( - - ); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should accommodate a custom overscan', () => { - ReactTestRenderer.create( - - ); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should not scan past the beginning of the list', () => { - ReactTestRenderer.create( - - ); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should not scan past the end of the list', () => { - ReactTestRenderer.create( - - ); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - }); - - describe('useIsScrolling', () => { - it('should not pass an isScrolling param to children unless requested', () => { - ReactTestRenderer.create(); - expect(itemRenderer.mock.calls[0][0].isScrolling).toBe(undefined); - }); - - it('should pass an isScrolling param to children if requested', () => { - // Use ReactDOM renderer so the container ref and "onScroll" work correctly. - const instance = ReactDOM.render( - , - document.createElement('div') - ); - expect(itemRenderer.mock.calls[0][0].isScrolling).toBe(false); - itemRenderer.mockClear(); - simulateScroll(instance, 100); - expect(itemRenderer.mock.calls[0][0].isScrolling).toBe(true); - itemRenderer.mockClear(); - jest.runAllTimers(); - expect(itemRenderer.mock.calls[0][0].isScrolling).toBe(false); - }); - - it('should not re-render children unnecessarily if isScrolling param is not used', () => { - // Use ReactDOM renderer so the container ref and "onScroll" work correctly. - const instance = ReactDOM.render( - , - document.createElement('div') - ); - simulateScroll(instance, 100); - itemRenderer.mockClear(); - jest.runAllTimers(); - expect(itemRenderer).not.toHaveBeenCalled(); - }); - }); - - describe('scrollTo method', () => { - it('should not report isScrolling', () => { - // Use ReactDOM renderer so the container ref and "onScroll" work correctly. - const instance = ReactDOM.render( - , - document.createElement('div') - ); - itemRenderer.mockClear(); - instance.scrollTo(100); - expect(itemRenderer.mock.calls[0][0].isScrolling).toBe(false); - }); - - it('should ignore values less than zero', () => { - const onScroll = jest.fn(); - const instance = ReactDOM.render( - , - document.createElement('div') - ); - instance.scrollTo(100); - onScroll.mockClear(); - instance.scrollTo(-1); - expect(onScroll.mock.calls[0][0].scrollOffset).toBe(0); - }); - }); - - describe('scrollToItem method', () => { - it('should not set invalid offsets when the list contains few items', () => { - const onScroll = jest.fn(); - const rendered = ReactTestRenderer.create( - - ); - expect(onItemsRendered).toMatchSnapshot(); - onItemsRendered.mockClear(); - rendered.getInstance().scrollToItem(0); - expect(onItemsRendered).not.toHaveBeenCalled(); - }); - - it('should scroll to the correct item for align = "auto"', () => { - const rendered = ReactTestRenderer.create( - - ); - // Scroll down enough to show item 10 at the bottom. - rendered.getInstance().scrollToItem(10, 'auto'); - // No need to scroll again; item 9 is already visible. - // Because there's no scrolling, it won't call onItemsRendered. - rendered.getInstance().scrollToItem(9, 'auto'); - // Scroll up enough to show item 2 at the top. - rendered.getInstance().scrollToItem(2, 'auto'); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('scroll with align = "auto" should work with partially-visible items', () => { - const rendered = ReactTestRenderer.create( - // Create list where items don't fit exactly into container. - // The container has space for 3 1/3 items. - - ); - // Scroll down enough to show item 10 at the bottom. - // Should show 4 items: 3 full and one partial at the beginning - rendered.getInstance().scrollToItem(10, 'auto'); - // No need to scroll again; item 9 is already visible. - // Because there's no scrolling, it won't call onItemsRendered. - rendered.getInstance().scrollToItem(9, 'auto'); - // Scroll to near the end. #96 will be shown as partial. - rendered.getInstance().scrollToItem(99, 'auto'); - // Scroll back to show #96 fully. This will cause #99 to be shown as a - // partial. Because #96 was already shown previously as a partial, all - // props of the onItemsRendered will be the same. This means that even - // though a scroll happened in the DOM, onItemsRendered won't be called. - rendered.getInstance().scrollToItem(96, 'auto'); - // Scroll forward again. Because item #99 was already shown partially, - // all props of the onItemsRendered will be the same. - rendered.getInstance().scrollToItem(99, 'auto'); - // Scroll to the second item. A partial fifth item should - // be shown after it. - rendered.getInstance().scrollToItem(1, 'auto'); - // Scroll to the first item. Now the fourth item should be a partial. - rendered.getInstance().scrollToItem(0, 'auto'); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('scroll with align = "auto" should work with very small lists and partial items', () => { - const rendered = ReactTestRenderer.create( - // Create list with only two items, one of which will be shown as a partial. - - ); - // Show the second item fully. The first item should be a partial. - rendered.getInstance().scrollToItem(1, 'auto'); - // Go back to the first item. The second should be a partial again. - rendered.getInstance().scrollToItem(0, 'auto'); - // None of the scrollToItem calls above should actually cause a scroll, - // so there will only be one snapshot. - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should scroll to the correct item for align = "start"', () => { - const rendered = ReactTestRenderer.create( - - ); - // Scroll down enough to show item 10 at the top. - rendered.getInstance().scrollToItem(10, 'start'); - // Scroll back up so that item 9 is at the top. - // Overscroll direction wil change too. - rendered.getInstance().scrollToItem(9, 'start'); - // Item 99 can't align at the top because there aren't enough items. - // Scroll down as far as possible though. - // Overscroll direction wil change again. - rendered.getInstance().scrollToItem(99, 'start'); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should scroll to the correct item for align = "end"', () => { - const rendered = ReactTestRenderer.create( - - ); - // Scroll down enough to show item 10 at the bottom. - rendered.getInstance().scrollToItem(10, 'end'); - // Scroll back up so that item 9 is at the bottom. - // Overscroll direction wil change too. - rendered.getInstance().scrollToItem(9, 'end'); - // Item 1 can't align at the bottom because it's too close to the beginning. - // Scroll up as far as possible though. - // Overscroll direction wil change again. - rendered.getInstance().scrollToItem(1, 'end'); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should scroll to the correct item for align = "center"', () => { - const rendered = ReactTestRenderer.create( - - ); - // Scroll down enough to show item 10 in the middle. - rendered.getInstance().scrollToItem(10, 'center'); - // Scroll back up so that item 9 is in the middle. - // Overscroll direction wil change too. - rendered.getInstance().scrollToItem(9, 'center'); - // Item 1 can't align in the middle because it's too close to the beginning. - // Scroll up as far as possible though. - // Overscroll direction wil change again. - rendered.getInstance().scrollToItem(1, 'center'); - // Item 99 can't align in the middle because it's too close to the end. - // Scroll down as far as possible though. - // Overscroll direction wil change again. - rendered.getInstance().scrollToItem(99, 'center'); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should scroll to the correct item for align = "smart"', () => { - const rendered = ReactTestRenderer.create( - - ); - // Scroll down enough to show item 10 in the middle. - rendered.getInstance().scrollToItem(10, 'smart'); - // Scrolldn't scroll at all because it's close enough. - rendered.getInstance().scrollToItem(9, 'smart'); - // Should scroll but not center because it's close enough. - rendered.getInstance().scrollToItem(6, 'smart'); - // Item 1 can't align in the middle because it's too close to the beginning. - // Scroll up as far as possible though. - rendered.getInstance().scrollToItem(1, 'smart'); - // Item 99 can't align in the middle because it's too close to the end. - // Scroll down as far as possible though. - rendered.getInstance().scrollToItem(99, 'smart'); - // This shouldn't scroll at all because it's close enough. - rendered.getInstance().scrollToItem(95, 'smart'); - rendered.getInstance().scrollToItem(99, 'smart'); - // This should scroll with the 'auto' behavior because it's within a screen. - rendered.getInstance().scrollToItem(94, 'smart'); - rendered.getInstance().scrollToItem(99, 'smart'); - // This should scroll with the 'center' behavior because it's too far. - rendered.getInstance().scrollToItem(90, 'smart'); - rendered.getInstance().scrollToItem(99, 'smart'); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should not report isScrolling', () => { - // Use ReactDOM renderer so the container ref and "onScroll" work correctly. - const instance = ReactDOM.render( - , - document.createElement('div') - ); - itemRenderer.mockClear(); - instance.scrollToItem(15); - expect(itemRenderer.mock.calls[0][0].isScrolling).toBe(false); - }); - - it('should ignore indexes less than zero', () => { - const instance = ReactDOM.render( - , - document.createElement('div') - ); - instance.scrollToItem(20); - onItemsRendered.mockClear(); - instance.scrollToItem(-1); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should ignore indexes greater than itemCount', () => { - const instance = ReactDOM.render( - , - document.createElement('div') - ); - onItemsRendered.mockClear(); - instance.scrollToItem(defaultProps.itemCount * 2); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should account for scrollbar size', () => { - getScrollbarSize.mockImplementation(() => 20); - - const ref = createRef(); - ReactDOM.render( - , - document.createElement('div') - ); - - // Mimic the vertical list not being horizontally scrollable. - // To be clear, this would be typical. - mockedScrollWidth = 0; - - ref.current.scrollToItem(20, 'auto'); - - // Now mimic the vertical list not being horizontally scrollable, - // and make sure the list accounts for the horizontal scrollbar height. - mockedScrollWidth = Number.MAX_SAFE_INTEGER; - - ref.current.scrollToItem(20, 'auto'); - - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - }); - - // onItemsRendered is pretty well covered by other snapshot tests - describe('onScroll', () => { - it('should call onScroll after mount', () => { - const onScroll = jest.fn(); - ReactTestRenderer.create( - - ); - expect(onScroll.mock.calls).toMatchSnapshot(); - }); - - it('should call onScroll when scroll position changes', () => { - const onScroll = jest.fn(); - const rendered = ReactTestRenderer.create( - - ); - rendered.getInstance().scrollTo(100); - rendered.getInstance().scrollTo(0); - expect(onScroll.mock.calls).toMatchSnapshot(); - }); - - it('should distinguish between "onScroll" events and scrollTo() calls', () => { - const onScroll = jest.fn(); - // Use ReactDOM renderer so the container ref and "onScroll" event work correctly. - const instance = ReactDOM.render( - , - document.createElement('div') - ); - - onScroll.mockClear(); - instance.scrollTo(100); - expect(onScroll.mock.calls[0][0].scrollUpdateWasRequested).toBe(true); - - onScroll.mockClear(); - simulateScroll(instance, 200); - expect(onScroll.mock.calls[0][0].scrollUpdateWasRequested).toBe(false); - }); - - it('scrolling should report partial items correctly in onItemsRendered', () => { - // Use ReactDOM renderer so the container ref works correctly. - const instance = ReactDOM.render( - , - document.createElement('div') - ); - // Scroll 2 items fwd, but thanks to the initialScrollOffset, we should - // still be showing partials on both ends. - simulateScroll(instance, 70); - // Scroll a little fwd to cause partials to be hidden - simulateScroll(instance, 75); - // Scroll backwards to show partials again - simulateScroll(instance, 70); - // Scroll near the end so that the last item is shown - // as a partial. - simulateScroll(instance, 96 * 25 - 5); - // Scroll to the end. No partials. - simulateScroll(instance, 96 * 25); - // Verify that backwards scrolling near the end works OK. - simulateScroll(instance, 96 * 25 - 5); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - }); - - describe('itemKey', () => { - it('should be used', () => { - const itemKey = jest.fn(index => index); - ReactTestRenderer.create( - - ); - expect(itemKey).toHaveBeenCalledTimes(3); - expect(itemKey.mock.calls[0][0]).toBe(0); - expect(itemKey.mock.calls[1][0]).toBe(1); - expect(itemKey.mock.calls[2][0]).toBe(2); - }); - - it('should allow items to be moved within the collection without causing caching problems', () => { - const keyMap = ['0', '1', '2']; - const keyMapItemRenderer = jest.fn(({ index, style }) => ( -
{keyMap[index]}
- )); - class ItemRenderer extends PureComponent { - render() { - return keyMapItemRenderer(this.props); - } - } - const itemKey = jest.fn(index => keyMap[index]); - const rendered = ReactTestRenderer.create( - - {ItemRenderer} - - ); - expect(itemKey).toHaveBeenCalledTimes(3); - itemKey.mockClear(); - - expect(keyMapItemRenderer).toHaveBeenCalledTimes(3); - keyMapItemRenderer.mockClear(); - - // Simulate swapping the first and last items. - keyMap[0] = '2'; - keyMap[2] = '0'; - - rendered.getInstance().forceUpdate(); - - // Our key getter should be called again for each key. - // Since we've modified the map, the first and last key will swap. - expect(itemKey).toHaveBeenCalledTimes(3); - - // The first and third item have swapped place, - // So they should have been re-rendered, - // But the second item should not. - expect(keyMapItemRenderer).toHaveBeenCalledTimes(2); - expect(keyMapItemRenderer.mock.calls[0][0].index).toBe(0); - expect(keyMapItemRenderer.mock.calls[1][0].index).toBe(2); - }); - - it('should receive a data value if itemData is provided', () => { - const itemKey = jest.fn(index => index); - const itemData = {}; - ReactTestRenderer.create( - - ); - expect(itemKey).toHaveBeenCalled(); - expect( - itemKey.mock.calls.filter(([index, data]) => data === itemData) - ).toHaveLength(itemKey.mock.calls.length); - }); - }); - - describe('refs', () => { - it('should pass through innerRef and outerRef ref functions', () => { - const innerRef = jest.fn(); - const outerRef = jest.fn(); - ReactDOM.render( - , - document.createElement('div') - ); - expect(innerRef).toHaveBeenCalled(); - expect(innerRef.mock.calls[0][0]).toBeInstanceOf(HTMLDivElement); - expect(outerRef).toHaveBeenCalled(); - expect(outerRef.mock.calls[0][0]).toBeInstanceOf(HTMLDivElement); - }); - - it('should pass through innerRef and outerRef createRef objects', () => { - const innerRef = createRef(); - const outerRef = createRef(); - ReactDOM.render( - , - document.createElement('div') - ); - expect(innerRef.current).toBeInstanceOf(HTMLDivElement); - expect(outerRef.current).toBeInstanceOf(HTMLDivElement); - }); - }); - - describe('custom element types', () => { - it('should use a custom innerElementType if specified', () => { - const rendered = ReactTestRenderer.create( - - ); - expect(rendered.root.findByType('section')).toBeDefined(); - }); - - it('should use a custom outerElementType if specified', () => { - const rendered = ReactTestRenderer.create( - - ); - expect(rendered.root.findByType('section')).toBeDefined(); - }); - - it('should support spreading additional, arbitrary props, e.g. id', () => { - const container = document.createElement('div'); - ReactDOM.render( - ( -
- ))} - outerElementType={forwardRef((props, ref) => ( -
- ))} - />, - container - ); - expect(container.firstChild.id).toBe('outer'); - expect(container.firstChild.firstChild.id).toBe('inner'); - }); - - it('should warn if legacy innerTagName or outerTagName props are used', () => { - spyOn(console, 'warn'); - - const renderer = ReactTestRenderer.create( - - ); - expect(console.warn).toHaveBeenCalledTimes(1); - expect(console.warn).toHaveBeenLastCalledWith( - 'The innerTagName and outerTagName props have been deprecated. ' + - 'Please use the innerElementType and outerElementType props instead.' - ); - - renderer.update( - - ); - - // But it should only warn once. - expect(console.warn).toHaveBeenCalledTimes(1); - }); - - it('should warn if legacy direction "horizontal" value is used', () => { - spyOn(console, 'warn'); - - const renderer = ReactTestRenderer.create( - - ); - expect(console.warn).toHaveBeenCalledTimes(1); - expect(console.warn).toHaveBeenLastCalledWith( - 'The direction prop should be either "ltr" (default) or "rtl". ' + - 'Please use the layout prop to specify "vertical" (default) or "horizontal" orientation.' - ); - - renderer.update( - - ); - - // But it should only warn once. - expect(console.warn).toHaveBeenCalledTimes(1); - }); - - it('should warn if legacy direction "vertical" value is used', () => { - spyOn(console, 'warn'); - - const renderer = ReactTestRenderer.create( - - ); - expect(console.warn).toHaveBeenCalledTimes(1); - expect(console.warn).toHaveBeenLastCalledWith( - 'The direction prop should be either "ltr" (default) or "rtl". ' + - 'Please use the layout prop to specify "vertical" (default) or "horizontal" orientation.' - ); - - renderer.update(); - - // But it should only warn once. - expect(console.warn).toHaveBeenCalledTimes(1); - }); - }); - - describe('itemData', () => { - it('should pass itemData to item renderers as a "data" prop', () => { - const itemData = {}; - ReactTestRenderer.create( - - ); - expect(itemRenderer).toHaveBeenCalled(); - expect( - itemRenderer.mock.calls.filter(([params]) => params.data === itemData) - ).toHaveLength(itemRenderer.mock.calls.length); - }); - - it('should re-render items if itemData changes', () => { - const itemData = {}; - const rendered = ReactTestRenderer.create( - - ); - expect(itemRenderer).toHaveBeenCalled(); - itemRenderer.mockClear(); - - // Re-rendering should not affect pure sCU children: - rendered.update(); - expect(itemRenderer).not.toHaveBeenCalled(); - - // Re-rendering with new itemData should re-render children: - const newItemData = {}; - rendered.update( - - ); - expect(itemRenderer).toHaveBeenCalled(); - expect( - itemRenderer.mock.calls.filter( - ([params]) => params.data === newItemData - ) - ).toHaveLength(itemRenderer.mock.calls.length); - }); - }); - - describe('props validation', () => { - beforeEach(() => spyOn(console, 'error')); - - it('should fail if non-numeric itemSize is provided', () => { - expect(() => - ReactTestRenderer.create( - - ) - ).toThrow( - 'An invalid "itemSize" prop has been specified. ' + - 'Value should be a number. ' + - '"string" was specified.' - ); - }); - - it('should fail if no children value is provided', () => { - expect(() => - ReactTestRenderer.create( - - ) - ).toThrow( - 'An invalid "children" prop has been specified. ' + - 'Value should be a React component. ' + - '"undefined" was specified.' - ); - }); - - it('should fail if an invalid layout is provided', () => { - expect(() => - ReactTestRenderer.create( - - ) - ).toThrow( - 'An invalid "layout" prop has been specified. ' + - 'Value should be either "horizontal" or "vertical". ' + - '"null" was specified.' - ); - }); - - it('should fail if an invalid direction is provided', () => { - expect(() => - ReactTestRenderer.create( - - ) - ).toThrow( - 'An invalid "direction" prop has been specified. ' + - 'Value should be either "ltr" or "rtl". ' + - '"null" was specified.' - ); - }); - - it('should fail if a string height is provided for a vertical list', () => { - expect(() => - ReactTestRenderer.create( - - ) - ).toThrow( - 'An invalid "height" prop has been specified. ' + - 'Vertical lists must specify a number for height. ' + - '"string" was specified.' - ); - }); - - it('should fail if a string width is provided for a horizontal list', () => { - expect(() => - ReactTestRenderer.create( - - ) - ).toThrow( - 'An invalid "width" prop has been specified. ' + - 'Horizontal lists must specify a number for width. ' + - '"string" was specified.' - ); - }); - }); -}); diff --git a/src/__tests__/VariableSizeGrid.js b/src/__tests__/VariableSizeGrid.js deleted file mode 100644 index 05661656..00000000 --- a/src/__tests__/VariableSizeGrid.js +++ /dev/null @@ -1,779 +0,0 @@ -import React, { createRef, PureComponent } from 'react'; -import { render } from 'react-dom'; -import { Simulate } from 'react-dom/test-utils'; -import ReactTestRenderer from 'react-test-renderer'; -import { VariableSizeGrid } from '..'; -import * as domHelpers from '../domHelpers'; - -const simulateScroll = (instance, { scrollLeft, scrollTop }) => { - instance._outerRef.scrollLeft = scrollLeft; - instance._outerRef.scrollTop = scrollTop; - Simulate.scroll(instance._outerRef); -}; - -const findScrollContainer = rendered => rendered.root.children[0].children[0]; - -describe('VariableSizeGrid', () => { - let columnWidth, - defaultProps, - getScrollbarSize, - itemRenderer, - onItemsRendered, - rowHeight; - - // Use PureComponent to test memoization. - // Pass through to itemRenderer mock for easier test assertions. - class PureItemRenderer extends PureComponent { - render() { - return itemRenderer(this.props); - } - } - - const findItemRendererCall = (rowIndex: number, columnIndex: number) => { - const found = itemRenderer.mock.calls.find( - ([params]) => - params.rowIndex === rowIndex && params.columnIndex === columnIndex - ); - return found.length === 1 ? found[0] : null; - }; - - beforeEach(() => { - jest.useFakeTimers(); - - // JSdom does not do actual layout and so doesn't return meaningful values here. - // For the purposes of our tests though, we can mock out semi-meaningful values. - // This mock is required for e.g. "onScroll" tests to work properly. - Object.defineProperties(HTMLElement.prototype, { - clientWidth: { - configurable: true, - get: function() { - return parseInt(this.style.width, 10) || 0; - }, - }, - clientHeight: { - configurable: true, - get: function() { - return parseInt(this.style.height, 10) || 0; - }, - }, - scrollHeight: { - configurable: true, - get: () => Number.MAX_SAFE_INTEGER, - }, - scrollWidth: { - configurable: true, - get: () => Number.MAX_SAFE_INTEGER, - }, - }); - - // Mock the DOM helper util for testing purposes. - getScrollbarSize = domHelpers.getScrollbarSize = jest.fn(() => 0); - - itemRenderer = jest.fn(({ style, ...rest }) => ( -
{JSON.stringify(rest, null, 2)}
- )); - onItemsRendered = jest.fn(); - columnWidth = jest.fn(index => 50 + index); - rowHeight = jest.fn(index => 25 + index); - defaultProps = { - children: PureItemRenderer, - columnCount: 10, - columnWidth, - height: 100, - onItemsRendered, - rowCount: 20, - rowHeight, - width: 200, - }; - }); - - // Much of the shared Grid functionality is already tested by VariableSizeGrid tests. - // This test covers functionality that is unique to VariableSizeGrid. - - it('should render an empty grid', () => { - ReactTestRenderer.create( - - ); - ReactTestRenderer.create( - - ); - ReactTestRenderer.create( - - ); - expect(itemRenderer).not.toHaveBeenCalled(); - expect(columnWidth).not.toHaveBeenCalled(); - expect(rowHeight).not.toHaveBeenCalled(); - expect(onItemsRendered).not.toHaveBeenCalled(); - }); - - it('changing item size does not impact the rendered items', () => { - const rendered = ReactTestRenderer.create( - - ); - itemRenderer.mockClear(); - rendered.update( - 100} - rowHeight={index => 50} - /> - ); - expect(itemRenderer).not.toHaveBeenCalled(); - }); - - describe('estimatedColumnWidth and estimatedRowHeight', () => { - it('should estimate an initial scrollable size based on this value', () => { - const columnWidth = jest.fn(() => 50); - const rowHeight = jest.fn(() => 25); - const rendered = ReactTestRenderer.create( - - ); - // We'll render 5 columns and 5 rows initially (250px wide by 125px tall). - // The remaining 45 columns and 45 rows will be estimated (9,000px wide by 4,500px tall). - expect(columnWidth).toHaveBeenCalledTimes(5); - expect(rowHeight).toHaveBeenCalledTimes(5); - const scrollContainer = findScrollContainer(rendered); - expect(scrollContainer.props.style.width).toEqual(9250); - expect(scrollContainer.props.style.height).toEqual(4625); - }); - - it('should udpate the scrollable size as more items are measured', () => { - const columnWidth = jest.fn(() => 50); - const rowHeight = jest.fn(() => 25); - const rendered = ReactTestRenderer.create( - - ); - rendered.getInstance().scrollToItem({ columnIndex: 13, rowIndex: 23 }); - // At this point we have measured 15 columns and 25 rows (750px wide by 625px tall). - // The remaining 35 columns and 25 rows will be estimated (7,000px wide by 2,500px tall). - expect(columnWidth).toHaveBeenCalledTimes(15); - expect(rowHeight).toHaveBeenCalledTimes(25); - const scrollContainer = findScrollContainer(rendered); - expect(scrollContainer.props.style.width).toEqual(7750); - expect(scrollContainer.props.style.height).toEqual(3125); - }); - }); - - describe('scrollToItem method', () => { - it('should not set invalid offsets when the list contains few items', () => { - const onScroll = jest.fn(); - const rendered = ReactTestRenderer.create( - - ); - expect(onItemsRendered).toMatchSnapshot(); - onItemsRendered.mockClear(); - rendered.getInstance().scrollToItem(0); - expect(onItemsRendered).not.toHaveBeenCalled(); - }); - - it('should scroll to the correct item for align = "auto"', () => { - const rendered = ReactTestRenderer.create( - - ); - // Scroll down enough to show item 10 at the bottom. - rendered - .getInstance() - .scrollToItem({ columnIndex: 5, rowIndex: 5, align: 'auto' }); - // No need to scroll again; item 9 is already visible. - // Overscan indices will change though, since direction changes. - rendered - .getInstance() - .scrollToItem({ columnIndex: 4, rowIndex: 4, align: 'auto' }); - // Scroll up enough to show item 2 at the top. - rendered - .getInstance() - .scrollToItem({ columnIndex: 2, rowIndex: 2, align: 'auto' }); - // Scroll down to row 10, without changing scrollLeft - rendered.getInstance().scrollToItem({ rowIndex: 10, align: 'auto' }); - // Scroll left to column 0, without changing scrollTop - rendered.getInstance().scrollToItem({ columnIndex: 0, align: 'auto' }); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('scroll with align = "auto" should work with partially-visible items', () => { - const rendered = ReactTestRenderer.create( - // Create list where items don't fit exactly into container. - // The container has space for 3 1/3 items. - 70} - rowCount={100} - rowHeight={() => 30} - /> - ); - // Scroll down enough to show row 10 at the bottom a nd column 10 at the right. - // Should show 4 rows: 3 full and one partial at the beginning - // Should show 3 columns: 2 full and one partial at the beginning - rendered - .getInstance() - .scrollToItem({ columnIndex: 10, rowIndex: 10, align: 'auto' }); - // No need to scroll again; row and column 9 are already visible. - // Because there's no scrolling, it won't call onItemsRendered. - rendered - .getInstance() - .scrollToItem({ columnIndex: 9, rowIndex: 9, align: 'auto' }); - // Scroll to near the end. row 96 and column 97 will be partly visible. - rendered - .getInstance() - .scrollToItem({ columnIndex: 99, rowIndex: 99, align: 'auto' }); - // Scroll back to row 91 and column 97. - // This will cause row 99 and column 99 to be partly viisble - // Even though a scroll happened, none of the items rendered have changed. - rendered - .getInstance() - .scrollToItem({ columnIndex: 97, rowIndex: 96, align: 'auto' }); - // Scroll forward again. Because row and column #99 were already partly visible, - // all props of the onItemsRendered will be the same. - rendered - .getInstance() - .scrollToItem({ columnIndex: 99, rowIndex: 99, align: 'auto' }); - // Scroll to the second row and column. - // This should leave row 4 and column 3 partly visible. - rendered - .getInstance() - .scrollToItem({ columnIndex: 1, rowIndex: 1, align: 'auto' }); - // Scroll to the first row and column. - // This should leave row 3 and column 2 partly visible. - rendered - .getInstance() - .scrollToItem({ columnIndex: 0, rowIndex: 0, align: 'auto' }); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should scroll to the correct item for align = "auto" at the bottom of the grid', () => { - getScrollbarSize.mockImplementation(() => 20); - - const rendered = ReactTestRenderer.create( - - ); - onItemsRendered.mockClear(); - - // Scroll down to the last row in the list. - rendered - .getInstance() - .scrollToItem({ columnIndex: 5, rowIndex: 19, align: 'auto' }); - - expect(onItemsRendered).toHaveBeenCalledTimes(1); - expect(onItemsRendered).toHaveBeenLastCalledWith( - expect.objectContaining({ - visibleRowStartIndex: 18, - visibleRowStopIndex: 19, - }) - ); - // Repeat the previous scrollToItem call. - rendered - .getInstance() - .scrollToItem({ columnIndex: 5, rowIndex: 19, align: 'auto' }); - - // Shouldn't have been called again - expect(onItemsRendered).toHaveBeenCalledTimes(1); - expect(onItemsRendered).toHaveBeenLastCalledWith( - expect.objectContaining({ - visibleRowStartIndex: 18, - visibleRowStopIndex: 19, - }) - ); - }); - - it('should scroll to the correct item for align = "auto" at the right hand side of the grid', () => { - getScrollbarSize.mockImplementation(() => 20); - - const rendered = ReactTestRenderer.create( - - ); - onItemsRendered.mockClear(); - - // Scroll scross to the last row in the list. - rendered - .getInstance() - .scrollToItem({ columnIndex: 9, rowIndex: 10, align: 'auto' }); - - expect(onItemsRendered).toHaveBeenCalledTimes(1); - expect(onItemsRendered).toHaveBeenLastCalledWith( - expect.objectContaining({ - visibleColumnStartIndex: 8, - visibleColumnStopIndex: 9, - }) - ); - // Repeat the previous scrollToItem call. - rendered - .getInstance() - .scrollToItem({ columnIndex: 9, rowIndex: 10, align: 'auto' }); - - // Shouldn't have been called again - expect(onItemsRendered).toHaveBeenCalledTimes(1); - expect(onItemsRendered).toHaveBeenLastCalledWith( - expect.objectContaining({ - visibleColumnStartIndex: 8, - visibleColumnStopIndex: 9, - }) - ); - }); - - it('should scroll to the correct item for align = "start"', () => { - const rendered = ReactTestRenderer.create( - - ); - // Scroll down enough to show item 10 at the top. - rendered - .getInstance() - .scrollToItem({ columnIndex: 5, rowIndex: 5, align: 'start' }); - // Scroll back up so that item 9 is at the top. - // Overscroll direction wil change too. - rendered - .getInstance() - .scrollToItem({ columnIndex: 4, rowIndex: 4, align: 'start' }); - // Item 99 can't align at the top because there aren't enough items. - // Scroll down as far as possible though. - // Overscroll direction wil change again. - rendered - .getInstance() - .scrollToItem({ columnIndex: 9, rowIndex: 19, align: 'start' }); - // Scroll up to row 10, without changing scrollLeft - rendered.getInstance().scrollToItem({ rowIndex: 10, align: 'start' }); - // Scroll left to column 0, without changing scrollTop - rendered.getInstance().scrollToItem({ columnIndex: 0, align: 'start' }); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should scroll to the correct item for align = "end"', () => { - const rendered = ReactTestRenderer.create( - - ); - // Scroll down enough to show item 10 at the bottom. - rendered - .getInstance() - .scrollToItem({ columnIndex: 5, rowIndex: 5, align: 'end' }); - // Scroll back up so that item 9 is at the bottom. - // Overscroll direction wil change too. - rendered - .getInstance() - .scrollToItem({ columnIndex: 4, rowIndex: 4, align: 'end' }); - // Item 1 can't align at the bottom because it's too close to the beginning. - // Scroll up as far as possible though. - // Overscroll direction wil change again. - rendered - .getInstance() - .scrollToItem({ columnIndex: 1, rowIndex: 1, align: 'end' }); - // Scroll down to row 10, without changing scrollLeft - rendered.getInstance().scrollToItem({ rowIndex: 10, align: 'end' }); - // Scroll right to column 9, without changing scrollTop - rendered.getInstance().scrollToItem({ columnIndex: 9, align: 'end' }); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should scroll to the correct item for align = "center"', () => { - const rendered = ReactTestRenderer.create( - - ); - // Scroll down enough to show item 10 in the middle. - rendered - .getInstance() - .scrollToItem({ columnIndex: 5, rowIndex: 5, align: 'center' }); - // Scroll back up so that item 9 is in the middle. - // Overscroll direction wil change too. - rendered - .getInstance() - .scrollToItem({ columnIndex: 4, rowIndex: 4, align: 'center' }); - // Item 1 can't align in the middle because it's too close to the beginning. - // Scroll up as far as possible though. - // Overscroll direction wil change again. - rendered - .getInstance() - .scrollToItem({ columnIndex: 1, rowIndex: 1, align: 'center' }); - // Item 99 can't align in the middle because it's too close to the end. - // Scroll down as far as possible though. - // Overscroll direction wil change again. - rendered - .getInstance() - .scrollToItem({ columnIndex: 9, rowIndex: 19, align: 'center' }); - // Scroll up to row 10, without changing scrollLeft - rendered.getInstance().scrollToItem({ rowIndex: 10, align: 'center' }); - // Scroll left to column 3, without changing scrollTop - rendered.getInstance().scrollToItem({ columnIndex: 3, align: 'center' }); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should scroll to the correct item for align = "smart"', () => { - const rendered = ReactTestRenderer.create( - - ); - - // Scroll down enough to show item 10 at the center. - // It was further than one screen away, so it gets centered. - rendered - .getInstance() - .scrollToItem({ columnIndex: 10, rowIndex: 10, align: 'smart' }); - // No need to scroll again; item 9 is already visible. - // Overscan indices will change though, since direction changes. - rendered - .getInstance() - .scrollToItem({ columnIndex: 9, rowIndex: 9, align: 'smart' }); - // Scroll up enough to show item 2 as close to the center as we can. - rendered - .getInstance() - .scrollToItem({ columnIndex: 2, rowIndex: 2, align: 'smart' }); - // Scroll down to row 10, without changing scrollLeft - rendered.getInstance().scrollToItem({ rowIndex: 10, align: 'smart' }); - // Scroll left to column 0, without changing scrollTop - rendered.getInstance().scrollToItem({ columnIndex: 0, align: 'smart' }); - - // Scrolling within a distance of a single screen from viewport - // should have the 'auto' behavior of scrolling as little as possible. - rendered - .getInstance() - .scrollToItem({ columnIndex: 5, rowIndex: 5, align: 'smart' }); - rendered - .getInstance() - .scrollToItem({ columnIndex: 10, rowIndex: 10, align: 'smart' }); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should account for scrollbar size', () => { - const onScroll = jest.fn(); - const rendered = ReactTestRenderer.create( - - ); - - onScroll.mockClear(); - rendered - .getInstance() - .scrollToItem({ columnIndex: 5, rowIndex: 10, align: 'end' }); - - // With hidden scrollbars (size === 0) we would expect... - expect(onScroll).toHaveBeenCalledWith({ - horizontalScrollDirection: 'forward', - scrollLeft: 115, - scrollTop: 230, - scrollUpdateWasRequested: true, - verticalScrollDirection: 'forward', - }); - - getScrollbarSize.mockImplementation(() => 20); - - onScroll.mockClear(); - rendered - .getInstance() - .scrollToItem({ columnIndex: 5, rowIndex: 10, align: 'end' }); - - // With scrollbars of size 20 we would expect those values ot increase by 20px - expect(onScroll).toHaveBeenCalledWith({ - horizontalScrollDirection: 'forward', - scrollLeft: 135, - scrollTop: 250, - scrollUpdateWasRequested: true, - verticalScrollDirection: 'forward', - }); - }); - - it('should not account for scrollbar size when no scrollbar is visible for a particular direction', () => { - getScrollbarSize.mockImplementation(() => 20); - - const onScroll = jest.fn(); - const rendered = ReactTestRenderer.create( - - ); - - onScroll.mockClear(); - rendered - .getInstance() - .scrollToItem({ columnIndex: 0, rowIndex: 10, align: 'end' }); - - // Since there aren't enough columns to require horizontal scrolling, - // the additional 20px for the scrollbar should not be taken into consideration. - expect(onScroll).toHaveBeenCalledWith({ - horizontalScrollDirection: 'backward', - scrollLeft: 0, - scrollTop: 230, - scrollUpdateWasRequested: true, - verticalScrollDirection: 'forward', - }); - - rendered.update( - - ); - - onScroll.mockClear(); - rendered - .getInstance() - .scrollToItem({ columnIndex: 5, rowIndex: 0, align: 'end' }); - - // Since there aren't enough rows to require vertical scrolling, - // the additional 20px for the scrollbar should not be taken into consideration. - expect(onScroll).toHaveBeenCalledWith({ - horizontalScrollDirection: 'forward', - scrollLeft: 115, - scrollTop: 0, - scrollUpdateWasRequested: true, - verticalScrollDirection: 'backward', - }); - }); - }); - - describe('resetAfterIndex method', () => { - it('should recalculate the estimated total size', () => { - const columnWidth = jest.fn(() => 75); - const rowHeight = jest.fn(() => 35); - const rendered = ReactTestRenderer.create( - 50} - rowHeight={index => 25} - /> - ); - rendered.getInstance().scrollToItem({ columnIndex: 9, rowIndex: 19 }); - // We've measured every item initially. - const scrollContainer = findScrollContainer(rendered); - expect(scrollContainer.props.style.height).toEqual(500); - expect(scrollContainer.props.style.width).toEqual(500); - // Supplying new item sizes alone should not impact anything. - rendered.update( - - ); - expect(scrollContainer.props.style.height).toEqual(500); - expect(scrollContainer.props.style.width).toEqual(500); - // Reset styles after index 75, - // And verify that the new estimated total takes this into account. - // This means 5 columns at 50px each and 5 at 75px each (625), - // And 15 rows at 25px each and 5 at 35px each (550px). - rendered - .getInstance() - .resetAfterIndices({ columnIndex: 5, rowIndex: 15 }); - rendered.getInstance().scrollToItem({ columnIndex: 9, rowIndex: 19 }); - expect(columnWidth).toHaveBeenCalledTimes(5); - expect(rowHeight).toHaveBeenCalledTimes(5); - expect(scrollContainer.props.style.height).toEqual(550); - expect(scrollContainer.props.style.width).toEqual(625); - }); - - it('should delay the recalculation of the estimated total size if shouldForceUpdate is false', () => { - const rendered = ReactTestRenderer.create( - 50} - rowHeight={index => 25} - /> - ); - const scrollContainer = findScrollContainer(rendered); - // The estimated total height should be (100 + 25 * 1 + 30 * 15)px = 575px. - // The estimated total width should be (200 + 50 * 1 + 30 * 5)px = 400px. - expect(scrollContainer.props.style.height).toEqual(575); - expect(scrollContainer.props.style.width).toEqual(400); - // Supplying new item sizes alone should not impact anything. - // Although the grid get re-rendered by passing inline functions, - // but it still use the cached metrics to calculate the estimated size. - rendered.update( - 40} - rowHeight={index => 20} - /> - ); - expect(scrollContainer.props.style.height).toEqual(575); - expect(scrollContainer.props.style.width).toEqual(400); - // Reset calculation cache but don't re-render the grid, - // the estimated total size should stay the same. - rendered.getInstance().resetAfterIndices({ - columnIndex: 0, - rowIndex: 0, - shouldForceUpdate: false, - }); - expect(scrollContainer.props.style.height).toEqual(575); - expect(scrollContainer.props.style.width).toEqual(400); - // Pass inline function to make the grid re-render. - rendered.update( - 40} - rowHeight={index => 20} - /> - ); - // The estimated total height should be (100 + 20 * 1 + 30 * 14)px = 540px. - // The estimated total width should be (200 + 40 * 1 + 30 * 4)px = 360px. - expect(scrollContainer.props.style.height).toEqual(540); - expect(scrollContainer.props.style.width).toEqual(360); - }); - - it('should re-render items after the specified index with updated styles', () => { - const columnWidth = jest.fn(() => 75); - const rowHeight = jest.fn(() => 35); - const rendered = ReactTestRenderer.create( - 50} - rowHeight={index => 25} - /> - ); - // We've rendered 5 columns and 5 rows initially. - expect(itemRenderer).toHaveBeenCalledTimes(25); - expect(findItemRendererCall(3, 3).style.height).toBe(25); - expect(findItemRendererCall(3, 3).style.width).toBe(50); - // Supplying new item sizes alone should not impact anything. - rendered.update( - - ); - // Reset styles for columns and rows 4 and 5. - // And verify that the affected rows are re-rendered with new styles. - itemRenderer.mockClear(); - rendered.getInstance().resetAfterIndices({ columnIndex: 3, rowIndex: 3 }); - expect(itemRenderer).toHaveBeenCalledTimes(25); - expect(findItemRendererCall(3, 3).style.height).toBe(35); - expect(findItemRendererCall(3, 3).style.width).toBe(75); - }); - }); - - describe('props validation', () => { - beforeEach(() => spyOn(console, 'error')); - - it('should fail if non-function columnWidth is provided', () => { - expect(() => - ReactTestRenderer.create( - - ) - ).toThrow( - 'An invalid "columnWidth" prop has been specified. ' + - 'Value should be a function. "number" was specified.' - ); - }); - - it('should fail if non-function rowHeight is provided', () => { - expect(() => - ReactTestRenderer.create( - - ) - ).toThrow( - 'An invalid "rowHeight" prop has been specified. ' + - 'Value should be a function. "number" was specified.' - ); - }); - }); - - describe('onScroll', () => { - it('scrolling should report partial items correctly in onItemsRendered', () => { - // Use ReactDOM renderer so the container ref works correctly. - const instance = render( - 100} - initialScrollLeft={20} - initialScrollTop={10} - rowCount={100} - rowHeight={() => 25} - />, - document.createElement('div') - ); - // grid 200w x 100h - // columnWidth: 100, rowHeight: 25, - // columnCount: 100, rowCount: 100 - // Scroll 2 items fwd, but thanks to the initialScrollOffset, we should - // still be showing partials on both ends. - instance.scrollTo({ scrollLeft: 150, scrollTop: 40 }); - // Scroll a little fwd to cause partials to be hidden - instance.scrollTo({ scrollLeft: 200, scrollTop: 50 }); - // Scroll backwards to show partials again - instance.scrollTo({ scrollLeft: 150, scrollTop: 40 }); - // Scroll near the end so that the last item is shown - // as a partial. - instance.scrollTo({ - scrollLeft: 98 * 100 - 5, - scrollTop: 96 * 25 - 5, - }); - // Scroll to the end. No partials. - instance.scrollTo({ - scrollLeft: 98 * 100, - scrollTop: 96 * 25, - }); - // Verify that backwards scrolling near the end works OK. - instance.scrollTo({ - scrollLeft: 98 * 100 - 5, - scrollTop: 96 * 25 - 5, - }); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - }); - - // https://github.com/bvaughn/react-window/pull/138 - it('should descrease scroll size when itemCount decreases', () => { - const innerRef = createRef(); - const gridRef = createRef(); - - class Wrapper extends PureComponent { - state = { columnCount: 100, rowCount: 200 }; - render() { - return ( - - ); - } - } - - // Use ReactDOM renderer so "scroll" events work correctly. - const instance = render(, document.createElement('div')); - - // Simulate scrolling past several rows. - simulateScroll(gridRef.current, { scrollLeft: 3000, scrollTop: 4000 }); - - // Decrease itemCount a lot and verify the scroll height is descreased as well. - instance.setState({ columnCount: 2, rowCount: 4 }); - expect(innerRef.current.style.height).toEqual('106px'); - expect(innerRef.current.style.width).toEqual('101px'); - }); -}); diff --git a/src/__tests__/VariableSizeList.js b/src/__tests__/VariableSizeList.js deleted file mode 100644 index 2396e769..00000000 --- a/src/__tests__/VariableSizeList.js +++ /dev/null @@ -1,442 +0,0 @@ -import React, { createRef, PureComponent } from 'react'; -import { render } from 'react-dom'; -import { Simulate } from 'react-dom/test-utils'; -import ReactTestRenderer from 'react-test-renderer'; -import { VariableSizeList } from '..'; - -const simulateScroll = (instance, scrollOffset, direction = 'vertical') => { - if (direction === 'horizontal') { - instance._outerRef.scrollLeft = scrollOffset; - } else { - instance._outerRef.scrollTop = scrollOffset; - } - Simulate.scroll(instance._outerRef); -}; - -const findScrollContainer = rendered => rendered.root.children[0].children[0]; - -describe('VariableSizeList', () => { - let itemRenderer, itemSize, defaultProps, onItemsRendered; - - // Use PureComponent to test memoization. - // Pass through to itemRenderer mock for easier test assertions. - class PureItemRenderer extends PureComponent { - render() { - return itemRenderer(this.props); - } - } - - beforeEach(() => { - jest.useFakeTimers(); - - // JSdom does not do actual layout and so doesn't return meaningful values here. - // For the purposes of our tests though, we can mock out semi-meaningful values. - // This mock is required for e.g. "onScroll" tests to work properly. - Object.defineProperties(HTMLElement.prototype, { - clientWidth: { - configurable: true, - get: function() { - return parseInt(this.style.width, 10) || 0; - }, - }, - clientHeight: { - configurable: true, - get: function() { - return parseInt(this.style.height, 10) || 0; - }, - }, - scrollHeight: { - configurable: true, - get: () => Number.MAX_SAFE_INTEGER, - }, - scrollWidth: { - configurable: true, - get: () => Number.MAX_SAFE_INTEGER, - }, - }); - - itemRenderer = jest.fn(({ style, ...rest }) => ( -
{JSON.stringify(rest, null, 2)}
- )); - itemSize = jest.fn(index => 25 + index); - onItemsRendered = jest.fn(); - defaultProps = { - children: PureItemRenderer, - estimatedItemSize: 25, - height: 100, - itemCount: 20, - itemSize, - onItemsRendered, - width: 50, - }; - }); - - // Much of the shared List functionality is already tested by FixedSizeList tests. - // This test covers functionality that is unique to VariableSizeList. - - it('should render an empty list', () => { - ReactTestRenderer.create( - - ); - expect(itemSize).not.toHaveBeenCalled(); - expect(itemRenderer).not.toHaveBeenCalled(); - expect(onItemsRendered).not.toHaveBeenCalled(); - }); - - it('changing itemSize does not impact the rendered items', () => { - const rendered = ReactTestRenderer.create( - - ); - itemRenderer.mockClear(); - rendered.update( - 50} - onItemsRendered={onItemsRendered} - /> - ); - expect(itemRenderer).not.toHaveBeenCalled(); - }); - - describe('estimatedItemSize', () => { - it('should estimate an initial scrollable size based on this value', () => { - const itemSize = jest.fn(() => 25); - const rendered = ReactTestRenderer.create( - - ); - // We'll render 5 rows initially, each at 25px tall (125px total). - // The remaining 95 rows will be estimated at 50px tall (4,750px total). - // This means an initial height estimate of 4,875px. - expect(itemSize).toHaveBeenCalledTimes(5); - const scrollContainer = findScrollContainer(rendered); - expect(scrollContainer.props.style.height).toEqual(4875); - }); - - it('should udpate the scrollable size as more items are measured', () => { - const itemSize = jest.fn(() => 25); - const rendered = ReactTestRenderer.create( - - ); - rendered.getInstance().scrollToItem(18); - // Including the additional 1 (minimum) overscan row, - // We've now measured 20 rows, each at 25px tall (500px total). - // The remaining 80 rows will be estimated at 50px tall (4,500px total). - // This means an updated height estimate of 4,500px. - expect(itemSize).toHaveBeenCalledTimes(20); - const scrollContainer = findScrollContainer(rendered); - expect(scrollContainer.props.style.height).toEqual(4500); - }); - }); - - describe('scrollToItem method', () => { - it('should not set invalid offsets when the list contains few items', () => { - const onScroll = jest.fn(); - const rendered = ReactTestRenderer.create( - - ); - expect(onItemsRendered).toMatchSnapshot(); - onItemsRendered.mockClear(); - rendered.getInstance().scrollToItem(0); - expect(onItemsRendered).not.toHaveBeenCalled(); - }); - - it('should scroll to the correct item for align = "auto"', () => { - const onItemsRendered = jest.fn(); - const rendered = ReactTestRenderer.create( - - ); - // Scroll down enough to show item 10 at the bottom. - rendered.getInstance().scrollToItem(10, 'auto'); - // No need to scroll again; item 9 is already visible. - // Overscan indices will change though, since direction changes. - rendered.getInstance().scrollToItem(9, 'auto'); - // Scroll up enough to show item 2 at the top. - rendered.getInstance().scrollToItem(2, 'auto'); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('scroll with align = "auto" should work with partially-visible items', () => { - const rendered = ReactTestRenderer.create( - // Create list where items don't fit exactly into container. - // The container has space for 3 1/3 items. - 30} - /> - ); - // Scroll down enough to show item 10 at the bottom. - // Should show 4 items: 3 full and one partial at the beginning - rendered.getInstance().scrollToItem(10, 'auto'); - // No need to scroll again; item 9 is already visible. - // Because there's no scrolling, it won't call onItemsRendered. - rendered.getInstance().scrollToItem(9, 'auto'); - // Scroll to near the end. #96 will be shown as partial. - rendered.getInstance().scrollToItem(99, 'auto'); - // Scroll back to show #96 fully. This will cause #99 to be shown as a - // partial. Because #96 was already shown previously as a partial, all - // props of the onItemsRendered will be the same. This means that even - // though a scroll happened in the DOM, onItemsRendered won't be called. - rendered.getInstance().scrollToItem(96, 'auto'); - // Scroll forward again. Because item #99 was already shown partially, - // all props of the onItemsRendered will be the same. - rendered.getInstance().scrollToItem(99, 'auto'); - // Scroll to the second item. A partial fifth item should - // be shown after it. - rendered.getInstance().scrollToItem(1, 'auto'); - // Scroll to the first item. Now the fourth item should be a partial. - rendered.getInstance().scrollToItem(0, 'auto'); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should scroll to the correct item for align = "start"', () => { - const onItemsRendered = jest.fn(); - const rendered = ReactTestRenderer.create( - - ); - // Scroll down enough to show item 10 at the top. - rendered.getInstance().scrollToItem(10, 'start'); - // Scroll back up so that item 9 is at the top. - // Overscroll direction wil change too. - rendered.getInstance().scrollToItem(9, 'start'); - // Item 19 can't align at the top because there aren't enough items. - // Scroll down as far as possible though. - // Overscroll direction wil change again. - rendered.getInstance().scrollToItem(19, 'start'); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should scroll to the correct item for align = "end"', () => { - const onItemsRendered = jest.fn(); - const rendered = ReactTestRenderer.create( - - ); - // Scroll down enough to show item 10 at the bottom. - rendered.getInstance().scrollToItem(10, 'end'); - // Scroll back up so that item 9 is at the bottom. - // Overscroll direction wil change too. - rendered.getInstance().scrollToItem(9, 'end'); - // Item 1 can't align at the bottom because it's too close to the beginning. - // Scroll up as far as possible though. - // Overscroll direction wil change again. - rendered.getInstance().scrollToItem(1, 'end'); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should scroll to the correct item for align = "center"', () => { - const onItemsRendered = jest.fn(); - const rendered = ReactTestRenderer.create( - - ); - // Scroll down enough to show item 10 in the middle. - rendered.getInstance().scrollToItem(10, 'center'); - // Scroll back up so that item 9 is in the middle. - // Overscroll direction wil change too. - rendered.getInstance().scrollToItem(9, 'center'); - // Item 1 can't align in the middle because it's too close to the beginning. - // Scroll up as far as possible though. - // Overscroll direction wil change again. - rendered.getInstance().scrollToItem(1, 'center'); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - // Item 19 can't align in the middle because it's too close to the end. - // Scroll down as far as possible though. - // Overscroll direction wil change again. - rendered.getInstance().scrollToItem(19, 'center'); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - - it('should scroll to the correct item for align = "smart"', () => { - const onItemsRendered = jest.fn(); - const rendered = ReactTestRenderer.create( - - ); - // Scroll down enough to show item 10 in the middle. - rendered.getInstance().scrollToItem(10, 'smart'); - // Scrolldn't scroll at all because it's close enough. - rendered.getInstance().scrollToItem(9, 'smart'); - // Should scroll but not center because it's close enough. - rendered.getInstance().scrollToItem(6, 'smart'); - // Item 1 can't align in the middle because it's too close to the beginning. - // Scroll up as far as possible though. - rendered.getInstance().scrollToItem(1, 'smart'); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - }); - - describe('onScroll', () => { - it('scrolling should report partial items correctly in onItemsRendered', () => { - // Use ReactDOM renderer so the container ref works correctly. - const instance = render( - 25} - />, - document.createElement('div') - ); - // Scroll 2 items fwd, but thanks to the initialScrollOffset, we should - // still be showing partials on both ends. - simulateScroll(instance, 70); - // Scroll a little fwd to cause partials to be hidden - simulateScroll(instance, 75); - // Scroll backwards to show partials again - simulateScroll(instance, 70); - // Scroll near the end so that the last item is shown - // as a partial. - simulateScroll(instance, 96 * 25 - 5); - // Scroll to the end. No partials. - simulateScroll(instance, 96 * 25); - // Verify that backwards scrolling near the end works OK. - simulateScroll(instance, 96 * 25 - 5); - expect(onItemsRendered.mock.calls).toMatchSnapshot(); - }); - }); - - describe('resetAfterIndex method', () => { - it('should recalculate the estimated total size', () => { - const itemSize = jest.fn(() => 75); - const rendered = ReactTestRenderer.create( - 25} /> - ); - rendered.getInstance().scrollToItem(19); - // We've measured every item initially. - const scrollContainer = findScrollContainer(rendered); - expect(scrollContainer.props.style.height).toEqual(500); - // Supplying a new itemSize alone should not impact anything. - rendered.update( - - ); - expect(scrollContainer.props.style.height).toEqual(500); - // Reset styles after index 15, - // And verify that the new estimated total takes this into account. - rendered.getInstance().resetAfterIndex(15); - rendered.getInstance().scrollToItem(19); - expect(itemSize).toHaveBeenCalledTimes(5); - expect(scrollContainer.props.style.height).toEqual(750); - }); - - it('should delay the recalculation of the estimated total size if shouldForceUpdate is false', () => { - const rendered = ReactTestRenderer.create( - 25} - /> - ); - const scrollContainer = findScrollContainer(rendered); - // The estimated total size should be (100 + 25 * 1 + 30 * 15)px = 575px. - expect(scrollContainer.props.style.height).toEqual(575); - // Supplying a new itemSize alone should not impact anything. - // Although the list get re-rendered by passing inline functions, - // but it still use the cached metrics to calculate the estimated size. - rendered.update( - 20} - /> - ); - expect(scrollContainer.props.style.height).toEqual(575); - // Reset calculation cache but don't re-render the list, - // the estimated total size should stay the same. - rendered.getInstance().resetAfterIndex(0, false); - expect(scrollContainer.props.style.height).toEqual(575); - // Pass inline function to make the list re-render. - rendered.update( - 20} - /> - ); - // The estimated total height should be (100 + 20 * 1 + 30 * 14)px = 540px. - expect(scrollContainer.props.style.height).toEqual(540); - }); - - it('should re-render items after the specified index with updated styles', () => { - const itemSize = jest.fn(() => 75); - const rendered = ReactTestRenderer.create( - 25} - /> - ); - // We've rendered 5 rows initially. - expect(itemRenderer).toHaveBeenCalledTimes(5); - expect(itemRenderer.mock.calls[3][0].style.height).toBe(25); - // Supplying a new itemSize alone should not impact anything. - rendered.update( - - ); - // Reset styles for rows 4 and 5. - // And verify that the affected rows are re-rendered with new styles. - itemRenderer.mockClear(); - rendered.getInstance().resetAfterIndex(3); - expect(itemRenderer).toHaveBeenCalledTimes(5); - expect(itemRenderer.mock.calls[3][0].style.height).toBe(75); - }); - }); - - describe('props validation', () => { - beforeEach(() => spyOn(console, 'error')); - - it('should fail if non-function itemSize is provided', () => { - expect(() => - ReactTestRenderer.create( - - ) - ).toThrow( - 'An invalid "itemSize" prop has been specified. ' + - 'Value should be a function. "number" was specified.' - ); - }); - }); - - // https://github.com/bvaughn/react-window/pull/138 - it('should descrease scroll size when itemCount decreases', () => { - const innerRef = createRef(); - const listRef = createRef(); - - class Wrapper extends PureComponent { - state = { itemCount: 100 }; - render() { - return ( - - ); - } - } - - // Use ReactDOM renderer so "scroll" events work correctly. - const instance = render(, document.createElement('div')); - - // Simulate scrolling past several rows. - simulateScroll(listRef.current, 3000); - - // Decrease itemCount a lot and verify the scroll height is descreased as well. - instance.setState({ itemCount: 3 }); - expect(innerRef.current.style.height).toEqual('78px'); - }); -}); diff --git a/src/__tests__/__snapshots__/FixedSizeGrid.js.snap b/src/__tests__/__snapshots__/FixedSizeGrid.js.snap deleted file mode 100644 index 1ed399c2..00000000 --- a/src/__tests__/__snapshots__/FixedSizeGrid.js.snap +++ /dev/null @@ -1,912 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`FixedSizeGrid changing item size updates the rendered items 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 2, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 4, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 1, - "visibleRowStartIndex": 0, - "visibleRowStopIndex": 3, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 2, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 2, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 1, - "visibleRowStartIndex": 0, - "visibleRowStopIndex": 1, - }, - ], -] -`; - -exports[`FixedSizeGrid onScroll scrolling should report partial items correctly in onItemsRendered 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 3, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 5, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 2, - "visibleRowStartIndex": 0, - "visibleRowStopIndex": 4, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 4, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 6, - "visibleColumnStartIndex": 1, - "visibleColumnStopIndex": 3, - "visibleRowStartIndex": 1, - "visibleRowStopIndex": 5, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 1, - "overscanColumnStopIndex": 4, - "overscanRowStartIndex": 1, - "overscanRowStopIndex": 6, - "visibleColumnStartIndex": 2, - "visibleColumnStopIndex": 3, - "visibleRowStartIndex": 2, - "visibleRowStopIndex": 5, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 4, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 6, - "visibleColumnStartIndex": 1, - "visibleColumnStopIndex": 3, - "visibleRowStartIndex": 1, - "visibleRowStopIndex": 5, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 96, - "overscanColumnStopIndex": 99, - "overscanRowStartIndex": 94, - "overscanRowStopIndex": 99, - "visibleColumnStartIndex": 97, - "visibleColumnStopIndex": 99, - "visibleRowStartIndex": 95, - "visibleRowStopIndex": 99, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 97, - "overscanColumnStopIndex": 99, - "overscanRowStartIndex": 95, - "overscanRowStopIndex": 99, - "visibleColumnStartIndex": 98, - "visibleColumnStopIndex": 99, - "visibleRowStartIndex": 96, - "visibleRowStopIndex": 99, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 96, - "overscanColumnStopIndex": 99, - "overscanRowStartIndex": 94, - "overscanRowStopIndex": 99, - "visibleColumnStartIndex": 97, - "visibleColumnStopIndex": 99, - "visibleRowStartIndex": 95, - "visibleRowStopIndex": 99, - }, - ], -] -`; - -exports[`FixedSizeGrid onScroll should call onScroll after mount 1`] = ` -Array [ - Array [ - Object { - "horizontalScrollDirection": "forward", - "scrollLeft": 0, - "scrollTop": 0, - "scrollUpdateWasRequested": false, - "verticalScrollDirection": "forward", - }, - ], -] -`; - -exports[`FixedSizeGrid onScroll should call onScroll when scroll position changes 1`] = ` -Array [ - Array [ - Object { - "horizontalScrollDirection": "forward", - "scrollLeft": 0, - "scrollTop": 0, - "scrollUpdateWasRequested": false, - "verticalScrollDirection": "forward", - }, - ], - Array [ - Object { - "horizontalScrollDirection": "forward", - "scrollLeft": 100, - "scrollTop": 50, - "scrollUpdateWasRequested": true, - "verticalScrollDirection": "forward", - }, - ], - Array [ - Object { - "horizontalScrollDirection": "backward", - "scrollLeft": 0, - "scrollTop": 150, - "scrollUpdateWasRequested": true, - "verticalScrollDirection": "forward", - }, - ], - Array [ - Object { - "horizontalScrollDirection": "forward", - "scrollLeft": 150, - "scrollTop": 0, - "scrollUpdateWasRequested": true, - "verticalScrollDirection": "backward", - }, - ], -] -`; - -exports[`FixedSizeGrid overscanColumnCount and overscanRowCount overscanCount should support deprecated overscanColumnsCount and overscanRowsCount 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 4, - "overscanRowStartIndex": 2, - "overscanRowStopIndex": 9, - "visibleColumnStartIndex": 1, - "visibleColumnStopIndex": 2, - "visibleRowStartIndex": 4, - "visibleRowStopIndex": 7, - }, - ], -] -`; - -exports[`FixedSizeGrid overscanColumnCount and overscanRowCount overscanCount should support deprecated overscanCount 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 4, - "overscanRowStartIndex": 2, - "overscanRowStopIndex": 9, - "visibleColumnStartIndex": 1, - "visibleColumnStopIndex": 2, - "visibleRowStartIndex": 4, - "visibleRowStopIndex": 7, - }, - ], -] -`; - -exports[`FixedSizeGrid overscanColumnCount and overscanRowCount overscanCount should use overscanColumnCount and overscanRowCount if both them and deprecated props are provided 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 5, - "overscanRowStartIndex": 1, - "overscanRowStopIndex": 10, - "visibleColumnStartIndex": 1, - "visibleColumnStopIndex": 2, - "visibleRowStartIndex": 4, - "visibleRowStopIndex": 7, - }, - ], -] -`; - -exports[`FixedSizeGrid overscanColumnCount and overscanRowCount overscanCount should use overscanColumnsCount if both it and overscanCount are provided 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 5, - "overscanRowStartIndex": 2, - "overscanRowStopIndex": 9, - "visibleColumnStartIndex": 1, - "visibleColumnStopIndex": 2, - "visibleRowStartIndex": 4, - "visibleRowStopIndex": 7, - }, - ], -] -`; - -exports[`FixedSizeGrid overscanColumnCount and overscanRowCount overscanCount should use overscanRowCount if both it and overscanCount are provided 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 4, - "overscanRowStartIndex": 1, - "overscanRowStopIndex": 10, - "visibleColumnStartIndex": 1, - "visibleColumnStopIndex": 2, - "visibleRowStartIndex": 4, - "visibleRowStopIndex": 7, - }, - ], -] -`; - -exports[`FixedSizeGrid overscanColumnCount and overscanRowCount should accommodate a custom overscan 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 6, - "overscanRowStartIndex": 8, - "overscanRowStopIndex": 15, - "visibleColumnStartIndex": 2, - "visibleColumnStopIndex": 4, - "visibleRowStartIndex": 10, - "visibleRowStopIndex": 13, - }, - ], -] -`; - -exports[`FixedSizeGrid overscanColumnCount and overscanRowCount should not scan past the beginning of the grid 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 2, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 4, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 1, - "visibleRowStartIndex": 0, - "visibleRowStopIndex": 3, - }, - ], -] -`; - -exports[`FixedSizeGrid overscanColumnCount and overscanRowCount should not scan past the end of the grid 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 8, - "overscanColumnStopIndex": 9, - "overscanRowStartIndex": 5, - "overscanRowStopIndex": 9, - "visibleColumnStartIndex": 9, - "visibleColumnStopIndex": 9, - "visibleRowStartIndex": 6, - "visibleRowStopIndex": 9, - }, - ], -] -`; - -exports[`FixedSizeGrid overscanColumnCount and overscanRowCount should overscan in both directions when not scrolling 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 6, - "overscanRowStartIndex": 8, - "overscanRowStopIndex": 15, - "visibleColumnStartIndex": 2, - "visibleColumnStopIndex": 4, - "visibleRowStartIndex": 10, - "visibleRowStopIndex": 13, - }, - ], -] -`; - -exports[`FixedSizeGrid overscanColumnCount and overscanRowCount should overscan in the direction being scrolled 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 6, - "overscanRowStartIndex": 8, - "overscanRowStopIndex": 15, - "visibleColumnStartIndex": 2, - "visibleColumnStopIndex": 4, - "visibleRowStartIndex": 10, - "visibleRowStopIndex": 13, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 8, - "overscanColumnStopIndex": 13, - "overscanRowStartIndex": 38, - "overscanRowStopIndex": 45, - "visibleColumnStartIndex": 10, - "visibleColumnStopIndex": 11, - "visibleRowStartIndex": 40, - "visibleRowStopIndex": 43, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 3, - "overscanColumnStopIndex": 8, - "overscanRowStartIndex": 18, - "overscanRowStopIndex": 25, - "visibleColumnStartIndex": 5, - "visibleColumnStopIndex": 6, - "visibleRowStartIndex": 20, - "visibleRowStopIndex": 23, - }, - ], -] -`; - -exports[`FixedSizeGrid overscanColumnCount and overscanRowCount should require a minimum of 1 overscan to support tabbing 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 1, - "overscanColumnStopIndex": 5, - "overscanRowStartIndex": 9, - "overscanRowStopIndex": 14, - "visibleColumnStartIndex": 2, - "visibleColumnStopIndex": 4, - "visibleRowStartIndex": 10, - "visibleRowStopIndex": 13, - }, - ], -] -`; - -exports[`FixedSizeGrid scrollToItem method scroll with align = "auto" should work with partially-visible items 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 3, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 4, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 2, - "visibleRowStartIndex": 0, - "visibleRowStopIndex": 3, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 7, - "overscanColumnStopIndex": 11, - "overscanRowStartIndex": 6, - "overscanRowStopIndex": 11, - "visibleColumnStartIndex": 8, - "visibleColumnStopIndex": 10, - "visibleRowStartIndex": 7, - "visibleRowStopIndex": 10, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 96, - "overscanColumnStopIndex": 99, - "overscanRowStartIndex": 95, - "overscanRowStopIndex": 99, - "visibleColumnStartIndex": 97, - "visibleColumnStopIndex": 99, - "visibleRowStartIndex": 96, - "visibleRowStopIndex": 99, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 4, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 5, - "visibleColumnStartIndex": 1, - "visibleColumnStopIndex": 3, - "visibleRowStartIndex": 1, - "visibleRowStopIndex": 4, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 3, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 4, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 2, - "visibleRowStartIndex": 0, - "visibleRowStopIndex": 3, - }, - ], -] -`; - -exports[`FixedSizeGrid scrollToItem method should ignore indexes greater than itemCount 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 97, - "overscanColumnStopIndex": 99, - "overscanRowStartIndex": 95, - "overscanRowStopIndex": 99, - "visibleColumnStartIndex": 98, - "visibleColumnStopIndex": 99, - "visibleRowStartIndex": 96, - "visibleRowStopIndex": 99, - }, - ], -] -`; - -exports[`FixedSizeGrid scrollToItem method should ignore indexes less than zero 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 2, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 4, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 1, - "visibleRowStartIndex": 0, - "visibleRowStopIndex": 3, - }, - ], -] -`; - -exports[`FixedSizeGrid scrollToItem method should not set invalid offsets when the list contains few items 1`] = `[Function]`; - -exports[`FixedSizeGrid scrollToItem method should scroll to the correct item for align = "auto" 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 2, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 4, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 1, - "visibleRowStartIndex": 0, - "visibleRowStopIndex": 3, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 8, - "overscanColumnStopIndex": 11, - "overscanRowStartIndex": 6, - "overscanRowStopIndex": 11, - "visibleColumnStartIndex": 9, - "visibleColumnStopIndex": 10, - "visibleRowStartIndex": 7, - "visibleRowStopIndex": 10, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 1, - "overscanColumnStopIndex": 4, - "overscanRowStartIndex": 1, - "overscanRowStopIndex": 6, - "visibleColumnStartIndex": 2, - "visibleColumnStopIndex": 3, - "visibleRowStartIndex": 2, - "visibleRowStopIndex": 5, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 1, - "overscanColumnStopIndex": 4, - "overscanRowStartIndex": 6, - "overscanRowStopIndex": 11, - "visibleColumnStartIndex": 2, - "visibleColumnStopIndex": 3, - "visibleRowStartIndex": 7, - "visibleRowStopIndex": 10, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 2, - "overscanRowStartIndex": 6, - "overscanRowStopIndex": 11, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 1, - "visibleRowStartIndex": 7, - "visibleRowStopIndex": 10, - }, - ], -] -`; - -exports[`FixedSizeGrid scrollToItem method should scroll to the correct item for align = "center" 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 2, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 4, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 1, - "visibleRowStartIndex": 0, - "visibleRowStopIndex": 3, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 8, - "overscanColumnStopIndex": 12, - "overscanRowStartIndex": 7, - "overscanRowStopIndex": 13, - "visibleColumnStartIndex": 9, - "visibleColumnStopIndex": 11, - "visibleRowStartIndex": 8, - "visibleRowStopIndex": 12, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 7, - "overscanColumnStopIndex": 11, - "overscanRowStartIndex": 6, - "overscanRowStopIndex": 12, - "visibleColumnStartIndex": 8, - "visibleColumnStopIndex": 10, - "visibleRowStartIndex": 7, - "visibleRowStopIndex": 11, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 2, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 4, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 1, - "visibleRowStartIndex": 0, - "visibleRowStopIndex": 3, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 97, - "overscanColumnStopIndex": 99, - "overscanRowStartIndex": 95, - "overscanRowStopIndex": 99, - "visibleColumnStartIndex": 98, - "visibleColumnStopIndex": 99, - "visibleRowStartIndex": 96, - "visibleRowStopIndex": 99, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 97, - "overscanColumnStopIndex": 99, - "overscanRowStartIndex": 7, - "overscanRowStopIndex": 13, - "visibleColumnStartIndex": 98, - "visibleColumnStopIndex": 99, - "visibleRowStartIndex": 8, - "visibleRowStopIndex": 12, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 1, - "overscanColumnStopIndex": 5, - "overscanRowStartIndex": 7, - "overscanRowStopIndex": 13, - "visibleColumnStartIndex": 2, - "visibleColumnStopIndex": 4, - "visibleRowStartIndex": 8, - "visibleRowStopIndex": 12, - }, - ], -] -`; - -exports[`FixedSizeGrid scrollToItem method should scroll to the correct item for align = "end" 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 2, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 4, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 1, - "visibleRowStartIndex": 0, - "visibleRowStopIndex": 3, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 8, - "overscanColumnStopIndex": 11, - "overscanRowStartIndex": 6, - "overscanRowStopIndex": 11, - "visibleColumnStartIndex": 9, - "visibleColumnStopIndex": 10, - "visibleRowStartIndex": 7, - "visibleRowStopIndex": 10, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 7, - "overscanColumnStopIndex": 10, - "overscanRowStartIndex": 5, - "overscanRowStopIndex": 10, - "visibleColumnStartIndex": 8, - "visibleColumnStopIndex": 9, - "visibleRowStartIndex": 6, - "visibleRowStopIndex": 9, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 2, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 4, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 1, - "visibleRowStartIndex": 0, - "visibleRowStopIndex": 3, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 2, - "overscanRowStartIndex": 6, - "overscanRowStopIndex": 11, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 1, - "visibleRowStartIndex": 7, - "visibleRowStopIndex": 10, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 7, - "overscanColumnStopIndex": 10, - "overscanRowStartIndex": 6, - "overscanRowStopIndex": 11, - "visibleColumnStartIndex": 8, - "visibleColumnStopIndex": 9, - "visibleRowStartIndex": 7, - "visibleRowStopIndex": 10, - }, - ], -] -`; - -exports[`FixedSizeGrid scrollToItem method should scroll to the correct item for align = "smart" 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 2, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 4, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 1, - "visibleRowStartIndex": 0, - "visibleRowStopIndex": 3, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 8, - "overscanColumnStopIndex": 12, - "overscanRowStartIndex": 7, - "overscanRowStopIndex": 13, - "visibleColumnStartIndex": 9, - "visibleColumnStopIndex": 11, - "visibleRowStartIndex": 8, - "visibleRowStopIndex": 12, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 8, - "overscanColumnStopIndex": 11, - "overscanRowStartIndex": 7, - "overscanRowStopIndex": 13, - "visibleColumnStartIndex": 9, - "visibleColumnStopIndex": 10, - "visibleRowStartIndex": 8, - "visibleRowStopIndex": 12, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 4, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 4, - "visibleColumnStartIndex": 1, - "visibleColumnStopIndex": 3, - "visibleRowStartIndex": 0, - "visibleRowStopIndex": 3, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 4, - "overscanRowStartIndex": 7, - "overscanRowStopIndex": 13, - "visibleColumnStartIndex": 1, - "visibleColumnStopIndex": 3, - "visibleRowStartIndex": 8, - "visibleRowStopIndex": 12, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 2, - "overscanRowStartIndex": 7, - "overscanRowStopIndex": 13, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 1, - "visibleRowStartIndex": 8, - "visibleRowStopIndex": 12, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 3, - "overscanColumnStopIndex": 7, - "overscanRowStartIndex": 4, - "overscanRowStopIndex": 9, - "visibleColumnStartIndex": 4, - "visibleColumnStopIndex": 6, - "visibleRowStartIndex": 5, - "visibleRowStopIndex": 8, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 8, - "overscanColumnStopIndex": 12, - "overscanRowStartIndex": 6, - "overscanRowStopIndex": 11, - "visibleColumnStartIndex": 9, - "visibleColumnStopIndex": 11, - "visibleRowStartIndex": 7, - "visibleRowStopIndex": 10, - }, - ], -] -`; - -exports[`FixedSizeGrid scrollToItem method should scroll to the correct item for align = "start" 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 2, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 4, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 1, - "visibleRowStartIndex": 0, - "visibleRowStopIndex": 3, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 9, - "overscanColumnStopIndex": 12, - "overscanRowStartIndex": 9, - "overscanRowStopIndex": 14, - "visibleColumnStartIndex": 10, - "visibleColumnStopIndex": 11, - "visibleRowStartIndex": 10, - "visibleRowStopIndex": 13, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 8, - "overscanColumnStopIndex": 11, - "overscanRowStartIndex": 8, - "overscanRowStopIndex": 13, - "visibleColumnStartIndex": 9, - "visibleColumnStopIndex": 10, - "visibleRowStartIndex": 9, - "visibleRowStopIndex": 12, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 97, - "overscanColumnStopIndex": 99, - "overscanRowStartIndex": 95, - "overscanRowStopIndex": 99, - "visibleColumnStartIndex": 98, - "visibleColumnStopIndex": 99, - "visibleRowStartIndex": 96, - "visibleRowStopIndex": 99, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 97, - "overscanColumnStopIndex": 99, - "overscanRowStartIndex": 9, - "overscanRowStopIndex": 14, - "visibleColumnStartIndex": 98, - "visibleColumnStopIndex": 99, - "visibleRowStartIndex": 10, - "visibleRowStopIndex": 13, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 2, - "overscanRowStartIndex": 9, - "overscanRowStopIndex": 14, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 1, - "visibleRowStartIndex": 10, - "visibleRowStopIndex": 13, - }, - ], -] -`; - -exports[`FixedSizeGrid should render a grid of items 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 2, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 4, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 1, - "visibleRowStartIndex": 0, - "visibleRowStopIndex": 3, - }, - ], -] -`; diff --git a/src/__tests__/__snapshots__/FixedSizeList.js.snap b/src/__tests__/__snapshots__/FixedSizeList.js.snap deleted file mode 100644 index 82c9268a..00000000 --- a/src/__tests__/__snapshots__/FixedSizeList.js.snap +++ /dev/null @@ -1,597 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`FixedSizeList changing itemSize updates the rendered items 1`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 5, - "visibleStartIndex": 0, - "visibleStopIndex": 3, - }, - ], - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 3, - "visibleStartIndex": 0, - "visibleStopIndex": 1, - }, - ], -] -`; - -exports[`FixedSizeList onScroll scrolling should report partial items correctly in onItemsRendered 1`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 6, - "visibleStartIndex": 0, - "visibleStopIndex": 4, - }, - ], - Array [ - Object { - "overscanStartIndex": 1, - "overscanStopIndex": 8, - "visibleStartIndex": 2, - "visibleStopIndex": 6, - }, - ], - Array [ - Object { - "overscanStartIndex": 2, - "overscanStopIndex": 8, - "visibleStartIndex": 3, - "visibleStopIndex": 6, - }, - ], - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 7, - "visibleStartIndex": 2, - "visibleStopIndex": 6, - }, - ], - Array [ - Object { - "overscanStartIndex": 94, - "overscanStopIndex": 99, - "visibleStartIndex": 95, - "visibleStopIndex": 99, - }, - ], - Array [ - Object { - "overscanStartIndex": 95, - "overscanStopIndex": 99, - "visibleStartIndex": 96, - "visibleStopIndex": 99, - }, - ], - Array [ - Object { - "overscanStartIndex": 93, - "overscanStopIndex": 99, - "visibleStartIndex": 95, - "visibleStopIndex": 99, - }, - ], -] -`; - -exports[`FixedSizeList onScroll should call onScroll after mount 1`] = ` -Array [ - Array [ - Object { - "scrollDirection": "forward", - "scrollOffset": 0, - "scrollUpdateWasRequested": false, - }, - ], -] -`; - -exports[`FixedSizeList onScroll should call onScroll when scroll position changes 1`] = ` -Array [ - Array [ - Object { - "scrollDirection": "forward", - "scrollOffset": 0, - "scrollUpdateWasRequested": false, - }, - ], - Array [ - Object { - "scrollDirection": "forward", - "scrollOffset": 100, - "scrollUpdateWasRequested": true, - }, - ], - Array [ - Object { - "scrollDirection": "backward", - "scrollOffset": 0, - "scrollUpdateWasRequested": true, - }, - ], -] -`; - -exports[`FixedSizeList overscanCount should accommodate a custom overscan 1`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 1, - "overscanStopIndex": 10, - "visibleStartIndex": 4, - "visibleStopIndex": 7, - }, - ], -] -`; - -exports[`FixedSizeList overscanCount should not scan past the beginning of the list 1`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 5, - "visibleStartIndex": 0, - "visibleStopIndex": 3, - }, - ], -] -`; - -exports[`FixedSizeList overscanCount should not scan past the end of the list 1`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 4, - "overscanStopIndex": 9, - "visibleStartIndex": 6, - "visibleStopIndex": 9, - }, - ], -] -`; - -exports[`FixedSizeList overscanCount should overscan in both directions when not scrolling 1`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 7, - "visibleStartIndex": 2, - "visibleStopIndex": 5, - }, - ], -] -`; - -exports[`FixedSizeList overscanCount should overscan in the direction being scrolled 1`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 7, - "visibleStartIndex": 2, - "visibleStopIndex": 5, - }, - ], - Array [ - Object { - "overscanStartIndex": 3, - "overscanStopIndex": 9, - "visibleStartIndex": 4, - "visibleStopIndex": 7, - }, - ], - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 6, - "visibleStartIndex": 2, - "visibleStopIndex": 5, - }, - ], -] -`; - -exports[`FixedSizeList overscanCount should require a minimum of 1 overscan to support tabbing 1`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 1, - "overscanStopIndex": 6, - "visibleStartIndex": 2, - "visibleStopIndex": 5, - }, - ], -] -`; - -exports[`FixedSizeList scrollToItem method scroll with align = "auto" should work with partially-visible items 1`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 5, - "visibleStartIndex": 0, - "visibleStopIndex": 3, - }, - ], - Array [ - Object { - "overscanStartIndex": 5, - "overscanStopIndex": 12, - "visibleStartIndex": 7, - "visibleStopIndex": 10, - }, - ], - Array [ - Object { - "overscanStartIndex": 94, - "overscanStopIndex": 99, - "visibleStartIndex": 96, - "visibleStopIndex": 99, - }, - ], - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 6, - "visibleStartIndex": 1, - "visibleStopIndex": 4, - }, - ], - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 5, - "visibleStartIndex": 0, - "visibleStopIndex": 3, - }, - ], -] -`; - -exports[`FixedSizeList scrollToItem method scroll with align = "auto" should work with very small lists and partial items 1`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 1, - "visibleStartIndex": 0, - "visibleStopIndex": 1, - }, - ], -] -`; - -exports[`FixedSizeList scrollToItem method should account for scrollbar size 1`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 5, - "visibleStartIndex": 0, - "visibleStopIndex": 3, - }, - ], - Array [ - Object { - "overscanStartIndex": 15, - "overscanStopIndex": 22, - "visibleStartIndex": 17, - "visibleStopIndex": 20, - }, - ], - Array [ - Object { - "overscanStartIndex": 15, - "overscanStopIndex": 23, - "visibleStartIndex": 17, - "visibleStopIndex": 21, - }, - ], -] -`; - -exports[`FixedSizeList scrollToItem method should ignore indexes greater than itemCount 1`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 94, - "overscanStopIndex": 99, - "visibleStartIndex": 96, - "visibleStopIndex": 99, - }, - ], -] -`; - -exports[`FixedSizeList scrollToItem method should ignore indexes less than zero 1`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 5, - "visibleStartIndex": 0, - "visibleStopIndex": 3, - }, - ], -] -`; - -exports[`FixedSizeList scrollToItem method should not set invalid offsets when the list contains few items 1`] = `[Function]`; - -exports[`FixedSizeList scrollToItem method should scroll to the correct item for align = "auto" 1`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 5, - "visibleStartIndex": 0, - "visibleStopIndex": 3, - }, - ], - Array [ - Object { - "overscanStartIndex": 5, - "overscanStopIndex": 12, - "visibleStartIndex": 7, - "visibleStopIndex": 10, - }, - ], - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 7, - "visibleStartIndex": 2, - "visibleStopIndex": 5, - }, - ], -] -`; - -exports[`FixedSizeList scrollToItem method should scroll to the correct item for align = "center" 1`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 5, - "visibleStartIndex": 0, - "visibleStopIndex": 3, - }, - ], - Array [ - Object { - "overscanStartIndex": 6, - "overscanStopIndex": 14, - "visibleStartIndex": 8, - "visibleStopIndex": 12, - }, - ], - Array [ - Object { - "overscanStartIndex": 5, - "overscanStopIndex": 13, - "visibleStartIndex": 7, - "visibleStopIndex": 11, - }, - ], - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 5, - "visibleStartIndex": 0, - "visibleStopIndex": 3, - }, - ], - Array [ - Object { - "overscanStartIndex": 94, - "overscanStopIndex": 99, - "visibleStartIndex": 96, - "visibleStopIndex": 99, - }, - ], -] -`; - -exports[`FixedSizeList scrollToItem method should scroll to the correct item for align = "end" 1`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 5, - "visibleStartIndex": 0, - "visibleStopIndex": 3, - }, - ], - Array [ - Object { - "overscanStartIndex": 5, - "overscanStopIndex": 12, - "visibleStartIndex": 7, - "visibleStopIndex": 10, - }, - ], - Array [ - Object { - "overscanStartIndex": 4, - "overscanStopIndex": 11, - "visibleStartIndex": 6, - "visibleStopIndex": 9, - }, - ], - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 5, - "visibleStartIndex": 0, - "visibleStopIndex": 3, - }, - ], -] -`; - -exports[`FixedSizeList scrollToItem method should scroll to the correct item for align = "smart" 1`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 5, - "visibleStartIndex": 0, - "visibleStopIndex": 3, - }, - ], - Array [ - Object { - "overscanStartIndex": 6, - "overscanStopIndex": 14, - "visibleStartIndex": 8, - "visibleStopIndex": 12, - }, - ], - Array [ - Object { - "overscanStartIndex": 4, - "overscanStopIndex": 11, - "visibleStartIndex": 6, - "visibleStopIndex": 9, - }, - ], - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 5, - "visibleStartIndex": 0, - "visibleStopIndex": 3, - }, - ], - Array [ - Object { - "overscanStartIndex": 94, - "overscanStopIndex": 99, - "visibleStartIndex": 96, - "visibleStopIndex": 99, - }, - ], - Array [ - Object { - "overscanStartIndex": 93, - "overscanStopIndex": 99, - "visibleStartIndex": 95, - "visibleStopIndex": 98, - }, - ], - Array [ - Object { - "overscanStartIndex": 94, - "overscanStopIndex": 99, - "visibleStartIndex": 96, - "visibleStopIndex": 99, - }, - ], - Array [ - Object { - "overscanStartIndex": 92, - "overscanStopIndex": 99, - "visibleStartIndex": 94, - "visibleStopIndex": 97, - }, - ], - Array [ - Object { - "overscanStartIndex": 94, - "overscanStopIndex": 99, - "visibleStartIndex": 96, - "visibleStopIndex": 99, - }, - ], - Array [ - Object { - "overscanStartIndex": 86, - "overscanStopIndex": 94, - "visibleStartIndex": 88, - "visibleStopIndex": 92, - }, - ], - Array [ - Object { - "overscanStartIndex": 94, - "overscanStopIndex": 99, - "visibleStartIndex": 96, - "visibleStopIndex": 99, - }, - ], -] -`; - -exports[`FixedSizeList scrollToItem method should scroll to the correct item for align = "start" 1`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 5, - "visibleStartIndex": 0, - "visibleStopIndex": 3, - }, - ], - Array [ - Object { - "overscanStartIndex": 8, - "overscanStopIndex": 15, - "visibleStartIndex": 10, - "visibleStopIndex": 13, - }, - ], - Array [ - Object { - "overscanStartIndex": 7, - "overscanStopIndex": 14, - "visibleStartIndex": 9, - "visibleStopIndex": 12, - }, - ], - Array [ - Object { - "overscanStartIndex": 94, - "overscanStopIndex": 99, - "visibleStartIndex": 96, - "visibleStopIndex": 99, - }, - ], -] -`; - -exports[`FixedSizeList should render a list of columns 1`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 3, - "visibleStartIndex": 0, - "visibleStopIndex": 1, - }, - ], -] -`; - -exports[`FixedSizeList should render a list of rows 1`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 5, - "visibleStartIndex": 0, - "visibleStopIndex": 3, - }, - ], -] -`; diff --git a/src/__tests__/__snapshots__/VariableSizeGrid.js.snap b/src/__tests__/__snapshots__/VariableSizeGrid.js.snap deleted file mode 100644 index 9ecd2192..00000000 --- a/src/__tests__/__snapshots__/VariableSizeGrid.js.snap +++ /dev/null @@ -1,542 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`VariableSizeGrid onScroll scrolling should report partial items correctly in onItemsRendered 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 3, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 5, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 2, - "visibleRowStartIndex": 0, - "visibleRowStopIndex": 4, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 4, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 6, - "visibleColumnStartIndex": 1, - "visibleColumnStopIndex": 3, - "visibleRowStartIndex": 1, - "visibleRowStopIndex": 5, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 1, - "overscanColumnStopIndex": 4, - "overscanRowStartIndex": 1, - "overscanRowStopIndex": 6, - "visibleColumnStartIndex": 2, - "visibleColumnStopIndex": 3, - "visibleRowStartIndex": 2, - "visibleRowStopIndex": 5, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 4, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 6, - "visibleColumnStartIndex": 1, - "visibleColumnStopIndex": 3, - "visibleRowStartIndex": 1, - "visibleRowStopIndex": 5, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 96, - "overscanColumnStopIndex": 99, - "overscanRowStartIndex": 94, - "overscanRowStopIndex": 99, - "visibleColumnStartIndex": 97, - "visibleColumnStopIndex": 99, - "visibleRowStartIndex": 95, - "visibleRowStopIndex": 99, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 97, - "overscanColumnStopIndex": 99, - "overscanRowStartIndex": 95, - "overscanRowStopIndex": 99, - "visibleColumnStartIndex": 98, - "visibleColumnStopIndex": 99, - "visibleRowStartIndex": 96, - "visibleRowStopIndex": 99, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 96, - "overscanColumnStopIndex": 99, - "overscanRowStartIndex": 94, - "overscanRowStopIndex": 99, - "visibleColumnStartIndex": 97, - "visibleColumnStopIndex": 99, - "visibleRowStartIndex": 95, - "visibleRowStopIndex": 99, - }, - ], -] -`; - -exports[`VariableSizeGrid scrollToItem method scroll with align = "auto" should work with partially-visible items 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 3, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 4, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 2, - "visibleRowStartIndex": 0, - "visibleRowStopIndex": 3, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 7, - "overscanColumnStopIndex": 11, - "overscanRowStartIndex": 6, - "overscanRowStopIndex": 11, - "visibleColumnStartIndex": 8, - "visibleColumnStopIndex": 10, - "visibleRowStartIndex": 7, - "visibleRowStopIndex": 10, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 96, - "overscanColumnStopIndex": 99, - "overscanRowStartIndex": 95, - "overscanRowStopIndex": 99, - "visibleColumnStartIndex": 97, - "visibleColumnStopIndex": 99, - "visibleRowStartIndex": 96, - "visibleRowStopIndex": 99, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 4, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 5, - "visibleColumnStartIndex": 1, - "visibleColumnStopIndex": 3, - "visibleRowStartIndex": 1, - "visibleRowStopIndex": 4, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 3, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 4, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 2, - "visibleRowStartIndex": 0, - "visibleRowStopIndex": 3, - }, - ], -] -`; - -exports[`VariableSizeGrid scrollToItem method should not set invalid offsets when the list contains few items 1`] = `[Function]`; - -exports[`VariableSizeGrid scrollToItem method should scroll to the correct item for align = "auto" 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 4, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 4, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 3, - "visibleRowStartIndex": 0, - "visibleRowStopIndex": 3, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 1, - "overscanColumnStopIndex": 6, - "overscanRowStartIndex": 1, - "overscanRowStopIndex": 6, - "visibleColumnStartIndex": 2, - "visibleColumnStopIndex": 5, - "visibleRowStartIndex": 2, - "visibleRowStopIndex": 5, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 1, - "overscanColumnStopIndex": 6, - "overscanRowStartIndex": 7, - "overscanRowStopIndex": 11, - "visibleColumnStartIndex": 2, - "visibleColumnStopIndex": 5, - "visibleRowStartIndex": 8, - "visibleRowStopIndex": 10, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 4, - "overscanRowStartIndex": 7, - "overscanRowStopIndex": 11, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 3, - "visibleRowStartIndex": 8, - "visibleRowStopIndex": 10, - }, - ], -] -`; - -exports[`VariableSizeGrid scrollToItem method should scroll to the correct item for align = "center" 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 4, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 4, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 3, - "visibleRowStartIndex": 0, - "visibleRowStopIndex": 3, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 2, - "overscanColumnStopIndex": 8, - "overscanRowStartIndex": 2, - "overscanRowStopIndex": 8, - "visibleColumnStartIndex": 3, - "visibleColumnStopIndex": 7, - "visibleRowStartIndex": 3, - "visibleRowStopIndex": 7, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 1, - "overscanColumnStopIndex": 7, - "overscanRowStartIndex": 1, - "overscanRowStopIndex": 7, - "visibleColumnStartIndex": 2, - "visibleColumnStopIndex": 6, - "visibleRowStartIndex": 2, - "visibleRowStopIndex": 6, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 5, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 5, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 4, - "visibleRowStartIndex": 0, - "visibleRowStopIndex": 4, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 5, - "overscanColumnStopIndex": 9, - "overscanRowStartIndex": 16, - "overscanRowStopIndex": 19, - "visibleColumnStartIndex": 6, - "visibleColumnStopIndex": 9, - "visibleRowStartIndex": 17, - "visibleRowStopIndex": 19, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 5, - "overscanColumnStopIndex": 9, - "overscanRowStartIndex": 8, - "overscanRowStopIndex": 12, - "visibleColumnStartIndex": 6, - "visibleColumnStopIndex": 9, - "visibleRowStartIndex": 9, - "visibleRowStopIndex": 11, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 6, - "overscanRowStartIndex": 8, - "overscanRowStopIndex": 12, - "visibleColumnStartIndex": 1, - "visibleColumnStopIndex": 5, - "visibleRowStartIndex": 9, - "visibleRowStopIndex": 11, - }, - ], -] -`; - -exports[`VariableSizeGrid scrollToItem method should scroll to the correct item for align = "end" 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 4, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 4, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 3, - "visibleRowStartIndex": 0, - "visibleRowStopIndex": 3, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 1, - "overscanColumnStopIndex": 6, - "overscanRowStartIndex": 1, - "overscanRowStopIndex": 6, - "visibleColumnStartIndex": 2, - "visibleColumnStopIndex": 5, - "visibleRowStartIndex": 2, - "visibleRowStopIndex": 5, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 5, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 5, - "visibleColumnStartIndex": 1, - "visibleColumnStopIndex": 4, - "visibleRowStartIndex": 1, - "visibleRowStopIndex": 4, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 4, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 4, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 3, - "visibleRowStartIndex": 0, - "visibleRowStopIndex": 3, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 4, - "overscanRowStartIndex": 7, - "overscanRowStopIndex": 11, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 3, - "visibleRowStartIndex": 8, - "visibleRowStopIndex": 10, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 5, - "overscanColumnStopIndex": 9, - "overscanRowStartIndex": 7, - "overscanRowStopIndex": 11, - "visibleColumnStartIndex": 6, - "visibleColumnStopIndex": 9, - "visibleRowStartIndex": 8, - "visibleRowStopIndex": 10, - }, - ], -] -`; - -exports[`VariableSizeGrid scrollToItem method should scroll to the correct item for align = "smart" 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 4, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 4, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 3, - "visibleRowStartIndex": 0, - "visibleRowStopIndex": 3, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 5, - "overscanColumnStopIndex": 9, - "overscanRowStartIndex": 8, - "overscanRowStopIndex": 12, - "visibleColumnStartIndex": 6, - "visibleColumnStopIndex": 9, - "visibleRowStartIndex": 9, - "visibleRowStopIndex": 11, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 5, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 5, - "visibleColumnStartIndex": 1, - "visibleColumnStopIndex": 4, - "visibleRowStartIndex": 1, - "visibleRowStopIndex": 4, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 5, - "overscanRowStartIndex": 8, - "overscanRowStopIndex": 12, - "visibleColumnStartIndex": 1, - "visibleColumnStopIndex": 4, - "visibleRowStartIndex": 9, - "visibleRowStopIndex": 11, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 4, - "overscanRowStartIndex": 8, - "overscanRowStopIndex": 12, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 3, - "visibleRowStartIndex": 9, - "visibleRowStopIndex": 11, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 1, - "overscanColumnStopIndex": 6, - "overscanRowStartIndex": 2, - "overscanRowStopIndex": 8, - "visibleColumnStartIndex": 2, - "visibleColumnStopIndex": 5, - "visibleRowStartIndex": 3, - "visibleRowStopIndex": 7, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 5, - "overscanColumnStopIndex": 9, - "overscanRowStartIndex": 8, - "overscanRowStopIndex": 12, - "visibleColumnStartIndex": 6, - "visibleColumnStopIndex": 9, - "visibleRowStartIndex": 9, - "visibleRowStopIndex": 11, - }, - ], -] -`; - -exports[`VariableSizeGrid scrollToItem method should scroll to the correct item for align = "start" 1`] = ` -Array [ - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 4, - "overscanRowStartIndex": 0, - "overscanRowStopIndex": 4, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 3, - "visibleRowStartIndex": 0, - "visibleRowStopIndex": 3, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 4, - "overscanColumnStopIndex": 9, - "overscanRowStartIndex": 4, - "overscanRowStopIndex": 9, - "visibleColumnStartIndex": 5, - "visibleColumnStopIndex": 8, - "visibleRowStartIndex": 5, - "visibleRowStopIndex": 8, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 3, - "overscanColumnStopIndex": 8, - "overscanRowStartIndex": 3, - "overscanRowStopIndex": 8, - "visibleColumnStartIndex": 4, - "visibleColumnStopIndex": 7, - "visibleRowStartIndex": 4, - "visibleRowStopIndex": 7, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 5, - "overscanColumnStopIndex": 9, - "overscanRowStartIndex": 16, - "overscanRowStopIndex": 19, - "visibleColumnStartIndex": 6, - "visibleColumnStopIndex": 9, - "visibleRowStartIndex": 17, - "visibleRowStopIndex": 19, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 5, - "overscanColumnStopIndex": 9, - "overscanRowStartIndex": 9, - "overscanRowStopIndex": 13, - "visibleColumnStartIndex": 6, - "visibleColumnStopIndex": 9, - "visibleRowStartIndex": 10, - "visibleRowStopIndex": 12, - }, - ], - Array [ - Object { - "overscanColumnStartIndex": 0, - "overscanColumnStopIndex": 4, - "overscanRowStartIndex": 9, - "overscanRowStopIndex": 13, - "visibleColumnStartIndex": 0, - "visibleColumnStopIndex": 3, - "visibleRowStartIndex": 10, - "visibleRowStopIndex": 12, - }, - ], -] -`; diff --git a/src/__tests__/__snapshots__/VariableSizeList.js.snap b/src/__tests__/__snapshots__/VariableSizeList.js.snap deleted file mode 100644 index 9d35525b..00000000 --- a/src/__tests__/__snapshots__/VariableSizeList.js.snap +++ /dev/null @@ -1,331 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`VariableSizeList onScroll scrolling should report partial items correctly in onItemsRendered 1`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 6, - "visibleStartIndex": 0, - "visibleStopIndex": 4, - }, - ], - Array [ - Object { - "overscanStartIndex": 1, - "overscanStopIndex": 8, - "visibleStartIndex": 2, - "visibleStopIndex": 6, - }, - ], - Array [ - Object { - "overscanStartIndex": 2, - "overscanStopIndex": 8, - "visibleStartIndex": 3, - "visibleStopIndex": 6, - }, - ], - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 7, - "visibleStartIndex": 2, - "visibleStopIndex": 6, - }, - ], - Array [ - Object { - "overscanStartIndex": 94, - "overscanStopIndex": 99, - "visibleStartIndex": 95, - "visibleStopIndex": 99, - }, - ], - Array [ - Object { - "overscanStartIndex": 95, - "overscanStopIndex": 99, - "visibleStartIndex": 96, - "visibleStopIndex": 99, - }, - ], - Array [ - Object { - "overscanStartIndex": 93, - "overscanStopIndex": 99, - "visibleStartIndex": 95, - "visibleStopIndex": 99, - }, - ], -] -`; - -exports[`VariableSizeList scrollToItem method scroll with align = "auto" should work with partially-visible items 1`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 5, - "visibleStartIndex": 0, - "visibleStopIndex": 3, - }, - ], - Array [ - Object { - "overscanStartIndex": 5, - "overscanStopIndex": 12, - "visibleStartIndex": 7, - "visibleStopIndex": 10, - }, - ], - Array [ - Object { - "overscanStartIndex": 94, - "overscanStopIndex": 99, - "visibleStartIndex": 96, - "visibleStopIndex": 99, - }, - ], - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 6, - "visibleStartIndex": 1, - "visibleStopIndex": 4, - }, - ], - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 5, - "visibleStartIndex": 0, - "visibleStopIndex": 3, - }, - ], -] -`; - -exports[`VariableSizeList scrollToItem method should not set invalid offsets when the list contains few items 1`] = `[Function]`; - -exports[`VariableSizeList scrollToItem method should scroll to the correct item for align = "auto" 1`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 5, - "visibleStartIndex": 0, - "visibleStopIndex": 3, - }, - ], - Array [ - Object { - "overscanStartIndex": 6, - "overscanStopIndex": 12, - "visibleStartIndex": 8, - "visibleStopIndex": 10, - }, - ], - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 7, - "visibleStartIndex": 2, - "visibleStopIndex": 5, - }, - ], -] -`; - -exports[`VariableSizeList scrollToItem method should scroll to the correct item for align = "center" 1`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 5, - "visibleStartIndex": 0, - "visibleStopIndex": 3, - }, - ], - Array [ - Object { - "overscanStartIndex": 7, - "overscanStopIndex": 13, - "visibleStartIndex": 9, - "visibleStopIndex": 11, - }, - ], - Array [ - Object { - "overscanStartIndex": 6, - "overscanStopIndex": 12, - "visibleStartIndex": 8, - "visibleStopIndex": 10, - }, - ], - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 6, - "visibleStartIndex": 0, - "visibleStopIndex": 4, - }, - ], -] -`; - -exports[`VariableSizeList scrollToItem method should scroll to the correct item for align = "center" 2`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 5, - "visibleStartIndex": 0, - "visibleStopIndex": 3, - }, - ], - Array [ - Object { - "overscanStartIndex": 7, - "overscanStopIndex": 13, - "visibleStartIndex": 9, - "visibleStopIndex": 11, - }, - ], - Array [ - Object { - "overscanStartIndex": 6, - "overscanStopIndex": 12, - "visibleStartIndex": 8, - "visibleStopIndex": 10, - }, - ], - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 6, - "visibleStartIndex": 0, - "visibleStopIndex": 4, - }, - ], - Array [ - Object { - "overscanStartIndex": 15, - "overscanStopIndex": 19, - "visibleStartIndex": 17, - "visibleStopIndex": 19, - }, - ], -] -`; - -exports[`VariableSizeList scrollToItem method should scroll to the correct item for align = "end" 1`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 5, - "visibleStartIndex": 0, - "visibleStopIndex": 3, - }, - ], - Array [ - Object { - "overscanStartIndex": 6, - "overscanStopIndex": 12, - "visibleStartIndex": 8, - "visibleStopIndex": 10, - }, - ], - Array [ - Object { - "overscanStartIndex": 4, - "overscanStopIndex": 11, - "visibleStartIndex": 6, - "visibleStopIndex": 9, - }, - ], - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 5, - "visibleStartIndex": 0, - "visibleStopIndex": 3, - }, - ], -] -`; - -exports[`VariableSizeList scrollToItem method should scroll to the correct item for align = "smart" 1`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 5, - "visibleStartIndex": 0, - "visibleStopIndex": 3, - }, - ], - Array [ - Object { - "overscanStartIndex": 7, - "overscanStopIndex": 13, - "visibleStartIndex": 9, - "visibleStopIndex": 11, - }, - ], - Array [ - Object { - "overscanStartIndex": 4, - "overscanStopIndex": 11, - "visibleStartIndex": 6, - "visibleStopIndex": 9, - }, - ], - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 6, - "visibleStartIndex": 0, - "visibleStopIndex": 4, - }, - ], -] -`; - -exports[`VariableSizeList scrollToItem method should scroll to the correct item for align = "start" 1`] = ` -Array [ - Array [ - Object { - "overscanStartIndex": 0, - "overscanStopIndex": 5, - "visibleStartIndex": 0, - "visibleStopIndex": 3, - }, - ], - Array [ - Object { - "overscanStartIndex": 8, - "overscanStopIndex": 14, - "visibleStartIndex": 10, - "visibleStopIndex": 12, - }, - ], - Array [ - Object { - "overscanStartIndex": 7, - "overscanStopIndex": 13, - "visibleStartIndex": 9, - "visibleStopIndex": 11, - }, - ], - Array [ - Object { - "overscanStartIndex": 15, - "overscanStopIndex": 19, - "visibleStartIndex": 17, - "visibleStopIndex": 19, - }, - ], -] -`; diff --git a/src/__tests__/areEqual.js b/src/__tests__/areEqual.js deleted file mode 100644 index 6551514a..00000000 --- a/src/__tests__/areEqual.js +++ /dev/null @@ -1,32 +0,0 @@ -import React, { memo } from 'react'; -import ReactTestRenderer from 'react-test-renderer'; -import { areEqual } from '..'; - -describe('areEqual', () => { - let Component, MemoizedComponent; - - beforeEach(() => { - Component = jest.fn(() => null); - MemoizedComponent = memo(Component, areEqual); - }); - - it('should not re-render when the style prop changes', () => { - const renderer = ReactTestRenderer.create( - - ); - expect(Component).toHaveBeenCalledTimes(1); - - renderer.update(); - expect(Component).toHaveBeenCalledTimes(1); - }); - - it('should re-render when other props change', () => { - const renderer = ReactTestRenderer.create( - - ); - expect(Component).toHaveBeenCalledTimes(1); - - renderer.update(); - expect(Component).toHaveBeenCalledTimes(2); - }); -}); diff --git a/src/__tests__/shouldComponentUpdate.js b/src/__tests__/shouldComponentUpdate.js deleted file mode 100644 index 8b1f2ba9..00000000 --- a/src/__tests__/shouldComponentUpdate.js +++ /dev/null @@ -1,36 +0,0 @@ -import React, { Component } from 'react'; -import ReactTestRenderer from 'react-test-renderer'; -import { shouldComponentUpdate } from '..'; - -describe('shouldComponentUpdate', () => { - let ClassComponent, render; - - beforeEach(() => { - render = jest.fn(() => null); - - ClassComponent = class extends Component { - shouldComponentUpdate = shouldComponentUpdate.bind(this); - render = render; - }; - }); - - it('should not re-render when the style prop changes', () => { - const renderer = ReactTestRenderer.create( - - ); - expect(render).toHaveBeenCalledTimes(1); - - renderer.update(); - expect(render).toHaveBeenCalledTimes(1); - }); - - it('should re-render when other props change', () => { - const renderer = ReactTestRenderer.create( - - ); - expect(render).toHaveBeenCalledTimes(1); - - renderer.update(); - expect(render).toHaveBeenCalledTimes(2); - }); -}); diff --git a/src/areEqual.js b/src/areEqual.js deleted file mode 100644 index 072951ef..00000000 --- a/src/areEqual.js +++ /dev/null @@ -1,18 +0,0 @@ -// @flow - -import shallowDiffers from './shallowDiffers'; - -// Custom comparison function for React.memo(). -// It knows to compare individual style props and ignore the wrapper object. -// See https://reactjs.org/docs/react-api.html#reactmemo -export default function areEqual( - prevProps: Object, - nextProps: Object -): boolean { - const { style: prevStyle, ...prevRest } = prevProps; - const { style: nextStyle, ...nextRest } = nextProps; - - return ( - !shallowDiffers(prevStyle, nextStyle) && !shallowDiffers(prevRest, nextRest) - ); -} diff --git a/src/components/Block.tsx b/src/components/Block.tsx new file mode 100644 index 00000000..679a0e58 --- /dev/null +++ b/src/components/Block.tsx @@ -0,0 +1,19 @@ +import type { HTMLAttributes, PropsWithChildren } from "react"; +import { ErrorBoundary } from "./ErrorBoundary"; + +export function Block({ + children, + className, + ...rest +}: PropsWithChildren & { className?: string }>) { + return ( + +
+ {children} +
+
+ ); +} diff --git a/src/components/Box.tsx b/src/components/Box.tsx new file mode 100644 index 00000000..ed49e71a --- /dev/null +++ b/src/components/Box.tsx @@ -0,0 +1,75 @@ +import type { CSSProperties, HTMLAttributes } from "react"; +import { cn } from "../utils/cn"; + +export function Box({ + align, + children, + className, + direction, + gap = 0, + grow, + justify, + shrink, + style, + wrap, + ...rest +}: HTMLAttributes & { + align?: "center" | "end" | "start" | "stretch"; + className?: string; + direction: "column" | "row"; + gap?: 0 | 1 | 2 | 3 | 4; + grow?: 0 | 1; + justify?: "around" | "between" | "center" | "end" | "start" | "stretch"; + shrink?: 0 | 1; + style?: CSSProperties; + wrap?: boolean; +}) { + return ( +
+ {children} +
+ ); +} diff --git a/src/components/Button.tsx b/src/components/Button.tsx new file mode 100644 index 00000000..d7ec8188 --- /dev/null +++ b/src/components/Button.tsx @@ -0,0 +1,63 @@ +import type { HTMLAttributes, PropsWithChildren } from "react"; +import type { Intent } from "../types"; +import { Button as HeadlessButton } from "@headlessui/react"; +import { cn } from "../utils/cn"; + +export function Button({ + children, + className, + disabled, + intent = "none", + ...rest +}: PropsWithChildren< + HTMLAttributes & { + className?: string; + disabled?: boolean; + intent?: Intent; + } +>) { + return ( + + {children} + + ); +} + +function getClassNames(intent: Intent, disabled: boolean) { + switch (intent) { + case "danger": { + return cn("bg-red-400 text-red-800 focus:text-black", { + "hover:bg-red-500 hover:text-red-950 focus:text-black": !disabled + }); + } + case "none": { + return cn("bg-emerald-400 text-emerald-800 focus:text-black", { + "hover:bg-emerald-500 hover:text-emerald-950 focus:text-black": + !disabled + }); + } + case "success": + case "primary": { + return cn("bg-sky-400 text-sky-800 focus:text-black", { + "hover:bg-sky-500 hover:text-sky-950 focus:text-black": !disabled + }); + } + case "warning": { + return cn("bg-amber-400 text-amber-800 focus:text-black", { + "hover:bg-amber-500 hover:text-amber-950 focus:text-black": !disabled + }); + } + } +} diff --git a/src/components/Callout.tsx b/src/components/Callout.tsx new file mode 100644 index 00000000..f6dfef23 --- /dev/null +++ b/src/components/Callout.tsx @@ -0,0 +1,65 @@ +import { + CheckCircleIcon, + ExclamationTriangleIcon, + InformationCircleIcon +} from "@heroicons/react/20/solid"; +import type { HTMLAttributes, PropsWithChildren } from "react"; +import type { Intent } from "../types"; + +export function Callout({ + children, + className, + intent = "none", + ...rest +}: PropsWithChildren< + HTMLAttributes & { + className?: string; + intent?: Intent; + } +>) { + let Icon = ExclamationTriangleIcon; + switch (intent) { + case "none": + case "primary": { + Icon = InformationCircleIcon; + break; + } + case "success": { + Icon = CheckCircleIcon; + break; + } + } + + return ( +
+
+ +
{children}
+
+
+ ); +} + +function getClassNames(intent: Intent) { + switch (intent) { + case "danger": { + return "bg-black/10 bg-border border-2 border-red-400 text-white [&_svg]:text-red-400"; + } + case "none": { + return "bg-black/10 bg-border border-2 border-white/40 text-white [&_svg]:text-white/60"; + } + case "primary": { + return "bg-black/10 bg-border border-2 border-sky-400 text-white [&_svg]:text-sky-400"; + } + case "success": { + return "bg-black/10 bg-border border-2 border-emerald-400 text-white [&_svg]:text-emerald-400"; + } + case "warning": { + return "bg-black/10 bg-border border-2 border-amber-400 text-white [&_svg]:text-amber-400"; + } + } +} diff --git a/src/components/Checkbox.tsx b/src/components/Checkbox.tsx new file mode 100644 index 00000000..8e1cfc78 --- /dev/null +++ b/src/components/Checkbox.tsx @@ -0,0 +1,57 @@ +import { + type FunctionComponent, + type HTMLAttributes, + type PropsWithChildren, + type SVGProps +} from "react"; +import CheckedIcon from "../../public/svgs/checkbox-checked.svg?react"; +import IndeterminateIcon from "../../public/svgs/checkbox-indeterminate.svg?react"; +import UncheckedIcon from "../../public/svgs/checkbox-unchecked.svg?react"; + +export function Checkbox({ + checked, + children, + className, + indeterminate, + onChange, + ...rest +}: PropsWithChildren< + Omit, "defaultChecked" | "onChange"> & { + checked: boolean; + className?: string; + indeterminate?: boolean; + onChange: (value: boolean) => void; + } +>) { + let IconElement: FunctionComponent>; + let iconClassName: string; + if (indeterminate) { + IconElement = IndeterminateIcon; + iconClassName = "fill-white"; + } else if (checked) { + IconElement = CheckedIcon; + iconClassName = "fill-blue-600"; + } else { + IconElement = UncheckedIcon; + iconClassName = "fill-slate-600"; + } + + return ( + + ); +} diff --git a/src/components/ContinueLink.tsx b/src/components/ContinueLink.tsx new file mode 100644 index 00000000..5fc62b9e --- /dev/null +++ b/src/components/ContinueLink.tsx @@ -0,0 +1,16 @@ +import type { routeMap } from "../routes"; +import { Link } from "./Link"; + +export function ContinueLink({ + title, + to +}: { + title: string; + to: keyof typeof routeMap; +}) { + return ( +
+ Continue to {title}… +
+ ); +} diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx new file mode 100644 index 00000000..d2200c89 --- /dev/null +++ b/src/components/ErrorBoundary.tsx @@ -0,0 +1,32 @@ +import type { PropsWithChildren } from "react"; +import { + ErrorBoundary as ErrorBoundaryExternal, + type FallbackProps +} from "react-error-boundary"; +import { Callout } from "./Callout"; +import { Box } from "./Box"; +import { Button } from "./Button"; + +export function ErrorBoundary({ children }: PropsWithChildren) { + return ( + + {children} + + ); +} + +function FallbackComponent({ error, resetErrorBoundary }: FallbackProps) { + return ( + + +
Something went wrong!
+
+          {error.message}
+        
+ +
+
+ ); +} diff --git a/src/components/ExternalLink.tsx b/src/components/ExternalLink.tsx new file mode 100644 index 00000000..d0a126d4 --- /dev/null +++ b/src/components/ExternalLink.tsx @@ -0,0 +1,19 @@ +import type { PropsWithChildren } from "react"; + +export function ExternalLink({ + children, + className, + href, + target = "_blank" +}: PropsWithChildren<{ className?: string; href: string; target?: string }>) { + return ( + + {children} + + ); +} diff --git a/src/components/Header.tsx b/src/components/Header.tsx new file mode 100644 index 00000000..b514e98f --- /dev/null +++ b/src/components/Header.tsx @@ -0,0 +1,33 @@ +import { ChevronRightIcon } from "@heroicons/react/20/solid"; +import { Box } from "./Box"; +import { useEffect } from "react"; + +export function Header({ + section, + title +}: { + section?: string; + title: string; +}) { + useEffect(() => { + const originalTitle = document.title; + + document.title = `react-window: ${section ? `${section}: ${title}` : title}`; + + return () => { + document.title = originalTitle; + }; + }); + + return ( + + {section && ( + <> +
{section}
+ + + )} +
{title}
+
+ ); +} diff --git a/src/components/Input.tsx b/src/components/Input.tsx new file mode 100644 index 00000000..cc961522 --- /dev/null +++ b/src/components/Input.tsx @@ -0,0 +1,29 @@ +import type { InputHTMLAttributes, PropsWithChildren } from "react"; + +export function Input({ + children, + className, + onChange, + value, + ...rest +}: PropsWithChildren< + Omit, "onChange"> & { + className?: string; + onChange: (value: Type) => void; + value: Type; + } +>) { + return ( + { + onChange(event.currentTarget.value as Type); + }} + value={value} + {...rest} + > + {children} + + ); +} diff --git a/src/components/Link.tsx b/src/components/Link.tsx new file mode 100644 index 00000000..ea81822c --- /dev/null +++ b/src/components/Link.tsx @@ -0,0 +1,12 @@ +import type { HTMLAttributes } from "react"; +import { Link as ReactRouterLink } from "react-router-dom"; +import type { routeMap } from "../routes"; + +export function Link({ + to, + ...rest +}: HTMLAttributes & { + to: keyof typeof routeMap; +}) { + return ; +} diff --git a/src/components/LoadingSpinner.tsx b/src/components/LoadingSpinner.tsx new file mode 100644 index 00000000..6f61bd2e --- /dev/null +++ b/src/components/LoadingSpinner.tsx @@ -0,0 +1,11 @@ +import { ArrowPathIcon } from "@heroicons/react/20/solid"; +import { Box } from "./Box"; + +export function LoadingSpinner() { + return ( + + + Loading... + + ); +} diff --git a/src/components/Radio.tsx b/src/components/Radio.tsx new file mode 100644 index 00000000..cd256530 --- /dev/null +++ b/src/components/Radio.tsx @@ -0,0 +1,56 @@ +import { + type FunctionComponent, + type HTMLAttributes, + type PropsWithChildren, + type SVGProps +} from "react"; +import CheckedIcon from "../../public/svgs/radio-checked.svg?react"; +import UncheckedIcon from "../../public/svgs/radio-unchecked.svg?react"; + +export function Radio({ + checked, + children, + className, + name, + onChange, + value, + ...rest +}: PropsWithChildren< + Omit, "defaultChecked" | "onChange"> & { + checked: boolean; + name: string; + onChange: (value: Value) => void; + value: Value; + } +>) { + let IconElement: FunctionComponent>; + let iconClassName: string; + if (checked) { + IconElement = CheckedIcon; + iconClassName = "fill-blue-600"; + } else { + IconElement = UncheckedIcon; + iconClassName = "fill-slate-600"; + } + + return ( + + ); +} diff --git a/src/components/RouteChangeHandler.tsx b/src/components/RouteChangeHandler.tsx new file mode 100644 index 00000000..5b7098e8 --- /dev/null +++ b/src/components/RouteChangeHandler.tsx @@ -0,0 +1,26 @@ +import { useLocation } from "react-router-dom"; +import { useIsomorphicLayoutEffect } from "../../lib/hooks/useIsomorphicLayoutEffect"; +import { useNavStore } from "../hooks/useNavStore"; + +export function RouteChangeHandler() { + const { hide } = useNavStore(); + + const { pathname } = useLocation(); + + useIsomorphicLayoutEffect(() => { + hide(); + + const main = document.body.querySelector("[data-main-scrollable]"); + if (main) { + const timeout = setTimeout(() => { + main.scrollTo(0, 0); + }, 1); + + return () => { + clearTimeout(timeout); + }; + } + }, [pathname]); + + return null; +} diff --git a/src/components/Select.tsx b/src/components/Select.tsx new file mode 100644 index 00000000..0c3349f8 --- /dev/null +++ b/src/components/Select.tsx @@ -0,0 +1,80 @@ +import { + Listbox, + ListboxButton, + ListboxOption, + ListboxOptions, + Transition +} from "@headlessui/react"; +import { ChevronUpDownIcon } from "@heroicons/react/20/solid"; +import { Fragment } from "react"; +import { cn } from "../utils/cn"; + +export type Option = { + label: string; + value: Value; +}; + +export function Select({ + className, + defaultValue, + onChange, + options, + placeholder = "", + value +}: { + className?: string; + defaultValue?: Option | undefined; + onChange: (value: Option) => void; + options: Option[]; + placeholder?: string; + value: Option | undefined; +}) { + return ( + +
+ + {value?.label ? ( + {value.label} + ) : ( + {placeholder} + )} + + + + + + {options.map((option, index) => ( + + {option.label} + + ))} + + +
+
+ ); +} diff --git a/src/components/code/Code.tsx b/src/components/code/Code.tsx new file mode 100644 index 00000000..94ded1ec --- /dev/null +++ b/src/components/code/Code.tsx @@ -0,0 +1,21 @@ +import { cn } from "../../utils/cn"; +import "./code-mirror.css"; + +export function Code({ + className = "", + html +}: { + className?: string; + html: string; +}) { + return ( + + ); +} diff --git a/src/components/code/FormattedCode.tsx b/src/components/code/FormattedCode.tsx new file mode 100644 index 00000000..ae4a0a9c --- /dev/null +++ b/src/components/code/FormattedCode.tsx @@ -0,0 +1,61 @@ +import { useJSONData } from "../../hooks/useJSONData"; +import useLocalStorage from "../../hooks/useLocalStorage"; +import { cn } from "../../utils/cn"; +import { ErrorBoundary } from "../ErrorBoundary"; +import { LoadingSpinner } from "../LoadingSpinner"; +import { Code } from "./Code"; + +type Markdown = { + javaScript: string; + typeScript?: string; +}; + +export function FormattedCode({ url }: { url: string }) { + return ( + + + + ); +} + +export function FormattedCodeLoader({ url }: { url: string }) { + const [ts, setTS] = useLocalStorage("CodeTabs::tab", true); + + const json = useJSONData(url); + + if (json === undefined) { + return ; + } + + const code = ( + + ); + + if (!json.typeScript) { + return code; + } + + return ( +
+ {code} + +
+ ); +} diff --git a/src/components/code/code-mirror.css b/src/components/code/code-mirror.css new file mode 100644 index 00000000..2a44b5ba --- /dev/null +++ b/src/components/code/code-mirror.css @@ -0,0 +1,36 @@ +.tok-comment { + color: var(--color-slate-500); +} +.tok-definition { +} +.tok-local { +} +.tok-keyword { + color: var(--color-pink-400); +} +.tok-meta { +} +.tok-number { +} +.tok-operator { + color: var(--color-slate-400); +} +.tok-propertyName { + color: var(--color-sky-300); +} +.tok-punctuation { + color: var(--color-slate-400); +} +.tok-string { + color: var(--color-teal-300); +} +.tok-string2 { + color: var(--color-sky-300); +} +.tok-typeName { + color: var(--color-pink-400); +} +.tok-variableName:not(.tok-definition) { +} +.tok-variableName2 { +} diff --git a/src/components/code/types.ts b/src/components/code/types.ts new file mode 100644 index 00000000..37c8821a --- /dev/null +++ b/src/components/code/types.ts @@ -0,0 +1 @@ +export type Language = "JSX" | "TypeScript" | "TSX"; diff --git a/src/components/props/ComponentProps.tsx b/src/components/props/ComponentProps.tsx new file mode 100644 index 00000000..07a9ccbb --- /dev/null +++ b/src/components/props/ComponentProps.tsx @@ -0,0 +1,54 @@ +import { ArrowTopRightOnSquareIcon } from "@heroicons/react/20/solid"; +import type { ComponentDoc } from "react-docgen-typescript"; +import { repository } from "../../../package.json"; +import { useJSONData } from "../../hooks/useJSONData"; +import { Box } from "../Box"; +import { ErrorBoundary } from "../ErrorBoundary"; +import { ExternalLink } from "../ExternalLink"; +import { Header } from "../Header"; +import { LoadingSpinner } from "../LoadingSpinner"; +import { PropsBlocks } from "./PropsBlocks"; + +export function ComponentProps({ + section, + url +}: { + section: string; + url: string; +}) { + return ( + + + + ); +} + +function ComponentPropsLoader({ + section, + url +}: { + section: string; + url: string; +}) { + const json = useJSONData(url); + + return json ? ( + <> + +
+ + + + + + + ) : ( + <> +
+ + + ); +} diff --git a/src/components/props/PropsBlocks.tsx b/src/components/props/PropsBlocks.tsx new file mode 100644 index 00000000..360e9226 --- /dev/null +++ b/src/components/props/PropsBlocks.tsx @@ -0,0 +1,65 @@ +import { useMemo } from "react"; +import type { ComponentDoc, PropItem } from "react-docgen-typescript"; +import { processPropsJSON } from "../../utils/processPropsJSON"; + +export function PropsBlocks({ json }: { json: ComponentDoc }) { + const { optionalProps, requiredProps } = useMemo( + () => processPropsJSON(json), + [json] + ); + + return ( + <> + + + + ); +} + +export function PropsBlock({ + header, + props +}: { + header: string; + props: PropItem[]; +}) { + if (props.length === 0) { + return null; + } + + return ( +
+
{header}
+
+ {props.map((prop) => ( +
+
+ + {prop.name} + {prop.required || "?"}:{" "} + {"raw" in prop.type ? prop.type.raw : prop.type.name} + {prop.defaultValue && ( + <> + {" = "} + + {prop.defaultValue.value} + + + )} + +
+
• ") + .replaceAll("\n\n", "

") + .replaceAll(/`([^`]+)`/g, "$1") + }} + >
+
+ ))} +
+
+ ); +} diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 00000000..6a1f7a02 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,3 @@ +export const EMPTY_ARRAY: unknown[] = []; +export const EMPTY_OBJECT = {}; +export const NOOP_FUNCTION = () => {}; diff --git a/src/createGridComponent.js b/src/createGridComponent.js deleted file mode 100644 index 14f498f5..00000000 --- a/src/createGridComponent.js +++ /dev/null @@ -1,919 +0,0 @@ -// @flow - -import memoizeOne from 'memoize-one'; -import { createElement, PureComponent } from 'react'; -import { cancelTimeout, requestTimeout } from './timer'; -import { getScrollbarSize, getRTLOffsetType } from './domHelpers'; - -import type { TimeoutID } from './timer'; - -type Direction = 'ltr' | 'rtl'; -export type ScrollToAlign = 'auto' | 'smart' | 'center' | 'start' | 'end'; - -type itemSize = number | ((index: number) => number); - -type RenderComponentProps = {| - columnIndex: number, - data: T, - isScrolling?: boolean, - rowIndex: number, - style: Object, -|}; -export type RenderComponent = React$ComponentType< - $Shape> ->; - -type ScrollDirection = 'forward' | 'backward'; - -type OnItemsRenderedCallback = ({ - overscanColumnStartIndex: number, - overscanColumnStopIndex: number, - overscanRowStartIndex: number, - overscanRowStopIndex: number, - visibleColumnStartIndex: number, - visibleColumnStopIndex: number, - visibleRowStartIndex: number, - visibleRowStopIndex: number, -}) => void; -type OnScrollCallback = ({ - horizontalScrollDirection: ScrollDirection, - scrollLeft: number, - scrollTop: number, - scrollUpdateWasRequested: boolean, - verticalScrollDirection: ScrollDirection, -}) => void; - -type ScrollEvent = SyntheticEvent; -type ItemStyleCache = { [key: string]: Object }; - -type OuterProps = {| - children: React$Node, - className: string | void, - onScroll: ScrollEvent => void, - style: { - [string]: mixed, - }, -|}; - -type InnerProps = {| - children: React$Node, - style: { - [string]: mixed, - }, -|}; - -export type Props = {| - children: RenderComponent, - className?: string, - columnCount: number, - columnWidth: itemSize, - direction: Direction, - height: number, - initialScrollLeft?: number, - initialScrollTop?: number, - innerRef?: any, - innerElementType?: string | React$AbstractComponent, - innerTagName?: string, // deprecated - itemData: T, - itemKey?: (params: {| - columnIndex: number, - data: T, - rowIndex: number, - |}) => any, - onItemsRendered?: OnItemsRenderedCallback, - onScroll?: OnScrollCallback, - outerRef?: any, - outerElementType?: string | React$AbstractComponent, - outerTagName?: string, // deprecated - overscanColumnCount?: number, - overscanColumnsCount?: number, // deprecated - overscanCount?: number, // deprecated - overscanRowCount?: number, - overscanRowsCount?: number, // deprecated - rowCount: number, - rowHeight: itemSize, - style?: Object, - useIsScrolling: boolean, - width: number, -|}; - -type State = {| - instance: any, - isScrolling: boolean, - horizontalScrollDirection: ScrollDirection, - scrollLeft: number, - scrollTop: number, - scrollUpdateWasRequested: boolean, - verticalScrollDirection: ScrollDirection, -|}; - -type getItemOffset = ( - props: Props, - index: number, - instanceProps: any -) => number; -type getItemSize = ( - props: Props, - index: number, - instanceProps: any -) => number; -type getEstimatedTotalSize = (props: Props, instanceProps: any) => number; -type GetOffsetForItemAndAlignment = ( - props: Props, - index: number, - align: ScrollToAlign, - scrollOffset: number, - instanceProps: any, - scrollbarSize: number -) => number; -type GetStartIndexForOffset = ( - props: Props, - offset: number, - instanceProps: any -) => number; -type GetStopIndexForStartIndex = ( - props: Props, - startIndex: number, - scrollOffset: number, - instanceProps: any -) => number; -type InitInstanceProps = (props: Props, instance: any) => any; -type ValidateProps = (props: Props) => void; - -const IS_SCROLLING_DEBOUNCE_INTERVAL = 150; - -const defaultItemKey = ({ columnIndex, data, rowIndex }) => - `${rowIndex}:${columnIndex}`; - -// In DEV mode, this Set helps us only log a warning once per component instance. -// This avoids spamming the console every time a render happens. -let devWarningsOverscanCount = null; -let devWarningsOverscanRowsColumnsCount = null; -let devWarningsTagName = null; -if (process.env.NODE_ENV !== 'production') { - if (typeof window !== 'undefined' && typeof window.WeakSet !== 'undefined') { - devWarningsOverscanCount = new WeakSet(); - devWarningsOverscanRowsColumnsCount = new WeakSet(); - devWarningsTagName = new WeakSet(); - } -} - -export default function createGridComponent({ - getColumnOffset, - getColumnStartIndexForOffset, - getColumnStopIndexForStartIndex, - getColumnWidth, - getEstimatedTotalHeight, - getEstimatedTotalWidth, - getOffsetForColumnAndAlignment, - getOffsetForRowAndAlignment, - getRowHeight, - getRowOffset, - getRowStartIndexForOffset, - getRowStopIndexForStartIndex, - initInstanceProps, - shouldResetStyleCacheOnItemSizeChange, - validateProps, -}: {| - getColumnOffset: getItemOffset, - getColumnStartIndexForOffset: GetStartIndexForOffset, - getColumnStopIndexForStartIndex: GetStopIndexForStartIndex, - getColumnWidth: getItemSize, - getEstimatedTotalHeight: getEstimatedTotalSize, - getEstimatedTotalWidth: getEstimatedTotalSize, - getOffsetForColumnAndAlignment: GetOffsetForItemAndAlignment, - getOffsetForRowAndAlignment: GetOffsetForItemAndAlignment, - getRowOffset: getItemOffset, - getRowHeight: getItemSize, - getRowStartIndexForOffset: GetStartIndexForOffset, - getRowStopIndexForStartIndex: GetStopIndexForStartIndex, - initInstanceProps: InitInstanceProps, - shouldResetStyleCacheOnItemSizeChange: boolean, - validateProps: ValidateProps, -|}) { - return class Grid extends PureComponent, State> { - _instanceProps: any = initInstanceProps(this.props, this); - _resetIsScrollingTimeoutId: TimeoutID | null = null; - _outerRef: ?HTMLDivElement; - - static defaultProps = { - direction: 'ltr', - itemData: undefined, - useIsScrolling: false, - }; - - state: State = { - instance: this, - isScrolling: false, - horizontalScrollDirection: 'forward', - scrollLeft: - typeof this.props.initialScrollLeft === 'number' - ? this.props.initialScrollLeft - : 0, - scrollTop: - typeof this.props.initialScrollTop === 'number' - ? this.props.initialScrollTop - : 0, - scrollUpdateWasRequested: false, - verticalScrollDirection: 'forward', - }; - - // Always use explicit constructor for React components. - // It produces less code after transpilation. (#26) - // eslint-disable-next-line no-useless-constructor - constructor(props: Props) { - super(props); - } - - static getDerivedStateFromProps( - nextProps: Props, - prevState: State - ): $Shape | null { - validateSharedProps(nextProps, prevState); - validateProps(nextProps); - return null; - } - - scrollTo({ - scrollLeft, - scrollTop, - }: { - scrollLeft: number, - scrollTop: number, - }): void { - if (scrollLeft !== undefined) { - scrollLeft = Math.max(0, scrollLeft); - } - if (scrollTop !== undefined) { - scrollTop = Math.max(0, scrollTop); - } - - this.setState(prevState => { - if (scrollLeft === undefined) { - scrollLeft = prevState.scrollLeft; - } - if (scrollTop === undefined) { - scrollTop = prevState.scrollTop; - } - - if ( - prevState.scrollLeft === scrollLeft && - prevState.scrollTop === scrollTop - ) { - return null; - } - - return { - horizontalScrollDirection: - prevState.scrollLeft < scrollLeft ? 'forward' : 'backward', - scrollLeft: scrollLeft, - scrollTop: scrollTop, - scrollUpdateWasRequested: true, - verticalScrollDirection: - prevState.scrollTop < scrollTop ? 'forward' : 'backward', - }; - }, this._resetIsScrollingDebounced); - } - - scrollToItem({ - align = 'auto', - columnIndex, - rowIndex, - }: { - align: ScrollToAlign, - columnIndex?: number, - rowIndex?: number, - }): void { - const { columnCount, height, rowCount, width } = this.props; - const { scrollLeft, scrollTop } = this.state; - const scrollbarSize = getScrollbarSize(); - - if (columnIndex !== undefined) { - columnIndex = Math.max(0, Math.min(columnIndex, columnCount - 1)); - } - if (rowIndex !== undefined) { - rowIndex = Math.max(0, Math.min(rowIndex, rowCount - 1)); - } - - const estimatedTotalHeight = getEstimatedTotalHeight( - this.props, - this._instanceProps - ); - const estimatedTotalWidth = getEstimatedTotalWidth( - this.props, - this._instanceProps - ); - - // The scrollbar size should be considered when scrolling an item into view, - // to ensure it's fully visible. - // But we only need to account for its size when it's actually visible. - const horizontalScrollbarSize = - estimatedTotalWidth > width ? scrollbarSize : 0; - const verticalScrollbarSize = - estimatedTotalHeight > height ? scrollbarSize : 0; - - this.scrollTo({ - scrollLeft: - columnIndex !== undefined - ? getOffsetForColumnAndAlignment( - this.props, - columnIndex, - align, - scrollLeft, - this._instanceProps, - verticalScrollbarSize - ) - : scrollLeft, - scrollTop: - rowIndex !== undefined - ? getOffsetForRowAndAlignment( - this.props, - rowIndex, - align, - scrollTop, - this._instanceProps, - horizontalScrollbarSize - ) - : scrollTop, - }); - } - - componentDidMount() { - const { initialScrollLeft, initialScrollTop } = this.props; - - if (this._outerRef != null) { - const outerRef = ((this._outerRef: any): HTMLElement); - if (typeof initialScrollLeft === 'number') { - outerRef.scrollLeft = initialScrollLeft; - } - if (typeof initialScrollTop === 'number') { - outerRef.scrollTop = initialScrollTop; - } - } - - this._callPropsCallbacks(); - } - - componentDidUpdate() { - const { direction } = this.props; - const { scrollLeft, scrollTop, scrollUpdateWasRequested } = this.state; - - if (scrollUpdateWasRequested && this._outerRef != null) { - // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements. - // This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left). - // So we need to determine which browser behavior we're dealing with, and mimic it. - const outerRef = ((this._outerRef: any): HTMLElement); - if (direction === 'rtl') { - switch (getRTLOffsetType()) { - case 'negative': - outerRef.scrollLeft = -scrollLeft; - break; - case 'positive-ascending': - outerRef.scrollLeft = scrollLeft; - break; - default: - const { clientWidth, scrollWidth } = outerRef; - outerRef.scrollLeft = scrollWidth - clientWidth - scrollLeft; - break; - } - } else { - outerRef.scrollLeft = Math.max(0, scrollLeft); - } - - outerRef.scrollTop = Math.max(0, scrollTop); - } - - this._callPropsCallbacks(); - } - - componentWillUnmount() { - if (this._resetIsScrollingTimeoutId !== null) { - cancelTimeout(this._resetIsScrollingTimeoutId); - } - } - - render() { - const { - children, - className, - columnCount, - direction, - height, - innerRef, - innerElementType, - innerTagName, - itemData, - itemKey = defaultItemKey, - outerElementType, - outerTagName, - rowCount, - style, - useIsScrolling, - width, - } = this.props; - const { isScrolling } = this.state; - - const [ - columnStartIndex, - columnStopIndex, - ] = this._getHorizontalRangeToRender(); - const [rowStartIndex, rowStopIndex] = this._getVerticalRangeToRender(); - - const items = []; - if (columnCount > 0 && rowCount) { - for ( - let rowIndex = rowStartIndex; - rowIndex <= rowStopIndex; - rowIndex++ - ) { - for ( - let columnIndex = columnStartIndex; - columnIndex <= columnStopIndex; - columnIndex++ - ) { - items.push( - createElement(children, { - columnIndex, - data: itemData, - isScrolling: useIsScrolling ? isScrolling : undefined, - key: itemKey({ columnIndex, data: itemData, rowIndex }), - rowIndex, - style: this._getItemStyle(rowIndex, columnIndex), - }) - ); - } - } - } - - // Read this value AFTER items have been created, - // So their actual sizes (if variable) are taken into consideration. - const estimatedTotalHeight = getEstimatedTotalHeight( - this.props, - this._instanceProps - ); - const estimatedTotalWidth = getEstimatedTotalWidth( - this.props, - this._instanceProps - ); - - return createElement( - outerElementType || outerTagName || 'div', - { - className, - onScroll: this._onScroll, - ref: this._outerRefSetter, - style: { - position: 'relative', - height, - width, - overflow: 'auto', - WebkitOverflowScrolling: 'touch', - willChange: 'transform', - direction, - ...style, - }, - }, - createElement(innerElementType || innerTagName || 'div', { - children: items, - ref: innerRef, - style: { - height: estimatedTotalHeight, - pointerEvents: isScrolling ? 'none' : undefined, - width: estimatedTotalWidth, - }, - }) - ); - } - - _callOnItemsRendered: ( - overscanColumnStartIndex: number, - overscanColumnStopIndex: number, - overscanRowStartIndex: number, - overscanRowStopIndex: number, - visibleColumnStartIndex: number, - visibleColumnStopIndex: number, - visibleRowStartIndex: number, - visibleRowStopIndex: number - ) => void; - _callOnItemsRendered = memoizeOne( - ( - overscanColumnStartIndex: number, - overscanColumnStopIndex: number, - overscanRowStartIndex: number, - overscanRowStopIndex: number, - visibleColumnStartIndex: number, - visibleColumnStopIndex: number, - visibleRowStartIndex: number, - visibleRowStopIndex: number - ) => - ((this.props.onItemsRendered: any): OnItemsRenderedCallback)({ - overscanColumnStartIndex, - overscanColumnStopIndex, - overscanRowStartIndex, - overscanRowStopIndex, - visibleColumnStartIndex, - visibleColumnStopIndex, - visibleRowStartIndex, - visibleRowStopIndex, - }) - ); - - _callOnScroll: ( - scrollLeft: number, - scrollTop: number, - horizontalScrollDirection: ScrollDirection, - verticalScrollDirection: ScrollDirection, - scrollUpdateWasRequested: boolean - ) => void; - _callOnScroll = memoizeOne( - ( - scrollLeft: number, - scrollTop: number, - horizontalScrollDirection: ScrollDirection, - verticalScrollDirection: ScrollDirection, - scrollUpdateWasRequested: boolean - ) => - ((this.props.onScroll: any): OnScrollCallback)({ - horizontalScrollDirection, - scrollLeft, - scrollTop, - verticalScrollDirection, - scrollUpdateWasRequested, - }) - ); - - _callPropsCallbacks() { - const { columnCount, onItemsRendered, onScroll, rowCount } = this.props; - - if (typeof onItemsRendered === 'function') { - if (columnCount > 0 && rowCount > 0) { - const [ - overscanColumnStartIndex, - overscanColumnStopIndex, - visibleColumnStartIndex, - visibleColumnStopIndex, - ] = this._getHorizontalRangeToRender(); - const [ - overscanRowStartIndex, - overscanRowStopIndex, - visibleRowStartIndex, - visibleRowStopIndex, - ] = this._getVerticalRangeToRender(); - this._callOnItemsRendered( - overscanColumnStartIndex, - overscanColumnStopIndex, - overscanRowStartIndex, - overscanRowStopIndex, - visibleColumnStartIndex, - visibleColumnStopIndex, - visibleRowStartIndex, - visibleRowStopIndex - ); - } - } - - if (typeof onScroll === 'function') { - const { - horizontalScrollDirection, - scrollLeft, - scrollTop, - scrollUpdateWasRequested, - verticalScrollDirection, - } = this.state; - this._callOnScroll( - scrollLeft, - scrollTop, - horizontalScrollDirection, - verticalScrollDirection, - scrollUpdateWasRequested - ); - } - } - - // Lazily create and cache item styles while scrolling, - // So that pure component sCU will prevent re-renders. - // We maintain this cache, and pass a style prop rather than index, - // So that List can clear cached styles and force item re-render if necessary. - _getItemStyle: (rowIndex: number, columnIndex: number) => Object; - _getItemStyle = (rowIndex: number, columnIndex: number): Object => { - const { columnWidth, direction, rowHeight } = this.props; - - const itemStyleCache = this._getItemStyleCache( - shouldResetStyleCacheOnItemSizeChange && columnWidth, - shouldResetStyleCacheOnItemSizeChange && direction, - shouldResetStyleCacheOnItemSizeChange && rowHeight - ); - - const key = `${rowIndex}:${columnIndex}`; - - let style; - if (itemStyleCache.hasOwnProperty(key)) { - style = itemStyleCache[key]; - } else { - const offset = getColumnOffset( - this.props, - columnIndex, - this._instanceProps - ); - const isRtl = direction === 'rtl'; - itemStyleCache[key] = style = { - position: 'absolute', - left: isRtl ? undefined : offset, - right: isRtl ? offset : undefined, - top: getRowOffset(this.props, rowIndex, this._instanceProps), - height: getRowHeight(this.props, rowIndex, this._instanceProps), - width: getColumnWidth(this.props, columnIndex, this._instanceProps), - }; - } - - return style; - }; - - _getItemStyleCache: (_: any, __: any, ___: any) => ItemStyleCache; - _getItemStyleCache = memoizeOne((_: any, __: any, ___: any) => ({})); - - _getHorizontalRangeToRender(): [number, number, number, number] { - const { - columnCount, - overscanColumnCount, - overscanColumnsCount, - overscanCount, - rowCount, - } = this.props; - const { horizontalScrollDirection, isScrolling, scrollLeft } = this.state; - - const overscanCountResolved: number = - overscanColumnCount || overscanColumnsCount || overscanCount || 1; - - if (columnCount === 0 || rowCount === 0) { - return [0, 0, 0, 0]; - } - - const startIndex = getColumnStartIndexForOffset( - this.props, - scrollLeft, - this._instanceProps - ); - const stopIndex = getColumnStopIndexForStartIndex( - this.props, - startIndex, - scrollLeft, - this._instanceProps - ); - - // Overscan by one item in each direction so that tab/focus works. - // If there isn't at least one extra item, tab loops back around. - const overscanBackward = - !isScrolling || horizontalScrollDirection === 'backward' - ? Math.max(1, overscanCountResolved) - : 1; - const overscanForward = - !isScrolling || horizontalScrollDirection === 'forward' - ? Math.max(1, overscanCountResolved) - : 1; - - return [ - Math.max(0, startIndex - overscanBackward), - Math.max(0, Math.min(columnCount - 1, stopIndex + overscanForward)), - startIndex, - stopIndex, - ]; - } - - _getVerticalRangeToRender(): [number, number, number, number] { - const { - columnCount, - overscanCount, - overscanRowCount, - overscanRowsCount, - rowCount, - } = this.props; - const { isScrolling, verticalScrollDirection, scrollTop } = this.state; - - const overscanCountResolved: number = - overscanRowCount || overscanRowsCount || overscanCount || 1; - - if (columnCount === 0 || rowCount === 0) { - return [0, 0, 0, 0]; - } - - const startIndex = getRowStartIndexForOffset( - this.props, - scrollTop, - this._instanceProps - ); - const stopIndex = getRowStopIndexForStartIndex( - this.props, - startIndex, - scrollTop, - this._instanceProps - ); - - // Overscan by one item in each direction so that tab/focus works. - // If there isn't at least one extra item, tab loops back around. - const overscanBackward = - !isScrolling || verticalScrollDirection === 'backward' - ? Math.max(1, overscanCountResolved) - : 1; - const overscanForward = - !isScrolling || verticalScrollDirection === 'forward' - ? Math.max(1, overscanCountResolved) - : 1; - - return [ - Math.max(0, startIndex - overscanBackward), - Math.max(0, Math.min(rowCount - 1, stopIndex + overscanForward)), - startIndex, - stopIndex, - ]; - } - - _onScroll = (event: ScrollEvent): void => { - const { - clientHeight, - clientWidth, - scrollLeft, - scrollTop, - scrollHeight, - scrollWidth, - } = event.currentTarget; - this.setState(prevState => { - if ( - prevState.scrollLeft === scrollLeft && - prevState.scrollTop === scrollTop - ) { - // Scroll position may have been updated by cDM/cDU, - // In which case we don't need to trigger another render, - // And we don't want to update state.isScrolling. - return null; - } - - const { direction } = this.props; - - // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements. - // This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left). - // It's also easier for this component if we convert offsets to the same format as they would be in for ltr. - // So the simplest solution is to determine which browser behavior we're dealing with, and convert based on it. - let calculatedScrollLeft = scrollLeft; - if (direction === 'rtl') { - switch (getRTLOffsetType()) { - case 'negative': - calculatedScrollLeft = -scrollLeft; - break; - case 'positive-descending': - calculatedScrollLeft = scrollWidth - clientWidth - scrollLeft; - break; - } - } - - // Prevent Safari's elastic scrolling from causing visual shaking when scrolling past bounds. - calculatedScrollLeft = Math.max( - 0, - Math.min(calculatedScrollLeft, scrollWidth - clientWidth) - ); - const calculatedScrollTop = Math.max( - 0, - Math.min(scrollTop, scrollHeight - clientHeight) - ); - - return { - isScrolling: true, - horizontalScrollDirection: - prevState.scrollLeft < scrollLeft ? 'forward' : 'backward', - scrollLeft: calculatedScrollLeft, - scrollTop: calculatedScrollTop, - verticalScrollDirection: - prevState.scrollTop < scrollTop ? 'forward' : 'backward', - scrollUpdateWasRequested: false, - }; - }, this._resetIsScrollingDebounced); - }; - - _outerRefSetter = (ref: any): void => { - const { outerRef } = this.props; - - this._outerRef = ((ref: any): HTMLDivElement); - - if (typeof outerRef === 'function') { - outerRef(ref); - } else if ( - outerRef != null && - typeof outerRef === 'object' && - outerRef.hasOwnProperty('current') - ) { - outerRef.current = ref; - } - }; - - _resetIsScrollingDebounced = () => { - if (this._resetIsScrollingTimeoutId !== null) { - cancelTimeout(this._resetIsScrollingTimeoutId); - } - - this._resetIsScrollingTimeoutId = requestTimeout( - this._resetIsScrolling, - IS_SCROLLING_DEBOUNCE_INTERVAL - ); - }; - - _resetIsScrolling = () => { - this._resetIsScrollingTimeoutId = null; - - this.setState({ isScrolling: false }, () => { - // Clear style cache after state update has been committed. - // This way we don't break pure sCU for items that don't use isScrolling param. - this._getItemStyleCache(-1); - }); - }; - }; -} - -const validateSharedProps = ( - { - children, - direction, - height, - innerTagName, - outerTagName, - overscanColumnsCount, - overscanCount, - overscanRowsCount, - width, - }: Props, - { instance }: State -): void => { - if (process.env.NODE_ENV !== 'production') { - if (typeof overscanCount === 'number') { - if (devWarningsOverscanCount && !devWarningsOverscanCount.has(instance)) { - devWarningsOverscanCount.add(instance); - console.warn( - 'The overscanCount prop has been deprecated. ' + - 'Please use the overscanColumnCount and overscanRowCount props instead.' - ); - } - } - - if ( - typeof overscanColumnsCount === 'number' || - typeof overscanRowsCount === 'number' - ) { - if ( - devWarningsOverscanRowsColumnsCount && - !devWarningsOverscanRowsColumnsCount.has(instance) - ) { - devWarningsOverscanRowsColumnsCount.add(instance); - console.warn( - 'The overscanColumnsCount and overscanRowsCount props have been deprecated. ' + - 'Please use the overscanColumnCount and overscanRowCount props instead.' - ); - } - } - - if (innerTagName != null || outerTagName != null) { - if (devWarningsTagName && !devWarningsTagName.has(instance)) { - devWarningsTagName.add(instance); - console.warn( - 'The innerTagName and outerTagName props have been deprecated. ' + - 'Please use the innerElementType and outerElementType props instead.' - ); - } - } - - if (children == null) { - throw Error( - 'An invalid "children" prop has been specified. ' + - 'Value should be a React component. ' + - `"${children === null ? 'null' : typeof children}" was specified.` - ); - } - - switch (direction) { - case 'ltr': - case 'rtl': - // Valid values - break; - default: - throw Error( - 'An invalid "direction" prop has been specified. ' + - 'Value should be either "ltr" or "rtl". ' + - `"${direction}" was specified.` - ); - } - - if (typeof width !== 'number') { - throw Error( - 'An invalid "width" prop has been specified. ' + - 'Grids must specify a number for width. ' + - `"${width === null ? 'null' : typeof width}" was specified.` - ); - } - - if (typeof height !== 'number') { - throw Error( - 'An invalid "height" prop has been specified. ' + - 'Grids must specify a number for height. ' + - `"${height === null ? 'null' : typeof height}" was specified.` - ); - } - } -}; diff --git a/src/createListComponent.js b/src/createListComponent.js deleted file mode 100644 index 86aa643d..00000000 --- a/src/createListComponent.js +++ /dev/null @@ -1,745 +0,0 @@ -// @flow - -import memoizeOne from 'memoize-one'; -import { createElement, PureComponent } from 'react'; -import { cancelTimeout, requestTimeout } from './timer'; -import { getScrollbarSize, getRTLOffsetType } from './domHelpers'; - -import type { TimeoutID } from './timer'; - -export type ScrollToAlign = 'auto' | 'smart' | 'center' | 'start' | 'end'; - -type itemSize = number | ((index: number) => number); -// TODO Deprecate directions "horizontal" and "vertical" -type Direction = 'ltr' | 'rtl' | 'horizontal' | 'vertical'; -type Layout = 'horizontal' | 'vertical'; - -type RenderComponentProps = {| - data: T, - index: number, - isScrolling?: boolean, - style: Object, -|}; -type RenderComponent = React$ComponentType<$Shape>>; - -type ScrollDirection = 'forward' | 'backward'; - -type onItemsRenderedCallback = ({ - overscanStartIndex: number, - overscanStopIndex: number, - visibleStartIndex: number, - visibleStopIndex: number, -}) => void; -type onScrollCallback = ({ - scrollDirection: ScrollDirection, - scrollOffset: number, - scrollUpdateWasRequested: boolean, -}) => void; - -type ScrollEvent = SyntheticEvent; -type ItemStyleCache = { [index: number]: Object }; - -type OuterProps = {| - children: React$Node, - className: string | void, - onScroll: ScrollEvent => void, - style: { - [string]: mixed, - }, -|}; - -type InnerProps = {| - children: React$Node, - style: { - [string]: mixed, - }, -|}; - -export type Props = {| - children: RenderComponent, - className?: string, - direction: Direction, - height: number | string, - initialScrollOffset?: number, - innerRef?: any, - innerElementType?: string | React$AbstractComponent, - innerTagName?: string, // deprecated - itemCount: number, - itemData: T, - itemKey?: (index: number, data: T) => any, - itemSize: itemSize, - layout: Layout, - onItemsRendered?: onItemsRenderedCallback, - onScroll?: onScrollCallback, - outerRef?: any, - outerElementType?: string | React$AbstractComponent, - outerTagName?: string, // deprecated - overscanCount: number, - style?: Object, - useIsScrolling: boolean, - width: number | string, -|}; - -type State = {| - instance: any, - isScrolling: boolean, - scrollDirection: ScrollDirection, - scrollOffset: number, - scrollUpdateWasRequested: boolean, -|}; - -type GetItemOffset = ( - props: Props, - index: number, - instanceProps: any -) => number; -type GetItemSize = ( - props: Props, - index: number, - instanceProps: any -) => number; -type GetEstimatedTotalSize = (props: Props, instanceProps: any) => number; -type GetOffsetForIndexAndAlignment = ( - props: Props, - index: number, - align: ScrollToAlign, - scrollOffset: number, - instanceProps: any -) => number; -type GetStartIndexForOffset = ( - props: Props, - offset: number, - instanceProps: any -) => number; -type GetStopIndexForStartIndex = ( - props: Props, - startIndex: number, - scrollOffset: number, - instanceProps: any -) => number; -type InitInstanceProps = (props: Props, instance: any) => any; -type ValidateProps = (props: Props) => void; - -const IS_SCROLLING_DEBOUNCE_INTERVAL = 150; - -const defaultItemKey = (index: number, data: any) => index; - -// In DEV mode, this Set helps us only log a warning once per component instance. -// This avoids spamming the console every time a render happens. -let devWarningsDirection = null; -let devWarningsTagName = null; -if (process.env.NODE_ENV !== 'production') { - if (typeof window !== 'undefined' && typeof window.WeakSet !== 'undefined') { - devWarningsDirection = new WeakSet(); - devWarningsTagName = new WeakSet(); - } -} - -export default function createListComponent({ - getItemOffset, - getEstimatedTotalSize, - getItemSize, - getOffsetForIndexAndAlignment, - getStartIndexForOffset, - getStopIndexForStartIndex, - initInstanceProps, - shouldResetStyleCacheOnItemSizeChange, - validateProps, -}: {| - getItemOffset: GetItemOffset, - getEstimatedTotalSize: GetEstimatedTotalSize, - getItemSize: GetItemSize, - getOffsetForIndexAndAlignment: GetOffsetForIndexAndAlignment, - getStartIndexForOffset: GetStartIndexForOffset, - getStopIndexForStartIndex: GetStopIndexForStartIndex, - initInstanceProps: InitInstanceProps, - shouldResetStyleCacheOnItemSizeChange: boolean, - validateProps: ValidateProps, -|}) { - return class List extends PureComponent, State> { - _instanceProps: any = initInstanceProps(this.props, this); - _outerRef: ?HTMLDivElement; - _resetIsScrollingTimeoutId: TimeoutID | null = null; - - static defaultProps = { - direction: 'ltr', - itemData: undefined, - layout: 'vertical', - overscanCount: 2, - useIsScrolling: false, - }; - - state: State = { - instance: this, - isScrolling: false, - scrollDirection: 'forward', - scrollOffset: - typeof this.props.initialScrollOffset === 'number' - ? this.props.initialScrollOffset - : 0, - scrollUpdateWasRequested: false, - }; - - // Always use explicit constructor for React components. - // It produces less code after transpilation. (#26) - // eslint-disable-next-line no-useless-constructor - constructor(props: Props) { - super(props); - } - - static getDerivedStateFromProps( - nextProps: Props, - prevState: State - ): $Shape | null { - validateSharedProps(nextProps, prevState); - validateProps(nextProps); - return null; - } - - scrollTo(scrollOffset: number): void { - scrollOffset = Math.max(0, scrollOffset); - - this.setState(prevState => { - if (prevState.scrollOffset === scrollOffset) { - return null; - } - return { - scrollDirection: - prevState.scrollOffset < scrollOffset ? 'forward' : 'backward', - scrollOffset: scrollOffset, - scrollUpdateWasRequested: true, - }; - }, this._resetIsScrollingDebounced); - } - - scrollToItem(index: number, align: ScrollToAlign = 'auto'): void { - const { itemCount, layout } = this.props; - const { scrollOffset } = this.state; - - index = Math.max(0, Math.min(index, itemCount - 1)); - - // The scrollbar size should be considered when scrolling an item into view, to ensure it's fully visible. - // But we only need to account for its size when it's actually visible. - // This is an edge case for lists; normally they only scroll in the dominant direction. - let scrollbarSize = 0; - if (this._outerRef) { - const outerRef = ((this._outerRef: any): HTMLElement); - if (layout === 'vertical') { - scrollbarSize = - outerRef.scrollWidth > outerRef.clientWidth - ? getScrollbarSize() - : 0; - } else { - scrollbarSize = - outerRef.scrollHeight > outerRef.clientHeight - ? getScrollbarSize() - : 0; - } - } - - this.scrollTo( - getOffsetForIndexAndAlignment( - this.props, - index, - align, - scrollOffset, - this._instanceProps, - scrollbarSize - ) - ); - } - - componentDidMount() { - const { direction, initialScrollOffset, layout } = this.props; - - if (typeof initialScrollOffset === 'number' && this._outerRef != null) { - const outerRef = ((this._outerRef: any): HTMLElement); - // TODO Deprecate direction "horizontal" - if (direction === 'horizontal' || layout === 'horizontal') { - outerRef.scrollLeft = initialScrollOffset; - } else { - outerRef.scrollTop = initialScrollOffset; - } - } - - this._callPropsCallbacks(); - } - - componentDidUpdate() { - const { direction, layout } = this.props; - const { scrollOffset, scrollUpdateWasRequested } = this.state; - - if (scrollUpdateWasRequested && this._outerRef != null) { - const outerRef = ((this._outerRef: any): HTMLElement); - - // TODO Deprecate direction "horizontal" - if (direction === 'horizontal' || layout === 'horizontal') { - if (direction === 'rtl') { - // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements. - // This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left). - // So we need to determine which browser behavior we're dealing with, and mimic it. - switch (getRTLOffsetType()) { - case 'negative': - outerRef.scrollLeft = -scrollOffset; - break; - case 'positive-ascending': - outerRef.scrollLeft = scrollOffset; - break; - default: - const { clientWidth, scrollWidth } = outerRef; - outerRef.scrollLeft = scrollWidth - clientWidth - scrollOffset; - break; - } - } else { - outerRef.scrollLeft = scrollOffset; - } - } else { - outerRef.scrollTop = scrollOffset; - } - } - - this._callPropsCallbacks(); - } - - componentWillUnmount() { - if (this._resetIsScrollingTimeoutId !== null) { - cancelTimeout(this._resetIsScrollingTimeoutId); - } - } - - render() { - const { - children, - className, - direction, - height, - innerRef, - innerElementType, - innerTagName, - itemCount, - itemData, - itemKey = defaultItemKey, - layout, - outerElementType, - outerTagName, - style, - useIsScrolling, - width, - } = this.props; - const { isScrolling } = this.state; - - // TODO Deprecate direction "horizontal" - const isHorizontal = - direction === 'horizontal' || layout === 'horizontal'; - - const onScroll = isHorizontal - ? this._onScrollHorizontal - : this._onScrollVertical; - - const [startIndex, stopIndex] = this._getRangeToRender(); - - const items = []; - if (itemCount > 0) { - for (let index = startIndex; index <= stopIndex; index++) { - items.push( - createElement(children, { - data: itemData, - key: itemKey(index, itemData), - index, - isScrolling: useIsScrolling ? isScrolling : undefined, - style: this._getItemStyle(index), - }) - ); - } - } - - // Read this value AFTER items have been created, - // So their actual sizes (if variable) are taken into consideration. - const estimatedTotalSize = getEstimatedTotalSize( - this.props, - this._instanceProps - ); - - return createElement( - outerElementType || outerTagName || 'div', - { - className, - onScroll, - ref: this._outerRefSetter, - style: { - position: 'relative', - height, - width, - overflow: 'auto', - WebkitOverflowScrolling: 'touch', - willChange: 'transform', - direction, - ...style, - }, - }, - createElement(innerElementType || innerTagName || 'div', { - children: items, - ref: innerRef, - style: { - height: isHorizontal ? '100%' : estimatedTotalSize, - pointerEvents: isScrolling ? 'none' : undefined, - width: isHorizontal ? estimatedTotalSize : '100%', - }, - }) - ); - } - - _callOnItemsRendered: ( - overscanStartIndex: number, - overscanStopIndex: number, - visibleStartIndex: number, - visibleStopIndex: number - ) => void; - _callOnItemsRendered = memoizeOne( - ( - overscanStartIndex: number, - overscanStopIndex: number, - visibleStartIndex: number, - visibleStopIndex: number - ) => - ((this.props.onItemsRendered: any): onItemsRenderedCallback)({ - overscanStartIndex, - overscanStopIndex, - visibleStartIndex, - visibleStopIndex, - }) - ); - - _callOnScroll: ( - scrollDirection: ScrollDirection, - scrollOffset: number, - scrollUpdateWasRequested: boolean - ) => void; - _callOnScroll = memoizeOne( - ( - scrollDirection: ScrollDirection, - scrollOffset: number, - scrollUpdateWasRequested: boolean - ) => - ((this.props.onScroll: any): onScrollCallback)({ - scrollDirection, - scrollOffset, - scrollUpdateWasRequested, - }) - ); - - _callPropsCallbacks() { - if (typeof this.props.onItemsRendered === 'function') { - const { itemCount } = this.props; - if (itemCount > 0) { - const [ - overscanStartIndex, - overscanStopIndex, - visibleStartIndex, - visibleStopIndex, - ] = this._getRangeToRender(); - this._callOnItemsRendered( - overscanStartIndex, - overscanStopIndex, - visibleStartIndex, - visibleStopIndex - ); - } - } - - if (typeof this.props.onScroll === 'function') { - const { - scrollDirection, - scrollOffset, - scrollUpdateWasRequested, - } = this.state; - this._callOnScroll( - scrollDirection, - scrollOffset, - scrollUpdateWasRequested - ); - } - } - - // Lazily create and cache item styles while scrolling, - // So that pure component sCU will prevent re-renders. - // We maintain this cache, and pass a style prop rather than index, - // So that List can clear cached styles and force item re-render if necessary. - _getItemStyle: (index: number) => Object; - _getItemStyle = (index: number): Object => { - const { direction, itemSize, layout } = this.props; - - const itemStyleCache = this._getItemStyleCache( - shouldResetStyleCacheOnItemSizeChange && itemSize, - shouldResetStyleCacheOnItemSizeChange && layout, - shouldResetStyleCacheOnItemSizeChange && direction - ); - - let style; - if (itemStyleCache.hasOwnProperty(index)) { - style = itemStyleCache[index]; - } else { - const offset = getItemOffset(this.props, index, this._instanceProps); - const size = getItemSize(this.props, index, this._instanceProps); - - // TODO Deprecate direction "horizontal" - const isHorizontal = - direction === 'horizontal' || layout === 'horizontal'; - - const isRtl = direction === 'rtl'; - const offsetHorizontal = isHorizontal ? offset : 0; - itemStyleCache[index] = style = { - position: 'absolute', - left: isRtl ? undefined : offsetHorizontal, - right: isRtl ? offsetHorizontal : undefined, - top: !isHorizontal ? offset : 0, - height: !isHorizontal ? size : '100%', - width: isHorizontal ? size : '100%', - }; - } - - return style; - }; - - _getItemStyleCache: (_: any, __: any, ___: any) => ItemStyleCache; - _getItemStyleCache = memoizeOne((_: any, __: any, ___: any) => ({})); - - _getRangeToRender(): [number, number, number, number] { - const { itemCount, overscanCount } = this.props; - const { isScrolling, scrollDirection, scrollOffset } = this.state; - - if (itemCount === 0) { - return [0, 0, 0, 0]; - } - - const startIndex = getStartIndexForOffset( - this.props, - scrollOffset, - this._instanceProps - ); - const stopIndex = getStopIndexForStartIndex( - this.props, - startIndex, - scrollOffset, - this._instanceProps - ); - - // Overscan by one item in each direction so that tab/focus works. - // If there isn't at least one extra item, tab loops back around. - const overscanBackward = - !isScrolling || scrollDirection === 'backward' - ? Math.max(1, overscanCount) - : 1; - const overscanForward = - !isScrolling || scrollDirection === 'forward' - ? Math.max(1, overscanCount) - : 1; - - return [ - Math.max(0, startIndex - overscanBackward), - Math.max(0, Math.min(itemCount - 1, stopIndex + overscanForward)), - startIndex, - stopIndex, - ]; - } - - _onScrollHorizontal = (event: ScrollEvent): void => { - const { clientWidth, scrollLeft, scrollWidth } = event.currentTarget; - this.setState(prevState => { - if (prevState.scrollOffset === scrollLeft) { - // Scroll position may have been updated by cDM/cDU, - // In which case we don't need to trigger another render, - // And we don't want to update state.isScrolling. - return null; - } - - const { direction } = this.props; - - let scrollOffset = scrollLeft; - if (direction === 'rtl') { - // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements. - // This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left). - // It's also easier for this component if we convert offsets to the same format as they would be in for ltr. - // So the simplest solution is to determine which browser behavior we're dealing with, and convert based on it. - switch (getRTLOffsetType()) { - case 'negative': - scrollOffset = -scrollLeft; - break; - case 'positive-descending': - scrollOffset = scrollWidth - clientWidth - scrollLeft; - break; - } - } - - // Prevent Safari's elastic scrolling from causing visual shaking when scrolling past bounds. - scrollOffset = Math.max( - 0, - Math.min(scrollOffset, scrollWidth - clientWidth) - ); - - return { - isScrolling: true, - scrollDirection: - prevState.scrollOffset < scrollOffset ? 'forward' : 'backward', - scrollOffset, - scrollUpdateWasRequested: false, - }; - }, this._resetIsScrollingDebounced); - }; - - _onScrollVertical = (event: ScrollEvent): void => { - const { clientHeight, scrollHeight, scrollTop } = event.currentTarget; - this.setState(prevState => { - if (prevState.scrollOffset === scrollTop) { - // Scroll position may have been updated by cDM/cDU, - // In which case we don't need to trigger another render, - // And we don't want to update state.isScrolling. - return null; - } - - // Prevent Safari's elastic scrolling from causing visual shaking when scrolling past bounds. - const scrollOffset = Math.max( - 0, - Math.min(scrollTop, scrollHeight - clientHeight) - ); - - return { - isScrolling: true, - scrollDirection: - prevState.scrollOffset < scrollOffset ? 'forward' : 'backward', - scrollOffset, - scrollUpdateWasRequested: false, - }; - }, this._resetIsScrollingDebounced); - }; - - _outerRefSetter = (ref: any): void => { - const { outerRef } = this.props; - - this._outerRef = ((ref: any): HTMLDivElement); - - if (typeof outerRef === 'function') { - outerRef(ref); - } else if ( - outerRef != null && - typeof outerRef === 'object' && - outerRef.hasOwnProperty('current') - ) { - outerRef.current = ref; - } - }; - - _resetIsScrollingDebounced = () => { - if (this._resetIsScrollingTimeoutId !== null) { - cancelTimeout(this._resetIsScrollingTimeoutId); - } - - this._resetIsScrollingTimeoutId = requestTimeout( - this._resetIsScrolling, - IS_SCROLLING_DEBOUNCE_INTERVAL - ); - }; - - _resetIsScrolling = () => { - this._resetIsScrollingTimeoutId = null; - - this.setState({ isScrolling: false }, () => { - // Clear style cache after state update has been committed. - // This way we don't break pure sCU for items that don't use isScrolling param. - this._getItemStyleCache(-1, null); - }); - }; - }; -} - -// NOTE: I considered further wrapping individual items with a pure ListItem component. -// This would avoid ever calling the render function for the same index more than once, -// But it would also add the overhead of a lot of components/fibers. -// I assume people already do this (render function returning a class component), -// So my doing it would just unnecessarily double the wrappers. - -const validateSharedProps = ( - { - children, - direction, - height, - layout, - innerTagName, - outerTagName, - width, - }: Props, - { instance }: State -): void => { - if (process.env.NODE_ENV !== 'production') { - if (innerTagName != null || outerTagName != null) { - if (devWarningsTagName && !devWarningsTagName.has(instance)) { - devWarningsTagName.add(instance); - console.warn( - 'The innerTagName and outerTagName props have been deprecated. ' + - 'Please use the innerElementType and outerElementType props instead.' - ); - } - } - - // TODO Deprecate direction "horizontal" - const isHorizontal = direction === 'horizontal' || layout === 'horizontal'; - - switch (direction) { - case 'horizontal': - case 'vertical': - if (devWarningsDirection && !devWarningsDirection.has(instance)) { - devWarningsDirection.add(instance); - console.warn( - 'The direction prop should be either "ltr" (default) or "rtl". ' + - 'Please use the layout prop to specify "vertical" (default) or "horizontal" orientation.' - ); - } - break; - case 'ltr': - case 'rtl': - // Valid values - break; - default: - throw Error( - 'An invalid "direction" prop has been specified. ' + - 'Value should be either "ltr" or "rtl". ' + - `"${direction}" was specified.` - ); - } - - switch (layout) { - case 'horizontal': - case 'vertical': - // Valid values - break; - default: - throw Error( - 'An invalid "layout" prop has been specified. ' + - 'Value should be either "horizontal" or "vertical". ' + - `"${layout}" was specified.` - ); - } - - if (children == null) { - throw Error( - 'An invalid "children" prop has been specified. ' + - 'Value should be a React component. ' + - `"${children === null ? 'null' : typeof children}" was specified.` - ); - } - - if (isHorizontal && typeof width !== 'number') { - throw Error( - 'An invalid "width" prop has been specified. ' + - 'Horizontal lists must specify a number for width. ' + - `"${width === null ? 'null' : typeof width}" was specified.` - ); - } else if (!isHorizontal && typeof height !== 'number') { - throw Error( - 'An invalid "height" prop has been specified. ' + - 'Vertical lists must specify a number for height. ' + - `"${height === null ? 'null' : typeof height}" was specified.` - ); - } - } -}; diff --git a/src/domHelpers.js b/src/domHelpers.js deleted file mode 100644 index 1bdc9ddd..00000000 --- a/src/domHelpers.js +++ /dev/null @@ -1,72 +0,0 @@ -// @flow - -let size: number = -1; - -// This utility copied from "dom-helpers" package. -export function getScrollbarSize(recalculate?: boolean = false): number { - if (size === -1 || recalculate) { - const div = document.createElement('div'); - const style = div.style; - style.width = '50px'; - style.height = '50px'; - style.overflow = 'scroll'; - - ((document.body: any): HTMLBodyElement).appendChild(div); - - size = div.offsetWidth - div.clientWidth; - - ((document.body: any): HTMLBodyElement).removeChild(div); - } - - return size; -} - -export type RTLOffsetType = - | 'negative' - | 'positive-descending' - | 'positive-ascending'; - -let cachedRTLResult: RTLOffsetType | null = null; - -// TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements. -// Chrome does not seem to adhere; its scrollLeft values are positive (measured relative to the left). -// Safari's elastic bounce makes detecting this even more complicated wrt potential false positives. -// The safest way to check this is to intentionally set a negative offset, -// and then verify that the subsequent "scroll" event matches the negative offset. -// If it does not match, then we can assume a non-standard RTL scroll implementation. -export function getRTLOffsetType(recalculate?: boolean = false): RTLOffsetType { - if (cachedRTLResult === null || recalculate) { - const outerDiv = document.createElement('div'); - const outerStyle = outerDiv.style; - outerStyle.width = '50px'; - outerStyle.height = '50px'; - outerStyle.overflow = 'scroll'; - outerStyle.direction = 'rtl'; - - const innerDiv = document.createElement('div'); - const innerStyle = innerDiv.style; - innerStyle.width = '100px'; - innerStyle.height = '100px'; - - outerDiv.appendChild(innerDiv); - - ((document.body: any): HTMLBodyElement).appendChild(outerDiv); - - if (outerDiv.scrollLeft > 0) { - cachedRTLResult = 'positive-descending'; - } else { - outerDiv.scrollLeft = 1; - if (outerDiv.scrollLeft === 0) { - cachedRTLResult = 'negative'; - } else { - cachedRTLResult = 'positive-ascending'; - } - } - - ((document.body: any): HTMLBodyElement).removeChild(outerDiv); - - return cachedRTLResult; - } - - return cachedRTLResult; -} diff --git a/src/hooks/useJSONData.ts b/src/hooks/useJSONData.ts new file mode 100644 index 00000000..f5c10157 --- /dev/null +++ b/src/hooks/useJSONData.ts @@ -0,0 +1,45 @@ +import { useEffect, useState } from "react"; + +export function useJSONData(url: string) { + const [json, setJSON] = useState(); + const [error, setError] = useState(null); + + if (error) { + throw error; + } + + useEffect(() => { + const abortController = new AbortController(); + + (async () => { + try { + const data = await fetch(url, { + signal: abortController.signal + }); + if (!abortController.signal.aborted) { + const json = await data.json(); + if (!abortController.signal.aborted) { + setJSON(json as Type); + } + } + } catch (error) { + if (error instanceof Error && error?.name === "AbortError") { + return; + } + + console.error("Error thrown for URL: %o\n\n%o", url, error); + + setError(error); + } + })(); + + return () => { + const error = new Error(); + error.name = "AbortError"; + + abortController.abort(error); + }; + }, [url]); + + return json; +} diff --git a/src/hooks/useLocalStorage.ts b/src/hooks/useLocalStorage.ts new file mode 100644 index 00000000..221625e7 --- /dev/null +++ b/src/hooks/useLocalStorage.ts @@ -0,0 +1,61 @@ +import { useLayoutEffect, useRef, useState } from "react"; + +export default function useLocalStorage( + key: string, + defaultValue: Type +): [value: Type, setValue: (newValue: Type) => void] { + const [value, setValue] = useState(() => { + const storedValue = localStorage.getItem(key); + if (storedValue != null) { + return JSON.parse(storedValue) as Type; + } else { + return defaultValue; + } + }); + + const committedValuesRef = useRef<{ + prevValue: string | null; + value: string; + }>({ + prevValue: null, + value: JSON.stringify(value) + }); + useLayoutEffect(() => { + committedValuesRef.current.prevValue = committedValuesRef.current.value; + committedValuesRef.current.value = JSON.stringify(value); + }); + + // Sync changes from local storage + useLayoutEffect(() => { + const onStorage = (event: StorageEvent) => { + if ( + key === event.key && + event.newValue && + event.newValue !== JSON.stringify(value) + ) { + setValue(JSON.parse(event.newValue)); + } + }; + + window.addEventListener("storage", onStorage); + + return () => { + window.removeEventListener("storage", onStorage); + }; + }, [key, value]); + + // Sync changes to local storage + useLayoutEffect(() => { + window.dispatchEvent( + new StorageEvent("storage", { + key, + newValue: committedValuesRef.current.value || "", + oldValue: committedValuesRef.current.prevValue || "" + }) + ); + + localStorage.setItem(key, committedValuesRef.current.value); + }, [key, value]); + + return [value, setValue]; +} diff --git a/src/hooks/useNavStore.tsx b/src/hooks/useNavStore.tsx new file mode 100644 index 00000000..25948302 --- /dev/null +++ b/src/hooks/useNavStore.tsx @@ -0,0 +1,15 @@ +import { create } from "zustand"; + +export const useNavStore = create<{ + visible: boolean; + + hide: () => void; + show: () => void; + toggle: () => void; +}>((set) => ({ + visible: false, + + hide: () => set({ visible: false }), + show: () => set({ visible: true }), + toggle: () => set((state) => ({ visible: !state.visible })) +})); diff --git a/src/index.js b/src/index.js deleted file mode 100644 index dff39bf1..00000000 --- a/src/index.js +++ /dev/null @@ -1,9 +0,0 @@ -// @flow - -export { default as VariableSizeGrid } from './VariableSizeGrid'; -export { default as VariableSizeList } from './VariableSizeList'; -export { default as FixedSizeGrid } from './FixedSizeGrid'; -export { default as FixedSizeList } from './FixedSizeList'; - -export { default as areEqual } from './areEqual'; -export { default as shouldComponentUpdate } from './shouldComponentUpdate'; diff --git a/src/nav/MobileNav.tsx b/src/nav/MobileNav.tsx new file mode 100644 index 00000000..4c90452c --- /dev/null +++ b/src/nav/MobileNav.tsx @@ -0,0 +1,27 @@ +import { useLayoutEffect } from "react"; +import { useLocation } from "react-router-dom"; +import { useNavStore } from "../hooks/useNavStore"; +import { cn } from "../utils/cn"; + +export function Nav() { + const { hide, visible } = useNavStore(); + + const { pathname } = useLocation(); + useLayoutEffect(() => { + hide(); + }, [hide, pathname]); + + return ( + + ); +} diff --git a/src/nav/Nav.tsx b/src/nav/Nav.tsx new file mode 100644 index 00000000..5997825e --- /dev/null +++ b/src/nav/Nav.tsx @@ -0,0 +1,30 @@ +import { NavLink } from "./NavLink"; +import { NavSection } from "./NavSection"; + +export function Nav() { + return ( +
+ Getting started + + Fixed row heights + Variable row heights + Component props + Imperative API + + + Rendering a grid + Component props + Imperative API + + + Tabular data + Right to left content + Horizontal lists + +
+ Requirements + Support +
+
+ ); +} diff --git a/src/nav/NavButton.tsx b/src/nav/NavButton.tsx new file mode 100644 index 00000000..e796921b --- /dev/null +++ b/src/nav/NavButton.tsx @@ -0,0 +1,24 @@ +import type { PropsWithChildren } from "react"; +import { Box } from "../components/Box"; +import { cn } from "../utils/cn"; + +export function NavButton({ + children, + className, + disabled +}: PropsWithChildren<{ className?: string; disabled?: boolean }>) { + return ( + + {children} + + ); +} diff --git a/src/nav/NavLink.tsx b/src/nav/NavLink.tsx new file mode 100644 index 00000000..1424bd6d --- /dev/null +++ b/src/nav/NavLink.tsx @@ -0,0 +1,32 @@ +import type { PropsWithChildren } from "react"; +import { NavLink as NavLinkExternal } from "react-router-dom"; +import type { routeMap } from "../routes"; +import { cn } from "../utils/cn"; +import { NavButton } from "./NavButton"; +import { Box } from "../components/Box"; + +export function NavLink({ + children, + className, + to +}: PropsWithChildren<{ className?: string; to: keyof typeof routeMap }>) { + return ( + + {({ isActive }) => ( + + + {children} + + + )} + + ); +} diff --git a/src/nav/NavRoute.tsx b/src/nav/NavRoute.tsx new file mode 100644 index 00000000..a38ed273 --- /dev/null +++ b/src/nav/NavRoute.tsx @@ -0,0 +1,13 @@ +import type { ReactNode } from "react"; +import { Route } from "react-router-dom"; + +export function NavRoute({ + route +}: { + route: { + component: ReactNode; + path: string; + }; +}) { + return ; +} diff --git a/src/nav/NavSection.tsx b/src/nav/NavSection.tsx new file mode 100644 index 00000000..16225362 --- /dev/null +++ b/src/nav/NavSection.tsx @@ -0,0 +1,28 @@ +import { + Disclosure, + DisclosureButton, + DisclosurePanel +} from "@headlessui/react"; +import { ChevronRightIcon } from "@heroicons/react/20/solid"; +import type { PropsWithChildren, ReactNode } from "react"; +import { NavButton } from "./NavButton"; + +export function NavSection({ + children, + label +}: PropsWithChildren<{ label: ReactNode }>) { + return ( + + + +
+ {label} +
+
+ + + + {children} + + ); +} diff --git a/src/routes.ts b/src/routes.ts new file mode 100644 index 00000000..b75701dc --- /dev/null +++ b/src/routes.ts @@ -0,0 +1,41 @@ +import { GettingStartedRoute } from "./routes/GettingStartedRoute"; +import { HorizontalListsRoute } from "./routes/grid/HorizontalListsRoute"; +import { GridImperativeApiRoute } from "./routes/grid/ImperativeApiRoute"; +import { GridPropsRoute } from "./routes/grid/PropsRoute"; +import { RenderingGridRoute } from "./routes/grid/RenderingGridRoute"; +import { RTLGridsRoute } from "./routes/grid/RTLGridsRoute"; +import { FixedRowHeightsRoute } from "./routes/list/FixedRowHeightsRoute"; +import { ListImperativeApiRoute } from "./routes/list/ImperativeApiRoute"; +import { ListPropsRoute } from "./routes/list/PropsRoute"; +import { VariableRowHeightsRoute } from "./routes/list/VariableRowHeightsRoute"; +import { PageNotFound } from "./routes/PageNotFound"; +import { PlatformRequirementsRoute } from "./routes/PlatformRequirementsRoute"; +import { ScratchpadRoute } from "./routes/ScratchpadRoute"; +import { SupportRoute } from "./routes/SupportRoute"; +import { TabularDataRoute } from "./routes/tables/TabularDataRoute"; + +export const routeMap = { + "*": PageNotFound, + + // Home page + "/": GettingStartedRoute, + + // List + "/list/fixed-row-height": FixedRowHeightsRoute, + "/list/variable-row-height": VariableRowHeightsRoute, + "/list/imperative-api": ListImperativeApiRoute, + "/list/props": ListPropsRoute, + "/list/tabular-data": TabularDataRoute, + + // SimpleGrid + "/grid/grid": RenderingGridRoute, + "/grid/horizontal-lists": HorizontalListsRoute, + "/grid/rtl-grids": RTLGridsRoute, + "/grid/props": GridPropsRoute, + "/grid/imperative-api": GridImperativeApiRoute, + + // Other + "/platform-requirements": PlatformRequirementsRoute, + "/support": SupportRoute, + "/test": ScratchpadRoute +} as const; diff --git a/src/routes/GettingStartedRoute.tsx b/src/routes/GettingStartedRoute.tsx new file mode 100644 index 00000000..8e062483 --- /dev/null +++ b/src/routes/GettingStartedRoute.tsx @@ -0,0 +1,52 @@ +import { Box } from "../components/Box"; +import { Callout } from "../components/Callout"; +import { ExternalLink } from "../components/ExternalLink"; +import { Header } from "../components/Header"; +import { Link } from "../components/Link"; + +export function GettingStartedRoute() { + return ( + +
+
+ react-window is a component library that helps render + large lists of data quickly and without the performance problems that + often go along with rendering a lot of data. It's used in a lot of + places, from{" "} + + React DevTools + {" "} + to the{" "} + + Replay browser + + . +
+
Installation
+
Begin by installing the library from NPM:
+ + npm install react-window + + + TypeScript definitions are included within the published{" "} + dist folder and documentation is included within the{" "} + docs folder. + +
+ This library provides two basic types of components; choose one below to + learn more: +
+
    +
  • + Lists (vertical scrolling) +
  • +
  • + Grids (horizontal and vertical scrolling) +
  • +
+
+ Check out the support page if you need help. +
+ + ); +} diff --git a/src/routes/PageNotFound.tsx b/src/routes/PageNotFound.tsx new file mode 100644 index 00000000..bf6a3576 --- /dev/null +++ b/src/routes/PageNotFound.tsx @@ -0,0 +1,19 @@ +import { Box } from "../components/Box"; +import { Callout } from "../components/Callout"; +import { ExternalLink } from "../components/ExternalLink"; +import { Header } from "../components/Header"; + +export function PageNotFound() { + return ( + +
+ + The URL you requested can't be found. If you think this is an error,{" "} + + please file a GitHub issue + + . + + + ); +} diff --git a/src/routes/PlatformRequirementsRoute.tsx b/src/routes/PlatformRequirementsRoute.tsx new file mode 100644 index 00000000..e5f054f7 --- /dev/null +++ b/src/routes/PlatformRequirementsRoute.tsx @@ -0,0 +1,32 @@ +import { Box } from "../components/Box"; +import { Callout } from "../components/Callout"; +import { ExternalLink } from "../components/ExternalLink"; +import { Header } from "../components/Header"; + +export function PlatformRequirementsRoute() { + return ( + +
+
+ This library requires React{" "} + + version 18 + {" "} + or newer. +
+
+ It also uses the{" "} + + ResizeObserver + {" "} + (or a polyfill) to calculate the available space for List{" "} + and Grid components. +
+ + ResizeObserver usage can be avoided if explicit pixel + dimensions are specified using the style prop. (Percentage + or EM/REM based dimensions do not count.) + + + ); +} diff --git a/src/routes/ScratchpadRoute.tsx b/src/routes/ScratchpadRoute.tsx new file mode 100644 index 00000000..fe91e4f7 --- /dev/null +++ b/src/routes/ScratchpadRoute.tsx @@ -0,0 +1,140 @@ +import { useState } from "react"; +import { + Grid, + useGridCallbackRef, + type Align, + type CellComponentProps +} from "react-window"; +import { Block } from "../components/Block"; +import { Box } from "../components/Box"; +import { Checkbox } from "../components/Checkbox"; +import { Input } from "../components/Input"; +import { Select, type Option } from "../components/Select"; +import { cn } from "../utils/cn"; + +const ALIGNMENTS: Option[] = ( + ["auto", "center", "end", "smart", "start"] satisfies Align[] +).map((value) => ({ + label: `align: ${value}`, + value +})); + +export function ScratchpadRoute() { + const [rtl, setRtl] = useState(false); + const [columnIndex, setColumnIndex] = useState(); + const [rowIndex, setRowIndex] = useState(); + const [gridRef, setGridRef] = useGridCallbackRef(null); + const [align, setAlign] = useState(ALIGNMENTS[0]); + + return ( + + { + switch (event.key) { + case "Enter": { + if (columnIndex !== undefined && rowIndex !== undefined) { + gridRef?.scrollToCell({ + columnAlign: align.value, + columnIndex, + rowAlign: align.value, + rowIndex + }); + } else if (columnIndex !== undefined) { + gridRef?.scrollToColumn({ + align: align.value, + index: columnIndex + }); + } else if (rowIndex !== undefined) { + gridRef?.scrollToRow({ + align: align.value, + index: rowIndex + }); + } + break; + } + } + }} + > + { + const parsed = parseInt(value); + setColumnIndex(isNaN(parsed) ? undefined : parsed); + }} + placeholder="Column" + step={1} + type="number" + value={columnIndex === undefined ? "" : "" + columnIndex} + /> + { + const parsed = parseInt(value); + setRowIndex(isNaN(parsed) ? undefined : parsed); + }} + placeholder="Row" + step={1} + type="number" + value={rowIndex === undefined ? "" : "" + rowIndex} + /> + + + + + + ); +} + +function CellComponent({ + columnIndex, + focusedColumnIndex, + focusedRowIndex, + rowIndex, + style +}: CellComponentProps<{ + focusedColumnIndex: number | undefined; + focusedRowIndex: number | undefined; +}>) { + return ( +
+ row {rowIndex}, col {columnIndex} +
+ ); +} diff --git a/src/routes/SupportRoute.tsx b/src/routes/SupportRoute.tsx new file mode 100644 index 00000000..f62fc3ff --- /dev/null +++ b/src/routes/SupportRoute.tsx @@ -0,0 +1,34 @@ +import { Box } from "../components/Box"; +import { ExternalLink } from "../components/ExternalLink"; +import { Header } from "../components/Header"; + +export function SupportRoute() { + return ( + +
+
+ + GitHub + {" "} + is the easiest place to look for help, but it's probably not the + fastest. This project is maintained by a single developer so there is + limited bandwidth for answering questions. +
+
+ I recommend asking questions on{" "} + + Stack Overflow + {" "} + or{" "} + Reddit{" "} + to start with. Both sites have active communities who often respond + quickly. If you don't find an answer there you can try opening a GitHub + issue- but please take a moment first to see if your question has{" "} + + has already been answered + {" "} + before opening a new one. +
+ + ); +} diff --git a/src/routes/grid/HorizontalListsRoute.tsx b/src/routes/grid/HorizontalListsRoute.tsx new file mode 100644 index 00000000..41a351f4 --- /dev/null +++ b/src/routes/grid/HorizontalListsRoute.tsx @@ -0,0 +1,25 @@ +import { Block } from "../../components/Block"; +import { Box } from "../../components/Box"; +import { FormattedCode } from "../../components/code/FormattedCode"; +import { LoadingSpinner } from "../../components/LoadingSpinner"; +import { HorizontalList } from "./examples/HorizontalList.example"; +import { useEmails } from "./hooks/useEmails"; + +export function HorizontalListsRoute() { + const emails = useEmails(); + + return ( + +
A horizontal list is just a grid with only one row.
+
Here's an example horizontal list (grid) of emails:
+ + {!emails.length && } + + +
Here's what the configuration for the grid above looks like:
+ +
And here's the cell renderer:
+ +
+ ); +} diff --git a/src/routes/grid/ImperativeApiRoute.tsx b/src/routes/grid/ImperativeApiRoute.tsx new file mode 100644 index 00000000..ef830d32 --- /dev/null +++ b/src/routes/grid/ImperativeApiRoute.tsx @@ -0,0 +1,189 @@ +import { useMemo, useState } from "react"; +import { Grid, useGridRef, type Align } from "react-window"; +import { Block } from "../../components/Block"; +import { Box } from "../../components/Box"; +import { Button } from "../../components/Button"; +import { Callout } from "../../components/Callout"; +import { FormattedCode } from "../../components/code/FormattedCode"; +import { Header } from "../../components/Header"; +import { Select, type Option } from "../../components/Select"; +import { CellComponent } from "./examples/CellComponent.example"; +import { columnWidth } from "./examples/columnWidth.example"; +import type { Contact } from "./examples/Grid.example"; +import { COLUMN_KEYS } from "./examples/shared"; +import { useContacts } from "./hooks/useContacts"; +import { LoadingSpinner } from "../../components/LoadingSpinner"; + +const EMPTY_OPTION: Option = { + label: "", + value: "" +}; + +const ALIGNMENTS: Option[] = ( + ["auto", "center", "end", "smart", "start"] satisfies Align[] +).map((value) => ({ + label: `align: ${value}`, + value +})); +ALIGNMENTS.unshift(EMPTY_OPTION as Option); + +const BEHAVIORS: Option[] = ( + ["auto", "instant", "smooth"] satisfies ScrollBehavior[] +).map((value) => ({ + label: `behavior: ${value}`, + value +})); +BEHAVIORS.unshift(EMPTY_OPTION as Option); + +const COLUMNS: Option[] = COLUMN_KEYS.map((key) => ({ + label: key, + value: key +})).sort((a, b) => a.label.localeCompare(b.label)); + +export function GridImperativeApiRoute() { + const contacts = useContacts(); + + const titleOptions = useMemo[]>(() => { + const options: Option[] = []; + if (contacts) { + contacts + .reduce((array, contact) => { + if (!array.includes(contact.title)) { + array.push(contact.title); + } + return array; + }, new Array()) + .sort() + .forEach((title) => { + options.push({ + label: title, + value: title + }); + }); + + options.unshift(EMPTY_OPTION); + } + + return options; + }, [contacts]); + + const [align, setAlign] = useState | undefined>(); + const [behavior, setBehavior] = useState< + Option | undefined + >(); + const [column, setColumn] = useState>(EMPTY_OPTION); + const [title, setTitle] = useState>(EMPTY_OPTION); + + const gridRef = useGridRef(null); + + const scrollToRow = () => { + const grid = gridRef.current; + if (grid) { + const columnIndex = column?.value + ? COLUMN_KEYS.indexOf(column.value as keyof Contact) + : undefined; + + const rowIndex = title?.value + ? contacts.findIndex((row) => row.title === title.value) + : undefined; + + if (columnIndex !== undefined && rowIndex !== undefined) { + grid.scrollToCell({ + behavior: behavior?.value, + columnAlign: align?.value, + columnIndex, + rowAlign: align?.value, + rowIndex + }); + } else if (columnIndex !== undefined) { + grid.scrollToColumn({ + align: align?.value, + behavior: behavior?.value, + index: columnIndex + }); + } else if (rowIndex !== undefined) { + grid.scrollToRow({ + align: align?.value, + behavior: behavior?.value, + index: rowIndex + }); + } + } + }; + + return ( + +
+
+ Grid provides an imperative API for responding to events. The + recommended way to access this API is to use the exported ref hook: +
+ +
Attach the ref during render:
+ +
And call API methods in an event handler:
+ +
The form below uses the imperative API to scroll the list:
+ + + + + + + + + {!contacts.length && } + + +
+ The Grid API also provides scrollToColumn and{" "} + scrollToRow methods for single-axis scrolling. +
+ + Note If you are passing the + ref to another component or hook, use the ref callback function instead. + + + + ); +} diff --git a/src/routes/grid/PropsRoute.tsx b/src/routes/grid/PropsRoute.tsx new file mode 100644 index 00000000..fb1cecd2 --- /dev/null +++ b/src/routes/grid/PropsRoute.tsx @@ -0,0 +1,12 @@ +import { Box } from "../../components/Box"; +import { ContinueLink } from "../../components/ContinueLink"; +import { ComponentProps } from "../../components/props/ComponentProps"; + +export function GridPropsRoute() { + return ( + + + + + ); +} diff --git a/src/routes/grid/RTLGridsRoute.tsx b/src/routes/grid/RTLGridsRoute.tsx new file mode 100644 index 00000000..740084da --- /dev/null +++ b/src/routes/grid/RTLGridsRoute.tsx @@ -0,0 +1,33 @@ +import { Block } from "../../components/Block"; +import { Box } from "../../components/Box"; +import { FormattedCode } from "../../components/code/FormattedCode"; +import { ExternalLink } from "../../components/ExternalLink"; +import { LoadingSpinner } from "../../components/LoadingSpinner"; +import { RtlExample } from "./examples/RtlGrid.example"; +import { useContacts } from "./hooks/useContacts"; + +export function RTLGridsRoute() { + const contacts = useContacts(); + + return ( + +
+ Grids can also display right to left languages (like Arabic). The grid + components check the{" "} + + dir attribute + {" "} + to determine content directionality. +
+
+ Using the same data as from the previous example, here is a grid + rendered right to left. +
+ + {!contacts.length && } + + + +
+ ); +} diff --git a/src/routes/grid/RenderingGridRoute.tsx b/src/routes/grid/RenderingGridRoute.tsx new file mode 100644 index 00000000..d528aaf2 --- /dev/null +++ b/src/routes/grid/RenderingGridRoute.tsx @@ -0,0 +1,62 @@ +import { Block } from "../../components/Block"; +import { Box } from "../../components/Box"; +import { Callout } from "../../components/Callout"; +import { FormattedCode } from "../../components/code/FormattedCode"; +import { ContinueLink } from "../../components/ContinueLink"; +import { ExternalLink } from "../../components/ExternalLink"; +import { Header } from "../../components/Header"; +import { LoadingSpinner } from "../../components/LoadingSpinner"; +import { Example } from "./examples/Grid.example"; +import { useContacts } from "./hooks/useContacts"; + +export function RenderingGridRoute() { + const contacts = useContacts(); + + return ( + +
+
+ Use the Grid component to render data with many rows and + columns: +
+ + {!contacts.length && } + + +
+ Grids require you to specify the number of rows and columns as well as + the width and height of each: +
+ +
+ Column widths and row heights can be either numbers or functions. In the + example above, row height is fixed and column width is variable. +
+ +
+ Lastly grids require a component to render cell, given a column and row + index. As with lists, this component receives additional props specified + as part of cellProps: +
+ + + +
+ Grids require space to render cells. Typically the{" "} + + ResizeObserver + {" "} + API is used to determine how much space there is available within + the parent DOM element. +
+
+ If an explicit width and height are specified (in pixels) using the{" "} + style prop, ResizeObserver will not be + used. +
+
+
+ + + ); +} diff --git a/src/routes/grid/examples/CellComponent.example.tsx b/src/routes/grid/examples/CellComponent.example.tsx new file mode 100644 index 00000000..3daece58 --- /dev/null +++ b/src/routes/grid/examples/CellComponent.example.tsx @@ -0,0 +1,28 @@ +import type { Contact } from "./Grid.example"; +import { indexToColumn } from "./shared"; + +// + +import { type CellComponentProps } from "react-window"; + +function CellComponent({ + contacts, + columnIndex, + rowIndex, + style +}: CellComponentProps<{ + contacts: Contact[]; +}>) { + const address = contacts[rowIndex]; + const content = address[indexToColumn(columnIndex)]; + + return ( +
+ {content} +
+ ); +} + +// + +export { CellComponent }; diff --git a/src/routes/grid/examples/Grid.example.tsx b/src/routes/grid/examples/Grid.example.tsx new file mode 100644 index 00000000..af691c6d --- /dev/null +++ b/src/routes/grid/examples/Grid.example.tsx @@ -0,0 +1,27 @@ +import json from "../../../../public/data/contacts.json"; +import { CellComponent } from "./CellComponent.example"; +import { columnWidth } from "./columnWidth.example"; + +type Contact = (typeof json)[0]; + +// + +import { Grid } from "react-window"; + +function Example({ contacts }: { contacts: Contact[] }) { + return ( + + ); +} + +// + +export { CellComponent, Example }; +export type { Contact }; diff --git a/src/routes/grid/examples/HorizontalList.example.tsx b/src/routes/grid/examples/HorizontalList.example.tsx new file mode 100644 index 00000000..06d6bbf3 --- /dev/null +++ b/src/routes/grid/examples/HorizontalList.example.tsx @@ -0,0 +1,22 @@ +import { CellComponent } from "./HorizontalListCellRenderer.example"; + +// + +import { Grid } from "react-window"; + +function HorizontalList({ emails }: { emails: string[] }) { + return ( + + ); +} + +// + +export { HorizontalList }; diff --git a/src/routes/grid/examples/HorizontalListCellRenderer.example.tsx b/src/routes/grid/examples/HorizontalListCellRenderer.example.tsx new file mode 100644 index 00000000..89c08aef --- /dev/null +++ b/src/routes/grid/examples/HorizontalListCellRenderer.example.tsx @@ -0,0 +1,26 @@ +import { cn } from "../../../utils/cn"; + +// + +import { type CellComponentProps } from "react-window"; + +function CellComponent({ + columnIndex, + emails, + style +}: CellComponentProps<{ emails: string[] }>) { + return ( +
+ {emails[columnIndex]} +
+ ); +} + +// + +export { CellComponent }; diff --git a/src/routes/grid/examples/RtlGrid.example.tsx b/src/routes/grid/examples/RtlGrid.example.tsx new file mode 100644 index 00000000..86d7bec3 --- /dev/null +++ b/src/routes/grid/examples/RtlGrid.example.tsx @@ -0,0 +1,51 @@ +import json from "../../../../public/data/contacts.json"; +import { columnWidth } from "./columnWidth.example"; + +type Contact = (typeof json)[0]; + +// + +import { Grid } from "react-window"; + +function RtlExample({ contacts }: { contacts: Contact[] }) { + return ( + + ); +} + +// + +import { type CellComponentProps } from "react-window"; +import { indexToColumn } from "./shared"; + +function CellComponent({ + contacts, + columnIndex, + rowIndex, + style +}: CellComponentProps<{ + contacts: Contact[]; +}>) { + const address = contacts[rowIndex]; + const content = address[indexToColumn(columnIndex)]; + + return ( +
+
{content}
+ + row {rowIndex}, col {columnIndex} + +
+ ); +} + +export { CellComponent, RtlExample }; +export type { Contact }; diff --git a/src/routes/grid/examples/columnWidth.example.ts b/src/routes/grid/examples/columnWidth.example.ts new file mode 100644 index 00000000..0c34112e --- /dev/null +++ b/src/routes/grid/examples/columnWidth.example.ts @@ -0,0 +1,30 @@ +import { indexToColumn } from "./shared"; + +// + +function columnWidth(index: number) { + switch (indexToColumn(index)) { + case "address": { + return 250; + } + case "email": { + return 300; + } + case "job_title": { + return 150; + } + case "timezone": { + return 200; + } + case "zip": { + return 75; + } + default: { + return 100; + } + } +} + +// + +export { columnWidth }; diff --git a/src/routes/grid/examples/gridRefClickEventHandler.example.ts b/src/routes/grid/examples/gridRefClickEventHandler.example.ts new file mode 100644 index 00000000..112704a8 --- /dev/null +++ b/src/routes/grid/examples/gridRefClickEventHandler.example.ts @@ -0,0 +1,21 @@ +import { createRef } from "react"; +import type { GridImperativeAPI } from "react-window"; + +const gridRef = createRef(); + +// + +const onClick = () => { + const grid = gridRef.current; + grid?.scrollToCell({ + behavior: "auto", // optional + columnAlign: "auto", // optional + columnIndex: 10, + rowAlign: "auto", // optional + rowIndex: 250 + }); +}; + +// + +export { onClick }; diff --git a/src/routes/grid/examples/shared.ts b/src/routes/grid/examples/shared.ts new file mode 100644 index 00000000..a1d678e5 --- /dev/null +++ b/src/routes/grid/examples/shared.ts @@ -0,0 +1,20 @@ +import type { Contact } from "./Grid.example"; + +export const COLUMN_KEYS: (keyof Contact)[] = [ + "title", + "first_name", + "last_name", + "email", + "gender", + "address", + "city", + "state", + "zip", + "timezone", + "company", + "job_title" +]; + +export function indexToColumn(columnIndex: number): keyof Contact { + return COLUMN_KEYS[columnIndex]; +} diff --git a/src/routes/grid/examples/useGridCallbackRef.example.tsx b/src/routes/grid/examples/useGridCallbackRef.example.tsx new file mode 100644 index 00000000..d52bc5e9 --- /dev/null +++ b/src/routes/grid/examples/useGridCallbackRef.example.tsx @@ -0,0 +1,23 @@ +import { Grid, type GridImperativeAPI, type GridProps } from "react-window"; + +type Props = GridProps; + +function useCustomHook(ref: GridImperativeAPI | null) { + return ref; +} + +// + +import { useGridCallbackRef } from "react-window"; + +function Example(props: Props) { + const [grid, setGrid] = useGridCallbackRef(null); + + useCustomHook(grid); + + return ; +} + +// + +export { Example }; diff --git a/src/routes/grid/examples/useGridRef.example.tsx b/src/routes/grid/examples/useGridRef.example.tsx new file mode 100644 index 00000000..fb98e1a9 --- /dev/null +++ b/src/routes/grid/examples/useGridRef.example.tsx @@ -0,0 +1,15 @@ +import { Grid, useGridRef, type GridProps } from "react-window"; + +type Props = GridProps; + +// + +function Example(props: Props) { + const gridRef = useGridRef(null); + + return ; +} + +// + +export { Example }; diff --git a/src/routes/grid/examples/useGridRefImport.example.ts b/src/routes/grid/examples/useGridRefImport.example.ts new file mode 100644 index 00000000..198b1631 --- /dev/null +++ b/src/routes/grid/examples/useGridRefImport.example.ts @@ -0,0 +1,5 @@ +import { useGridRef } from "react-window"; + +// + +export { useGridRef }; diff --git a/src/routes/grid/hooks/useContacts.ts b/src/routes/grid/hooks/useContacts.ts new file mode 100644 index 00000000..7a3c3b1b --- /dev/null +++ b/src/routes/grid/hooks/useContacts.ts @@ -0,0 +1,25 @@ +import { useMemo } from "react"; +import type json from "../../../../public/data/contacts.json"; +import { useJSONData } from "../../../hooks/useJSONData"; + +type Contact = (typeof json)[0]; + +export function useContacts(): Contact[] { + const json = useJSONData("/data/contacts.json"); + + return useMemo(() => { + if (json) { + return json.sort((a, b) => { + if (a.title !== b.title) { + return a.title.localeCompare(b.title); + } else if (a.first_name !== b.first_name) { + return a.first_name.localeCompare(b.first_name); + } else { + return a.last_name.localeCompare(b.last_name); + } + }); + } + + return []; + }, [json]); +} diff --git a/src/routes/grid/hooks/useEmails.ts b/src/routes/grid/hooks/useEmails.ts new file mode 100644 index 00000000..e1e49f6a --- /dev/null +++ b/src/routes/grid/hooks/useEmails.ts @@ -0,0 +1,19 @@ +import { useMemo } from "react"; +import type json from "../../../../public/data/contacts.json"; +import { useJSONData } from "../../../hooks/useJSONData"; + +type Contact = (typeof json)[0]; + +export function useEmails(): string[] { + const json = useJSONData("/data/contacts.json"); + + return useMemo(() => { + if (json) { + return json + .map((contact) => contact.email) + .sort((a, b) => a.localeCompare(b)); + } + + return []; + }, [json]); +} diff --git a/src/routes/list/FixedRowHeightsRoute.tsx b/src/routes/list/FixedRowHeightsRoute.tsx new file mode 100644 index 00000000..195828ef --- /dev/null +++ b/src/routes/list/FixedRowHeightsRoute.tsx @@ -0,0 +1,61 @@ +import { Block } from "../../components/Block"; +import { Box } from "../../components/Box"; +import { Callout } from "../../components/Callout"; +import { FormattedCode } from "../../components/code/FormattedCode"; +import { ContinueLink } from "../../components/ContinueLink"; +import { ExternalLink } from "../../components/ExternalLink"; +import { Header } from "../../components/Header"; +import { LoadingSpinner } from "../../components/LoadingSpinner"; +import { Example } from "./examples/FixedHeightList.example"; +import { useNames } from "./hooks/useNames"; + +export function FixedRowHeightsRoute() { + const names = useNames(); + + return ( + +
+
+ The simplest type of list to render is one with fixed row heights. +
+ + {!names.length && } + + +
+ To render this type of list, you need to specify how many rows it + contains (rowCount), which component should render rows ( + rowComponent), and the height of each row ( + rowHeight): +
+ +
+ The rowProps object can also be used to share between + components. Values passed in rowProps will also be passed + as props to the row component: +
+ + + +
+ Lists require vertical space to render rows. Typically the{" "} + + ResizeObserver + {" "} + API is used to determine how much space there is available within + the parent DOM element. +
+
+ If an explicit height is specified (in pixels) using the{" "} + style prop, ResizeObserver will not be + used. +
+
+
+ + + ); +} diff --git a/src/routes/list/ImperativeApiRoute.tsx b/src/routes/list/ImperativeApiRoute.tsx new file mode 100644 index 00000000..20811205 --- /dev/null +++ b/src/routes/list/ImperativeApiRoute.tsx @@ -0,0 +1,132 @@ +import { useMemo, useState } from "react"; +import { List, useListRef, type Align } from "react-window"; +import { Block } from "../../components/Block"; +import { Box } from "../../components/Box"; +import { Button } from "../../components/Button"; +import { Callout } from "../../components/Callout"; +import { FormattedCode } from "../../components/code/FormattedCode"; +import { Header } from "../../components/Header"; +import { LoadingSpinner } from "../../components/LoadingSpinner"; +import { Select, type Option } from "../../components/Select"; +import { RowComponent } from "./examples/ListVariableRowHeights.example"; +import { rowHeight } from "./examples/rowHeight.example"; +import { useCitiesByState } from "./hooks/useCitiesByState"; + +const EMPTY_OPTION: Option = { + label: "", + value: "" +}; + +const ALIGNMENTS: Option[] = ( + ["auto", "center", "end", "smart", "start"] satisfies Align[] +).map((value) => ({ + label: `align: ${value}`, + value +})); +ALIGNMENTS.unshift(EMPTY_OPTION as Option); + +const BEHAVIORS: Option[] = ( + ["auto", "instant", "smooth"] satisfies ScrollBehavior[] +).map((value) => ({ + label: `behavior: ${value}`, + value +})); +BEHAVIORS.unshift(EMPTY_OPTION as Option); + +export function ListImperativeApiRoute() { + const [align, setAlign] = useState | undefined>(); + const [behavior, setBehavior] = useState< + Option | undefined + >(); + const [state, setState] = useState>(EMPTY_OPTION); + + const citiesByState = useCitiesByState(); + + const stateOptions = useMemo[]>(() => { + const options: Option[] = citiesByState + .filter((item) => item.type === "state") + .map((item) => ({ + label: item.state, + value: item.state + })); + options.unshift(EMPTY_OPTION); + + return options; + }, [citiesByState]); + + const listRef = useListRef(null); + + const scrollToRow = () => { + const index = citiesByState.findIndex( + (item) => item.type === "state" && item.state === state.value + ); + listRef.current?.scrollToRow({ + align: align?.value, + behavior: behavior?.value, + index + }); + }; + + return ( + +
+
+ List provides an imperative API for responding to events. The + recommended way to access this API is to use the exported ref hook: +
+ +
Attach the ref during render:
+ +
And call API methods in an event handler:
+ +
The form below uses the imperative API to scroll the list:
+ + + + +