├── .eslintrc.cjs ├── .gitignore ├── .nvmrc ├── .prettierrc ├── README.md ├── mdsvex.config.js ├── netlify.toml ├── package.json ├── src ├── app.html ├── global.d.ts ├── lib │ ├── FlatList.svelte │ ├── _actions │ │ ├── clickOutside.ts │ │ └── draggable.ts │ └── index.ts └── routes │ ├── _Codeblock.svx │ ├── _GHLink.svelte │ ├── _Install.svelte.md │ ├── _Props.svelte.md │ ├── _TableOfContents.svelte │ ├── __layout.svelte │ └── index.svelte ├── static └── favicon.png ├── svelte.config.js └── tsconfig.json /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 5 | plugins: ['svelte3', '@typescript-eslint'], 6 | ignorePatterns: ['*.cjs'], 7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], 8 | settings: { 9 | 'svelte3/typescript': () => require('typescript') 10 | }, 11 | parserOptions: { 12 | sourceType: 'module', 13 | ecmaVersion: 2019 14 | }, 15 | env: { 16 | browser: true, 17 | es2017: true, 18 | node: true 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | /.netlify 8 | .sveltegen.json 9 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | NODE_VERSION=16 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Svelte FlatList 2 | 3 | A mobile-friendly, simple, and customizable draggable menu. 4 | 5 | **Documentation / Demo site**: https://svelte-flatlist.netlify.app/ 6 | 7 | [Find me on NPM!](https://www.npmjs.com/package/svelte-flatlist) 8 | 9 | ## Demo 10 | 11 | [Demo/Docs](https://svelte-flatlist.netlify.app/) 12 | [REPL](https://svelte.dev/repl/b1b6b42ca4c944ca99f0063d5ca1ccdb?version=3.44.2) 13 | 14 | ### Installation 15 | 16 | `npm install -D svelte-flatlist` 17 | 18 | # Usage 19 | 20 | Using Svelte FlatList is very simple. Below are some usage examples. 21 | 22 | ## Each Block 23 | 24 | _See this in action at the Svelte REPL!_ 25 | [Link](https://svelte.dev/repl/2cfb8d8b8ff2447688874a2e0dacb731?version=3.44.2) 26 | 27 | ```html 28 | 37 | 38 | 41 | 42 | 43 | {#each items as item} 44 | {item} 45 | {/each} 46 | 47 | ``` 48 | 49 | ## Customize the theme 50 | 51 | _See this in action at the Svelte REPL!_ 52 | [Link](https://svelte.dev/repl/dbe96ccc7e974dee80ce57a45d17ae2e?version=3.44.2) 53 | 54 | ```html 55 | 64 | 65 | 68 | 69 | 70 | item 1 71 | item 2 72 | item 3 73 | item 4 74 | item 5 75 | 76 | ``` 77 | 78 | ## Manually list items 79 | 80 | _See this in action at the Svelte REPL!_ 81 | [Link](https://svelte.dev/repl/dbe96ccc7e974dee80ce57a45d17ae2e?version=3) 82 | 83 | ```html 84 | 92 | 93 | 96 | 97 | 98 | item 1 99 | item 2 100 | item 3 101 | item 4 102 | 103 | ``` 104 | 105 | ## Props 106 | 107 | | Prop | Description | 108 | | -------- | --------------------------------------------------------------------------- | 109 | | visible | (REQUIRED) visibility state | 110 | | style | (See below for options) Customize the colors to fit your needs. | 111 | | zIndex | Same as CSS z-index (defaults to 9999) | 112 | | position | Position of the list on screen (defaults to 'center') | 113 | | duration | Duration of the opening & closing transition (defaults to 400) | 114 | | maxWidth | Maximum width of the list - must include a CSS unit (defaults to 640px) | 115 | | gap | Gap in between list items - must include a CSS unit (defaults to 0.8rem) | 116 | | overflow | Behavior for vertical overflow, same as CSS overflow-y (defaults to 'auto') | 117 | 118 | ## Style Properties 119 | 120 | Svelte-Flatlist allows you to modify the styles to fit your needs best using `style` prop. 121 | 122 | Example: 123 | 124 | ```html 125 | 135 | ``` 136 | 137 | ### ```style``` Type 138 | 139 | The type of the ```style``` prop: 140 | 141 | ```typescript 142 | type ListStyle = { 143 | bgColor?: string; 144 | handle?: HandleStyle; 145 | }; 146 | 147 | type HandleStyle = { 148 | bgColor?: string; 149 | fgColor?: string; 150 | height?: string; 151 | }; 152 | ``` 153 | 154 | ### More Info 155 | 156 | 157 | | Style Props | Description | 158 | | ----------- | ---------------------------------------------------- | 159 | | bgColor | Base background color | 160 | | handle | (see below for options) Color options for the handle | 161 | 162 |
163 | 164 | | Handle Properties | Description | 165 | | ----------------- | -------------------------- | 166 | | bgColor | Handle background color | 167 | | fgColor | Handle foreground color | 168 | | height | Handle height (ex: '5rem') | 169 | 170 | ## Events 171 | 172 | | Event | Description | 173 | | ----- | ------------------------------- | 174 | | close | Event fires when list is closed | 175 | 176 | ## Events 177 | 178 | | Event | Description | 179 | | ----- | ------------------------------- | 180 | | close | Event fires when list is closed | 181 | -------------------------------------------------------------------------------- /mdsvex.config.js: -------------------------------------------------------------------------------- 1 | import shiki from 'shiki' 2 | import { escapeSvelte } from "mdsvex"; 3 | 4 | const config = { 5 | extensions: ['.svelte.md', '.md', '.svx'], 6 | 7 | smartypants: { 8 | dashes: 'oldschool' 9 | }, 10 | highlight: { 11 | highlighter: async (code, lang = "svelte") => { 12 | const highlighter = await shiki.getHighlighter({ theme: 'min-dark' }); 13 | const highlightedCode = escapeSvelte(highlighter.codeToHtml(code, lang)); 14 | return `{@html \`${highlightedCode}\` }`; 15 | }, 16 | }, 17 | remarkPlugins: [], 18 | rehypePlugins: [] 19 | }; 20 | 21 | export default config; 22 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | command = "npm run build" 3 | publish = "build" 4 | 5 | [dev] 6 | command = "svelte-kit dev" 7 | 8 | [functions] 9 | directory = "netlify/functions" 10 | node_bundler = "esbuild" 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-flatlist", 3 | "version": "0.2.6", 4 | "repository": { 5 | "url": "https://github.com/snuffyDev/svelte-flatlist" 6 | }, 7 | "author": "snuffydev", 8 | "description": "A draggable, customizable, mobile-friendly menu that's easy to use for Svelte.", 9 | "keywords": [ 10 | "svelte", 11 | "svelte3", 12 | "svelte-component", 13 | "mobile", 14 | "drawer", 15 | "draggable", 16 | "sveltejs", 17 | "menu", 18 | "flatlist" 19 | ], 20 | "scripts": { 21 | "dev": "svelte-kit dev --host", 22 | "build": "svelte-kit build", 23 | "deploy:docs": "npm run build && netlify deploy --prod", 24 | "package": "svelte-kit package", 25 | "publish": "npm run package && npm publish ./package", 26 | "publish:all": "npm run deploy:docs && npm run package && npm publish ./package", 27 | "preview": "svelte-kit preview", 28 | "check": "svelte-check --tsconfig ./tsconfig.json", 29 | "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch", 30 | "lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .", 31 | "format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. ." 32 | }, 33 | "main": "index.js", 34 | "types": "index.d.ts", 35 | "svelte": "index.js", 36 | "exports": { 37 | "./package.json": "./package.json", 38 | ".": "./index.js" 39 | }, 40 | "devDependencies": { 41 | "@sveltejs/adapter-netlify": "next", 42 | "@sveltejs/kit": "next", 43 | "@typescript-eslint/eslint-plugin": "^4.31.1", 44 | "@typescript-eslint/parser": "^4.31.1", 45 | "autoprefixer": "^10.4.0", 46 | "eslint": "^7.32.0", 47 | "eslint-config-prettier": "^8.3.0", 48 | "eslint-plugin-svelte3": "^3.2.1", 49 | "mdsvex": "^0.9.8", 50 | "prettier": "^2.4.1", 51 | "prettier-plugin-svelte": "^2.4.0", 52 | "shiki": "^0.9.12", 53 | "svelte": "^3.42.6", 54 | "svelte-check": "^2.2.6", 55 | "svelte-preprocess": "^4.9.4", 56 | "svelte2tsx": "^0.4.10", 57 | "tslib": "^2.3.1", 58 | "typescript": "^4.4.3" 59 | }, 60 | "type": "module", 61 | "engines": { 62 | "node": ">=16" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | %svelte.head% 10 | 11 | 12 |
%svelte.body%
13 | 14 | 15 | -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/lib/FlatList.svelte: -------------------------------------------------------------------------------- 1 | 84 | 85 | 86 | {#if visible} 87 |
{}} 91 | /> 92 |
{}} 98 | > 99 |
107 |
{ 113 | trackMovement({ y: e.detail.y }); 114 | }} 115 | on:dragend={release} 116 | > 117 | 118 |
119 |
120 | 121 |
122 |
123 |
124 | {/if} 125 | 126 | 229 | -------------------------------------------------------------------------------- /src/lib/_actions/clickOutside.ts: -------------------------------------------------------------------------------- 1 | export default function clickOutside(node) { 2 | function clickhandler(event) { 3 | if (!node.contains(event.target)) { 4 | node.dispatchEvent(new CustomEvent('outside')); 5 | } 6 | } 7 | window.addEventListener('click', clickhandler, { capture: true, passive: true }); 8 | return { 9 | destroy: () => { 10 | window.removeEventListener('click', clickhandler); 11 | } 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /src/lib/_actions/draggable.ts: -------------------------------------------------------------------------------- 1 | export default function draggable(node: HTMLElement) { 2 | let y; 3 | let initTop, initHeight; 4 | let frame; 5 | let initOverflow; 6 | function preventScroll(event: UIEvent) { 7 | event.preventDefault(); 8 | } 9 | function handleDragStart(event) { 10 | const { top, height } = node.getBoundingClientRect(); 11 | 12 | initTop = top; 13 | initHeight = height; 14 | if (event.type == 'touchstart') { 15 | /* iOS Safari has issues with the body scrolling, even when not targeted, 16 | below is a hacky solution that (should) work on Android as well as Safari 17 | 18 | - event.preventDefault() on scroll 19 | - 'overflow: hidden;' on the HTML element 20 | 21 | */ 22 | 23 | document.body.addEventListener('scroll', preventScroll, { capture: true }); 24 | initOverflow = getComputedStyle(document.querySelector('html')).overflow; 25 | 26 | document.querySelector('html').style.overflow = 'hidden'; 27 | y = initHeight + event.touches[0].clientY - initTop; 28 | } else { 29 | y = event.clientY - initTop - 8; 30 | } 31 | 32 | node.dispatchEvent( 33 | new CustomEvent('dragstart', { 34 | detail: { y } 35 | }) 36 | ); 37 | 38 | window.addEventListener('touchmove', handleDrag, { passive: true }); 39 | window.addEventListener('touchend', handleDragEnd, { passive: true }); 40 | window.addEventListener('mousemove', handleDrag); 41 | window.addEventListener('mouseup', handleDragEnd, { passive: true }); 42 | } 43 | 44 | function handleDrag(event) { 45 | if (event.type == 'touchmove') { 46 | /* Probably overkill, but this should hopefully 47 | help performance on low-end touch devices 48 | */ 49 | y = event.touches[0].clientY - initTop; 50 | 51 | frame = () => 52 | node.dispatchEvent( 53 | new CustomEvent('dragmove', { 54 | detail: { 55 | y 56 | } 57 | }) 58 | ); 59 | requestAnimationFrame(frame); 60 | } else { 61 | y = event.clientY - initTop - 8; 62 | node.dispatchEvent( 63 | new CustomEvent('dragmove', { 64 | detail: { y } 65 | }) 66 | ); 67 | } 68 | } 69 | 70 | function handleDragEnd(event) { 71 | if (event.type == 'touchend') { 72 | y = event.changedTouches[0].clientY; 73 | } else { 74 | y = event.clientY - initTop - 8; 75 | } 76 | node.dispatchEvent( 77 | new CustomEvent('dragend', { 78 | detail: { y } 79 | }) 80 | ); 81 | y = 0; 82 | if (event.type == 'touchend') { 83 | /* Removes the scroll block from the body element when finished dragging, 84 | reverts to the initial overflow value set on the HTML node, along with 85 | some (probably unneeded) cleanup with the RAF. 86 | */ 87 | document.body.removeEventListener('scroll', preventScroll, { capture: true }); 88 | document.querySelector('html').style.overflow = initOverflow; 89 | cancelAnimationFrame(frame); 90 | } 91 | window.removeEventListener('touchmove', handleDrag); 92 | window.removeEventListener('touchend', handleDragEnd); 93 | window.removeEventListener('mousemove', handleDrag); 94 | window.removeEventListener('mouseup', handleDragEnd); 95 | } 96 | 97 | node.addEventListener('touchstart', handleDragStart, { passive: true }); 98 | node.addEventListener('mousedown', handleDragStart, { passive: true }); 99 | 100 | return { 101 | destroy: () => { 102 | if (frame) { 103 | cancelAnimationFrame(frame); 104 | } 105 | node.removeEventListener('touchstart', handleDragStart); 106 | node.removeEventListener('mousedown', handleDragStart); 107 | } 108 | }; 109 | } 110 | -------------------------------------------------------------------------------- /src/lib/index.ts: -------------------------------------------------------------------------------- 1 | export { default as default } from './FlatList.svelte'; 2 | -------------------------------------------------------------------------------- /src/routes/_Codeblock.svx: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | Using Svelte FlatList is very simple. Below are some usage examples. 4 | 5 | ## Each Block 6 | *See this in action at the Svelte REPL!* 7 | Link 8 | 9 | ```html 10 | 19 | 20 | 23 | 24 | 25 | {#each items as item} 26 | {item} 27 | {/each} 28 | 29 | ``` 30 | 31 | ## Customize the theme 32 | *See this in action at the Svelte REPL!* 33 | Link 34 | 35 | ```html 36 | 45 | 46 | 49 | 50 | 51 | item 1 52 | item 2 53 | item 3 54 | item 4 55 | item 5 56 | 57 | ``` 58 | 59 | ## Manually list items 60 | *See this in action at the Svelte REPL!* 61 | Link 62 | 63 | ```html 64 | 72 | 73 | 76 | 77 | 78 | item 1 79 | item 2 80 | item 3 81 | item 4 82 | 83 | ``` 84 | -------------------------------------------------------------------------------- /src/routes/_GHLink.svelte: -------------------------------------------------------------------------------- 1 | 2 | 13 | 16 | 17 | 18 | 19 | 30 | -------------------------------------------------------------------------------- /src/routes/_Install.svelte.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | ```bash 4 | npm install -D svelte-flatlist // npm 5 | pnpm install -D svelte-flatlist // pnpm 6 | yarn add svelte-flatlist -D // yarn 7 | ``` 8 | -------------------------------------------------------------------------------- /src/routes/_Props.svelte.md: -------------------------------------------------------------------------------- 1 |

Props

2 | 3 | | Prop | Description | 4 | | -------- | --------------------------------------------------------------------------- | 5 | | visible | (REQUIRED) visibility state | 6 | | style | (See below for options) Customize the colors to fit your needs. | 7 | | zIndex | Same as CSS z-index (defaults to 9999) | 8 | | position | Position of the list on screen (defaults to 'center') | 9 | | duration | Duration of the opening & closing transition (defaults to 400) | 10 | | maxWidth | Maximum width of the list - must include a CSS unit (defaults to 640px) | 11 | | gap | Gap in between list items - must include a CSS unit (defaults to 0.8rem) | 12 | | overflow | Behavior for vertical overflow, same as CSS overflow-y (defaults to 'auto') | 13 | 14 | ## Style Properties 15 | 16 | Svelte-Flatlist allows you to modify the styles to fit your needs best using `style` prop. 17 | 18 | Example: 19 | 20 | ```html 21 | 31 | ``` 32 | 33 | ### ```style``` Type 34 | 35 | The type of the ```style``` prop: 36 | 37 | ```typescript 38 | type ListStyle = { 39 | bgColor?: string; 40 | handle?: HandleStyle; 41 | }; 42 | 43 | type HandleStyle = { 44 | bgColor?: string; 45 | fgColor?: string; 46 | height?: string; 47 | }; 48 | ``` 49 | 50 | ### More Info 51 | 52 | 53 | | Style Props | Description | 54 | | ----------- | ---------------------------------------------------- | 55 | | bgColor | Base background color | 56 | | handle | (see below for options) Color options for the handle | 57 | 58 |
59 | 60 | | Handle Properties | Description | 61 | | ----------------- | -------------------------- | 62 | | bgColor | Handle background color | 63 | | fgColor | Handle foreground color | 64 | | height | Handle height (ex: '5rem') | 65 | 66 |

Events

67 | 68 | | Event | Description | 69 | | ----- | ------------------------------- | 70 | | close | Event fires when list is closed | 71 | -------------------------------------------------------------------------------- /src/routes/_TableOfContents.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | {#if !hidden} 12 | 24 | {:else} 25 | 31 | {/if} 32 | 33 | 81 | -------------------------------------------------------------------------------- /src/routes/__layout.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | Svelte-Flatlist docs 7 | 8 | 9 | 10 | 11 | 90 | -------------------------------------------------------------------------------- /src/routes/index.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 26 | 27 |
28 |

svelte-flatlist

29 |

A mobile-friendly, simple, and customizable draggable menu.

30 | 31 | 32 |
33 | 34 | 35 |

Demo

36 |
37 |
38 | 39 | 42 | 45 | 55 |
56 | { 62 | // visible = false; 63 | visible = false; 64 | }} 65 | bind:visible 66 | > 67 | {#each items as item, i} 68 | {i} 69 | 70 | {/each} 71 | 72 |
73 | 79 | 85 | 91 |
92 |
93 |
94 |
95 | 96 | 97 |
98 | 99 | 170 | -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snuffyDev/svelte-flatlist/8ed67f7ec0d5561efa85e898fc0399700596dec8/static/favicon.png -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import { mdsvex } from 'mdsvex'; 2 | import mdsvexConfig from './mdsvex.config.js'; 3 | import preprocess from 'svelte-preprocess'; 4 | import adapter from '@sveltejs/adapter-netlify' 5 | /** @type {import('@sveltejs/kit').Config} */ 6 | const config = { 7 | extensions: ['.svelte', ...mdsvexConfig.extensions], 8 | 9 | // Consult https://github.com/sveltejs/svelte-preprocess 10 | // for more information about preprocessors 11 | preprocess: [preprocess(), mdsvex(mdsvexConfig)], 12 | 13 | kit: { 14 | // hydrate the
element in src/app.html 15 | adapter: adapter(), 16 | target: '#svelte', 17 | } 18 | }; 19 | 20 | export default config; 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "module": "es2020", 5 | "lib": ["es2020", "DOM"], 6 | "target": "es2020", 7 | /** 8 | svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript 9 | to enforce using \`import type\` instead of \`import\` for Types. 10 | */ 11 | "importsNotUsedAsValues": "error", 12 | "isolatedModules": true, 13 | "resolveJsonModule": true, 14 | /** 15 | To have warnings/errors of the Svelte compiler at the correct position, 16 | enable source maps by default. 17 | */ 18 | "sourceMap": true, 19 | "esModuleInterop": true, 20 | "skipLibCheck": true, 21 | "forceConsistentCasingInFileNames": true, 22 | "baseUrl": ".", 23 | "allowJs": true, 24 | "checkJs": true, 25 | "paths": { 26 | "$lib": ["src/lib"], 27 | "$lib/*": ["src/lib/*"] 28 | } 29 | }, 30 | "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts", "src/**/*.svelte"] 31 | } 32 | --------------------------------------------------------------------------------