diff --git a/.claude/settings.json b/.claude/settings.json deleted file mode 100644 index 0f77587..0000000 --- a/.claude/settings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "permissions": { - "allow": ["Bash(git status:*)", "Bash(git diff:*)", "Bash(git log:*)", "Bash(git show:*)"], - "deny": ["Read(./.env)", "Read(./.env.*)", "Read(**/*.pem)", "Read(**/*.key)"] - } -} diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index a2df57f..30efd6b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -2,6 +2,22 @@ name: Bug report description: Report something that is not working as expected labels: [bug] body: + - type: dropdown + id: package + attributes: + label: Package + description: Which package shows the problem? + options: + - saccade-keyboards (the component and its options) + - saccade-en + - saccade-es + - saccade-fr + - saccade-hi + - saccade-te + - saccade-ur + - Not sure + validations: + required: true - type: textarea id: what-happened attributes: @@ -13,18 +29,29 @@ body: id: steps attributes: label: Steps to reproduce + description: Include the language, arrangement and region you used, and any options such as `numpad`, `currencypad`, `theme`, `font` or `fontSize`. placeholder: | 1. ... 2. ... validations: required: true + - type: input + id: width + attributes: + label: Keyboard width + description: How wide the keyboard was when it happened, for example 480 px. Keys look different below 800 px. - type: input id: version attributes: - label: Version - description: Release or commit you are using. + label: Package version + description: The version of the package, for example 0.1.0. + - type: input + id: environment + attributes: + label: React version, browser and device + description: For example React 19.1, Chrome 130 on Windows, or Safari on iPhone. - type: textarea id: logs attributes: label: Logs or screenshots - description: Remove any secrets or personal information first. + description: A screenshot of the keyboard helps most. Remove any secrets or personal information first. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..35501e6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Report a security vulnerability + url: https://github.com/coldsofttech/SaccadeKeyboards/security/advisories/new + about: Please report vulnerabilities privately, not in a public issue. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 247c561..9226c70 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -2,6 +2,22 @@ name: Feature request description: Suggest an idea or improvement labels: [enhancement] body: + - type: dropdown + id: package + attributes: + label: Package + description: Which package is this about? + options: + - saccade-keyboards (the component and its options) + - saccade-en + - saccade-es + - saccade-fr + - saccade-hi + - saccade-te + - saccade-ur + - Not sure + validations: + required: true - type: textarea id: problem attributes: diff --git a/.github/ISSUE_TEMPLATE/layout_fix.yml b/.github/ISSUE_TEMPLATE/layout_fix.yml new file mode 100644 index 0000000..23dac6c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/layout_fix.yml @@ -0,0 +1,37 @@ +name: Layout fix +description: A key is wrong, missing or in the wrong place on an existing keyboard +title: 'Layout fix: ' +labels: [bug, layout] +body: + - type: dropdown + id: package + attributes: + label: Language + options: + - English (saccade-en) + - Spanish (saccade-es) + - French (saccade-fr) + - Hindi (saccade-hi) + - Telugu (saccade-te) + - Urdu (saccade-ur) + validations: + required: true + - type: input + id: variant + attributes: + label: Arrangement and region + description: For example "French, FR, native" or "Hindi, InScript, devanagari digits". + validations: + required: true + - type: textarea + id: key + attributes: + label: Which key is wrong? + description: Say which key, which layer it is on (normal, Shift, AltGr or symbols), what it types now and what it should type. + validations: + required: true + - type: textarea + id: reference + attributes: + label: Reference + description: A link to the official layout, or a photo of a real keyboard, that shows the correct key. diff --git a/.github/ISSUE_TEMPLATE/new_language.yml b/.github/ISSUE_TEMPLATE/new_language.yml new file mode 100644 index 0000000..74fa64f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/new_language.yml @@ -0,0 +1,56 @@ +name: New language +description: Ask for a keyboard for a language that is not here yet +title: 'New language: ' +labels: [enhancement, language] +body: + - type: markdown + attributes: + value: | + Keyboards are available for English, Spanish, French, Hindi, Telugu and Urdu. Each language is its own package, so a new one does not make the others bigger. + - type: input + id: language + attributes: + label: Language + description: The name and the language code, for example Tamil (ta). + validations: + required: true + - type: input + id: script + attributes: + label: Script + description: For example Latin, Devanagari, Arabic or Cyrillic. Say if it is written right to left. + validations: + required: true + - type: textarea + id: standard + attributes: + label: Keyboard layout to follow + description: The layout people already use, such as a national standard, InScript, or the one on their phone or computer. A link to its reference chart helps most. + validations: + required: true + - type: textarea + id: arrangements + attributes: + label: Other arrangements + description: For example a phonetic layout that maps Latin keys to the letters of the language, or an alphabetical order. + - type: textarea + id: regions + attributes: + label: Regions or digits + description: Countries whose keyboards differ, or digit styles (such as Latin and native digits) that people need. + - type: textarea + id: special + attributes: + label: Special characters + description: Letters, signs that sit on a letter, ligatures or punctuation that are easy to get wrong. + - type: dropdown + id: help + attributes: + label: Can you help review it? + description: A native speaker checking the keys is the best way to get a layout right. + options: + - Yes, I can check the layout + - Yes, and I can write the layout + - No, I only need it + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/new_region.yml b/.github/ISSUE_TEMPLATE/new_region.yml new file mode 100644 index 0000000..917b453 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/new_region.yml @@ -0,0 +1,50 @@ +name: Region-specific layout +description: Ask for a regional variant of a keyboard that already exists +title: 'Region: ' +labels: [enhancement, region] +body: + - type: dropdown + id: language + attributes: + label: Language + options: + - English + - Spanish + - French + - Hindi + - Telugu + - Urdu + - Another language + validations: + required: true + - type: input + id: region + attributes: + label: Region + description: The country or area, for example Ireland for English or Germany for French. + validations: + required: true + - type: textarea + id: difference + attributes: + label: How does the keyboard differ? + description: Which keys sit in other places or type other characters compared with the closest layout we have, for example AZERTY instead of QWERTY, or a different @ or currency key. + validations: + required: true + - type: textarea + id: reference + attributes: + label: Reference + description: A link to the official layout or a picture of a real keyboard from that region. + validations: + required: true + - type: dropdown + id: help + attributes: + label: Can you help review it? + options: + - Yes, I can check the layout + - Yes, and I can write the layout + - No, I only need it + validations: + required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 3472f05..fa34b61 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,6 +2,10 @@ +## Packages affected + + + ## Related issues @@ -10,5 +14,6 @@ - [ ] The change is focused on one thing - [ ] `CHANGELOG.md` has a short `[Unreleased]` entry -- [ ] README or docs are updated if behavior changed +- [ ] Tests are added or updated, including an `axe` check for new components and `validateLayout` for a new or changed layout +- [ ] The package README is updated if exports or props changed - [ ] No secrets or personal data are included diff --git a/.github/social-preview.html b/.github/social-preview.html new file mode 100644 index 0000000..84b680b --- /dev/null +++ b/.github/social-preview.html @@ -0,0 +1,82 @@ + +
+

Saccade
Keyboards

+

On-screen keyboards for React

+
6 languagesLight & darkAccessible
+
+ + + + + + + + + + + + + + + + + + diff --git a/.github/social-preview.png b/.github/social-preview.png new file mode 100644 index 0000000..1858638 Binary files /dev/null and b/.github/social-preview.png differ diff --git a/.github/workflows/init.yml b/.github/workflows/init.yml deleted file mode 100644 index ba8a38d..0000000 --- a/.github/workflows/init.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Init - -# Run once from the Actions tab in a repository created from the template. -# Replaces the template's owner and repository name, then opens a pull request. -on: - workflow_dispatch: - -permissions: - contents: write - pull-requests: write - -jobs: - init: - if: github.repository != 'coldsofttech/js-public-repo-template' - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@v7 - - - name: Replace placeholders - env: - REPO: ${{ github.repository }} - OWNER: ${{ github.repository_owner }} - NAME: ${{ github.event.repository.name }} - run: | - git ls-files -z -- . \ - ':(exclude).github/workflows' ':(exclude)LICENSE' ':(exclude).pre-commit-config.yaml' \ - | xargs -0 grep -lZ -e coldsofttech -e public-repo-template \ - | xargs -0 -r sed -i \ - -e "s|coldsofttech/public-repo-template|$REPO|g" \ - -e "s|public-repo-template|$NAME|g" \ - -e "s|coldsofttech|$OWNER|g" - - - name: Open pull request - env: - GH_TOKEN: ${{ github.token }} - BASE: ${{ github.event.repository.default_branch }} - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git switch -c chore/init-template - git add -A - if git diff --cached --quiet; then - echo "Nothing to change" - exit 0 - fi - git commit -m "chore: initialize repository from template" - git push origin chore/init-template - gh pr create --base "$BASE" --head chore/init-template \ - --title "chore: initialize repository from template" \ - --body "Replaces the template owner and repository name. Review, merge, then delete \`.github/workflows/init.yml\`." diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b514757..21a1667 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,7 +1,7 @@ name: Release # Publishes to npm when a GitHub release is published, using trusted publishing (no stored tokens). -# Tag the release as @, for example @scope/example-react-package@1.0.0. +# Tag the release as @, for example @coldsofttech/saccade-keyboards@1.0.0. on: release: types: [published] @@ -31,6 +31,8 @@ jobs: echo "PACKAGE=$package" >> "$GITHUB_ENV" - run: npm run build - run: npm run check:packages + # dist is not in the repository, and npm pack does not create the folder it writes to. + - run: mkdir -p dist - run: npm pack --workspace "$PACKAGE" --pack-destination dist - uses: actions/upload-artifact@v7 with: @@ -53,4 +55,5 @@ jobs: with: name: dist path: dist/ - - run: npm publish dist/*.tgz --provenance --access public + # The leading ./ matters: without it npm reads dist/ as a GitHub user/repo, not a file. + - run: npm publish ./dist/*.tgz --provenance --access public diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 3662224..631e5d0 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -40,6 +40,7 @@ jobs: --config p/owasp-top-ten --config p/github-actions --exclude '*.test.js' + --exclude '*.test.jsx' --exclude-rule yaml.github-actions.security.github-actions-mutable-action-tag.github-actions-mutable-action-tag --error --sarif --output semgrep.sarif diff --git a/.gitignore b/.gitignore index 0da6047..a278127 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,7 @@ desktop.ini node_modules/ dist/ coverage/ + +.temp/ +CLAUDE.md +.claude/ diff --git a/.size-limit.json b/.size-limit.json index 033d1d4..b2fd6c3 100644 --- a/.size-limit.json +++ b/.size-limit.json @@ -1,9 +1,58 @@ [ { - "name": "@scope/example-react-package", - "path": "packages/example-react-package/dist/index.js", + "name": "@coldsofttech/saccade-keyboards", + "path": "packages/saccade-keyboards/dist/index.js", "import": "*", - "limit": "2 kB", + "limit": "4 kB", + "ignore": ["react", "react-dom"] + }, + { + "name": "@coldsofttech/saccade-keyboards/core", + "path": "packages/saccade-keyboards/dist/core/index.js", + "import": "*", + "limit": "4.5 kB", "ignore": ["react", "react-dom"] + }, + { + "name": "@coldsofttech/saccade-en", + "path": "packages/saccade-en/dist/index.js", + "import": "*", + "limit": "2 kB", + "ignore": ["@coldsofttech/saccade-keyboards"] + }, + { + "name": "@coldsofttech/saccade-es", + "path": "packages/saccade-es/dist/index.js", + "import": "*", + "limit": "2 kB", + "ignore": ["@coldsofttech/saccade-keyboards"] + }, + { + "name": "@coldsofttech/saccade-fr", + "path": "packages/saccade-fr/dist/index.js", + "import": "*", + "limit": "2 kB", + "ignore": ["@coldsofttech/saccade-keyboards"] + }, + { + "name": "@coldsofttech/saccade-hi", + "path": "packages/saccade-hi/dist/index.js", + "import": "*", + "limit": "2 kB", + "ignore": ["@coldsofttech/saccade-keyboards"] + }, + { + "name": "@coldsofttech/saccade-te", + "path": "packages/saccade-te/dist/index.js", + "import": "*", + "limit": "2 kB", + "ignore": ["@coldsofttech/saccade-keyboards"] + }, + { + "name": "@coldsofttech/saccade-ur", + "path": "packages/saccade-ur/dist/index.js", + "import": "*", + "limit": "2 kB", + "ignore": ["@coldsofttech/saccade-keyboards"] } ] diff --git a/CHANGELOG.md b/CHANGELOG.md index 761a1a5..4fc5a5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,3 +3,18 @@ All notable changes to this project are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/). ## [Unreleased] + +## [0.1.0] - 2026-10-31 + +### Added + +- On-screen keyboard for React with a built-in light and dark theme, picked by language or built from your own layout. +- The keyboard keeps a minimum width (430 px, or 600 px with the number pad) so its keys never overlap. +- Keyboards 800 px wide or less show only the typed character on each key; Shift and AltGr symbols appear when those keys are pressed. +- `currencypad` option adds a currency key before the space key that shows a row of currency symbols. +- Headless hook, a helper to apply key presses to text, and a layout checker for building custom keyboards. +- English keyboards for the US, GB, AU, CA and IN, Spanish keyboards for Spain, Mexico, Argentina, Colombia, the US and Latin America, and French keyboards for France, Belgium, Canada and Switzerland, with the letters in the order of the region or alphabetical. +- Hindi and Telugu keyboards in InScript and phonetic layouts, and Urdu keyboards in standard and phonetic layouts, with Latin or native digits. +- Keyboards laid out like real ones: number row, tall Enter, Shift, Caps Lock and AltGr, with keys that show both characters (such as 1 and !) and control keys that show an icon and a name. +- Optional number pad with Num Lock, and a decimal sign that follows the language. +- Choose the font of the keys: the device's own font (default), sans, serif, mono or any font you load. diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 127baef..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,67 +0,0 @@ -# Working rules for this repository - -These override default behavior. Follow them exactly. - -1. **Ask before coding**: if anything is ambiguous or you have a design/scope question before - writing code, use `AskUserQuestion` to ask first — don't guess, implement, and explain the - guess afterward. - -2. **Running tests/lint/commit/push**: don't run `npm test`, `npm run lint`, `git commit`, or - `git push` unless the user explicitly asks for it, or it's genuinely necessary to verify the - specific change you just made (e.g. confirming a bug fix actually fixes the bug). Implementing - or fixing code does not by itself imply "and then test/lint/commit/push it." - - When a test run is genuinely necessary, scope it narrowly — the specific test file or - workspace package the change touches (e.g. `npx vitest run packages/`) — never a full-suite run, unless the user explicitly asked for one. - -3. **Investigation budget [IMPORTANT]**: if a task needs investigation (reading code, searching, tracing a - bug), budget roughly 1,000 tokens of exploration for it. If you hit that limit and need more, - stop and ask the user for permission before spending more. - -4. **Claude session reference**: While creating commit and raising PR for GitHub, do not - include Generated by Claude Code and Claude session references within the description. - -5. **Commit Monitoring**: While raising commit, do not keep running the Claude, instead use monitoring to - avoid spending tokens with a hook to identify when it is done and act on it. - -6. **CHANGELOG.md entry style**: keep new `[Unreleased]` entries short — one or two lines, - what changed from a reader's point of view, no file paths/function names/implementation - rationale. That level of detail belongs in the commit message or PR description, not the - changelog. This applies going forward only — don't rewrite past released versions' entries - to match. - -7. **README.md**: add/update the README.md from a user perspective rather than technical. For technical - please create documents within docs/ folder. - -## React conventions - -Apply these when writing or changing package code. They follow `docs/development.md`. - -8. **Peer dependencies**: `react` and `react-dom` are `peerDependencies` (`^18 || ^19`), never - bundled. Don't import `react/cjs/*` or add React to `dependencies`. After changing build config, - run `npm run check:packages` for the affected package. - -9. **Components and hooks**: - - Named exports only, no default exports. - - Function components with `.jsx` for JSX; hooks are `use*` in `.js` and have their own tests. - - Document props with a JSDoc `@typedef` and export it. No PropTypes, no TypeScript source. - - Data comes in through props or callbacks. Never read global state or import from an app. - -10. **Hooks rules**: follow the Rules of Hooks (`eslint-plugin-react-hooks`). Keep effect and memo - dependency arrays complete, clean up subscriptions and timers, and don't use effects for values - that can be derived during render. - -11. **Styles**: CSS modules, shipped as one `style.css` that consumers import. No global CSS, no - CSS-in-JS. - -12. **Accessibility**: use semantic elements first, label every control, and keep `jsx-a11y` - clean. Don't silence a11y lint rules without a comment saying why. - -13. **Tests**: Vitest, jsdom, Testing Library and `vitest-axe`. Query by role and label, not test - IDs or class names. Use `userEvent` for interactions. Every component gets an `axe` check, - and tests must pass under StrictMode (the default via `vitest.setup.js`). - -14. **Client-only by default**: don't use Server Component features. If an entry needs - `"use client"`, make sure the build keeps the directive. - -15. **Public API changes**: adding or removing an export, or changing a peer range, needs a - short `[Unreleased]` changelog entry and an update to the package README. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 7012fbe..4bc94a8 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -2,7 +2,7 @@ ## Our pledge -We pledge to make participation in this community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, appearance, race, religion, or sexual identity and orientation. +We pledge to make participation in Saccade Keyboards a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, appearance, race, religion, or sexual identity and orientation. ## Expected behavior @@ -17,11 +17,15 @@ We pledge to make participation in this community a harassment-free experience f - Publishing others' private information without permission - Other conduct that would reasonably be considered inappropriate in a professional setting +## Scope + +This code applies to issues, pull requests, discussions and any other space where people take part in this project. + ## Enforcement Maintainers may remove, edit or reject contributions that violate this code, and may temporarily or permanently ban contributors for behavior they deem inappropriate. -Report violations to the maintainers at ``. All reports are reviewed and handled confidentially. +Report violations privately to the maintainer, [@coldsofttech](https://github.com/coldsofttech), by opening a report from the [Security tab](https://github.com/coldsofttech/SaccadeKeyboards/security/advisories/new) and saying that it is a conduct report. All reports are reviewed and handled confidentially. ## Attribution diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 277073f..066eab1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,13 +6,37 @@ Thanks for taking the time to contribute! By participating you agree to follow t Open an issue using one of the provided forms. Search existing issues first. For security problems, do **not** open an issue; follow [SECURITY.md](SECURITY.md). +| Form | Use it for | +| ---------------------- | ---------------------------------------------------------------------------- | +| Bug report | The component or a keyboard does not work as expected | +| Layout fix | A key is wrong, missing or in the wrong place on an existing keyboard | +| New language | A language that has no keyboard yet | +| Region-specific layout | A regional variant of a language we have, such as another country's keyboard | +| Feature request | A new option or an improvement to the component | + +For layouts, a link to the official layout or a photo of a real keyboard is the most useful thing you can add. If you speak the language, say so: a native speaker checking the keys is the best way to get a layout right. + +## How the repository is organized + +Each language is its own npm package under `packages/`, named `@coldsofttech/saccade-`. `packages/saccade-keyboards` holds the `Keyboard` component and the code shared by every language. `examples/demo` is a small app for trying the packages. See [docs/development.md](docs/development.md) for the details and [docs/adding-a-language.md](docs/adding-a-language.md) to add a language or a region. + ## Submitting changes 1. Fork the repository and create a branch from `main`. -2. Make your change, keeping it focused on one thing. +2. Make your change, keeping it focused on one thing, ideally one package. 3. Install dependencies and the commit checks: `npm ci && pip install pre-commit && pre-commit install --hook-type pre-commit --hook-type post-commit`. Node 22 or newer is required (see `.nvmrc`). -4. Add an entry under `[Unreleased]` in `CHANGELOG.md` (one or two lines, written from a reader's point of view). -5. Open a pull request and fill in the template. +4. Add or update tests for the package you changed. Components need an `axe` accessibility check, and every layout needs a test that runs `validateLayout`. You can run one package with `npx vitest run packages/saccade-fr`. +5. Add an entry under `[Unreleased]` in `CHANGELOG.md` (one or two lines, written from a reader's point of view). If you add or remove an export or option, update that package's README too. +6. Open a pull request and fill in the template. + +## Code guidelines + +- Plain JavaScript with JSDoc types, `.jsx` for components, named exports only. +- React and `react-dom` stay peer dependencies and are never bundled. +- Layouts are data. Follow the [layout format](docs/layout-format.md) and the real keyboard you are copying, key by key. +- The component never reads global state and never imports from an app. Data comes in through props and callbacks. +- Components must work with the keyboard and screen readers. +- Styles are CSS modules, shipped as one `style.css`. ## Pull request rules @@ -22,4 +46,4 @@ Open an issue using one of the provided forms. Search existing issues first. For ## Commit messages -Use [Conventional Commits](https://www.conventionalcommits.org/), for example `fix: handle empty input`. +Use [Conventional Commits](https://www.conventionalcommits.org/), for example `fix: handle empty input` or `feat: add saccade-ta Tamil keyboard`. diff --git a/README.md b/README.md index 4c6664e..6b229bd 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,94 @@ -# react-SaccadeKeyboards +# Saccade Keyboards -A ready-to-use starting point for public React component and hook libraries (React 18 and 19). It comes with community files, issue and pull request templates, automated dependency updates, security checks, testing, and publishing to npm already in place. +[![CI](https://github.com/coldsofttech/SaccadeKeyboards/actions/workflows/ci.yml/badge.svg)](https://github.com/coldsofttech/SaccadeKeyboards/actions/workflows/ci.yml) +[![npm](https://img.shields.io/npm/v/@coldsofttech/saccade-keyboards.svg)](https://www.npmjs.com/package/@coldsofttech/saccade-keyboards) +[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![React 18 | 19](https://img.shields.io/badge/react-18%20%7C%2019-61dafb.svg)](https://react.dev) -## What you get +On-screen keyboards for React apps. Show a keyboard for a language next to any text field, so people can type in their own language on a device that has no keyboard for it. Install only the languages you need. -- One or more npm packages in a single repository, each built and tested on its own -- An example React package (component, hook, styles, tests) and a small demo app to copy from -- Checks that React is never bundled, plus bundle-size limits -- Automatic checks on every pull request across Node 22, 24 and 26 -- Publishing to npm when you create a GitHub release, with no stored tokens -- Contribution, conduct, support and security-reporting guidelines -- Issue forms and a pull request template -- Automated dependency updates, dependency review and weekly vulnerability checks -- Secret scanning before every commit +## Why Saccade Keyboards -## Getting started +- **Laid out like real keyboards.** Number row, tall Enter, Shift, Caps Lock and AltGr, with keys that show both characters (such as `1` and `!`). +- **Your language, your region.** Keyboards follow the layout people already know, such as QWERTY or AZERTY, and the keyboards of different countries. +- **Install only what you use.** Each language is its own package and is loaded the first time it is needed. +- **Works with any input.** The keyboard tells you which key was pressed and you decide where the text goes. The focus and the caret stay in your field. +- **Light and dark.** Built-in themes, your own colours, and a choice of font and key size. +- **Fits the space.** On a narrow keyboard (800 px or less) each key shows only the character it types, and Shift and AltGr show the rest when pressed. +- **Accessible.** Every key can be read by a screen reader and used from the keyboard. +- **Small.** React is never bundled, and there are no other dependencies. -1. Click **Use this template** on GitHub and create your new repository. -2. Run the **Init** workflow from the Actions tab and merge the pull request it opens. This fills in your owner and repository name. Then delete `.github/workflows/init.yml`. -3. Rename `packages/example-react-package`, set your npm scope (replace `@scope`) and fill in the details in its `package.json`. Replace the example component, and update `.size-limit.json` and the demo imports. -4. Add your contact details in `CODE_OF_CONDUCT.md` and replace this description with your own. -5. Apply the repository protections and set up npm publishing by following the [setup guide](docs/repo-setup.md). -6. Install the checks that run on each commit: `npm ci && pip install pre-commit && pre-commit install --hook-type pre-commit --hook-type post-commit`. +## Languages -## Using a package +| Package | Language | Layouts | Regions and digits | +| ------------------------------------------------- | -------- | --------------------------------------------------------------- | --------------------------------- | +| [`@coldsofttech/saccade-en`](packages/saccade-en) | English | QWERTY (the order of each region) or alphabetical | US, GB, AU, CA, IN | +| [`@coldsofttech/saccade-es`](packages/saccade-es) | Spanish | The keyboard of each region, or alphabetical | ES, MX, AR, CO, US, Latin America | +| [`@coldsofttech/saccade-fr`](packages/saccade-fr) | French | The keyboard of each region (AZERTY or QWERTZ), or alphabetical | FR, BE, CA, CH | +| [`@coldsofttech/saccade-hi`](packages/saccade-hi) | Hindi | InScript or phonetic | Latin or Devanagari digits | +| [`@coldsofttech/saccade-te`](packages/saccade-te) | Telugu | InScript or phonetic | Latin or Telugu digits | +| [`@coldsofttech/saccade-ur`](packages/saccade-ur) | Urdu | Standard or phonetic | Latin or Urdu digits | + +[`@coldsofttech/saccade-keyboards`](packages/saccade-keyboards/README.md) is the main package. It has the `Keyboard` component and loads a language only when needed. + +Need another language or a keyboard from your country? [Ask for it](https://github.com/coldsofttech/SaccadeKeyboards/issues/new/choose), or see [adding a language](docs/adding-a-language.md) if you would like to write it. + +## Install + +```bash +npm install @coldsofttech/saccade-keyboards @coldsofttech/saccade-en +``` + +Add a package for each language you want. React and `react-dom` 18 or 19 must be installed in your app. Use a bundler that understands ES modules and dynamic `import()`, such as Vite, webpack or Rollup. + +## Use it ```jsx -import { ExampleComponent } from '@scope/example-react-package'; -import '@scope/example-react-package/style.css'; +import { Keyboard, registerLanguage } from '@coldsofttech/saccade-keyboards'; +import '@coldsofttech/saccade-keyboards/style.css'; + +registerLanguage('en', () => import('@coldsofttech/saccade-en')); -; + console.log(key)} theme="dark" font="native" />; ``` -React and `react-dom` 18 or 19 must be installed in your app. +Each language is installed separately. If one is missing, the keyboard tells you which package to install. To pick a region or an arrangement, use the language package's `createLayout` and pass the result as `layout`: + +```jsx +import { createLayout } from '@coldsofttech/saccade-fr'; + +; +``` + +The [package guide](packages/saccade-keyboards/README.md) covers typing into a field, all the options (theme, font, number pad, currency key), sizing and building your own keyboard. + +## Good to know + +- **Size:** the keyboard fills the width of the element it sits in. It needs at least 430 px, or 600 px with the number pad. +- **Narrow keyboards:** at 800 px or less, each key shows only what it types. Press Shift or AltGr to see the other characters. +- **Right to left and complex scripts:** keys always sit left to right, like the keys on a real keyboard. A dotted circle is shown for signs that sit on a letter. +- **Client-side only:** the keyboard runs in the browser and sends nothing anywhere. + +## Try it + +The demo app lets you pick a language, region, theme, font, key size and width, and type into a text box. + +```bash +npm ci +npm run build +npm run dev +``` + +## Documentation + +- The [package guide](packages/saccade-keyboards/README.md) for all options. +- [CHANGELOG.md](CHANGELOG.md) for what changed. +- [docs/](docs/README.md) for how the repository works: [architecture](docs/architecture.md), [layout format](docs/layout-format.md), [adding a language](docs/adding-a-language.md), [development](docs/development.md), [security](docs/security.md), [third-party notices](docs/third-party-notices.md) and [repository setup](docs/repo-setup.md). +- [SUPPORT.md](SUPPORT.md) for where to ask questions and report bugs. ## Contributing -See [CONTRIBUTING.md](CONTRIBUTING.md). Please report security issues privately as described in [SECURITY.md](SECURITY.md). +Languages and regional layouts are best written with someone who uses the keyboard every day, so native speakers are especially welcome. See [CONTRIBUTING.md](CONTRIBUTING.md). Please report security issues privately as described in [SECURITY.md](SECURITY.md). Everyone taking part follows the [Code of Conduct](CODE_OF_CONDUCT.md). ## License diff --git a/SECURITY.md b/SECURITY.md index cb8180c..12cdb4e 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,7 +2,19 @@ ## Supported versions -Only the latest release receives security fixes. +Only the latest release of each `@coldsofttech/saccade-*` package receives security fixes. + +## What counts as a vulnerability + +Saccade Keyboards runs in the browser next to text that people type, so these are in scope: + +- A keyboard or layout that makes the component run script, inject HTML or reach the host page +- A key press that is reported to the host app, or sent anywhere, other than through `onKey` +- Anything that makes the keyboard read or change the text field, or other data on the page, by itself +- A layout that crashes, freezes or exhausts memory in the component +- Vulnerabilities in a dependency that a package exposes to users + +A key that types the wrong character is a bug, not a vulnerability. Use the **Layout fix** form for it. ## Reporting a vulnerability @@ -10,4 +22,4 @@ Please do **not** report vulnerabilities in public issues. Use GitHub's private reporting: go to the **Security** tab of this repository and choose **Report a vulnerability**. -Include what you found, how to reproduce it, and the potential impact. You can expect an acknowledgement within a few days and updates as the fix progresses. Once a fix is released, we will credit you if you wish. +Include the package and version, what you found, how to reproduce it, and the potential impact. You can expect an acknowledgement within a few days and updates as the fix progresses. Once a fix is released, we will credit you if you wish. diff --git a/SUPPORT.md b/SUPPORT.md index dd00710..11e98b6 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -1,5 +1,8 @@ # Support -- **Questions and ideas:** use [GitHub Discussions](../../discussions) if enabled, otherwise open an issue. -- **Bugs:** open an issue with the bug report form. +- **Using the keyboard:** start with the [package guide](packages/saccade-keyboards/README.md) for the options, and the README of the language package you installed, for example `packages/saccade-fr/README.md`. +- **Questions and ideas:** open an issue and choose the feature request form, or ask in [GitHub Discussions](../../discussions) if it is enabled. +- **Bugs:** open an issue with the bug report form. Name the package and its version, the language, arrangement and region, and the width of the keyboard. A screenshot helps. +- **A wrong or missing key:** use the layout fix form, and link to the official layout or a photo of a real keyboard. +- **A new language or region:** use the new language or region-specific layout form. - **Security problems:** see [SECURITY.md](SECURITY.md). diff --git a/docs/README.md b/docs/README.md index 97d821c..180e744 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,7 +2,10 @@ Technical documentation for this project lives in this folder. The top-level [README](../README.md) is for users. -- [Architecture](architecture.md): how checks, workflows and releases fit together. +- [Architecture](architecture.md): how the keyboard package, checks, workflows and releases fit together. +- [Layout format](layout-format.md): the data that describes a keyboard, and how to check one. +- [Adding a language](adding-a-language.md): the steps to add a language, an arrangement or a region. - [Security](security.md): security layers, scanners and safe browser coding. - [Development](development.md): Node setup, scripts, workspaces, CI and the release flow. -- [Repository setup](repo-setup.md): protections, ruleset and manual steps for new repos. +- [Third-party notices](third-party-notices.md): what the packages depend on and the licenses they use. +- [Repository setup](repo-setup.md): protections, ruleset and manual steps for the repository. diff --git a/docs/adding-a-language.md b/docs/adding-a-language.md new file mode 100644 index 0000000..ec5c55a --- /dev/null +++ b/docs/adding-a-language.md @@ -0,0 +1,36 @@ +# Adding a language + +How to add a new language, a new arrangement of a language (such as a phonetic layout) or a new region. For what a layout contains, see [layout format](layout-format.md). Use `packages/saccade-en` as the reference, and `packages/saccade-fr` for a language with regions of its own. + +## Before you start + +- Find the official layout, or a photo of a real keyboard, for every layer: normal, Shift, AltGr and symbols. Copy it key by key. +- Ask a native speaker to check the result. Write down who checked it in the pull request. +- Open the **New language** or **Region-specific layout** issue form first if you want to agree on the scope. + +## A new language + +1. Create `packages/saccade-`, copying the files of an existing language package. The package is named `@coldsofttech/saccade-` and starts at version `0.1.0`. +2. Write the layouts in `src/index.js`. Export `layout` (the default) and `createLayout(arrangement, region)`, and list any font the script needs in `fonts`. `saccade-keyboards` is a peer dependency, imported from `@coldsofttech/saccade-keyboards/core`. +3. Add `src/index.test.js`. It runs `validateLayout` on every arrangement and region, and checks the keys that are easy to get wrong. +4. Add a `README.md` for the package: what it provides, its arrangements, regions and digits, and how to register it. +5. Add the package to `.size-limit.json`, to the demo in `examples/demo/src/main.jsx` (its layout, regions and arrangements) and to `examples/demo/package.json`. +6. Add it to the language table in the root `README.md`, to the bug report, feature request and layout fix forms in `.github/ISSUE_TEMPLATE`, and to the package list in [architecture](architecture.md). +7. Add an `[Unreleased]` entry in `CHANGELOG.md`. +8. Run `npm run build`, then `npm run check:packages`, then `npx vitest run packages/saccade-`. + +After the first release, register the package as a trusted publisher on npm (see [repository setup](repo-setup.md)). + +## A new arrangement or region + +1. Add it to the language's `createLayout`, reusing the shared helpers instead of repeating keys that do not change. +2. Add the region or arrangement to the test, so it is checked by `validateLayout`. +3. Add it to the demo, the language's README and the root README table. +4. Add an `[Unreleased]` entry in `CHANGELOG.md`. + +## Checking your work + +- Open the demo (`npm run build`, then `npm run dev`) and press every key on every layer. +- Look at the keyboard at 430 px and at 801 px or wider. At 800 px or less the Shift and AltGr characters are hidden until those keys are pressed. +- Check with the number pad and the currency pad on, and in the dark theme. +- Check a script that has signs sitting on a letter: they should show a dotted circle. diff --git a/docs/architecture.md b/docs/architecture.md index a1e0c53..24cd54e 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,6 +1,6 @@ # Architecture -How the pieces of this template fit together. For day-to-day commands, see [development](development.md). For one-time repository settings, see [repository setup](repo-setup.md). +How the pieces of Saccade Keyboards fit together. For day-to-day commands, see [development](development.md). For one-time repository settings, see [repository setup](repo-setup.md). ## Overview @@ -30,7 +30,7 @@ flowchart LR ```text packages// one publishable package: src/, tests next to source, own vite.config.js -examples/demo/ small Vite app that consumes the packages +examples/demo/ the demo app, which uses every package scripts/ repository helper scripts (see scripts/README.md) vitest.config.js runs every package as a Vitest project, sets coverage thresholds eslint.config.js shared lint rules @@ -49,13 +49,12 @@ Each package owns its version in its `package.json`, builds on its own with Vite | `audit.yml` | Weekly, manual, and pull requests that change `package-lock.json` | Runs `npm audit` on production dependencies to catch newly published vulnerabilities. | | `security.yml` | Pull request, push to `main`, weekly, manual | Runs Semgrep and uploads SARIF to the Security tab. Not part of the required `ci` check. | | `release.yml` | GitHub release published | Checks the tag matches the package version, builds, then publishes that package to npm. | -| `init.yml` | Manual | One-time placeholder replacement in a repo created from the template. Skipped in the template itself. | CodeQL runs through GitHub's default setup rather than a workflow file. ## Branch governance -The `main` ruleset in `.github/rulesets/main.json` is applied by `scripts/apply-repo-settings.js`. It requires: +The `main` ruleset in `.github/rulesets/main.json` is applied by hand (see [repository setup](repo-setup.md)). It requires: - a pull request with a code owner review and resolved conversations, squash merge only - the `ci` and `dependency-review` checks, with the branch up to date @@ -67,9 +66,35 @@ Repository admins can bypass through a pull request so a solo maintainer is not ## Release flow 1. Bump `version` in the package's `package.json` in a pull request. -2. After merge, publish a GitHub release tagged `@`, for example `@scope/example-react-package@1.0.0`. +2. After merge, publish a GitHub release tagged `@`, for example `@coldsofttech/saccade-keyboards@1.0.0`. 3. `release.yml` builds and packs that package in one job, then a second job in the `npm` environment publishes it using OpenID Connect. No token is stored, and only the publish job gets the `id-token` permission. -## Template lifecycle +## Packages -GitHub copies files, not settings. A new repo therefore needs three steps: run **Init** to replace the template owner and name, rename the example package and set your npm scope, and run `npm run repo:settings` to apply protections and the ruleset. +| Package | What it is | +| --------------------------------- | --------------------------------------------------------------------------- | +| `@coldsofttech/saccade-keyboards` | The `Keyboard` component, the registry and the layout helpers shared by all | +| `@coldsofttech/saccade-en` | English: US, GB, AU, CA and IN, QWERTY or alphabetical | +| `@coldsofttech/saccade-es` | Spanish: ES, MX, AR, CO, US and Latin America | +| `@coldsofttech/saccade-fr` | French: FR, BE, CA and CH | +| `@coldsofttech/saccade-hi` | Hindi: InScript or phonetic, with Latin or Devanagari digits | +| `@coldsofttech/saccade-te` | Telugu: InScript or phonetic, with Latin or Telugu digits | +| `@coldsofttech/saccade-ur` | Urdu: standard or phonetic, with Latin or Urdu digits | + +## Keyboard package + +`packages/saccade-keyboards` has two entry points: + +| Entry | What it is | +| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | +| `@coldsofttech/saccade-keyboards` | `Keyboard` picked by `language`, with a registry that loads each layout when first needed. | +| `@coldsofttech/saccade-keyboards/core` | The engine: `Keyboard` for a layout object, `useKeyboard`, `applyKey`, `validateLayout` and the layout helpers. | +| `@coldsofttech/saccade-keyboards/style.css` | The styles, imported once by the app. | + +Each language is its own package, `@coldsofttech/saccade-` (for example `saccade-en`), holding its layouts and depending on `saccade-keyboards/core` as a peer. The layout format is described in [layout format](layout-format.md), and the steps to add a language are in [adding a language](adding-a-language.md). + +### Sizing + +The keyboard fills the width of its parent and needs at least 430 px, or 600 px with the number pad. At 800 px or less, a CSS container query hides the second and third characters of a key (the Shift symbol and the AltGr corner) until Shift or AltGr is pressed, because the keys are too small to show them without overlapping. + +`Keyboard` never edits a field. It reports key presses through `onKey`, and the app applies them, with `applyKey` for a plain input or textarea. Keys sit left to right for every language, because they follow the physical keys, so the keyboard is always `dir="ltr"`. diff --git a/docs/development.md b/docs/development.md index 917442a..2d2974f 100644 --- a/docs/development.md +++ b/docs/development.md @@ -3,49 +3,53 @@ ## Setup - Node 22 or newer. `.nvmrc` pins 24, the version used for linting, building and releasing. -- `npm ci` installs every workspace (`packages/*`, `examples/*`). +- `npm ci` installs every workspace (`packages/*` and `examples/*`). - `pip install pre-commit && pre-commit install --hook-type pre-commit --hook-type post-commit`. ## Scripts All scripts run from the repository root. -| Script | Purpose | -| --------------------------------- | ---------------------------------------------------------------- | -| `npm run dev` | Start the demo app (build packages first with `npm run build`) | -| `npm run build` | Build every package (Vite library mode, then `.d.ts` from JSDoc) | -| `npm run lint` | ESLint across the repository | -| `npm run format` / `format:check` | Prettier write / check, including Markdown | -| `npm test` / `test:coverage` | Vitest; coverage thresholds live in `vitest.config.js` | -| `npm run check:packages` | `publint` and `@arethetypeswrong/cli` on each built package | +| Script | Purpose | +| --------------------------------- | ----------------------------------------------------------------------------------------- | +| `npm run build` | Build every package (Vite library mode, then `.d.ts` from JSDoc); the engine builds first | +| `npm run dev` | Start the demo in `examples/demo` (build packages first) | +| `npm run lint` | ESLint across the repository | +| `npm run format` / `format:check` | Prettier write / check, including Markdown | +| `npm test` / `test:coverage` | Vitest; coverage thresholds live in `vitest.config.js` | +| `npm run check:packages` | `publint` and `@arethetypeswrong/cli` on each built package | ## Conventions - Plain JavaScript (`.jsx` for components) with ES modules. Types are written as JSDoc and checked through `jsconfig.json`. - Each package declares `exports`, `types`, `files` and `sideEffects: false`, and builds and tests on its own. Its test environment is set in its `vite.config.js`. - React and `react-dom` are `peerDependencies` (`^18 || ^19`), never bundled. `npm run check:packages` fails if a build contains React. -- Components and hooks are named exports and document props with JSDoc typedefs. Styles are CSS modules, shipped as one `style.css` that consumers import (`@scope/pkg/style.css`). +- Components and hooks are named exports and document props with JSDoc typedefs. Styles are CSS modules, shipped as one `style.css` that consumers import (`@coldsofttech/saccade-keyboards/style.css`). - Packages are client-only unless stated otherwise. If an entry needs `"use client"`, make sure the build keeps the directive. - Tests use Vitest, jsdom, Testing Library and `vitest-axe`; `vitest.setup.js` renders everything in StrictMode. Query by role and label. - Packages never import from an application. - Prettier owns formatting for all files. markdownlint is not used. +## Adding a language + +See [adding-a-language.md](adding-a-language.md) for a new language, a new arrangement or a new region. + ## Keeping up with the JS template This repo is created from `js-SaccadeKeyboards` and receives no automatic updates. When a fix lands there: 1. Compare the changed files with this repo and apply the fix by hand. -2. Re-check the files this template extends: `eslint.config.js`, `vitest.config.js`, `jsconfig.json`, `.github/workflows/ci.yml`, the example package and `examples/demo`. +2. Re-check the files this template extends: `eslint.config.js`, `vitest.config.js`, `jsconfig.json`, and `.github/workflows/ci.yml`. 3. Note the change in `CHANGELOG.md`. ## CI -`ci.yml` runs `lint`, `test` (Node 22, 24, 26 on Ubuntu; Node 24 on Windows and macOS, all with React 19; plus Node 24 with React 18) and `build`. `build` also builds the demo, checks tarball contents and reports gzipped bundle size against `.size-limit.json`. The final `ci` job is the single required check for the `main` ruleset. `audit.yml` runs `npm audit` weekly and when `package-lock.json` changes. +`ci.yml` runs `lint`, `test` (Node 22, 24, 26 on Ubuntu; Node 24 on Windows and macOS, all with React 19; plus Node 24 with React 18) and `build`. `build` also checks tarball contents, builds the demo against the built packages and reports gzipped bundle size against `.size-limit.json`. A new language package needs an entry in `.size-limit.json`. The final `ci` job is the single required check for the `main` ruleset. `audit.yml` runs `npm audit` weekly and when `package-lock.json` changes. ## Release 1. Set the new `version` in the package's `package.json` and merge it. -2. Publish a GitHub release tagged `@`, for example `@scope/example-react-package@1.0.0`. +2. Publish a GitHub release tagged `@`, for example `@coldsofttech/saccade-keyboards@1.0.0`. 3. `release.yml` checks the tag against the package version, builds, and publishes to npm with provenance through the `npm` environment. See [repo-setup.md](repo-setup.md) for the one-time npm setup. ## Cleanup diff --git a/docs/layout-format.md b/docs/layout-format.md new file mode 100644 index 0000000..5be9b34 --- /dev/null +++ b/docs/layout-format.md @@ -0,0 +1,63 @@ +# Layout format + +A keyboard layout is plain data. The keyboard draws it and handles the key presses, so adding a language never needs a change to the engine. A layout has no rendering code. + +```js +export const layout = { + id: 'hi', + name: 'Hindi', + dir: 'ltr', + fonts: ['Noto Sans Devanagari'], + layers: { letters: [/* rows */], symbols: [/* rows */] }, + names: { '।': 'danda' }, +}; +``` + +| Field | Required | Meaning | +| --------- | -------- | ----------------------------------------------------------------------------------------------------------- | +| `id` | Yes | Language code. It becomes the `lang` of the keyboard and the package name, `@coldsofttech/saccade-`. | +| `name` | Yes | Name of the language, used for the default label "`` on-screen keyboard". | +| `dir` | Yes | Reading direction, `ltr` or `rtl`. The keys themselves always sit left to right, like the physical keys. | +| `fonts` | No | Font families the keys need. Fonts are never bundled; the app loads them and the layout package lists them. | +| `layers` | Yes | Layer name to rows of keys. The first layer is shown first. | +| `decimal` | No | The sign of the decimal key of the number pad (`.`). Spain, for one, uses `,`. | +| `names` | No | What a screen reader says for a character, merged over the built-in punctuation names. | + +## Keys + +A row is a list of keys. `width` is the share of the row a key takes; 1 is an ordinary key. A row always fills the width of the keyboard. `rowSpan` makes a key taller: `{ type: 'enter', rowSpan: 2 }` at the end of a row also covers the row below, which then shares only the space that is left. Use it for a tall Enter at the start or the end of a row. + +| Key | What it does | +| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `{ type: 'text', value, shift?, altgr?, capital? }` | Types `value`; `shift` while shift is on, or `altgr` after AltGr (capitalised with shift if it is a letter), or `capital` while caps lock is on (É on the é key of a French keyboard, which types 2 with shift). | +| `{ type: 'shift' }` | Shift: capitals for the next key only. | +| `{ type: 'caps' }` | Caps lock: capitals until it is pressed again. | +| `{ type: 'numlock' }` | Num Lock of the number pad: its `numpad` keys are off while it is off. | +| `{ type: 'altgr' }` | AltGr: the next key types its `altgr` character. | +| `{ type: 'space' }` | Types a space. | +| `{ type: 'backspace' }`, `{ type: 'enter' }` | Reported through `onKey` for the app to apply. | +| `{ type: 'close' }` | Calls `onClose`. | +| `{ type: 'layer', to, label, name? }` | Shows another layer and turns shift off. | + +The `currencypad` prop of `Keyboard` adds a currency key before every space key at render time, taking 1 unit of the space key's width, so layouts never include it. It shows or hides a row of currency signs above the keys. + +A sign that only sits on a letter, such as a vowel sign or a virama, is shown on a dotted circle automatically. + +## Helpers + +`@coldsofttech/saccade-keyboards/core` exports helpers to write layouts briefly: `keys`, `cased`, `physicalRows`, `numpadRows`, `bottomRow`, `symbolsLayer`, `shiftKey`, `capsKey`, `altGrKey`, `backspaceKey` and `PUNCTUATION_NAMES`. + +## Checking a layout + +`validateLayout(layout)` returns a list of problems, empty when the layout is valid. Every layout package should assert this in its tests. The `language` prop of the meta `Keyboard` runs it as well and shows the problems instead of a broken keyboard. + +## Adding a language + +1. Create a package `@coldsofttech/saccade-` that exports the layout as `layout` and depends on `@coldsofttech/saccade-keyboards` as a peer. +2. Add a test that runs `validateLayout`. +3. List its font (if any) in `fonts` and document how to load it. +4. Apps register it with `registerLanguage('', () => import('@coldsofttech/saccade-'))` so `language=""` finds it, or pass the layout through the `layout` prop. + +`@coldsofttech/saccade-en` is the reference: it exports `layout` (QWERTY) and `createLayout(type, region)`. + +Composition rules (conjuncts, vowel signs joining a consonant) are not part of the format yet. A key types exactly its `value`. diff --git a/docs/repo-setup.md b/docs/repo-setup.md index 06bd633..41d214b 100644 --- a/docs/repo-setup.md +++ b/docs/repo-setup.md @@ -1,25 +1,16 @@ # Repository setup -Template repositories copy files only, not settings. After creating a repo from this template, apply the settings below. - -## Automated - -```bash -npm run repo:settings # current repo -npm run repo:settings -- owner/repo # a specific repo -``` - -This configures squash-only merging, delete branch on merge, the `skip-changelog` label, secret scanning with push protection, Dependabot alerts and security updates, CodeQL default setup, and the `main` ruleset from `.github/rulesets/main.json`. Requires the `gh` CLI with admin rights. +The repository settings below are applied by hand in the GitHub UI, or with the `gh` CLI. The `main` ruleset is stored in `.github/rulesets/main.json` and can be imported from Settings > Rules. ## Protections | Setting | Value | How | | ------------------------------- | ------------------------ | --------------------------------------------- | | Dependency graph | On | Default for public repos | -| Dependabot alerts | On | Script | -| Dependabot security updates | On | Script | -| Secret protection | On, with push protection | Script | -| CodeQL | Default setup | Script | +| Dependabot alerts | On | Settings > Advanced Security | +| Dependabot security updates | On | Settings > Advanced Security | +| Secret protection | On, with push protection | Settings > Advanced Security | +| CodeQL | Default setup | Settings > Advanced Security | | Malware alerts | On | Manual: Settings > Advanced Security | | Copilot Autofix | Off | Manual: Settings > Advanced Security > CodeQL | | Private vulnerability reporting | On | Manual: Settings > Advanced Security | @@ -37,9 +28,9 @@ Repository admins can bypass the ruleset through a pull request. This lets a sol ## Manual steps -- Tick **Template repository** on this repo (Settings > General). -- Run the **Init** workflow (Actions tab) to replace the template owner and repository name, merge its pull request, then delete `.github/workflows/init.yml`. It requires "Allow GitHub Actions to create and approve pull requests", which the settings script turns on. If the owner is an organization, change `.github/CODEOWNERS` to a team. -- Fill in the contact method in `CODE_OF_CONDUCT.md`. +- Allow squash merging only and delete head branches after merge (Settings > General). Create the `skip-changelog` label used by `.github/release.yml`, and the `language`, `region` and `layout` labels used by the issue forms (a label that does not exist is not applied). +- If the owner is an organization, change `.github/CODEOWNERS` to a team. - Run `pre-commit install --hook-type pre-commit --hook-type post-commit` locally (the post-commit type is needed for Sweepito cleanup). - Publish to npm with trusted publishing (no token): create a GitHub environment named `npm` (Settings > Environments), then on npmjs.com open each package's Settings > Trusted publisher and register this repository, the workflow `release.yml` and the `npm` environment. A package must exist on npm before it can be configured, so publish the first version manually with `npm publish --access public`, then register the trusted publisher. -- CodeQL default setup only works once the repo contains a supported language, so run the script after adding code. +- There are 7 packages to publish: `saccade-keyboards` and the languages `en`, `es`, `fr`, `hi`, `te` and `ur`. Each needs its own trusted publisher on npm. A new language package needs the same. +- CodeQL default setup only works once the repo contains a supported language, so enable it after adding code. diff --git a/docs/security.md b/docs/security.md index 95ee210..dea4309 100644 --- a/docs/security.md +++ b/docs/security.md @@ -1,6 +1,16 @@ # Security -How this template catches security problems, from the editor to a published package. To report a vulnerability, see [SECURITY.md](../SECURITY.md). +How Saccade Keyboards stays safe next to text that people type, and how security problems are caught, from the editor to a published package. To report a vulnerability, see [SECURITY.md](../SECURITY.md). + +## Handling typed text + +The keyboard sits next to fields where people may type passwords or private messages, so it keeps to a few rules. + +- It reports a key press only through `onKey`. It stores nothing and sends nothing anywhere. +- It never reads or changes the field. The host app applies the key, for example with `applyKey`. +- It makes no network requests. A language is a package the app installs and loads, and a layout is plain data that `validateLayout` checks before it is used. +- Layout text is rendered as text, never as HTML. +- Styles are scoped CSS modules, so the keyboard cannot restyle the host page. ## Layers diff --git a/docs/third-party-notices.md b/docs/third-party-notices.md new file mode 100644 index 0000000..1b80e09 --- /dev/null +++ b/docs/third-party-notices.md @@ -0,0 +1,25 @@ +# Third-party notices + +Saccade Keyboards is released under the [MIT license](../LICENSE). This page lists what its packages depend on at runtime and the licenses they use. Development tools (test runners, linters, build tools) are not shipped and are not listed. + +None of the packages has a runtime dependency, so there are no third-party licenses to carry along with them. + +## Runtime dependencies + +| Package | License | Runtime dependencies | +| --------------------------------- | ------- | -------------------------------- | +| `@coldsofttech/saccade-keyboards` | MIT | None | +| `@coldsofttech/saccade-en` | MIT | None (peer: `saccade-keyboards`) | +| `@coldsofttech/saccade-es` | MIT | None (peer: `saccade-keyboards`) | +| `@coldsofttech/saccade-fr` | MIT | None (peer: `saccade-keyboards`) | +| `@coldsofttech/saccade-hi` | MIT | None (peer: `saccade-keyboards`) | +| `@coldsofttech/saccade-te` | MIT | None (peer: `saccade-keyboards`) | +| `@coldsofttech/saccade-ur` | MIT | None (peer: `saccade-keyboards`) | + +`react` and `react-dom` (18 or 19) are peer dependencies of `@coldsofttech/saccade-keyboards` that your app provides. They are never bundled, and they are released under the MIT license by their own authors. + +## Notes + +- **Fonts.** No font is bundled. The keys use the fonts of the device by default. If you load a font for a script, for example a Noto font, that font has its own license, which you accept when you add it to your app. +- **Layouts.** The keyboard layouts are plain data written for this project. They describe common keyboards and published layouts such as QWERTY, AZERTY and InScript, and contain no code from other projects. +- **Demo and tests.** The demo app and the tests use React, Vite and Vitest, which are development tools and are not part of any published package. diff --git a/examples/demo/index.html b/examples/demo/index.html index 5321f20..12fc63d 100644 --- a/examples/demo/index.html +++ b/examples/demo/index.html @@ -3,7 +3,7 @@ - Demo + Saccade Keyboards demo
diff --git a/examples/demo/package.json b/examples/demo/package.json index 8b46076..5321af5 100644 --- a/examples/demo/package.json +++ b/examples/demo/package.json @@ -8,7 +8,13 @@ "build": "vite build" }, "dependencies": { - "@scope/example-react-package": "*", + "@coldsofttech/saccade-en": "*", + "@coldsofttech/saccade-es": "*", + "@coldsofttech/saccade-fr": "*", + "@coldsofttech/saccade-hi": "*", + "@coldsofttech/saccade-keyboards": "*", + "@coldsofttech/saccade-te": "*", + "@coldsofttech/saccade-ur": "*", "react": "^19.1.0", "react-dom": "^19.1.0" }, diff --git a/examples/demo/src/main.jsx b/examples/demo/src/main.jsx index 2eb0ba6..22ee40c 100644 --- a/examples/demo/src/main.jsx +++ b/examples/demo/src/main.jsx @@ -1,13 +1,151 @@ -import { StrictMode } from 'react'; +import { StrictMode, useRef, useState } from 'react'; import { createRoot } from 'react-dom/client'; -import { ExampleComponent } from '@scope/example-react-package'; -import '@scope/example-react-package/style.css'; - -const root = document.getElementById('root'); -if (root) { - createRoot(root).render( - - - , +import { Keyboard } from '@coldsofttech/saccade-keyboards'; +import { applyKey } from '@coldsofttech/saccade-keyboards/core'; +import '@coldsofttech/saccade-keyboards/style.css'; +import { createLayout as createEnglish } from '@coldsofttech/saccade-en'; +import { createLayout as createSpanish } from '@coldsofttech/saccade-es'; +import { createLayout as createFrench } from '@coldsofttech/saccade-fr'; +import { createLayout as createHindi } from '@coldsofttech/saccade-hi'; +import { createLayout as createTelugu } from '@coldsofttech/saccade-te'; +import { createLayout as createUrdu } from '@coldsofttech/saccade-ur'; + +const LAYOUTS = new Map([ + ['English', createEnglish], + ['Spanish', createSpanish], + ['French', createFrench], + // Hindi has no regions: the keyboard is InScript or phonetic and the region picks the digits. + ['Hindi', createHindi], + ['Telugu', createTelugu], + ['Urdu', createUrdu], +]); +const LANGUAGES = [...LAYOUTS.keys()]; +const REGIONS = new Map([ + ['English', ['US', 'GB', 'AU', 'CA', 'IN']], + ['Spanish', ['ES', 'MX', 'AR', 'CO', 'US', 'LATAM']], + ['French', ['FR', 'BE', 'CA', 'CH']], + ['Hindi', ['latin', 'devanagari']], + ['Telugu', ['latin', 'telugu']], + ['Urdu', ['latin', 'urdu']], +]); +// `native` is the order of the keyboard of the region: QWERTY, AZERTY and so on. +const ARRANGEMENTS = ['native', 'alphabetical']; +const HINDI_ARRANGEMENTS = ['inscript', 'phonetic']; +const THEMES = ['light', 'dark']; +const FONTS = ['native', 'sans', 'serif', 'mono']; +// The keyboard fills the element it sits in, so the demo sets its width on a wrapper. +const WIDTHS = ['full', '430 px', '600 px', '800 px', '1000 px']; + +function Select({ label, value, options, onChange }) { + return ( + ); } + +function Demo() { + const field = useRef(null); + const [language, setLanguage] = useState('English'); + const [chosenRegion, setRegion] = useState('US'); + const [arrangement, setArrangement] = useState('native'); + const [theme, setTheme] = useState('light'); + const [font, setFont] = useState('native'); + const [fontSize, setFontSize] = useState(20); + const [width, setWidth] = useState('full'); + const [shown, setShown] = useState(true); + const [numpad, setNumpad] = useState(false); + const [currencypad, setCurrencypad] = useState(false); + + // A region from the other language falls back to that language's first one. + const arrangements = ['Hindi', 'Telugu'].includes(language) ? HINDI_ARRANGEMENTS : ARRANGEMENTS; + const letters = arrangements.includes(arrangement) ? arrangement : arrangements[0]; + const regions = REGIONS.get(language); + const region = regions.includes(chosenRegion) ? chosenRegion : regions[0]; + + const onKey = (key) => { + const { value, selectionStart, selectionEnd } = field.current; + const next = applyKey(value, { start: selectionStart, end: selectionEnd }, key); + field.current.value = next.text; + field.current.setSelectionRange(next.selection.start, next.selection.end); + }; + + return ( +
+

{language} keyboard

+

Click in the field, then press keys on the on-screen keyboard.

+
+ + + + + + +
+