diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index ed9533d..d947a49 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -2,25 +2,24 @@ name: Node CI on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] jobs: build: - - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v1 - - name: Use Node.js - uses: actions/setup-node@v1 - with: - node-version: '20.x' - - name: npm install, build, and test - run: | - npm install - npm run build --if-present - npm test - env: - CI: true + - uses: actions/checkout@v1 + - name: Use Node.js + uses: actions/setup-node@v1 + with: + node-version: '20.x' + - name: npm install, build, and test + run: | + npm install + npm run build --if-present + npm test + env: + CI: true diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 55166b2..b73d739 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -6,7 +6,7 @@ on: jobs: publish-npm: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 diff --git a/README.md b/README.md index 8afc8ee..73ec1d2 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ expander.addEventListener('text-expander-committed', function(event) { **`text-expander-activate`** is fired just after the menu has been assigned and appended to the DOM, and just before it is about to be positioned near the text to expand. This is useful for assigning classes or calling imperative methods to show the menu, such as `.showPopover()`. -**`text-expander-dectivate`** is fired just before the menu is goind to be unassigned and removed from the DOM. This is useful for removing classes or running cleanup like removing from caches. +**`text-expander-deactivate`** is fired just before the menu is going to be unassigned and removed from the DOM. This is useful for removing classes or running cleanup like removing from caches. ## Browser support diff --git a/package-lock.json b/package-lock.json index 6c20e38..d0a4089 100644 --- a/package-lock.json +++ b/package-lock.json @@ -267,9 +267,9 @@ "dev": true }, "node_modules/@types/cors": { - "version": "2.8.16", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.16.tgz", - "integrity": "sha512-Trx5or1Nyg1Fq138PCuWqoApzvoSLWzZ25ORBiHMbbUT42g578lH1GT4TwYDbiUOLFuDsCkfLneT2105fsFWGg==", + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", "dev": true, "dependencies": { "@types/node": "*" @@ -1298,9 +1298,9 @@ } }, "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "dev": true, "engines": { "node": ">= 0.6" @@ -1664,9 +1664,9 @@ } }, "node_modules/engine.io": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", - "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", + "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", "dev": true, "dependencies": { "@types/cookie": "^0.4.1", @@ -1674,7 +1674,7 @@ "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "2.0.0", - "cookie": "~0.4.1", + "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", @@ -1685,9 +1685,9 @@ } }, "node_modules/engine.io-parser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", - "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", "dev": true, "engines": { "node": ">=10.0.0" @@ -4915,9 +4915,9 @@ } }, "node_modules/rollup": { - "version": "2.45.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.45.1.tgz", - "integrity": "sha512-vPD+JoDj3CY8k6m1bLcAFttXMe78P4CMxoau0iLVS60+S9kLsv2379xaGy4NgYWu+h2WTlucpoLPAoUoixFBag==", + "version": "2.79.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", + "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "dev": true, "bin": { "rollup": "dist/bin/rollup" @@ -4926,7 +4926,7 @@ "node": ">=10.0.0" }, "optionalDependencies": { - "fsevents": "~2.3.1" + "fsevents": "~2.3.2" } }, "node_modules/rollup-plugin-node-resolve": { @@ -5153,16 +5153,16 @@ } }, "node_modules/socket.io": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.2.tgz", - "integrity": "sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.0.tgz", + "integrity": "sha512-8U6BEgGjQOfGz3HHTYaC/L1GaxDCJ/KM0XTkJly0EhZ5U/du9uNEZy4ZgYzEzIqlx2CMm25CrCqr1ck899eLNA==", "dev": true, "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", - "engine.io": "~6.5.2", + "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" }, diff --git a/src/index.ts b/src/index.ts index 6f5e2f1..c2c042e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ import TextExpanderElement from './text-expander-element' export {TextExpanderElement as default} +export type * from './text-expander-element' declare global { interface Window { diff --git a/src/text-expander-element.ts b/src/text-expander-element.ts index 48a413a..dcb0126 100644 --- a/src/text-expander-element.ts +++ b/src/text-expander-element.ts @@ -2,22 +2,30 @@ import Combobox from '@github/combobox-nav' import query from './query' import {InputRange} from 'dom-input-range' -type Match = { +export type TextExpanderMatch = { text: string key: string position: number } -type Result = { - fragment: HTMLElement +export type TextExpanderResult = { + fragment?: HTMLElement matched: boolean } -type Key = { +export type TextExpanderKey = { key: string multiWord: boolean } +export type TextExpanderChangeEvent = Event & { + detail?: { + key: string + text: string + provide: (result: Promise | TextExpanderResult) => void + } +} + const states = new WeakMap() class TextExpander { @@ -31,7 +39,7 @@ class TextExpander { onblur: (event: Event) => void onmousedown: (event: Event) => void combobox: Combobox | null - match: Match | null + match: TextExpanderMatch | null justPasted: boolean lookBackIndex: number interactingWithList: boolean @@ -70,7 +78,7 @@ class TextExpander { } } - private activate(match: Match, menu: HTMLElement) { + private activate(match: TextExpanderMatch, menu: HTMLElement) { if (this.input !== document.activeElement && this.input !== document.activeElement?.shadowRoot?.activeElement) { return } @@ -218,7 +226,7 @@ class TextExpander { } } - findMatch(): Match | void { + findMatch(): TextExpanderMatch | void { const cursor = this.input.selectionEnd || 0 const text = this.input.value if (cursor <= this.lookBackIndex) { @@ -236,12 +244,14 @@ class TextExpander { } } - async notifyProviders(match: Match): Promise { - const providers: Array | Result> = [] - const provide = (result: Promise | Result) => providers.push(result) - const canceled = !this.expander.dispatchEvent( - new CustomEvent('text-expander-change', {cancelable: true, detail: {provide, text: match.text, key: match.key}}) - ) + async notifyProviders(match: TextExpanderMatch): Promise { + const providers: Array | TextExpanderResult> = [] + const provide = (result: Promise | TextExpanderResult) => providers.push(result) + const changeEvent = new CustomEvent('text-expander-change', { + cancelable: true, + detail: {provide, text: match.text, key: match.key} + }) as TextExpanderChangeEvent + const canceled = !this.expander.dispatchEvent(changeEvent) if (canceled) return const all = await Promise.all(providers) @@ -265,7 +275,7 @@ class TextExpander { } } export default class TextExpanderElement extends HTMLElement { - get keys(): Key[] { + get keys(): TextExpanderKey[] { const keysAttr = this.getAttribute('keys') const keys = keysAttr ? keysAttr.split(' ') : [] @@ -276,6 +286,10 @@ export default class TextExpanderElement extends HTMLElement { return keys.map(key => ({key, multiWord: globalMultiWord || multiWord.includes(key)})) } + set keys(value: string) { + this.setAttribute('keys', value) + } + connectedCallback(): void { const input = this.querySelector('input[type="text"], textarea') if (!(input instanceof HTMLInputElement || input instanceof HTMLTextAreaElement)) return diff --git a/test/text-expander-element-test.js b/test/text-expander-element-test.js index bec7ec6..fa8b4b7 100644 --- a/test/text-expander-element-test.js +++ b/test/text-expander-element-test.js @@ -192,6 +192,28 @@ describe('text-expander element', function () { ) }) + it('sets keys', function () { + const expander = document.querySelector('text-expander') + assert.deepEqual( + [ + {key: '@', multiWord: false}, + {key: '#', multiWord: true}, + {key: '[[', multiWord: true} + ], + expander.keys + ) + + expander.keys = '@ [[' + + assert.deepEqual( + [ + {key: '@', multiWord: false}, + {key: '[[', multiWord: true} + ], + expander.keys + ) + }) + it('dispatches change event for multi-word', async function () { const expander = document.querySelector('text-expander') const input = expander.querySelector('textarea')