├── .gitignore
├── .vscode
└── extensions.json
├── LICENSE.txt
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.png
├── fonts
│ └── kai-icons.ttf
├── global.css
├── icons
│ ├── icon112x112.png
│ └── icon56x56.png
├── index.html
├── langs
│ ├── en-US.json
│ └── jp-JP.json
├── manifest.webapp
└── manifest.webmanifest
├── rollup.config.js
├── src
├── App.svelte
├── components
│ ├── AppBar.svelte
│ ├── Button.svelte
│ ├── Checkbox.svelte
│ ├── DatePicker.svelte
│ ├── Dialog.svelte
│ ├── LinearProgress.svelte
│ ├── ListView.svelte
│ ├── LoadingBar.svelte
│ ├── MultiSelector.svelte
│ ├── OptionMenu.svelte
│ ├── Radio.svelte
│ ├── RangeSlider.svelte
│ ├── Separator.svelte
│ ├── SingleSelector.svelte
│ ├── SoftwareKey.svelte
│ ├── TextAreaDialog.svelte
│ ├── TextAreaField.svelte
│ ├── TextInputDialog.svelte
│ ├── TextInputField.svelte
│ ├── TimePicker.svelte
│ └── index.ts
├── global.d.ts
├── main.ts
├── routes
│ ├── Demo.svelte
│ ├── Room.svelte
│ ├── Welcome.svelte
│ └── index.ts
└── utils
│ ├── localization.ts
│ └── navigation.ts
├── tsconfig.json
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /public/build/
3 |
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["svelte.svelte-vscode"]
3 | }
4 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Ahmad Malik
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # kaios-svelte-starter
4 |
5 | A simple starter template for building a KaiOS app using Svelte and TypeScript.
6 |
7 | ### Development and testing
8 |
9 | `npm run dev` builds the app in watch mode and serves the site. Great for testing your app in a desktop browser.
10 |
11 | ### Deploying to a device
12 |
13 | 1. Connect your device to your computer and make sure it appears in WebIDE.
14 | 2. `npm run build`
15 | 3. In WebIDE, load the `/public` folder as a packaged app.
16 | 4. Or, `npm run dev` then visit `localhost:port/index.html`
17 |
18 | ### Synthetic flavors:
19 | - Support D-pad navigation & Software Key listener
20 | - Support i18n
21 |
22 | ### Built-in components:
23 | 1. Dialog
24 | 2. ListView
25 | 3. Separator
26 | 4. Option Menu
27 | 5. Radio
28 | 6. Single Selector(Radio)
29 | 7. Checkbox
30 | 8. Multi Selector(Checkbox)
31 | 9. Date Picker
32 | 10. Time Picker
33 | 11. Loading Bar
34 | 11. Progress Bar
35 | 12. Range Slider
36 | 13. Button
37 | 14. InputText
38 | 15. TextArea
39 | 14. InputTextDialog
40 | 15. TextAreaDialog
41 | 16. Toaster(@zerodevx/svelte-toast)
42 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "kaios-svelte-starter",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "build": "rollup -c",
7 | "dev": "rollup -c -w",
8 | "start": "sirv public --no-clear",
9 | "check": "svelte-check --tsconfig ./tsconfig.json"
10 | },
11 | "devDependencies": {
12 | "@babel/core": "^7.17.2",
13 | "@babel/plugin-syntax-dynamic-import": "^7.8.3",
14 | "@babel/plugin-transform-runtime": "^7.16.4",
15 | "@babel/preset-env": "^7.16.4",
16 | "@rollup/plugin-commonjs": "^17.0.0",
17 | "@rollup/plugin-json": "^4.1.0",
18 | "@rollup/plugin-node-resolve": "^11.0.0",
19 | "@rollup/plugin-replace": "^3.0.0",
20 | "@rollup/plugin-typescript": "^8.0.0",
21 | "@tsconfig/svelte": "^2.0.0",
22 | "rollup": "^2.3.4",
23 | "rollup-plugin-babel": "^4.4.0",
24 | "rollup-plugin-css-only": "^3.1.0",
25 | "rollup-plugin-livereload": "^2.0.0",
26 | "rollup-plugin-svelte": "^7.0.0",
27 | "rollup-plugin-terser": "^7.0.0",
28 | "svelte": "^3.0.0",
29 | "svelte-check": "^2.0.0",
30 | "svelte-preprocess": "^4.0.0",
31 | "tslib": "^2.0.0",
32 | "typescript": "^4.0.0"
33 | },
34 | "dependencies": {
35 | "@zerodevx/svelte-toast": "^0.6.3",
36 | "sirv-cli": "^1.0.0",
37 | "sprintf-js": "^1.1.2",
38 | "svelte-navigator": "^3.1.5"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arma7x/kaios-svelte-starter/b1a957f04644b62e0871dc5a38f1e0f033aee54c/public/favicon.png
--------------------------------------------------------------------------------
/public/fonts/kai-icons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arma7x/kaios-svelte-starter/b1a957f04644b62e0871dc5a38f1e0f033aee54c/public/fonts/kai-icons.ttf
--------------------------------------------------------------------------------
/public/global.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --themeColor: #ff3e00;
3 | --themeColorLight: #ECE8F2;
4 | --themeColorTransparent: rgba(255, 62, 0, 0.1);
5 | --toastContainerTop: 26px;
6 | --toastContainerRight: 0;
7 | --toastWidth: 100vw;
8 | --toastBarHeight: 0px;
9 | --toastMinHeight: 30px;
10 | --toastBackground: #2f2f2f;
11 | --toastColor: #ffffff;
12 | --toastBorderRadius: 0;
13 | --toastBarBackground: var(--themeColor);
14 | }
15 |
16 | @font-face {
17 | font-family: 'kai-icons';
18 | src: url('fonts/kai-icons.ttf') format('truetype');
19 | font-weight: normal;
20 | font-style: normal;
21 | }
22 |
23 | [class^="kai-icon-"], [class*=" kai-icon-"] {
24 | /* use !important to prevent issues with browser extensions that change fonts */
25 | font-family: 'kai-icons' !important;
26 | speak: none;
27 | font-style: normal;
28 | font-weight: normal;
29 | font-variant: normal;
30 | text-transform: none;
31 | line-height: 1;
32 |
33 | /* Better Font Rendering =========== */
34 | -webkit-font-smoothing: antialiased;
35 | -moz-osx-font-smoothing: grayscale;
36 | }
37 |
38 | .kai-icon-arrow:before {
39 | content: "\e900";
40 | }
41 |
42 | .kai-icon-checkbox-checked:before {
43 | content: "\e901";
44 | }
45 |
46 | .kai-icon-checkbox-unchecked:before {
47 | content: "\e902";
48 | }
49 |
50 | .kai-icon-radio-button-selected:before {
51 | content: "\e906";
52 | }
53 |
54 | .kai-icon-radio-button-unselected:before {
55 | content: "\e90f";
56 | }
57 |
58 | .kai-icon-bluetooth:before {
59 | content: "\e903";
60 | }
61 |
62 | .kai-icon-calendar:before {
63 | content: "\e904";
64 | }
65 |
66 | .kai-icon-camera:before {
67 | content: "\e905";
68 | }
69 |
70 | .kai-icon-contacts:before {
71 | content: "\e907";
72 | }
73 |
74 | .kai-icon-download:before {
75 | content: "\e908";
76 | }
77 |
78 | .kai-icon-email:before {
79 | content: "\e909";
80 | }
81 |
82 | .kai-icon-favorite-off:before {
83 | content: "\e90a";
84 | }
85 |
86 | .kai-icon-favorite-on:before {
87 | content: "\e90b";
88 | }
89 |
90 | .kai-icon-message:before {
91 | content: "\e90c";
92 | }
93 |
94 | .kai-icon-mic:before {
95 | content: "\e90d";
96 | }
97 |
98 | .kai-icon-phone:before {
99 | content: "\e90e";
100 | }
101 |
102 | .kai-icon-search:before {
103 | content: "\e910";
104 | }
105 |
106 | .kai-icon-settings:before {
107 | content: "\e911";
108 | }
109 |
110 | .kai-icon-video:before {
111 | content: "\e912";
112 | }
113 |
114 | .kai-icon-wifi:before {
115 | content: "\e913";
116 | }
117 |
118 | html,
119 | body {
120 | position: relative;
121 | width: 100%;
122 | height: 100%;
123 | background-color: #ffffff;
124 | }
125 |
126 | div {
127 | padding: 0;
128 | margin: 0;
129 | box-sizing: border-box;
130 | }
131 |
132 | body {
133 | color: #333;
134 | margin: 0;
135 | padding: 0px;
136 | box-sizing: border-box;
137 | font-family: 'Open Sans';
138 | }
139 |
140 | a {
141 | color: rgb(0, 100, 200);
142 | text-decoration: none;
143 | }
144 |
145 | a:hover {
146 | text-decoration: underline;
147 | }
148 |
149 | a:visited {
150 | color: rgb(0, 80, 160);
151 | }
152 |
153 | label {
154 | display: block;
155 | }
156 |
157 | input,
158 | button,
159 | select,
160 | textarea {
161 | font-family: inherit;
162 | font-size: inherit;
163 | -webkit-padding: 0.4em 0;
164 | padding: 0.4em;
165 | margin: 0 0 0.5em 0;
166 | box-sizing: border-box;
167 | border: 1px solid #ccc;
168 | border-radius: 2px;
169 | }
170 |
171 | input:disabled {
172 | color: #ccc;
173 | }
174 |
175 | button {
176 | color: #333;
177 | background-color: #f4f4f4;
178 | outline: none;
179 | }
180 |
181 | button:disabled {
182 | color: #999;
183 | }
184 |
185 | button:not(:disabled):active {
186 | background-color: #ddd;
187 | }
188 |
189 | button:focus {
190 | border-color: #666;
191 | }
192 |
--------------------------------------------------------------------------------
/public/icons/icon112x112.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arma7x/kaios-svelte-starter/b1a957f04644b62e0871dc5a38f1e0f033aee54c/public/icons/icon112x112.png
--------------------------------------------------------------------------------
/public/icons/icon56x56.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arma7x/kaios-svelte-starter/b1a957f04644b62e0871dc5a38f1e0f033aee54c/public/icons/icon56x56.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Svelte App
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/public/langs/en-US.json:
--------------------------------------------------------------------------------
1 | {
2 | "hello": "Hello %s",
3 | "change_locale": "Change Locale",
4 | "change_locale_subtitle": "Click to change app locale",
5 | "select_locale": "Select Locale"
6 | }
7 |
--------------------------------------------------------------------------------
/public/langs/jp-JP.json:
--------------------------------------------------------------------------------
1 | {
2 | "hello": "こんにちは %s",
3 | "change_locale": "ロケールを選択",
4 | "change_locale_subtitle": "クリックしてアプリのロケールを変更します",
5 | "select_locale": "ロケールを選択"
6 | }
7 |
--------------------------------------------------------------------------------
/public/manifest.webapp:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Svelte App",
3 | "description": "A starter template for Svelte and TypeScript.",
4 | "version": "1.0.0",
5 | "launch_path": "/index.html",
6 | "theme": "#ff3e00",
7 | "theme_color": "#ff3e00",
8 | "background_color": "#ff3e00",
9 | "fullscreen": "true",
10 | "chrome": {
11 | "statusbar": "overlap"
12 | },
13 | "orientation": "default",
14 | "icons": {
15 | "56": "/icons/icon56x56.png",
16 | "112": "/icons/icon112x112.png"
17 | },
18 | "categories": [
19 | "utilities"
20 | ],
21 | "developer": {
22 | "name": "arma7x",
23 | "url": "https://github.com/arma7x/kaios-svelte-starter"
24 | },
25 | "origin": "app://svelte-app.arma7x.com",
26 | "type": "privileged",
27 | "permissions": {},
28 | "locales": {
29 | "en-US": {
30 | "name": "Svelte App",
31 | "subtitle": "A starter template for Svelte and TypeScript.",
32 | "description": "A starter template for Svelte and TypeScript."
33 | }
34 | },
35 | "default_locale": "en"
36 | }
37 |
--------------------------------------------------------------------------------
/public/manifest.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "id": "Svelte App",
3 | "name": "Svelte App",
4 | "description": "A starter template for Svelte and TypeScript.",
5 | "start_url": "/index.html",
6 | "theme": "#ff3e00",
7 | "theme_color": "#ff3e00",
8 | "background_color": "#ff3e00",
9 | "display": "fullscreen",
10 | "orientation": "default",
11 | "categories": [
12 | "utilities"
13 | ],
14 | "icons": [
15 | {
16 | "src": "/icons/icon56x56.png",
17 | "type": "image/png",
18 | "sizes": "56x56"
19 | },
20 | {
21 | "src": "/icons/icon112x112.png",
22 | "type": "image/png",
23 | "sizes": "112x112"
24 | }
25 | ],
26 | "b2g_features": {
27 | "version": "3.0.0",
28 | "type": "privileged",
29 | "core": true,
30 | "default_locale": "en-US",
31 | "developer": {
32 | "name": "arma7x",
33 | "url": "https://github.com/arma7x/kaios-svelte-starter"
34 | },
35 | "chrome": {
36 | "statusbar": "overlap"
37 | },
38 | "origin": "svelte-app",
39 | "permissions": {},
40 | "dependencies": {},
41 | "locales": {
42 | "en-US": {
43 | "name": "Svelte App",
44 | "subtitle": "A starter template for Svelte and TypeScript.",
45 | "description": "A starter template for Svelte and TypeScript."
46 | }
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import svelte from 'rollup-plugin-svelte';
2 | import commonjs from '@rollup/plugin-commonjs';
3 | import resolve from '@rollup/plugin-node-resolve';
4 | import livereload from 'rollup-plugin-livereload';
5 | import { terser } from 'rollup-plugin-terser';
6 | import sveltePreprocess from 'svelte-preprocess';
7 | import typescript from '@rollup/plugin-typescript';
8 | import css from 'rollup-plugin-css-only';
9 | import babel from 'rollup-plugin-babel';
10 | import replace from '@rollup/plugin-replace';
11 | import json from '@rollup/plugin-json';
12 |
13 | const production = !process.env.ROLLUP_WATCH;
14 |
15 | function serve() {
16 | let server;
17 |
18 | function toExit() {
19 | if (server) server.kill(0);
20 | }
21 |
22 | return {
23 | writeBundle() {
24 | if (server) return;
25 | server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
26 | stdio: ['ignore', 'inherit', 'inherit'],
27 | shell: true,
28 | });
29 |
30 | process.on('SIGTERM', toExit);
31 | process.on('exit', toExit);
32 | },
33 | };
34 | }
35 |
36 | export default {
37 | input: 'src/main.ts',
38 | output: {
39 | sourcemap: !production,
40 | format: 'iife',
41 | name: 'app',
42 | file: 'public/build/bundle.js',
43 | },
44 | context: 'window',
45 | plugins: [
46 | svelte({
47 | preprocess: sveltePreprocess({
48 | sourceMap: !production,
49 | typescript: {
50 | compilerOptions: {
51 | target: 'ES2015',
52 | module: 'ES2015',
53 | },
54 | },
55 | replace: [[/process\.env\.(\w+)/g, (_, prop) => JSON.stringify(process.env[prop])]],
56 | }),
57 | compilerOptions: {
58 | // enable run-time checks when not in production
59 | dev: !production,
60 | },
61 | }),
62 | // we'll extract any component CSS out into
63 | // a separate file - better for performance
64 | css({ output: 'bundle.css' }),
65 |
66 | babel({
67 | extensions: ['.js', '.ts', '.mjs', '.html', '.svelte'],
68 | runtimeHelpers: true,
69 | exclude: ['node_modules/@babel/**'],
70 | presets: [
71 | [
72 | '@babel/preset-env',
73 | {
74 | targets: { firefox: '48' },
75 | exclude: [
76 | '@babel/plugin-transform-regenerator'
77 | ]
78 | },
79 | ],
80 | ],
81 | plugins: [
82 | '@babel/plugin-syntax-dynamic-import',
83 | [
84 | '@babel/plugin-transform-runtime',
85 | {
86 | "regenerator": false,
87 | useESModules: true,
88 | },
89 | ],
90 | ],
91 | }),
92 |
93 | replace({
94 | preventAssignment: true,
95 | 'process.env.NODE_ENV': !production ? "'development'" : "'production'",
96 | }),
97 |
98 | json(),
99 |
100 | // If you have external dependencies installed from
101 | // npm, you'll most likely need these plugins. In
102 | // some cases you'll need additional configuration -
103 | // consult the documentation for details:
104 | // https://github.com/rollup/plugins/tree/master/packages/commonjs
105 | resolve({
106 | browser: true,
107 | dedupe: ['svelte'],
108 | }),
109 | commonjs(),
110 | typescript({
111 | sourceMap: !production,
112 | inlineSources: !production,
113 | }),
114 |
115 | // In dev mode, call `npm run start` once
116 | // the bundle has been generated
117 | !production && serve(),
118 |
119 | // Watch the `public` directory and refresh the
120 | // browser on changes when not in production
121 | !production && livereload('public'),
122 |
123 | // If we're building for production (npm run build
124 | // instead of npm run dev), minify
125 | production && terser(),
126 | ],
127 | watch: {
128 | clearScreen: false,
129 | },
130 | };
131 |
--------------------------------------------------------------------------------
/src/App.svelte:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
63 |
--------------------------------------------------------------------------------
/src/components/AppBar.svelte:
--------------------------------------------------------------------------------
1 |
20 |
21 | {title}
22 |
23 |
39 |
--------------------------------------------------------------------------------
/src/components/Button.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 | {text}
10 |
11 |
12 |
13 |
39 |
--------------------------------------------------------------------------------
/src/components/Checkbox.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
15 |
--------------------------------------------------------------------------------
/src/components/DatePicker.svelte:
--------------------------------------------------------------------------------
1 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
-1M
205 |
M
206 |
+1M
207 |
208 |
209 |
210 |
-1D
211 |
D
212 |
+1D
213 |
214 |
215 |
216 |
-1Y
217 |
Y
218 |
+1Y
219 |
220 |
221 |
222 |
223 |
224 |
225 |
293 |
--------------------------------------------------------------------------------
/src/components/Dialog.svelte:
--------------------------------------------------------------------------------
1 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | {#if html}
100 |
{@html body}
101 | {:else}
102 |
{body}
103 | {/if}
104 |
105 |
106 |
107 |
141 |
--------------------------------------------------------------------------------
/src/components/LinearProgress.svelte:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 | {#if progressType != 0 || label != null}
21 |
29 | {/if}
30 |
35 |
36 |
37 |
94 |
--------------------------------------------------------------------------------
/src/components/ListView.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 | {#if $$slots.leftWidget}
11 |
12 | {/if}
13 |
14 |
15 |
{title}
16 | {#if subtitle}
{subtitle}{/if}
17 |
18 |
19 |
20 |
21 |
22 |
81 |
--------------------------------------------------------------------------------
/src/components/LoadingBar.svelte:
--------------------------------------------------------------------------------
1 |
15 |
16 |
20 |
21 |
273 |
--------------------------------------------------------------------------------
/src/components/MultiSelector.svelte:
--------------------------------------------------------------------------------
1 |
104 |
105 |
106 |
107 |
119 |
120 |
155 |
--------------------------------------------------------------------------------
/src/components/OptionMenu.svelte:
--------------------------------------------------------------------------------
1 |
75 |
76 |
77 |
78 |
91 |
92 |
127 |
--------------------------------------------------------------------------------
/src/components/Radio.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
15 |
--------------------------------------------------------------------------------
/src/components/RangeSlider.svelte:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 | {#if progressType != 0 || label != null}
21 |
29 | {/if}
30 |
36 |
37 |
38 |
111 |
--------------------------------------------------------------------------------
/src/components/Separator.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 | {title}
6 |
7 |
21 |
--------------------------------------------------------------------------------
/src/components/SingleSelector.svelte:
--------------------------------------------------------------------------------
1 |
93 |
94 |
95 |
96 |
108 |
109 |
144 |
--------------------------------------------------------------------------------
/src/components/SoftwareKey.svelte:
--------------------------------------------------------------------------------
1 |
37 |
38 |
39 |
{leftText}
40 |
{centerText}
41 |
{rightText}
42 |
43 |
44 |
78 |
--------------------------------------------------------------------------------
/src/components/TextAreaDialog.svelte:
--------------------------------------------------------------------------------
1 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
146 |
--------------------------------------------------------------------------------
/src/components/TextAreaField.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 | {#if label}
15 |
16 | {/if}
17 |
18 |
19 |
20 |
48 |
--------------------------------------------------------------------------------
/src/components/TextInputDialog.svelte:
--------------------------------------------------------------------------------
1 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
147 |
--------------------------------------------------------------------------------
/src/components/TextInputField.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 | {#if label}
14 |
15 | {/if}
16 |
17 |
18 |
19 |
48 |
--------------------------------------------------------------------------------
/src/components/TimePicker.svelte:
--------------------------------------------------------------------------------
1 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
-HH
235 |
HH
236 |
+HH
237 |
238 |
239 |
240 |
-MM
241 |
MM
242 |
+MM
243 |
244 | {#if is12HourSystem }
245 |
246 |
247 |
-DD
248 |
DD
249 |
+DD
250 |
251 | {/if}
252 |
253 |
254 |
255 |
256 |
257 |
325 |
--------------------------------------------------------------------------------
/src/components/index.ts:
--------------------------------------------------------------------------------
1 | import AppBar from "./AppBar.svelte";
2 | import SoftwareKey from "./SoftwareKey.svelte";
3 | import Dialog from "./Dialog.svelte";;
4 | import OptionMenu from "./OptionMenu.svelte";
5 | import SingleSelector from "./SingleSelector.svelte";
6 | import MultiSelector from "./MultiSelector.svelte";
7 | import ListView from "./ListView.svelte";
8 | import Separator from "./Separator.svelte";
9 | import Radio from "./Radio.svelte";
10 | import Checkbox from "./Checkbox.svelte";
11 | import LoadingBar from "./LoadingBar.svelte";
12 | import LinearProgress from "./LinearProgress.svelte";
13 | import Button from "./Button.svelte";
14 | import RangeSlider from "./RangeSlider.svelte";
15 | import TextInputField from "./TextInputField.svelte";
16 | import TextAreaField from "./TextAreaField.svelte";
17 | import TextInputDialog from "./TextInputDialog.svelte";
18 | import TextAreaDialog from "./TextAreaDialog.svelte";
19 | import DatePicker from "./DatePicker.svelte";
20 | import TimePicker from "./TimePicker.svelte";
21 | import { SvelteToast, toast } from '@zerodevx/svelte-toast'
22 |
23 | export {
24 | AppBar,
25 | SoftwareKey,
26 | Dialog,
27 | OptionMenu,
28 | SingleSelector,
29 | MultiSelector,
30 | ListView,
31 | Separator,
32 | Radio,
33 | Checkbox,
34 | LoadingBar,
35 | LinearProgress,
36 | Button,
37 | RangeSlider,
38 | TextInputField,
39 | TextAreaField,
40 | TextInputDialog,
41 | TextAreaDialog,
42 | DatePicker,
43 | TimePicker,
44 | SvelteToast as Toast,
45 | toast as Toaster
46 | }
47 |
--------------------------------------------------------------------------------
/src/global.d.ts:
--------------------------------------------------------------------------------
1 | ///
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import App from './App.svelte';
2 |
3 | const app = new App({
4 | target: document.body,
5 | props: {}
6 | });
7 |
8 | export default app;
9 |
--------------------------------------------------------------------------------
/src/routes/Demo.svelte:
--------------------------------------------------------------------------------
1 |
552 |
553 |
554 | onClickHandler('room')}/>
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 |
567 |
568 |
569 |
570 |
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 |
579 |
580 |
581 |
582 |
583 |
584 |
585 |
586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 |
594 |
595 |
599 |
600 |
601 |
607 |
--------------------------------------------------------------------------------
/src/routes/Room.svelte:
--------------------------------------------------------------------------------
1 |
47 |
48 |
49 | Hello {name}!
50 |
51 |
Vertical 1
52 |
Vertical 2
53 |
54 |
55 |
Horizontal 1
56 |
Horizontal 2
57 |
58 |
59 |
60 |
85 |
--------------------------------------------------------------------------------
/src/routes/Welcome.svelte:
--------------------------------------------------------------------------------
1 |
46 |
47 |
48 | Hello {name}!
49 |
50 |
Vertical 1
51 |
Vertical 2
52 |
53 |
54 |
Horizontal 1
55 |
Horizontal 2
56 |
57 |
58 |
59 |
84 |
--------------------------------------------------------------------------------
/src/routes/index.ts:
--------------------------------------------------------------------------------
1 | import Welcome from "./Welcome.svelte";
2 | import Demo from "./Demo.svelte";
3 | import Room from "./Room.svelte";
4 |
5 | export {
6 | Welcome,
7 | Demo,
8 | Room
9 | }
10 |
--------------------------------------------------------------------------------
/src/utils/localization.ts:
--------------------------------------------------------------------------------
1 | import { sprintf } from 'sprintf-js';
2 |
3 | interface Translation {
4 | [key: string]: string;
5 | }
6 |
7 | interface Locale {
8 | [locale: string]: Translation;
9 | }
10 |
11 | class Localization {
12 |
13 | locales: Locale = {};
14 | namespace: string;
15 | defaultLocale: string;
16 |
17 | constructor(locale: string, namespace: string) {
18 | this.defaultLocale = locale;
19 | this.namespace = namespace;
20 | this.loadLocale(this.defaultLocale);
21 | }
22 |
23 | loadLocale(locale: string, cache: boolean = true, origin: string = document.location.origin): boolean {
24 | if (cache && this.locales[locale] != null) {
25 | this.defaultLocale = locale;
26 | return true;
27 | } else {
28 | const url = [];
29 | url.push(origin);
30 | if (this.namespace !== '' && origin === document.location.origin)
31 | url.push(this.namespace);
32 | url.push(`${locale}.json`);
33 | const request = new XMLHttpRequest();
34 | request.open('GET', url.join('/'), false);
35 | request.send(null);
36 | if (request.readyState === 4 && request.status >= 200 && request.status <= 399) {
37 | this.defaultLocale = locale;
38 | this.locales[this.defaultLocale] = JSON.parse(request.responseText);
39 | return true;
40 | } else if (request.readyState === 4) {
41 | return false;
42 | }
43 | }
44 | }
45 |
46 | lang(key: string, ...args: any): string | boolean {
47 | const line = this.locales[this.defaultLocale][key];
48 | if (line == null)
49 | return false;
50 | return sprintf(line, ...args);
51 | }
52 |
53 | langByLocale(key: string, locale: string, ...args: any): string | boolean {
54 | if (this.locales[locale] == null)
55 | return false;
56 | const line = this.locales[locale][key];
57 | if (line == null)
58 | return false;
59 | return sprintf(line, ...args);
60 | }
61 |
62 | getLocaleTranslation(locale: string): Translation {
63 | return this.locales[locale];
64 | }
65 |
66 | }
67 |
68 | export {
69 | Localization
70 | }
71 |
--------------------------------------------------------------------------------
/src/utils/navigation.ts:
--------------------------------------------------------------------------------
1 | function keydownEventHandler(evt, scope) {
2 | switch (evt.key) {
3 | case 'Backspace':
4 | case 'EndCall':
5 | if (['INPUT', 'TEXTAREA'].indexOf(evt.target.tagName) > -1 && evt.target.value == '') {
6 | evt.preventDefault();
7 | evt.stopPropagation();
8 | } else {
9 | scope.backspaceListener(evt);
10 | }
11 | break;
12 | case 'SoftLeft':
13 | case 'PageUp':
14 | case 'Shift':
15 | scope.softkeyLeftListener(evt);
16 | evt.preventDefault();
17 | break;
18 | case 'SoftRight':
19 | case 'PageDown':
20 | case 'Control':
21 | scope.softkeyRightListener(evt);
22 | evt.preventDefault();
23 | break;
24 | case 'Enter':
25 | scope.enterListener(evt);
26 | break;
27 | case 'ArrowUp':
28 | scope.arrowUpListener(evt);
29 | break;
30 | case 'ArrowDown':
31 | scope.arrowDownListener(evt);
32 | break;
33 | case 'ArrowLeft':
34 | scope.arrowLeftListener(evt);
35 | break;
36 | case 'ArrowRight':
37 | scope.arrowRightListener(evt);
38 | break;
39 | }
40 | }
41 |
42 | function isElementInViewport(el, marginTop = 0, marginBottom = 0) {
43 | if (el.parentElement.getAttribute("data-pad-top"))
44 | marginTop = parseFloat(el.parentElement.getAttribute("data-pad-top"));
45 | if (el.parentElement.getAttribute("data-pad-bottom"))
46 | marginBottom = parseFloat(el.parentElement.getAttribute("data-pad-bottom"));
47 | const rect = el.getBoundingClientRect();
48 | return (
49 | rect.top >= 0 + marginTop &&
50 | rect.left >= 0 &&
51 | rect.bottom <= ((window.innerHeight || document.documentElement.clientHeight) - marginBottom) && /* or $(window).height() */
52 | rect.right <= (window.innerWidth || document.documentElement.clientWidth) /* or $(window).width() */
53 | );
54 | }
55 |
56 | function dispatchScroll(target, newScrollTop) {
57 | target.scroll({ top: newScrollTop, behavior: 'smooth' });
58 | }
59 |
60 | function dispatchFocus(target, newScrollTop) {
61 | target.scrollTop = newScrollTop;
62 | const e = document.createEvent("UIEvents");
63 | e.initUIEvent("scroll", true, true, window, 1);
64 | target.dispatchEvent(e);
65 | }
66 |
67 | class KaiNavigator {
68 | private init: boolean = false;
69 | private eventHandler: any; // actual is EventListenerObject, any to suppress error
70 | target: string;
71 | verticalNavIndex: number = -1;
72 | verticalNavClass: string;
73 | horizontalNavIndex: number = -1;
74 | horizontalNavClass: string;
75 | arrowUpListener: Function = (evt) => {
76 | if (this.verticalNavClass) {
77 | evt.preventDefault();
78 | this.navigateListNav(-1);
79 | }
80 | };
81 | arrowDownListener: Function = (evt) => {
82 | if (this.verticalNavClass) {
83 | evt.preventDefault();
84 | this.navigateListNav(1);
85 | }
86 | };
87 | arrowLeftListener: Function = (evt) => {
88 | if (this.horizontalNavClass) {
89 | evt.preventDefault();
90 | this.navigateTabNav(-1);
91 | }
92 | };
93 | arrowRightListener: Function = (evt) => {
94 | if (this.horizontalNavClass) {
95 | evt.preventDefault();
96 | this.navigateTabNav(1);
97 | }
98 | };
99 | softkeyLeftListener: Function = (evt) => {};
100 | softkeyRightListener: Function = (evt) => {};
101 | enterListener: Function = (evt) => {};
102 | backspaceListener: Function = (evt) => {};
103 |
104 | constructor(opts = {}) {
105 | for(const x in opts) {
106 | if (typeof opts[x] === 'function')
107 | typeof opts[x];
108 | this[x] = opts[x];
109 | }
110 | this.eventHandler = (evt: any) => {
111 | keydownEventHandler(evt, this);
112 | }
113 | }
114 |
115 | navigateListNav(next) {
116 | return this.nav(next, 'verticalNavIndex', 'verticalNavClass');
117 | }
118 |
119 | navigateTabNav(next) {
120 | return this.nav(next, 'horizontalNavIndex', 'horizontalNavClass');
121 | }
122 |
123 | nav(next, navIndex, navClass) {
124 | const currentIndex = this[navIndex];
125 | const nav = document.getElementsByClassName(this[navClass]);
126 | if (nav.length === 0) {
127 | return;
128 | }
129 | var move = currentIndex + next;
130 | var cursor:any = nav[move];
131 | if (cursor != undefined) {
132 | cursor.focus();
133 | this[navIndex] = move;
134 | } else {
135 | if (move < 0) {
136 | move = nav.length - 1;
137 | } else if (move >= nav.length) {
138 | move = 0;
139 | }
140 | cursor = nav[move];
141 | cursor.focus();
142 | this[navIndex] = move;
143 | }
144 | cursor.classList.add('focus');
145 | if (currentIndex > -1 && nav.length > 1) {
146 | nav[currentIndex].classList.remove('focus');
147 | }
148 | if (!isElementInViewport(cursor)) {
149 | var marginTop = 0, marginBottom = 0;
150 | if (cursor.parentElement.getAttribute("data-pad-top"))
151 | marginTop = parseFloat(cursor.parentElement.getAttribute("data-pad-top"));
152 | if (cursor.parentElement.getAttribute("data-pad-bottom"))
153 | marginBottom = parseFloat(cursor.parentElement.getAttribute("data-pad-bottom"));
154 | let offsetTop = cursor.offsetTop - ((cursor.parentElement.clientHeight - marginTop - marginBottom) / 2);
155 | if ((cursor.clientHeight / cursor.parentElement.clientHeight) >= 0.7)
156 | offsetTop = cursor.offsetTop;
157 | dispatchScroll(cursor.parentElement, offsetTop);
158 | setTimeout(() => {
159 | if (!isElementInViewport(cursor)) {
160 | dispatchFocus(cursor.parentElement, offsetTop);
161 | }
162 | }, 150);
163 | }
164 | if (['INPUT', 'TEXTAREA'].indexOf(document.activeElement.tagName) > -1) {
165 | if (document.activeElement instanceof HTMLElement) {
166 | document.activeElement.blur();
167 | }
168 | }
169 | const keys = Object.keys(cursor.children);
170 | for (var k in keys) {
171 | if (['INPUT', 'TEXTAREA'].indexOf(cursor.children[k].tagName) > -1) {
172 | setTimeout(() => {
173 | cursor.children[k].focus();
174 | cursor.children[k].selectionStart = cursor.children[k].selectionEnd = (cursor.children[k].value.length || cursor.children[k].value.length);
175 | }, 100);
176 | break;
177 | }
178 | }
179 | }
180 |
181 | attachListener(next:number = 1) {
182 | document.addEventListener('keydown', this.eventHandler);
183 | if (!this.init)
184 | this.init = true;
185 | else
186 | return;
187 | setTimeout(() => {
188 | if (this.verticalNavClass != null)
189 | this.navigateListNav(next);
190 | else if (this.horizontalNavClass != null)
191 | this.navigateTabNav(next);
192 | }, 100);
193 | }
194 |
195 | detachListener() {
196 | document.removeEventListener('keydown', this.eventHandler);
197 | }
198 | }
199 |
200 | const createKaiNavigator = (opts = {}) => {
201 | return new KaiNavigator(opts);
202 | }
203 |
204 | export {
205 | createKaiNavigator,
206 | KaiNavigator
207 | }
208 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "target": "es5",
3 | "extends": "@tsconfig/svelte/tsconfig.json",
4 | "compilerOptions": { "resolveJsonModule": true },
5 | "include": ["src/**/*"],
6 | "exclude": ["node_modules/*", "__sapper__/*", "public/*"]
7 | }
8 |
--------------------------------------------------------------------------------