diff --git a/.eslintrc.json b/.eslintrc.json
index 0163fa7..fba7281 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,6 +1,5 @@
{
"root": true,
- "plugins": ["custom-elements"],
"extends": [
"plugin:github/browser",
"plugin:github/recommended",
@@ -8,47 +7,34 @@
"plugin:custom-elements/recommended"
],
"rules": {
- "github/no-then": "off",
- "no-invalid-this": "off",
- "custom-elements/tag-name-matches-class": ["error", {"suffix": "Element"}],
+ "custom-elements/tag-name-matches-class": [
+ "error",
+ {
+ "suffix": "Element"
+ }
+ ],
+ "custom-elements/define-tag-after-class-definition": "off",
+ "custom-elements/no-method-prefixed-with-on": "off",
+ "custom-elements/expose-class-on-global": "off",
"import/extensions": ["error", "always"],
"import/no-unresolved": "off"
},
- "globals": {
- "AutocompleteElement": "readonly"
- },
- "ignorePatterns": ["dist/", "examples/dist"],
"overrides": [
{
- "files": ["test/*.js", "rollup.config.js"],
- "parser": "espree",
- "parserOptions": {
- "ecmaVersion": 8
- },
+ "files": "src/*-define.ts",
"rules": {
- "github/unescaped-html-literal": "off"
+ "@typescript-eslint/no-namespace": "off"
}
},
{
"files": "test/**/*.js",
- "excludedFiles": "test/karma.config.js",
+ "rules": {
+ "github/unescaped-html-literal": "off",
+ "github/no-inner-html": "off",
+ "i18n-text/no-en": "off"
+ },
"env": {
"mocha": true
- },
- "globals": {
- "assert": true
- }
- },
- {
- "files": "validator.js",
- "rules": {
- "@typescript-eslint/explicit-module-boundary-types": "off"
- }
- },
- {
- "files": ["*.config.js"],
- "rules": {
- "filenames/match-regex": "off"
}
}
]
diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml
index 0b1aea0..86d7496 100644
--- a/.github/workflows/nodejs.yml
+++ b/.github/workflows/nodejs.yml
@@ -12,10 +12,10 @@ jobs:
steps:
- uses: actions/checkout@v3
- - name: Use Node.js 16.x
+ - name: Use Node.js 18.x
uses: actions/setup-node@v3
with:
- node-version: 16.x
+ node-version: 18.x
- name: npm install, build, and test
run: |
npm install
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index c7e9474..5afd89f 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -11,7 +11,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
- node-version: 16
+ node-version: 18
registry-url: https://registry.npmjs.org/
cache: npm
- run: npm ci
diff --git a/README.md b/README.md
index 4731700..64324a2 100644
--- a/README.md
+++ b/README.md
@@ -69,6 +69,14 @@ item whose display text needs to be different:
BB-8 (astromech)
```
+Use `data-no-result-found="true"` to show a no results message inside the autocomplete popover. Be sure to add `role="presentation"`
+to this element so that screen readers do not mistake this as an auto-complete option. The auto-complete-element has built in functionality that
+handles aria-live announcing number of search results so this should be purely decorative.
+
+```html
+No results found!
+```
+
### A Note on Clear button
While `input type="search"` comes with an `x` that clears the content of the field and refocuses it on many browsers, the implementation for this control is not keyboard accessible, and so we've opted to enable a customizable clear button so that your keyboard users will be able to interact with it.
@@ -83,7 +91,7 @@ As an example:
## Properties
-- `fetchResult` you can override the default method used to query for results by overriding this property: `document.querySelector('auto-complete').fetchResult = async (el, url) => (await fetch(url)).text()`
+- `fetchResult` you can override the default method used to query for results by overriding this property: `document.querySelector('auto-complete').fetchResult = async (url) => (await fetch(url)).text()`
## Events
@@ -109,7 +117,7 @@ completer.addEventListener('error', () => container.classList.add('is-error'))
### Auto-complete events
-**`auto-complete-change`** is dispatched after a value is selected. In `event.detail` you can find:
+**`auto-complete-change`** is dispatched after a value is selected. In `event` you can find:
- `relatedTarget`: The HTMLInputElement controlling the auto-complete result list.
@@ -120,6 +128,65 @@ completer.addEventListener('auto-complete-change', function(event) {
})
```
+### CSP Trusted Types
+
+You can call
+`setCSPTrustedTypesPolicy(policy: TrustedTypePolicy | Promise | null)`
+from JavaScript to set a
+[CSP trusted types policy](https://web.dev/trusted-types/), which can perform
+(synchronous) filtering or rejection of the `fetch` response before it is
+inserted into the page:
+
+```ts
+import AutoCompleteElement from 'auto-complete-element'
+import DOMPurify from 'dompurify' // Using https://github.com/cure53/DOMPurify
+
+// This policy removes all HTML markup except links.
+const policy = trustedTypes.createPolicy('links-only', {
+ createHTML: (htmlText: string) => {
+ return DOMPurify.sanitize(htmlText, {
+ ALLOWED_TAGS: ['a'],
+ ALLOWED_ATTR: ['href'],
+ RETURN_TRUSTED_TYPE: true
+ })
+ }
+})
+AutoCompleteElement.setCSPTrustedTypesPolicy(policy)
+```
+
+The policy has access to the `fetch` response object. Due to platform
+constraints, only synchronous information from the response (in addition to the
+HTML text body) can be used in the policy:
+
+```ts
+import AutoCompleteElement from 'auto-complete-element'
+
+const policy = trustedTypes.createPolicy('require-server-header', {
+ createHTML: (htmlText: string, response: Response) => {
+ if (response.headers.get('X-Server-Sanitized') !== 'sanitized=true') {
+ // Note: this will reject the contents, but the error may be caught before it shows in the JS console.
+ throw new Error('Rejecting HTML that was not marked by the server as sanitized.')
+ }
+ return htmlText
+ }
+})
+AutoCompleteElement.setCSPTrustedTypesPolicy(policy)
+```
+
+Note that:
+
+- Only a single policy can be set, shared by all `AutoCompleteElement` fetches.
+- You should call `setCSPTrustedTypesPolicy()` ahead of any other load of
+ `auto-complete` element in your code.
+ - If your policy itself requires asynchronous work to construct, you can also
+ pass a `Promise`.
+ - Pass `null` to remove the policy.
+- Not all browsers
+ [support the trusted types API in JavaScript](https://caniuse.com/mdn-api_trustedtypes).
+ You may want to use the
+ [recommended tinyfill](https://github.com/w3c/trusted-types#tinyfill) to
+ construct a policy without causing issues in other browsers.
+
## Browser support
Browsers without native [custom element support][support] require a [polyfill][].
diff --git a/custom-elements-manifest.config.js b/custom-elements-manifest.config.js
new file mode 100644
index 0000000..9abea15
--- /dev/null
+++ b/custom-elements-manifest.config.js
@@ -0,0 +1,5 @@
+export default {
+ packagejson: true,
+ globs: ['src/*-element.ts'],
+ plugins: [],
+}
diff --git a/custom-elements.json b/custom-elements.json
new file mode 100644
index 0000000..85c31e3
--- /dev/null
+++ b/custom-elements.json
@@ -0,0 +1,209 @@
+{
+ "schemaVersion": "1.0.0",
+ "readme": "",
+ "modules": [
+ {
+ "kind": "javascript-module",
+ "path": "src/auto-complete-element.ts",
+ "declarations": [
+ {
+ "kind": "class",
+ "description": "",
+ "name": "AutoCompleteEvent",
+ "members": [
+ {
+ "kind": "field",
+ "name": "relatedTarget",
+ "type": {
+ "text": "HTMLInputElement"
+ },
+ "default": "relatedTarget"
+ }
+ ],
+ "superclass": {
+ "name": "Event",
+ "module": "src/auto-complete-element.ts"
+ }
+ },
+ {
+ "kind": "class",
+ "description": "",
+ "name": "AutoCompleteElement",
+ "members": [
+ {
+ "kind": "method",
+ "name": "define",
+ "static": true,
+ "parameters": [
+ {
+ "name": "tag",
+ "default": "'auto-complete'"
+ },
+ {
+ "name": "registry",
+ "default": "customElements"
+ }
+ ]
+ },
+ {
+ "kind": "method",
+ "name": "setCSPTrustedTypesPolicy",
+ "static": true,
+ "return": {
+ "type": {
+ "text": "void"
+ }
+ },
+ "parameters": [
+ {
+ "name": "policy",
+ "type": {
+ "text": "CSPTrustedTypesPolicy | Promise | null"
+ }
+ }
+ ]
+ },
+ {
+ "kind": "field",
+ "name": "#forElement",
+ "privacy": "private",
+ "type": {
+ "text": "HTMLElement | null"
+ },
+ "default": "null"
+ },
+ {
+ "kind": "field",
+ "name": "forElement",
+ "type": {
+ "text": "HTMLElement | null"
+ }
+ },
+ {
+ "kind": "field",
+ "name": "#inputElement",
+ "privacy": "private",
+ "type": {
+ "text": "HTMLInputElement | null"
+ },
+ "default": "null"
+ },
+ {
+ "kind": "field",
+ "name": "inputElement",
+ "type": {
+ "text": "HTMLInputElement | null"
+ }
+ },
+ {
+ "kind": "method",
+ "name": "#reattachState"
+ },
+ {
+ "kind": "field",
+ "name": "src",
+ "type": {
+ "text": "string"
+ }
+ },
+ {
+ "kind": "field",
+ "name": "value",
+ "type": {
+ "text": "string"
+ }
+ },
+ {
+ "kind": "field",
+ "name": "open",
+ "type": {
+ "text": "boolean"
+ }
+ },
+ {
+ "kind": "field",
+ "name": "fetchOnEmpty",
+ "type": {
+ "text": "boolean"
+ }
+ },
+ {
+ "kind": "field",
+ "name": "#requestController",
+ "privacy": "private",
+ "type": {
+ "text": "AbortController | undefined"
+ }
+ },
+ {
+ "kind": "method",
+ "name": "fetchResult",
+ "return": {
+ "type": {
+ "text": "Promise"
+ }
+ },
+ "parameters": [
+ {
+ "name": "url",
+ "type": {
+ "text": "URL"
+ }
+ }
+ ]
+ }
+ ],
+ "events": [
+ {
+ "name": "auto-complete-change",
+ "type": {
+ "text": "AutoCompleteEvent"
+ }
+ }
+ ],
+ "attributes": [
+ {
+ "name": "open"
+ },
+ {
+ "name": "value"
+ },
+ {
+ "name": "for"
+ }
+ ],
+ "superclass": {
+ "name": "HTMLElement"
+ },
+ "customElement": true
+ }
+ ],
+ "exports": [
+ {
+ "kind": "js",
+ "name": "AutoCompleteEvent",
+ "declaration": {
+ "name": "AutoCompleteEvent",
+ "module": "src/auto-complete-element.ts"
+ }
+ },
+ {
+ "kind": "js",
+ "name": "AutoCompleteElement",
+ "declaration": {
+ "name": "AutoCompleteElement",
+ "module": "src/auto-complete-element.ts"
+ }
+ },
+ {
+ "kind": "js",
+ "name": "default",
+ "declaration": {
+ "name": "AutoCompleteElement",
+ "module": "src/auto-complete-element.ts"
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/examples/index.html b/examples/index.html
index 3825529..15a81a0 100644
--- a/examples/index.html
+++ b/examples/index.html
@@ -1,5 +1,5 @@
-
+
Codestin Search App
@@ -49,7 +49,7 @@
@@ -67,7 +67,7 @@
@@ -88,17 +88,29 @@
+