├── .editorconfig ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .prettierrc ├── eslint.config.mjs ├── license.txt ├── locales ├── index.js ├── locales │ ├── cn.js │ ├── de.js │ └── en.js └── package.json ├── package.json ├── readme.md ├── site ├── index.html ├── index.js ├── package.json ├── public │ └── placeholder.md ├── src │ ├── Demo.svelte │ ├── Main.svelte │ ├── ThemeSelect.svelte │ └── data.js ├── svelte.config.js └── vite.config.js ├── svelte ├── .gitignore ├── cypress.config.js ├── cypress │ ├── e2e │ │ └── demos.cy.js │ ├── fixtures │ │ └── example.json │ └── support │ │ ├── commands.js │ │ └── e2e.js ├── demos │ ├── cases │ │ ├── ActionMenu.svelte │ │ ├── BackendComplex.svelte │ │ ├── BasicInit.svelte │ │ ├── Batches.svelte │ │ ├── ChangesAuto.svelte │ │ ├── ChangesConfirmed.svelte │ │ ├── Comments.svelte │ │ ├── CustomButtons.svelte │ │ ├── Gantt.svelte │ │ ├── Kanban.svelte │ │ ├── Layouts.svelte │ │ ├── Locales.svelte │ │ ├── ModalColumns.svelte │ │ ├── ModalPanel.svelte │ │ ├── MultipleCombos.svelte │ │ ├── NotImplemented.svelte │ │ ├── Overflow.svelte │ │ ├── RequiredFields.svelte │ │ ├── SaveValidatedValues.svelte │ │ ├── Scheduler.svelte │ │ ├── SidebarPanel.svelte │ │ ├── SubPanels.svelte │ │ ├── Tasklist.svelte │ │ ├── ToolbarValues.svelte │ │ └── Validation.svelte │ ├── common │ │ ├── Index.svelte │ │ ├── Link.svelte │ │ ├── ListRoutes.svelte │ │ ├── Router.svelte │ │ └── helpers.js │ ├── custom │ │ ├── gantt │ │ │ └── Links.svelte │ │ ├── kanban │ │ │ ├── PriorityCombo.svelte │ │ │ ├── UserIcon.svelte │ │ │ └── UserMultiselect.svelte │ │ └── scheduler │ │ │ ├── ColorPickerSchema.svelte │ │ │ ├── Combo.svelte │ │ │ ├── DateTimePicker.svelte │ │ │ ├── Multiselect.svelte │ │ │ ├── Uploader.svelte │ │ │ └── templates │ │ │ ├── ComboOption.svelte │ │ │ └── MultiselectOption.svelte │ ├── data.js │ ├── index.js │ └── routes.js ├── index.html ├── license.txt ├── package.json ├── postcss.config.js ├── readme.md ├── src │ ├── components │ │ ├── Columns.svelte │ │ ├── Editor.svelte │ │ ├── Form.svelte │ │ ├── FormEditor.svelte │ │ ├── Helpers.svelte.js │ │ ├── Layout.svelte │ │ ├── Values.svelte │ │ ├── buttons │ │ │ ├── Toolbar.svelte │ │ │ └── buttons.js │ │ └── sections │ │ │ ├── ReadOnly.svelte │ │ │ └── Section.svelte │ ├── editor.js │ ├── en.js │ ├── helpers.js │ ├── index.js │ └── themes │ │ ├── Material.svelte │ │ ├── Willow.svelte │ │ └── WillowDark.svelte ├── svelte.config.js ├── tests │ ├── Index.svelte │ ├── cases │ │ └── LocalData.svelte │ ├── data.js │ ├── index.html │ ├── index.js │ └── routes.js ├── vite.config.js └── whatsnew.md └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 4 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | 4 | *.zip 5 | .Ds_store 6 | *.tgz 7 | *.log 8 | .vscode 9 | .idea 10 | .env.local -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | yarn run lint-staged 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "semi": true, 4 | "singleQuote": false, 5 | "quoteProps": "as-needed", 6 | "trailingComma": "es5", 7 | "bracketSpacing": true, 8 | "arrowParens": "avoid", 9 | "svelteSortOrder": "options-scripts-markup-styles", 10 | "plugins": [ 11 | "prettier-plugin-svelte" 12 | ], 13 | "overrides": [ 14 | { 15 | "files": "*.svelte", 16 | "options": { 17 | "parser": "svelte" 18 | } 19 | }, 20 | { 21 | "files": "*.ts", 22 | "options": { 23 | "parser": "typescript" 24 | } 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import eslintConfigPrettier from "eslint-config-prettier"; 2 | import eslintPluginSvelte from 'eslint-plugin-svelte'; 3 | import * as svelteParser from 'svelte-eslint-parser'; 4 | import tsLint from "typescript-eslint"; 5 | import jsLint from "@eslint/js"; 6 | import vitest from "eslint-plugin-vitest"; 7 | import globals from "globals"; 8 | 9 | export default [{ 10 | ignores: ["node_modules/", "dist/", "build/", "coverage/", "public/", "svelte/vite.config.js"], 11 | }, 12 | jsLint.configs.recommended, 13 | ...tsLint.configs.recommended, 14 | ...eslintPluginSvelte.configs['flat/recommended'], 15 | eslintConfigPrettier, 16 | vitest.configs.recommended, 17 | ...eslintPluginSvelte.configs["flat/prettier"], 18 | { 19 | rules: { 20 | "no-bitwise": ["error"], 21 | // there is a misconception between esLint and svelte compiler 22 | // rules that are necessary for compiler, throw errors in esLint 23 | // need to be revised with next version of toolchain 24 | "svelte/no-unused-svelte-ignore": "off", 25 | "svelte/valid-compile": "off", 26 | // Ignore unused vars starting with _ 27 | // "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], 28 | // // Turn off the need for explicit function return types 29 | // "@typescript-eslint/explicit-function-return-type": "off", 30 | // // Warn when "any" type is used 31 | "@typescript-eslint/no-explicit-any": "off", 32 | // // Warn on @ts-ignore comments 33 | // "@typescript-eslint/ban-ts-comment": "warn", 34 | // // Public methods should have return types 35 | // "@typescript-eslint/explicit-module-boundary-types": "error", 36 | }, 37 | }, 38 | { 39 | languageOptions: { 40 | globals: { ...globals.browser, ...globals.es2022 }, 41 | ecmaVersion: 2022, 42 | sourceType: "module", 43 | parserOptions: { 44 | extraFileExtensions: [".svelte"], 45 | warnOnUnsupportedTypeScriptVersion: false, 46 | tsconfigRootDir: import.meta.dirname, 47 | }, 48 | }, 49 | 50 | }, 51 | { 52 | 53 | files: ["**/*.svelte"], 54 | rules: { 55 | "@typescript-eslint/no-unused-expressions": "off" 56 | } 57 | }, 58 | { 59 | // temporarily ignore cypress folder 60 | ignores: [ 61 | "**/cypress/" 62 | ] 63 | }, 64 | { 65 | 66 | files: ["**/*.svelte.js"], 67 | languageOptions: { 68 | parser: svelteParser, 69 | } 70 | } 71 | ]; 72 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 XB Software Sp. z o.o. 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. -------------------------------------------------------------------------------- /locales/index.js: -------------------------------------------------------------------------------- 1 | export { default as en } from "./locales/en"; 2 | export { default as cn } from "./locales/cn"; 3 | export { default as de } from "./locales/de"; 4 | -------------------------------------------------------------------------------- /locales/locales/cn.js: -------------------------------------------------------------------------------- 1 | export default { 2 | editor: { 3 | "This field is required": "该字段为必填字段", 4 | "Invalid value": "无效值", 5 | Yes: "是", 6 | No: "不", 7 | Save: "节省", 8 | Cancel: "取消", 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /locales/locales/de.js: -------------------------------------------------------------------------------- 1 | export default { 2 | editor: { 3 | "This field is required": "Dieses Feld ist erforderlich", 4 | "Invalid value": "Ungültiger Wert", 5 | Yes: "Ja", 6 | No: "Nein", 7 | Save: "Speichern", 8 | Cancel: "Abbrechen", 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /locales/locales/en.js: -------------------------------------------------------------------------------- 1 | export default { 2 | editor: { 3 | "This field is required": "This field is required", 4 | "Invalid value": "Invalid value", 5 | Yes: "Yes", 6 | No: "No", 7 | Save: "Save", 8 | Cancel: "Cancel", 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /locales/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wx-editor-locales", 3 | "version": "2.1.2", 4 | "description": "Locales for WX Editor widget", 5 | "type": "module", 6 | "main": "index.js", 7 | "scripts": { 8 | "build": "true", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "keywords": [ 12 | "wx", 13 | "editor", 14 | "locales" 15 | ], 16 | "author": "", 17 | "license": "MIT" 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "wx-editor", 4 | "workspaces": [ 5 | "svelte", 6 | "locales", 7 | "site" 8 | ], 9 | "scripts": { 10 | "build:deps": "true", 11 | "build:site": "cd site && yarn build", 12 | "build:tests": "cd svelte && yarn build:tests", 13 | "build": "cd svelte && yarn build", 14 | "lint": "yarn eslint ./svelte/src ./svelte/demos ./locales", 15 | "prepare": "husky", 16 | "start:demos": "cd svelte && yarn start", 17 | "start:site": "cd site && yarn start", 18 | "start:tests": "cd svelte && yarn start:tests", 19 | "start": "cd svelte && yarn start", 20 | "test:cypress": "cd svelte && yarn test:cypress", 21 | "test": "true" 22 | }, 23 | "devDependencies": { 24 | "@sveltejs/vite-plugin-svelte": "4.0.0", 25 | "@vitest/coverage-v8": "1.6.0", 26 | "wx-vite-tools": "1.0.5", 27 | "autoprefixer": "10.4.20", 28 | "cypress": "13.6.4", 29 | "eslint": "9.14.0", 30 | "eslint-config-prettier": "9.1.0", 31 | "eslint-plugin-cypress": "4.1.0", 32 | "eslint-plugin-svelte": "2.46.0", 33 | "eslint-plugin-vitest": "0.5.4", 34 | "husky": "9.1.6", 35 | "lint-staged": "15.2.10", 36 | "npm-run-all": "4.1.5", 37 | "postcss": "8.4.47", 38 | "prettier": "3.3.3", 39 | "prettier-plugin-svelte": "3.2.7", 40 | "rollup-plugin-visualizer": "5.12.0", 41 | "shx": "0.3.4", 42 | "svelte": "5.1.9", 43 | "svelte-spa-router": "4.0.1", 44 | "typescript-eslint": "8.13.0", 45 | "typescript": "5.6.3", 46 | "vite-plugin-conditional-compile": "1.4.5", 47 | "vite-plugin-dts": "3.7.2", 48 | "vite": "5.4.10", 49 | "vitest": "1.5.0" 50 | }, 51 | "lint-staged": { 52 | "*.{ts,js,svelte}": [ 53 | "eslint --fix --no-warn-ignored", 54 | "prettier --write" 55 | ], 56 | "*.{css,md,json}": [ 57 | "prettier --write" 58 | ] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # SVAR Svelte Editor 4 | 5 | [![npm](https://img.shields.io/npm/v/wx-svelte-editor.svg)](https://www.npmjs.com/package/wx-svelte-editor) 6 | [![License](https://img.shields.io/github/license/svar-widgets/editor)](https://github.com/svar-widgets/editor/blob/main/license.txt) 7 | [![npm downloads](https://img.shields.io/npm/dm/wx-svelte-editor.svg)](https://www.npmjs.com/package/wx-svelte-editor) 8 | 9 |
10 | 11 |
12 | 13 | [Documentation](https://docs.svar.dev/svelte/editor/) • [Demos](https://docs.svar.dev/svelte/editor/samples/#/base/willow) 14 | 15 |
16 | 17 | An intuitive Svelte component for creating content editing forms to manage data within UI elements on a page. You can use it for editing structured data like table rows, informational cards, blocks with text, etc. 18 | 19 |
20 | 21 | SVAR Core - Svelte UI Library 22 | 23 |
24 | 25 | ### :sparkles: Key features: 26 | 27 | - **Flexible display options**: add the editor as a modal popup, inline form or as a seamless sidebar for convenient access. 28 | - **Multiple input types**: Use various input fields like text inputs, checkboxes, date pickers, sliders, and more controls from [SVAR Core](https://github.com/svar-widgets/core) library. 29 | - **Built-in validation**: Includes basic validation for required fields and supports custom validation rules for advanced scenarios. 30 | - **Flexible save options**: Choose between manual saves, auto-save, or custom saving logic adjusted to your needs. 31 | - **Compact layout**: Organize forms into expandable sections or a 2-column layout for efficient use of screen space. 32 | 33 | ### :hammer_and_wrench: How to Use 34 | 35 | To use the Editor widget, simply import the package and include the component in your Svelte file: 36 | 37 | ```svelte 38 | 43 | 44 | 45 | ``` 46 | For more details, visit the [getting started guide](https://docs.svar.dev/svelte/editor/getting_started/). 47 | 48 | ### :wrench: How to Modify 49 | 50 | Typically, you don't need to modify the code. However, if you wish to do so, follow these steps: 51 | 52 | 1. Run `yarn` to install dependencies. Note that this project is a monorepo using `yarn` workspaces, so npm will not work 53 | 2. Start the project in development mode with `yarn start` 54 | 55 | ### :white_check_mark: Run Tests 56 | 57 | To run the test: 58 | 59 | 1. Start the test examples with: 60 | ```sh 61 | yarn start:tests 62 | ``` 63 | 2. In a separate console, run the end-to-end tests with: 64 | ```sh 65 | yarn test:cypress 66 | ``` 67 | -------------------------------------------------------------------------------- /site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Svelte Widgets 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /site/index.js: -------------------------------------------------------------------------------- 1 | import Demo from "./src/Demo.svelte"; 2 | import { mount } from "svelte"; 3 | 4 | mount(Demo, { 5 | target: document.querySelector("#wx_demo_area") || document.body, 6 | props: { 7 | themeSelect: true, 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /site/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wx-site-svelte-editor", 3 | "version": "2.1.2", 4 | "type": "module", 5 | "scripts": { 6 | "build": "vite build", 7 | "lint": "yarn eslint ./src", 8 | "start": "yarn vite --open" 9 | }, 10 | "license": "MIT", 11 | "dependencies": { 12 | "wx-svelte-editor": "2.1.2", 13 | "wx-svelte-core": "2.0.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /site/public/placeholder.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svar-widgets/editor/14a818bba18addf54ce128482bde226bc1f93d95/site/public/placeholder.md -------------------------------------------------------------------------------- /site/src/Demo.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | {#if themeSelect} 25 |
26 |
27 |
28 | Theme 29 | 30 |
31 |
32 |
33 |
34 |
35 |
36 | {:else} 37 |
38 | {/if} 39 | 40 | 41 |
42 | 43 | 76 | -------------------------------------------------------------------------------- /site/src/Main.svelte: -------------------------------------------------------------------------------- 1 | 35 | 36 |
37 | {#each data as card} 38 | 39 | 40 |
(selected = card)} 43 | class:active={selected?.id === card.id} 44 | > 45 |
49 |
50 |

{card.title}

51 |
52 | Assigned to: 53 |
58 | {users[card.assigned - 1].initials} 59 |
60 | {users[card.assigned - 1].name} 61 |
62 |

{card.description || ""}

63 |
64 |

65 | Priority: 66 | {priority[card.priority - 1].name} 67 |

68 |

69 | Progress: 70 | {card.progress + "%"} 71 |

72 |

73 | Due date: 74 | {format(card.start_date)} 75 |

76 |
77 |
78 |
79 | {/each} 80 |
81 | 82 | {#if selected} 83 | 91 | {/if} 92 | 93 | 162 | -------------------------------------------------------------------------------- /site/src/ThemeSelect.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 |
24 | 25 | {#snippet children(option)} 26 |
27 |
28 |
29 |
30 |
31 | {option.label} 32 |
33 | 51 | {/snippet} 52 |
53 |
54 | -------------------------------------------------------------------------------- /site/src/data.js: -------------------------------------------------------------------------------- 1 | export function getData() { 2 | const users = [ 3 | { 4 | id: 1, 5 | label: "Alice Johnson", 6 | name: "Alice Johnson", 7 | color: "#FF5387", 8 | initials: "AJ", 9 | }, 10 | { 11 | id: 2, 12 | label: "Carol Martinez", 13 | name: "Carol Martinez", 14 | color: "#00D19A", 15 | initials: "CM", 16 | }, 17 | { 18 | id: 3, 19 | label: "Bob Smith", 20 | name: "Bob Smith", 21 | color: "#37A9EF", 22 | initials: "BS", 23 | }, 24 | { 25 | id: 4, 26 | label: "David Lee", 27 | name: "David Lee", 28 | color: "#8A62DF", 29 | initials: "DL", 30 | }, 31 | ]; 32 | 33 | const priority = [ 34 | { id: 1, label: "High", color: "#DF282F" }, 35 | { id: 2, label: "Medium", color: "#FFC975" }, 36 | { id: 3, label: "Low", color: "#65D3B3" }, 37 | ]; 38 | 39 | const items = [ 40 | { 41 | key: "title", 42 | comp: "text", 43 | label: "Title", 44 | column: "left", 45 | required: true, 46 | }, 47 | { 48 | key: "description", 49 | comp: "textarea", 50 | label: "Description", 51 | column: "left", 52 | required: true, 53 | }, 54 | { 55 | comp: "select", 56 | label: "Priority", 57 | key: "priority", 58 | options: priority, 59 | column: "left", 60 | }, 61 | { 62 | comp: "slider", 63 | key: "progress", 64 | column: "left", 65 | labelTemplate: value => `Progress ${value}%`, 66 | }, 67 | { 68 | comp: "date", 69 | key: "start_date", 70 | label: "Due date", 71 | column: "left", 72 | required: true, 73 | }, 74 | { 75 | comp: "select", 76 | key: "assigned", 77 | label: "Assigned to", 78 | options: users, 79 | column: "left", 80 | }, 81 | { 82 | comp: "comments", 83 | key: "comments", 84 | label: "Comments", 85 | users, 86 | activeUser: 1, 87 | }, 88 | ]; 89 | 90 | const data = [ 91 | { 92 | id: 1, 93 | title: "List of New Features", 94 | priority: 2, 95 | description: 96 | "Develop detailed wireframes for the upcoming features of the web app. This includes sketching out the user interface for new functionalities, ensuring they are consistent with the existing design language, and preparing for user feedback sessions. The wireframes should be clear and detailed, providing a solid foundation for the subsequent design and development phases.", 97 | start_date: new Date(2024, 7, 5), 98 | progress: 20, 99 | assigned: 1, 100 | comments: [ 101 | { 102 | id: 1, 103 | user: 1, 104 | cardId: 1, 105 | content: 106 | "I've started sketching the initial wireframes and will share the drafts by the end of the week.", 107 | date: new Date(), 108 | }, 109 | { 110 | id: 2, 111 | user: 2, 112 | cardId: 1, 113 | content: 114 | "Great, Alice! Can you ensure the new dashboard aligns with our design?", 115 | date: new Date(), 116 | }, 117 | { 118 | id: 3, 119 | user: 1, 120 | cardId: 1, 121 | content: 122 | "Absolutely, I’m cross-referencing the design guide as we speak.", 123 | date: new Date(), 124 | }, 125 | // { 126 | // id: 4, 127 | // user: 3, 128 | // cardId: 1, 129 | // content:"Make sure to leave some space for potential additional widgets. We might need to add more features later.", 130 | // date: new Date(), 131 | // }, 132 | ], 133 | }, 134 | { 135 | id: 2, 136 | title: "Implement User Authentication", 137 | priority: 1, 138 | description: 139 | "Develop and integrate a secure user authentication system using OAuth 2.0. The system should support user registration, login, password recovery, and account management. Ensure it includes robust security measures to protect user data, handles various authentication scenarios, and provides a seamless user experience.", 140 | start_date: new Date(2024, 7, 10), 141 | progress: 85, 142 | assigned: 3, 143 | comments: [ 144 | { 145 | id: 5, 146 | user: 3, 147 | cardId: 2, 148 | content: 149 | "I'll start by setting up the OAuth 2.0 framework and then integrate the registration and login functionality.", 150 | date: new Date(), 151 | }, 152 | { 153 | id: 6, 154 | user: 1, 155 | cardId: 2, 156 | content: 157 | "Do you need any UI components for the login and registration forms?", 158 | date: new Date(), 159 | }, 160 | { 161 | id: 7, 162 | user: 3, 163 | cardId: 2, 164 | content: 165 | "Yes, it would be great to have a consistent design for those.", 166 | date: new Date(), 167 | }, 168 | // { 169 | // id:8, 170 | // user:4, 171 | // cardId: 2, 172 | // content:"For password recovery, we should consider both email and SMS options. What do you think?", 173 | // date:new Date() 174 | // }, 175 | // { 176 | // id:9, 177 | // user:3, 178 | // cardId: 2, 179 | // content:"Good idea, David. I’ll plan for both. Let’s discuss the details in our next meeting.", 180 | // date:new Date() 181 | // } 182 | ], 183 | }, 184 | { 185 | id: 3, 186 | title: "Conduct Usability Testing", 187 | priority: 3, 188 | description: 189 | "Plan and execute usability testing sessions with target users to identify usability issues and gather feedback. Prepare testing scripts, recruit participants, and facilitate testing sessions. Analyze the results to uncover insights into user behavior and pain points. Provide actionable recommendations to improve the user experience based on the collected data.", 190 | start_date: new Date(2024, 7, 8), 191 | progress: 30, 192 | assigned: 2, 193 | comments: [ 194 | { 195 | id: 10, 196 | user: 3, 197 | cardId: 3, 198 | content: 199 | "I've drafted the testing scripts. Please review and provide feedback.", 200 | date: new Date(), 201 | }, 202 | { 203 | id: 11, 204 | user: 1, 205 | cardId: 3, 206 | content: 207 | "I’ll take a look today. Any specific areas we should focus on?", 208 | date: new Date(), 209 | }, 210 | { 211 | id: 12, 212 | user: 3, 213 | cardId: 3, 214 | content: 215 | "Mainly navigation flow and form usability. We need to ensure a smooth user experience.", 216 | date: new Date(), 217 | }, 218 | // { 219 | // id: 13, 220 | // user: 2, 221 | // cardId: 3, 222 | // content: "I've recruited participants for the testing. Sessions will be conducted over the next three days.", 223 | // date: new Date(), 224 | // }, 225 | // { 226 | // id: 14, 227 | // user: 4, 228 | // cardId: 3, 229 | // content: "After the testing, let’s meet to discuss the findings and prioritize the issues.", 230 | // date: new Date(), 231 | // }, 232 | ], 233 | }, 234 | { 235 | id: 4, 236 | title: "Optimize Database Queries", 237 | priority: 2, 238 | description: 239 | "Review and optimize database queries to enhance performance. This involves identifying and analyzing slow-performing queries, adding appropriate indexing, and refactoring inefficient queries. The goal is to reduce load times and improve data retrieval efficiency. Document changes and ensure that optimizations do not compromise data integrity or application functionality.", 240 | start_date: new Date(2024, 7, 12), 241 | progress: 15, 242 | assigned: 4, 243 | comments: [ 244 | { 245 | id: 15, 246 | user: 4, 247 | cardId: 4, 248 | content: 249 | "I've identified a few slow queries that need immediate attention.", 250 | date: new Date(), 251 | }, 252 | { 253 | id: 16, 254 | user: 1, 255 | cardId: 2, 256 | content: 257 | "David, can you share the list of queries you’re focusing on? I might have some insights.", 258 | date: new Date(), 259 | }, 260 | { 261 | id: 17, 262 | user: 4, 263 | cardId: 4, 264 | content: 265 | "Sure, Bob. I’ll upload the list to our shared folder.", 266 | date: new Date(), 267 | }, 268 | // { 269 | // id: 18, 270 | // user: 1, 271 | // cardId: 4, 272 | // content: "Once you’re done, can you also look at the queries related to the reporting module? We’ve had some delays there.", 273 | // date: new Date(), 274 | // }, 275 | // { 276 | // id: 19, 277 | // user: 4, 278 | // cardId: 4, 279 | // content: "I’ll include those in my next round of optimizations. Thanks for the heads-up, Alice.", 280 | // date: new Date(), 281 | // }, 282 | ], 283 | }, 284 | ]; 285 | 286 | return { items, data, users, priority }; 287 | } 288 | -------------------------------------------------------------------------------- /site/svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; 2 | 3 | export default { 4 | // Consult https://svelte.dev/docs#compile-time-svelte-preprocess 5 | // for more information about preprocessors 6 | preprocess: vitePreprocess(), 7 | }; 8 | -------------------------------------------------------------------------------- /site/vite.config.js: -------------------------------------------------------------------------------- 1 | import { resolve } from "path"; 2 | import { svelte } from "@sveltejs/vite-plugin-svelte"; 3 | 4 | export default () => { 5 | let build, 6 | publicDir = resolve(__dirname, "public"), 7 | server = {}, 8 | base = "", 9 | plugins = [svelte({})]; 10 | 11 | build = { 12 | rollupOptions: { 13 | input: { index: resolve(__dirname, "index.html") }, 14 | }, 15 | }; 16 | 17 | return { 18 | base, 19 | build, 20 | publicDir, 21 | resolve: { dedupe: ["svelte"] }, 22 | plugins, 23 | server, 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /svelte/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | cypress/screenshots 3 | cypress/videos 4 | -------------------------------------------------------------------------------- /svelte/cypress.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "cypress"; 2 | 3 | export default defineConfig({ 4 | video: false, 5 | e2e: { 6 | setupNodeEvents() {}, 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /svelte/cypress/e2e/demos.cy.js: -------------------------------------------------------------------------------- 1 | const cases = [ 2 | "/base/:skin", 3 | "/base/:skin", 4 | "/modal/:skin", 5 | "/columns/:skin", 6 | "/sidebar/:skin", 7 | "/changes-auto/:skin", 8 | "/changes-confirmed/:skin", 9 | "/subpanels/:skin", 10 | "/batches/:skin", 11 | "/actions/:skin", 12 | "/buttons/:skin", 13 | "/comments/:skin", 14 | "/taslkist/:skin", 15 | ]; 16 | 17 | const skins = ["material", "willow", "willow-dark"]; 18 | const links = []; 19 | 20 | cases.forEach(w => { 21 | skins.forEach(s => { 22 | links.push(w.replace(":skin", s)); 23 | }); 24 | }); 25 | 26 | context("Basic functionality", () => { 27 | it("widget", () => { 28 | links.forEach(w => { 29 | cy.visit(`/index.html#${w}`); 30 | cy.shot(w, { area: ".content" }); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /svelte/cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /svelte/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | Cypress.Commands.add("shot", (...args) => { 2 | // eslint-disable-next-line cypress/no-unnecessary-waiting 3 | cy.wait(100); 4 | 5 | const name = args.filter(a => typeof a !== "object").join("-"); 6 | const conf = 7 | typeof args[args.length - 1] === "object" ? args[args.length - 1] : {}; 8 | const sconf = { ...conf, overwrite: true }; 9 | 10 | if (conf.area) cy.get(conf.area).screenshot(name, sconf); 11 | else cy.screenshot(name, sconf); 12 | }); 13 | -------------------------------------------------------------------------------- /svelte/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import "./commands"; 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /svelte/demos/cases/ActionMenu.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 |
{message}
15 |
16 |
17 |

Top Bar, Right

18 |
19 | 55 |
56 |
57 |
58 | 59 | 86 | -------------------------------------------------------------------------------- /svelte/demos/cases/BackendComplex.svelte: -------------------------------------------------------------------------------- 1 | 56 | 57 |
{message}
58 | 59 |
60 | {#each listData as data} 61 | 62 | 63 |
(selected = data)} 66 | class:active={selected?.id === data.id} 67 | > 68 |

{data.label}

69 |

{data.description || ""}

70 |
71 | {/each} 72 |
73 | 74 | {#if selected} 75 | 76 | {/if} 77 | 78 | 102 | -------------------------------------------------------------------------------- /svelte/demos/cases/BasicInit.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 |
10 |

Normal

11 |
12 | 13 |
14 |
15 |
16 |

Readonly

17 |
18 | 19 |
20 |
21 |
22 | 23 | 39 | -------------------------------------------------------------------------------- /svelte/demos/cases/Batches.svelte: -------------------------------------------------------------------------------- 1 | 25 | 26 |
27 |
28 |

Segmented

29 |
30 | 31 |
32 | 33 |
34 |
35 |
36 |
37 |
38 |

Tabbar

39 |
40 | 41 |
42 | 43 |
44 |
45 |
46 |
47 |
48 |

Toolbar

49 |
50 | 51 | { 59 | if (item.key === "batch") activeBatch = value; 60 | }} 61 | /> 62 | 63 |
64 |
65 |
66 | 67 | 86 | -------------------------------------------------------------------------------- /svelte/demos/cases/ChangesAuto.svelte: -------------------------------------------------------------------------------- 1 | 37 | 38 |
{message}
39 | 40 |
41 | {#each listData as data (data.id)} 42 | 43 | 44 |
(selected = data)} 47 | class:active={selected?.id === data.id} 48 | > 49 |

{data.label}

50 |

{data.description || ""}

51 | starts on: {data.start_date?.toLocaleDateString() || ""} 52 |
53 | {/each} 54 |
55 | 56 | {#if selected} 57 | 65 | {/if} 66 | 67 | 91 | -------------------------------------------------------------------------------- /svelte/demos/cases/ChangesConfirmed.svelte: -------------------------------------------------------------------------------- 1 | 37 | 38 |
{message}
39 | 40 |
41 | {#each listData as data} 42 | 43 | 44 |
(selected = data)} 47 | class:active={selected?.id === data.id} 48 | > 49 |

{data.label}

50 |

{data.description || ""}

51 | starts on: {data.start_date?.toLocaleDateString() || ""} 52 |
53 | {/each} 54 |
55 | 56 | {#if selected} 57 | 65 | {/if} 66 | 67 | 91 | -------------------------------------------------------------------------------- /svelte/demos/cases/Comments.svelte: -------------------------------------------------------------------------------- 1 | 42 | 43 |
{message}
44 | 45 |
46 | {#each listData as data} 47 | 48 | 49 |
select(data.id)} 52 | class:active={selected?.id === data.id} 53 | > 54 |

{data.label}

55 |

{data.description || ""}

56 | starts on: {data.start_date.toLocaleDateString()} 57 |
58 | {/each} 59 |
60 | 61 | {#if selected} 62 | 71 | {/if} 72 | 73 | 97 | -------------------------------------------------------------------------------- /svelte/demos/cases/CustomButtons.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 |
{message}
15 |
16 |
17 |

None

18 |
19 | 20 |
21 |
22 |
23 |

Top Bar

24 |
25 | 26 |
27 |
28 |
29 |

Top Bar, Right

30 |
31 | 42 |
43 |
44 |
45 |

Top Bar, Left

46 |
47 | 62 |
63 |
64 |
65 |

Top Bar, Mixed

66 |
67 | 85 |
86 |
87 |
88 |

Bottom Bar

89 |
90 | 106 |
107 |
108 |
109 | 110 | 134 | -------------------------------------------------------------------------------- /svelte/demos/cases/Gantt.svelte: -------------------------------------------------------------------------------- 1 | 169 | 170 |

Imitation of gantt project progress

171 | 172 |
173 | {#each tasks as data} 174 | 175 | 176 |
selectActive(data)} 179 | class:active={activeTask?.id === data.id} 180 | > 181 |
{data.text}
182 |
183 | {/each} 184 |
185 | 186 | {#if activeTask} 187 | 217 | {/if} 218 | 219 | 240 | -------------------------------------------------------------------------------- /svelte/demos/cases/Kanban.svelte: -------------------------------------------------------------------------------- 1 | 134 | 135 |

Imitation of kanban cards

136 | 137 |
138 | {#each cards as data} 139 | 140 | 141 |
(activeCard = data)} 144 | class:active={activeCard?.id === data.id} 145 | > 146 |
{data.label}
147 |
148 | {/each} 149 |
150 | 151 | {#if activeCard} 152 | 170 | {/if} 171 | 172 | 192 | -------------------------------------------------------------------------------- /svelte/demos/cases/Layouts.svelte: -------------------------------------------------------------------------------- 1 | 119 | 120 |
121 |
122 |
131 |
132 |
140 | 141 |
142 |
150 | 151 |
152 |
160 |
161 | 162 | {#if visible} 163 | (visible = false)} 167 | /> 168 | {/if} 169 | 170 | 180 | -------------------------------------------------------------------------------- /svelte/demos/cases/Locales.svelte: -------------------------------------------------------------------------------- 1 | 52 | 53 |
54 | 55 |
74 |
75 | {#key lang} 76 | 77 | 78 | {#if visible} 79 | 86 | {/if} 87 | 88 | {/key} 89 |
90 | 91 | 101 | -------------------------------------------------------------------------------- /svelte/demos/cases/ModalColumns.svelte: -------------------------------------------------------------------------------- 1 | 21 | 22 |
{message}
23 | 24 |
25 |
26 |
39 |
40 |
53 |
54 |
66 |
67 | 68 | {#if visible} 69 | 78 | {/if} 79 | 80 | 97 | -------------------------------------------------------------------------------- /svelte/demos/cases/ModalPanel.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 |
{message}
22 | 23 |
24 |
25 |
36 |
37 |
48 |
49 |
59 |
60 | 61 | {#if visible} 62 | 70 | {/if} 71 | 72 | 89 | -------------------------------------------------------------------------------- /svelte/demos/cases/MultipleCombos.svelte: -------------------------------------------------------------------------------- 1 | 76 | 77 |
78 |

Changing the country updates list of options in the cities combo

79 |
80 | 81 |
82 |
83 | 84 | 96 | -------------------------------------------------------------------------------- /svelte/demos/cases/NotImplemented.svelte: -------------------------------------------------------------------------------- 1 |
2 |

Not implemented yet

3 |
4 | 5 | 15 | -------------------------------------------------------------------------------- /svelte/demos/cases/Overflow.svelte: -------------------------------------------------------------------------------- 1 | 94 | 95 |
96 |
97 |
106 |
107 |
115 | 116 |
117 |
125 | 126 |
127 |
135 | 136 |
137 |
145 | 146 |
147 |
155 |
156 | 157 |

Inline Editor example

158 |
159 | 160 |
161 | 162 | {#if visible} 163 | (visible = false)} 167 | /> 168 | {/if} 169 | 170 | 192 | -------------------------------------------------------------------------------- /svelte/demos/cases/RequiredFields.svelte: -------------------------------------------------------------------------------- 1 | 61 | 62 |
63 |

Editor with required fields

64 |
65 | 81 |
82 |
83 | 84 | 92 | -------------------------------------------------------------------------------- /svelte/demos/cases/SaveValidatedValues.svelte: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 |
(visible = true)} class:active={visible}> 27 | {#each Object.values(values) as value} 28 | {value} 29 | {/each} 30 |
31 | 32 | {#if visible} 33 | 41 | {/if} 42 | 43 | 60 | -------------------------------------------------------------------------------- /svelte/demos/cases/Scheduler.svelte: -------------------------------------------------------------------------------- 1 | 227 | 228 |

Imitation of scheduler events

229 | 230 |
231 | {#each events as data} 232 | 233 | 234 |
selectActive(data)} 237 | class:active={activeEvent?.id === data.id} 238 | > 239 |
{data.text}
240 |
241 | {/each} 242 |
243 | 244 | {#if activeEvent} 245 | 275 | {/if} 276 | 277 | 298 | -------------------------------------------------------------------------------- /svelte/demos/cases/SidebarPanel.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 |
{message}
22 | 23 |
24 |
25 |
37 |
38 |
50 |
51 |
62 |
63 | 64 | {#if visible} 65 | 73 | {/if} 74 | 75 | 92 | -------------------------------------------------------------------------------- /svelte/demos/cases/SubPanels.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 |
20 |
21 |

Normal sections

22 | 23 |
24 |
25 |

Exclusive mode

26 | 27 |
28 |
29 |

Normal sections

30 | 31 |
32 |
33 |

Accordion mode

34 | 35 |
36 |
37 | 38 | 58 | -------------------------------------------------------------------------------- /svelte/demos/cases/Tasklist.svelte: -------------------------------------------------------------------------------- 1 | 39 | 40 |
{message}
41 | 42 |
43 | {#each listData as data} 44 | 45 | 46 |
select(data.id)} 49 | class:active={selected?.id === data.id} 50 | > 51 |

{data.label}

52 |

{data.description || ""}

53 | starts on: {data.start_date.toLocaleDateString()} 54 |
55 | {/each} 56 |
57 | 58 | {#if selected} 59 | 67 | {/if} 68 | 69 | 93 | -------------------------------------------------------------------------------- /svelte/demos/cases/ToolbarValues.svelte: -------------------------------------------------------------------------------- 1 | 42 | 43 |
{message}
44 | 45 |
46 | {#each listData as data} 47 | 48 | 49 |
select(data.id)} 52 | class:active={selected?.id === data.id} 53 | > 54 |

{data.label}

55 |

{data.description || ""}

56 | {#if data.state} 57 |
State: {data.state}
58 | {/if} 59 |
60 | {/each} 61 |
62 | 63 | {#if selected} 64 | 79 | {/if} 80 | 81 | 105 | -------------------------------------------------------------------------------- /svelte/demos/cases/Validation.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 |

Editor with validation and error messages

10 |
11 | 12 |
13 |
14 | 15 | 22 | -------------------------------------------------------------------------------- /svelte/demos/common/Index.svelte: -------------------------------------------------------------------------------- 1 | 74 | 75 | 76 | 77 | 78 | 79 |
80 | 81 | 82 | 83 | 120 | 121 |
(show = false)} 127 | > 128 | 129 | 130 | 131 | 132 | 133 |
134 |
135 | 136 | 323 | -------------------------------------------------------------------------------- /svelte/demos/common/Link.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | {data[1]} 10 | 11 | 12 | 33 | -------------------------------------------------------------------------------- /svelte/demos/common/ListRoutes.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
 7 | {#each routes as route}
 8 | 			{'"' + route + '",\n'}
 9 | 		{/each}
10 | 
12 | 13 | 18 | -------------------------------------------------------------------------------- /svelte/demos/common/Router.svelte: -------------------------------------------------------------------------------- 1 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /svelte/demos/common/helpers.js: -------------------------------------------------------------------------------- 1 | import { push } from "svelte-spa-router"; 2 | import { wrap } from "svelte-spa-router/wrap"; 3 | import { links as raw } from "../routes"; 4 | 5 | const routes = { 6 | "/": wrap({ 7 | component: {}, 8 | conditions: () => { 9 | push("/base/willow"); 10 | return false; 11 | }, 12 | }), 13 | }; 14 | 15 | function getRoutes(skinSettings, cb) { 16 | raw.forEach( 17 | a => 18 | (routes[a[0]] = wrap({ 19 | component: a[2], 20 | userData: a, 21 | props: { ...skinSettings }, 22 | conditions: x => { 23 | cb(x.location); 24 | return true; 25 | }, 26 | })) 27 | ); 28 | 29 | return routes; 30 | } 31 | 32 | function getLinks() { 33 | return raw; 34 | } 35 | 36 | export { push, getRoutes, getLinks }; 37 | -------------------------------------------------------------------------------- /svelte/demos/custom/gantt/Links.svelte: -------------------------------------------------------------------------------- 1 | 24 | 25 | {#if value} 26 | {#each value as links} 27 | {#if links.data.length} 28 | 70 | {/if} 71 | {/each} 72 | {/if} 73 | 74 | 114 | -------------------------------------------------------------------------------- /svelte/demos/custom/kanban/PriorityCombo.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | {#snippet children({ option })} 10 |
11 | {#if option.color} 12 |
13 | {:else if option.avatar || option.avatarColor} 14 | 15 | {/if} 16 | {option.label} 17 |
18 | {/snippet} 19 |
20 | 21 | 33 | -------------------------------------------------------------------------------- /svelte/demos/custom/kanban/UserIcon.svelte: -------------------------------------------------------------------------------- 1 | 23 | 24 |
25 | {#if data.avatar} 26 | {data.label} 27 | {:else if noTransform}{data.label}{:else}{firstLetters}{/if} 28 |
29 | 30 | 66 | -------------------------------------------------------------------------------- /svelte/demos/custom/kanban/UserMultiselect.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | {#snippet children({ option })} 11 |
12 | 13 | 14 | {option.label} 15 | 16 |
17 | {/snippet} 18 |
19 | 20 | 30 | -------------------------------------------------------------------------------- /svelte/demos/custom/scheduler/ColorPickerSchema.svelte: -------------------------------------------------------------------------------- 1 | 27 | 28 | 34 | -------------------------------------------------------------------------------- /svelte/demos/custom/scheduler/Combo.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | {#snippet children({ option })} 9 | {@const Component = template} 10 | 11 | {/snippet} 12 | 13 | -------------------------------------------------------------------------------- /svelte/demos/custom/scheduler/DateTimePicker.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 |
9 | 10 |
11 | {#if time} 12 | 13 | {/if} 14 |
15 | 16 | 33 | -------------------------------------------------------------------------------- /svelte/demos/custom/scheduler/Multiselect.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | {#snippet children({ option })} 9 | {@const Component = template} 10 |
11 | 12 |
13 | {/snippet} 14 |
15 | -------------------------------------------------------------------------------- /svelte/demos/custom/scheduler/Uploader.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 |
13 | 14 | 15 |
16 | 17 | 39 | -------------------------------------------------------------------------------- /svelte/demos/custom/scheduler/templates/ComboOption.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | {option.label} 7 | {#if option.color} 8 |
12 | {/if} 13 |
14 | 15 | 27 | -------------------------------------------------------------------------------- /svelte/demos/custom/scheduler/templates/MultiselectOption.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 7 | {option.label} 8 |
9 | 10 | 23 | -------------------------------------------------------------------------------- /svelte/demos/data.js: -------------------------------------------------------------------------------- 1 | export function getData() { 2 | const start_date = new Date("01/05/2021"); 3 | const end_date = new Date("01/15/2021"); 4 | 5 | const items = [ 6 | { comp: "text", key: "name", label: "Name", column: "left" }, 7 | { comp: "checkbox", key: "admin", label: "Is Admin" }, 8 | { comp: "text", key: "email", label: "Email" }, 9 | { 10 | comp: "textarea", 11 | key: "descr", 12 | label: "Description", 13 | column: "left", 14 | }, 15 | ]; 16 | 17 | const batchItems = [ 18 | { 19 | comp: "text", 20 | key: "name", 21 | batch: "main", 22 | label: "Name", 23 | }, 24 | { 25 | comp: "textarea", 26 | key: "descr", 27 | batch: "main", 28 | label: "Description", 29 | }, 30 | { 31 | comp: "text", 32 | key: "email", 33 | batch: "main", 34 | label: "Email", 35 | }, 36 | { 37 | comp: "checkbox", 38 | key: "admin", 39 | batch: "cfg", 40 | label: "Is Admin", 41 | }, 42 | { 43 | key: "theme", 44 | batch: "cfg", 45 | comp: "checkbox", 46 | label: "Dark theme", 47 | }, 48 | ]; 49 | 50 | const values = { 51 | name: "John Doe", 52 | descr: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor.", 53 | admin: true, 54 | email: "john.doe@example.org", 55 | theme: false, 56 | comments: [], 57 | tasks: [], 58 | }; 59 | 60 | const users = [ 61 | { 62 | id: 1, 63 | name: "Sarah Smith", 64 | label: "Sarah Smith", 65 | }, 66 | { 67 | id: 2, 68 | name: "Diego Redmoor", 69 | label: "Diego Redmoor", 70 | }, 71 | { 72 | id: 3, 73 | name: "Alex Johnson", 74 | label: "Alex Johnson", 75 | }, 76 | { 77 | id: 4, 78 | name: "Marta Kowalski", 79 | label: "Marta Kowalski", 80 | }, 81 | ]; 82 | 83 | const listItems = [ 84 | { 85 | key: "label", 86 | comp: "text", 87 | label: "Label", 88 | }, 89 | { 90 | key: "description", 91 | comp: "textarea", 92 | label: "Description", 93 | }, 94 | { 95 | comp: "combo", 96 | label: "Priority", 97 | key: "priority", 98 | options: [ 99 | { id: 1, label: "High" }, 100 | { id: 2, label: "Medium" }, 101 | { id: 3, label: "Low" }, 102 | ], 103 | }, 104 | { 105 | comp: "color", 106 | label: "Color", 107 | key: "color", 108 | }, 109 | { 110 | comp: "slider", 111 | key: "progress", 112 | label: "Progress", 113 | }, 114 | { 115 | comp: "date", 116 | key: "start_date", 117 | label: "Start date", 118 | }, 119 | { 120 | comp: "date", 121 | key: "end_date", 122 | label: "End date", 123 | }, 124 | { 125 | comp: "multiselect", 126 | key: "users", 127 | label: "Users", 128 | options: users, 129 | }, 130 | { 131 | comp: "hidden", 132 | key: "state", 133 | }, 134 | ]; 135 | 136 | const listData = [ 137 | { 138 | id: 1, 139 | label: "Integration with React", 140 | priority: 1, 141 | color: "#65D3B3", 142 | description: 143 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", 144 | 145 | start_date, 146 | end_date, 147 | 148 | progress: 25, 149 | users: [1, 2, 3, 4], 150 | sprint: "1.0", 151 | column: "backlog", 152 | type: "feature", 153 | comments: [ 154 | { 155 | id: 1, 156 | user: 1, 157 | cardId: 1, 158 | content: 159 | "Greetings, fellow colleagues. I would like to share my insights on this task. I reckon we should deal with at least half of the points in the plan without further delays.", 160 | date: new Date(2025, 2, 1, 10, 35), 161 | }, 162 | { 163 | id: 2, 164 | user: 2, 165 | cardId: 1, 166 | content: 167 | "Hi, Diego. I am sure that that's exactly what is thought best out there in Dunwall. Let's just do what we are supposed to do to get the result.", 168 | date: new Date(2025, 2, 1, 10, 57), 169 | }, 170 | { 171 | id: 5, 172 | user: 3, 173 | cardId: 1, 174 | content: 175 | "Absolutely, Diego. Action speaks louder than words, and in this case, it's about executing the plan efficiently. Let's prioritize tasks and tackle them head-on.", 176 | date: new Date(2025, 2, 1, 11, 13), 177 | }, 178 | { 179 | id: 6, 180 | user: 2, 181 | cardId: 1, 182 | content: 183 | "I couldn't agree more, Sarah. Time is of the essence, and we can't afford to waste any more of it. Let's dive into the plan and start ticking off those points one by one.", 184 | date: new Date(2025, 2, 2, 7, 2), 185 | }, 186 | ], 187 | task: [ 188 | { 189 | id: 1, 190 | date: new Date(), 191 | content: 192 | "Research best practices for integrating third-party libraries with React", 193 | status: 1, 194 | }, 195 | { 196 | id: 2, 197 | date: new Date(), 198 | content: 199 | "Explore modern approaches to building applications using React", 200 | status: 0, 201 | }, 202 | { 203 | id: 3, 204 | date: new Date(), 205 | content: 206 | "Explore different methods for integrating React with existing JavaScript frameworks", 207 | status: 0, 208 | }, 209 | { 210 | id: 4, 211 | date: new Date(), 212 | content: "Learn about routing in React using React Router", 213 | status: 1, 214 | }, 215 | { 216 | id: 5, 217 | date: new Date(), 218 | content: 219 | "Understand principles and best practices for component development in React", 220 | status: 0, 221 | }, 222 | { 223 | id: 6, 224 | date: new Date(), 225 | content: 226 | "Explore different methods for integrating React with existing JavaScript frameworks", 227 | status: 0, 228 | }, 229 | { 230 | id: 7, 231 | date: new Date(), 232 | content: "Optimize performance in React applications", 233 | status: 0, 234 | }, 235 | { 236 | id: 8, 237 | date: new Date(), 238 | content: 239 | "Work with API requests and data handling in React applications", 240 | status: 0, 241 | }, 242 | ], 243 | votes: [1, 3, 4], 244 | }, 245 | { 246 | id: 2, 247 | label: "Archive the cards/boards ", 248 | priority: 2, 249 | color: "#FFC975", 250 | 251 | start_date, 252 | end_date, 253 | comments: [], 254 | task: [], 255 | 256 | sprint: "1.0", 257 | column: "backlog", 258 | type: "feature", 259 | users: [3, 4], 260 | }, 261 | { 262 | id: 3, 263 | label: "Searching and filtering", 264 | priority: 1, 265 | color: "#65D3B3", 266 | 267 | start_date, 268 | 269 | sprint: "1.2", 270 | column: "backlog", 271 | type: "task", 272 | comments: [ 273 | { 274 | id: 7, 275 | user: 4, 276 | cardId: 3, 277 | content: "Nice seven", 278 | date: new Date(2025, 2, 5, 8, 24), 279 | }, 280 | { 281 | id: 8, 282 | user: 3, 283 | cardId: 3, 284 | content: "Nice eight", 285 | date: new Date(2025, 2, 5, 9, 14), 286 | }, 287 | { 288 | id: 9, 289 | user: 1, 290 | cardId: 3, 291 | content: "Nice nine", 292 | date: new Date(2025, 2, 5, 11, 12), 293 | }, 294 | ], 295 | task: [ 296 | { 297 | id: 1, 298 | date: new Date(), 299 | content: 300 | "Implement basic search functionality to filter items by keyword", 301 | status: 1, 302 | }, 303 | { 304 | id: 2, 305 | date: new Date(), 306 | content: 307 | "Add filtering options to categorize items based on specific criteria", 308 | status: 0, 309 | }, 310 | ], 311 | votes: [1, 3, 4], 312 | }, 313 | ]; 314 | 315 | const valuesValidation = { 316 | firstName: "John", 317 | lastName: "Doe", 318 | company: "Innovate company", 319 | contact: "+0122333456", 320 | email: "john.doe@example.org", 321 | additional: 322 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor.", 323 | }; 324 | 325 | const itemsValidation = [ 326 | { 327 | comp: "text", 328 | key: "firstName", 329 | label: "First name", 330 | validation: val => { 331 | const regEx = /^[a-zA-Z]+$/; 332 | return val && regEx.test(val); 333 | }, 334 | validationMessage: "Incorrect name!", 335 | }, 336 | { 337 | comp: "text", 338 | key: "lastName", 339 | label: "Last name", 340 | validation: val => { 341 | const regEx = /^[a-zA-Z]+$/; 342 | return val && regEx.test(val); 343 | }, 344 | validationMessage: "Incorrect name!", 345 | }, 346 | { 347 | comp: "text", 348 | key: "company", 349 | label: "Company", 350 | validation: val => val.length > 2, 351 | }, 352 | { 353 | comp: "text", 354 | key: "contact", 355 | label: "Contact number", 356 | validation: val => { 357 | const regEx = /^\+?\d*$/; 358 | return val.length > 9 && regEx.test(val); 359 | }, 360 | validationMessage: "Please, enter correct phone number", 361 | }, 362 | { 363 | comp: "text", 364 | key: "email", 365 | label: "Email", 366 | validation: val => { 367 | //eslint-disable-next-line 368 | const regEx = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/g; 369 | return val && regEx.test(val); 370 | }, 371 | validationMessage: "Incorrect email!", 372 | }, 373 | { 374 | comp: "textarea", 375 | key: "additional", 376 | label: "Additional information", 377 | validation: val => val.length >= 20 && val.length <= 160, 378 | validationMessage: 379 | "Value should have at least 20 characters and no more than 160 characters", 380 | }, 381 | ]; 382 | 383 | return { 384 | items, 385 | values, 386 | listData, 387 | listItems, 388 | batchItems, 389 | users, 390 | valuesValidation, 391 | itemsValidation, 392 | }; 393 | } 394 | 395 | export function sectionItems(sec = {}) { 396 | const data = [ 397 | { 398 | comp: "text", 399 | key: "name", 400 | label: "Name", 401 | }, 402 | { 403 | comp: "textarea", 404 | key: "descr", 405 | label: "Description", 406 | }, 407 | { 408 | comp: "section", 409 | key: "comments-section", 410 | label: "Comments", 411 | }, 412 | { 413 | comp: "comments", 414 | key: "comments", 415 | section: "comments-section", 416 | }, 417 | { 418 | comp: "section", 419 | key: "tasks-section", 420 | label: "Tasks", 421 | }, 422 | { 423 | comp: "tasks", 424 | key: "tasks", 425 | section: "tasks-section", 426 | }, 427 | ]; 428 | 429 | data.forEach(item => { 430 | if (item.comp === "section") Object.assign(item, sec); 431 | }); 432 | return data; 433 | } 434 | 435 | export function onlySectionItems(sec = {}) { 436 | const data = [ 437 | { 438 | comp: "section", 439 | key: "personal-section", 440 | label: "Personal Info", 441 | active: true, 442 | }, 443 | { 444 | comp: "text", 445 | key: "name", 446 | batch: "main", 447 | label: "Name", 448 | section: "personal-section", 449 | }, 450 | { 451 | comp: "textarea", 452 | key: "descr", 453 | batch: "main", 454 | label: "Description", 455 | section: "personal-section", 456 | }, 457 | { 458 | comp: "text", 459 | key: "email", 460 | label: "Email", 461 | section: "personal-section", 462 | }, 463 | 464 | // the data below is used instead of the commented lines below 465 | { 466 | comp: "section", 467 | key: "role-section", 468 | label: "Role", 469 | }, 470 | { 471 | comp: "checkbox", 472 | key: "admin", 473 | label: "Is admin", 474 | section: "role-section", 475 | }, 476 | { 477 | comp: "section", 478 | key: "comments-section", 479 | label: "Comments", 480 | }, 481 | { 482 | comp: "comments", 483 | key: "comments", 484 | section: "comments-section", 485 | }, 486 | { 487 | comp: "section", 488 | key: "tasks-section", 489 | label: "Tasks", 490 | }, 491 | { 492 | comp: "tasks", 493 | key: "tasks", 494 | section: "tasks-section", 495 | }, 496 | ]; 497 | 498 | data.forEach(item => { 499 | if (item.comp === "section") Object.assign(item, sec); 500 | }); 501 | return data; 502 | } 503 | -------------------------------------------------------------------------------- /svelte/demos/index.js: -------------------------------------------------------------------------------- 1 | import { mount } from "svelte"; 2 | import Demos from "./common/Index.svelte"; 3 | 4 | mount(Demos, { 5 | target: document.querySelector("#wx_demo_area") || document.body, 6 | }); 7 | -------------------------------------------------------------------------------- /svelte/demos/routes.js: -------------------------------------------------------------------------------- 1 | import BasicInit from "./cases/BasicInit.svelte"; 2 | import ModalPanel from "./cases/ModalPanel.svelte"; 3 | import ModalColumns from "./cases/ModalColumns.svelte"; 4 | import SidebarPanel from "./cases/SidebarPanel.svelte"; 5 | import Batches from "./cases/Batches.svelte"; 6 | import SubPanels from "./cases/SubPanels.svelte"; 7 | import ChangesAuto from "./cases/ChangesAuto.svelte"; 8 | import ChangesConfirmed from "./cases/ChangesConfirmed.svelte"; 9 | import CustomButtons from "./cases/CustomButtons.svelte"; 10 | import ActionMenu from "./cases/ActionMenu.svelte"; 11 | import ToolbarValues from "./cases/ToolbarValues.svelte"; 12 | import Tasklist from "./cases/Tasklist.svelte"; 13 | import Comments from "./cases/Comments.svelte"; 14 | import Layouts from "./cases/Layouts.svelte"; 15 | import BackendComplex from "./cases/BackendComplex.svelte"; 16 | import MultipleCombos from "./cases/MultipleCombos.svelte"; 17 | import Overflow from "./cases/Overflow.svelte"; 18 | import SaveValidatedValues from "./cases/SaveValidatedValues.svelte"; 19 | import Validation from "./cases/Validation.svelte"; 20 | import RequiredFields from "./cases/RequiredFields.svelte"; 21 | import Locales from "./cases/Locales.svelte"; 22 | // import Gantt from "./cases/Gantt.svelte"; 23 | // import Kanban from "./cases/Kanban.svelte"; 24 | // import Scheduler from "./cases/Scheduler.svelte"; 25 | 26 | export const links = [ 27 | ["/base/:skin", "Basic Form", BasicInit], 28 | ["/modal/:skin", "Modal Editor", ModalPanel], 29 | ["/columns/:skin", "Modal Editor with columns", ModalColumns], 30 | ["/sidebar/:skin", "Sidebar Editor", SidebarPanel], 31 | ["/changes-auto/:skin", "Changes: auto mode", ChangesAuto], 32 | ["/changes-confirmed/:skin", "Changes: confirmation", ChangesConfirmed], 33 | ["/batches/:skin", "Batches", Batches], 34 | ["/subpanels/:skin", "Sub panels", SubPanels], 35 | ["/actions/:skin", "Action menu", ActionMenu], 36 | ["/buttons/:skin", "Custom buttons", CustomButtons], 37 | ["/comments/:skin", "Comments", Comments], 38 | ["/taslkist/:skin", "Tasklist", Tasklist], 39 | ["/layouts/:skin", "Layouts", Layouts], 40 | ["/backend-complex/:skin", "Separate backends", BackendComplex], 41 | ["/combos/:skin", "Multiple combos", MultipleCombos], 42 | ["/overflow/:skin", "Overflow", Overflow], 43 | ["/toolbar/:skin", "Toolbar values", ToolbarValues], 44 | ["/required/:skin", "Required fields", RequiredFields], 45 | ["/validation/:skin", "Validation", Validation], 46 | ["/validation-save/:skin", "Save validated values", SaveValidatedValues], 47 | ["/locales/:skin", "Locales", Locales], 48 | // ["/gantt/:skin", "Gantt", Gantt], 49 | // ["/kanban/:skin", "Kanban", Kanban], 50 | // ["/scheduler/:skin", "Scheduler", Scheduler], 51 | ]; 52 | -------------------------------------------------------------------------------- /svelte/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Svelte Widgets 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /svelte/license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 XB Software Sp. z o.o. 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. -------------------------------------------------------------------------------- /svelte/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wx-svelte-editor", 3 | "version": "2.1.2", 4 | "description": "Svelte component for creating forms to edit structured data on a page (info cards, text blocks, table rows, etc", 5 | "keywords": [ 6 | "svelte", 7 | "ui component", 8 | "editor", 9 | "form", 10 | "editing form", 11 | "data editing" 12 | ], 13 | "productTag": "editor", 14 | "productTrial": false, 15 | "type": "module", 16 | "scripts": { 17 | "build": "vite build", 18 | "build:dist": "vite build --mode dist", 19 | "build:tests": "vite build --mode test", 20 | "lint": "yarn eslint ./demos ./src", 21 | "start": "vite --open", 22 | "start:tests": "vite --open=/tests/ --host 0.0.0.0 --port 5100 --mode test", 23 | "test": "true", 24 | "test:cypress": "cypress run -P ./ --config \"baseUrl=http://localhost:5100/tests\"" 25 | }, 26 | "svelte": "src/index.js", 27 | "exports": { 28 | ".": { 29 | "svelte": "./src/index.js" 30 | }, 31 | "./package.json": "./package.json" 32 | }, 33 | "license": "MIT", 34 | "repository": { 35 | "type": "git", 36 | "url": "https://github.com/svar-widgets/editor.git" 37 | }, 38 | "bugs": { 39 | "url": "https://forum.svar.dev" 40 | }, 41 | "homepage": "https://svar.dev/svelte/editor/", 42 | "dependencies": { 43 | "wx-editor-locales": "2.1.2", 44 | "wx-lib-dom": "0.8.0", 45 | "wx-lib-state": "1.9.0", 46 | "wx-lib-svelte": "0.5.1", 47 | "wx-svelte-comments": "2.1.1", 48 | "wx-svelte-core": "2.1.1", 49 | "wx-svelte-tasklist": "2.1.1", 50 | "wx-svelte-toolbar": "2.1.1" 51 | }, 52 | "files": [ 53 | "src", 54 | "readme.md", 55 | "whatsnew.md", 56 | "license.txt" 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /svelte/postcss.config.js: -------------------------------------------------------------------------------- 1 | import autoprefixer from "autoprefixer"; 2 | 3 | const plugins = [autoprefixer]; 4 | export { plugins }; 5 | -------------------------------------------------------------------------------- /svelte/readme.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # SVAR Svelte Editor 4 | 5 | [![npm](https://img.shields.io/npm/v/wx-svelte-editor.svg)](https://www.npmjs.com/package/wx-svelte-editor) 6 | [![License](https://img.shields.io/github/license/svar-widgets/editor)](https://github.com/svar-widgets/editor/blob/main/license.txt) 7 | [![npm downloads](https://img.shields.io/npm/dm/wx-svelte-editor.svg)](https://www.npmjs.com/package/wx-svelte-editor) 8 | 9 |
10 | 11 |
12 | 13 | [Documentation](https://docs.svar.dev/svelte/editor/) • [Demos](https://docs.svar.dev/svelte/editor/samples/#/base/willow) 14 | 15 |
16 | 17 | An intuitive Svelte component for creating content editing forms to manage data within UI elements on a page. You can use it for editing structured data like table rows, informational cards, blocks with text, etc. 18 | 19 | ### Key features: 20 | 21 | - **Flexible display options**: open the editor in a modal popup or as a seamless sidebar for convenient access. 22 | - **Multiple input types**: Use various input fields like text inputs, checkboxes, date pickers, sliders, and more controls from [SVAR Svelte Core](https://svar.dev/svelte/core/) library. 23 | - **Built-in validation**: Includes basic validation for required fields and supports custom validation rules for advanced scenarios. 24 | - **Flexible save options**: Choose between manual saves, auto-save, or custom saving logic adjusted to your needs. 25 | - **Compact layout**: Organize forms into expandable sections or a 2-column layout for efficient use of screen space. 26 | 27 | ### How to Use 28 | 29 | To use the widget, simply import the package and include the component in your Svelte file: 30 | 31 | ```svelte 32 | 37 | 38 | 39 | ``` 40 | 41 | ### How to Modify 42 | 43 | Typically, you don't need to modify the code. However, if you wish to do so, follow these steps: 44 | 45 | 1. Run `yarn` to install dependencies. Note that this project is a monorepo using `yarn` workspaces, so npm will not work 46 | 2. Start the project in development mode with `yarn start` 47 | 48 | ### Run Tests 49 | 50 | To run the test: 51 | 52 | 1. Start the test examples with: 53 | ```sh 54 | yarn start:tests 55 | ``` 56 | 2. In a separate console, run the end-to-end tests with: 57 | ```sh 58 | yarn test:cypress 59 | ``` 60 | -------------------------------------------------------------------------------- /svelte/src/components/Columns.svelte: -------------------------------------------------------------------------------- 1 | 32 | 33 | {#if layout === "columns"} 34 |
35 |
36 | 37 |
38 |
39 | 47 |
48 |
49 | {:else} 50 | 51 | {/if} 52 | 53 | 72 | -------------------------------------------------------------------------------- /svelte/src/components/Editor.svelte: -------------------------------------------------------------------------------- 1 | 26 | 27 | 28 | 44 | {#if children}{@render children()}{/if} 45 | 46 | 47 | -------------------------------------------------------------------------------- /svelte/src/components/Form.svelte: -------------------------------------------------------------------------------- 1 | 42 | 43 | 44 |
45 | 56 | {#if children}{@render children()}{/if} 57 | 58 |
59 |
60 | 61 | 67 | -------------------------------------------------------------------------------- /svelte/src/components/FormEditor.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 |
18 | {#if children}{@render children()}{/if} 19 | {#each editors.config as editor} 20 | {#if !editor.hidden} 21 | {#if editor.comp === "readonly" || editor.comp === "section"} 22 | {@const Component = getItemHandler(editor.comp)} 23 | 29 | {:else} 30 |
31 | 37 | {#snippet children({ id })} 38 | {@const Component2 = getItemHandler(editor.comp)} 39 | 41 | onchange && 42 | onchange({ 43 | value: ev.value, 44 | key: editor.key, 45 | })} 46 | {...editor} 47 | {id} 48 | label={undefined} 49 | error={errors && errors[editor.key]} 50 | value={data[editor.key]} 51 | /> 52 | {/snippet} 53 | 54 | {#if errors && errors[editor.key] && editor.validationMessage} 55 |
{editor.validationMessage}
56 | {/if} 57 |
58 | {/if} 59 | {/if} 60 | {/each} 61 |
62 | 63 | 77 | -------------------------------------------------------------------------------- /svelte/src/components/Helpers.svelte.js: -------------------------------------------------------------------------------- 1 | export function link(getValue) { 2 | let localState = $state(undefined); 3 | const linkedDerived = $derived.by(() => { 4 | const linkedValue = getValue(); 5 | return typeof localState !== "undefined" ? localState : linkedValue; 6 | }); 7 | 8 | return [ 9 | () => linkedDerived, 10 | v => { 11 | localState = v; 12 | }, 13 | ]; 14 | } 15 | 16 | export function dataLink(getValue) { 17 | const [getErrors, setErrors] = link(getValue); 18 | 19 | return { 20 | get errors() { 21 | return getErrors(); 22 | }, 23 | set errors(value) { 24 | setErrors(value); 25 | }, 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /svelte/src/components/Layout.svelte: -------------------------------------------------------------------------------- 1 | 76 | 77 | {#if placement === "modal"} 78 | 79 |
80 | 87 |
91 | {#if children}{@render children()}{/if} 92 | 101 | 109 |
110 |
111 |
112 | {:else if placement === "sidebar"} 113 | 114 |
115 | 122 |
126 | {#if children}{@render children()}{/if} 127 | 136 | 144 |
145 |
146 |
147 | {:else} 148 |
149 | 150 |
151 | {#if children}{@render children()}{/if} 152 | 161 | 169 |
170 |
171 | {/if} 172 | 173 | 199 | -------------------------------------------------------------------------------- /svelte/src/components/Values.svelte: -------------------------------------------------------------------------------- 1 | 171 | 172 | 173 | 174 | 189 | {#if children}{@render children()}{/if} 190 | 191 | -------------------------------------------------------------------------------- /svelte/src/components/buttons/Toolbar.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 | {#if items.length} 17 |
18 | 24 |
25 | {/if} 26 | 27 | 48 | -------------------------------------------------------------------------------- /svelte/src/components/buttons/buttons.js: -------------------------------------------------------------------------------- 1 | export const Spacer = () => ({ comp: "spacer" }); 2 | export const CancelButton = _ => ({ 3 | comp: "button", 4 | text: _("Cancel"), 5 | id: "cancel", 6 | }); 7 | export const SaveButton = _ => ({ 8 | type: "primary", 9 | comp: "button", 10 | text: _("Save"), 11 | id: "save", 12 | }); 13 | export const CloseIcon = () => ({ 14 | comp: "icon", 15 | icon: "wxi-close", 16 | id: "close", 17 | }); 18 | -------------------------------------------------------------------------------- /svelte/src/components/sections/ReadOnly.svelte: -------------------------------------------------------------------------------- 1 | 25 | 26 | {#if text} 27 | {text} 28 | {/if} 29 | -------------------------------------------------------------------------------- /svelte/src/components/sections/Section.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 |
11 | onclick && 12 | onclick({ item: { id: "toggle-section", key: active ? null : key } })} 13 | > 14 |

{label}

15 | 16 |
17 | 18 | 37 | -------------------------------------------------------------------------------- /svelte/src/editor.js: -------------------------------------------------------------------------------- 1 | import { uid, isSame } from "wx-lib-state"; 2 | 3 | function rawGetter(key) { 4 | if (typeof key === "string" && key.includes(".")) { 5 | const parts = key.split("."); 6 | return obj => { 7 | let out = obj; 8 | parts.forEach(p => { 9 | out = out[p]; 10 | }); 11 | return out; 12 | }; 13 | } 14 | 15 | return obj => obj[key]; 16 | } 17 | function rawSetter(key) { 18 | if (typeof key === "string" && key.includes(".")) { 19 | const parts = key.split("."); 20 | return (obj, v) => { 21 | let out = obj; 22 | parts.forEach((p, i) => { 23 | if (i === parts.length - 1) out[p] = v; 24 | else out = out[p]; 25 | }); 26 | }; 27 | } 28 | 29 | return (obj, v) => (obj[key] = v); 30 | } 31 | 32 | export function itemsToEditors(sections) { 33 | const config = sections.map(s => { 34 | const obj = { ...s }; 35 | if (s.config) Object.assign(obj, s.config); 36 | 37 | obj.key = s.key || uid(); 38 | obj.setter = s.setter || rawSetter(s.key); 39 | obj.getter = s.getter || rawGetter(s.key); 40 | return obj; 41 | }); 42 | 43 | const getValues = raw => { 44 | const out = {}; 45 | config.forEach(ed => { 46 | if (ed.comp === "section") return; 47 | if (ed.getter) out[ed.key] = ed.getter(raw); 48 | else out[ed.key] = raw[ed.key]; 49 | }); 50 | return out; 51 | }; 52 | 53 | const setValues = (out, values, changes) => { 54 | const fields = changes.length 55 | ? changes.map(key => config.find(a => a.key === key)) 56 | : config; 57 | fields.forEach(ed => { 58 | if (ed.setter) ed.setter(out, values[ed.key]); 59 | else out[ed.key] = values[ed.key]; 60 | }); 61 | 62 | return out; 63 | }; 64 | 65 | const diff = (raw, values) => { 66 | const initial = getValues(raw); 67 | const changes = []; 68 | config.forEach(ed => { 69 | const a = initial[ed.key]; 70 | const b = values[ed.key]; 71 | // we can have a situation when initial value is undefined 72 | // but empty editor will return empty value instead of undefined 73 | // this behavior is correct, but we need to ignore this case 74 | if (!isSame(a, b) && (a !== undefined || !!b)) { 75 | changes.push(ed.key); 76 | } 77 | }); 78 | 79 | return changes; 80 | }; 81 | 82 | const validateValues = (values, _) => { 83 | let any = 0; 84 | const errors = {}; 85 | 86 | config.forEach(ed => { 87 | if (ed.required && !values[ed.key]) { 88 | errors[ed.key] = { 89 | errorType: "required", 90 | }; 91 | ed.validationMessage = 92 | ed.validationMessage || _("This field is required"); 93 | any++; 94 | } else if (ed.validation && !ed.validation(values[ed.key])) { 95 | errors[ed.key] = { 96 | errorType: "validation", 97 | }; 98 | ed.validationMessage = 99 | ed.validationMessage || _("Invalid value"); 100 | any++; 101 | } 102 | }); 103 | 104 | return any > 0 ? errors : null; 105 | }; 106 | 107 | return { 108 | config: config.filter(x => x.comp !== "hidden"), 109 | getValues, 110 | setValues, 111 | diff, 112 | validateValues, 113 | }; 114 | } 115 | -------------------------------------------------------------------------------- /svelte/src/en.js: -------------------------------------------------------------------------------- 1 | export default { 2 | editor: {}, 3 | }; 4 | -------------------------------------------------------------------------------- /svelte/src/helpers.js: -------------------------------------------------------------------------------- 1 | const handlers = {}; 2 | export function getItemHandler(type) { 3 | return handlers[type] || handlers["text"]; 4 | } 5 | export function registerEditorItem(type, handler) { 6 | handlers[type] = handler; 7 | } 8 | -------------------------------------------------------------------------------- /svelte/src/index.js: -------------------------------------------------------------------------------- 1 | import { registerEditorItem } from "./helpers"; 2 | import Form from "./components/Form.svelte"; 3 | import Editor from "./components/Editor.svelte"; 4 | 5 | import Material from "./themes/Material.svelte"; 6 | import Willow from "./themes/Willow.svelte"; 7 | import WillowDark from "./themes/WillowDark.svelte"; 8 | 9 | import ReadOnly from "./components/sections/ReadOnly.svelte"; 10 | import Section from "./components/sections/Section.svelte"; 11 | import { Text } from "wx-svelte-core"; 12 | import { TextArea } from "wx-svelte-core"; 13 | import { Checkbox } from "wx-svelte-core"; 14 | 15 | registerEditorItem("text", Text); 16 | registerEditorItem("textarea", TextArea); 17 | registerEditorItem("checkbox", Checkbox); 18 | 19 | registerEditorItem("readonly", ReadOnly); 20 | registerEditorItem("section", Section); 21 | 22 | import { setEnv } from "wx-lib-dom"; 23 | import { env } from "wx-lib-svelte"; 24 | setEnv(env); 25 | 26 | export { registerEditorItem, Form, Editor, Material, Willow, WillowDark }; 27 | -------------------------------------------------------------------------------- /svelte/src/themes/Material.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | {#if children} 7 | {@render children()} 8 | {:else} 9 | 10 | {/if} 11 | 12 | 17 | -------------------------------------------------------------------------------- /svelte/src/themes/Willow.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | {#if children} 7 | {@render children()} 8 | {:else} 9 | 10 | {/if} 11 | 12 | 17 | -------------------------------------------------------------------------------- /svelte/src/themes/WillowDark.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | {#if children} 7 | {@render children()} 8 | {:else} 9 | 10 | {/if} 11 | 12 | 17 | -------------------------------------------------------------------------------- /svelte/svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; 2 | 3 | export default { 4 | // Consult https://svelte.dev/docs#compile-time-svelte-preprocess 5 | // for more information about preprocessors 6 | preprocess: vitePreprocess(), 7 | }; 8 | -------------------------------------------------------------------------------- /svelte/tests/Index.svelte: -------------------------------------------------------------------------------- 1 | 47 | 48 | 49 | 50 | 51 | 52 |
53 | 54 | 55 | 56 | 57 | 58 |
59 | 60 | 98 | -------------------------------------------------------------------------------- /svelte/tests/cases/LocalData.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 |
12 | 13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /svelte/tests/data.js: -------------------------------------------------------------------------------- 1 | export function getData() { 2 | return {}; 3 | } 4 | -------------------------------------------------------------------------------- /svelte/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Svelte Widgets 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /svelte/tests/index.js: -------------------------------------------------------------------------------- 1 | import Demos from "./Index.svelte"; 2 | import { mount } from "svelte"; 3 | 4 | mount(Demos, { 5 | target: document.querySelector("#wx_demo_area") || document.body, 6 | }); 7 | -------------------------------------------------------------------------------- /svelte/tests/routes.js: -------------------------------------------------------------------------------- 1 | import LocalData from "./cases/LocalData.svelte"; 2 | 3 | export const links = [["/local-data", "", LocalData]]; 4 | -------------------------------------------------------------------------------- /svelte/vite.config.js: -------------------------------------------------------------------------------- 1 | import { loadEnv } from "vite"; 2 | import { resolve } from "path"; 3 | import { existsSync } from "fs"; 4 | import { svelte } from "@sveltejs/vite-plugin-svelte"; 5 | import { visualizer } from "rollup-plugin-visualizer"; 6 | import { waitChanges, waitOn } from "wx-vite-tools"; 7 | import conditionalCompile from "vite-plugin-conditional-compile"; 8 | import pkg from "./package.json" with { type: "json" }; 9 | 10 | export default async ({ mode }) => { 11 | process.env = { ...process.env, ...loadEnv(mode, process.cwd(), "WX") }; 12 | const files = []; 13 | 14 | if (mode !== "production") { 15 | const paths = [ 16 | resolve(__dirname, "../store/dist/index.js"), 17 | resolve(__dirname, "../provider/dist/index.js"), 18 | ]; 19 | 20 | paths.forEach(path => { 21 | if (existsSync(path)) { 22 | files.push(path); 23 | } 24 | }); 25 | } 26 | 27 | const plugins = []; 28 | 29 | if (files.length) plugins.push(waitChanges({ files })); 30 | if (mode !== "development") plugins.push(conditionalCompile()); 31 | plugins.push(svelte({})); 32 | 33 | const name = pkg.productTag; 34 | 35 | let build, 36 | publicDir = resolve(__dirname, "public"), 37 | server = {}, 38 | base = ""; 39 | 40 | if (mode === "test") { 41 | build = { 42 | rollupOptions: { 43 | input: { tests: resolve(__dirname, "tests/index.html") }, 44 | }, 45 | }; 46 | server.port = 5100; 47 | } else { 48 | build = { 49 | rollupOptions: { 50 | input: { index: resolve(__dirname, "index.html") }, 51 | }, 52 | }; 53 | } 54 | 55 | if (process.env.WX_BUILD_STATS) { 56 | build = { 57 | lib: { 58 | entry: resolve(__dirname, "src/index.js"), 59 | name, 60 | formats: ["es"], 61 | fileName: format => `${name}.${format}.js`, 62 | }, 63 | outDir: "./dist", 64 | sourcemap: true, 65 | minify: true, 66 | target: "esnext", 67 | }; 68 | publicDir = false; 69 | plugins.push(visualizer({ filename: "dist/stats.html" })); 70 | } 71 | 72 | if (files.length) await waitOn({ files }); 73 | 74 | return { 75 | base, 76 | build, 77 | publicDir, 78 | resolve: { dedupe: ["svelte"] }, 79 | plugins, 80 | server, 81 | watch: { 82 | persistent: true, 83 | include: ["src/**/*.ts", "src/**/*.js"], 84 | }, 85 | }; 86 | }; 87 | -------------------------------------------------------------------------------- /svelte/whatsnew.md: -------------------------------------------------------------------------------- 1 | ## 2.1.2 2 | 3 | ### Fixes 4 | 5 | - Editor en-US locale is not applied by default 6 | 7 | ## 2.1.1 8 | 9 | ### Fixes 10 | 11 | - Section bars have light styling in WillowDark skin 12 | - Section bars are not clickable for inline Editor 13 | - Content overflow for inline Editor 14 | 15 | ## 2.1.0 16 | 17 | ### New features 18 | 19 | - TextArea and Checkbox as built-in types 20 | 21 | ### Fixes 22 | 23 | - values are not changed until recreated 24 | 25 | ## 2.0.1 26 | 27 | - Public release 28 | 29 | ### New features 30 | 31 | - Svelte 5 support 32 | - Comments and TaskList sections 33 | 34 | ## 0.6.0 35 | 36 | ### New features 37 | 38 | - Validation 39 | 40 | ## 0.5.0 41 | 42 | ### New features 43 | 44 | - Ability to define collapsible sections 45 | - Ability to bind values to toolbar controls 46 | 47 | ### Updates 48 | 49 | - `section:left|right` changed to `column:left|right` 50 | 51 | ## 0.2.0 52 | 53 | - Initial version 54 | --------------------------------------------------------------------------------