@api-client/ui 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.aiexclude +3 -0
- package/.vscode/settings.json +4 -1
- package/build/src/elements/autocomplete/internals/autocomplete.d.ts +53 -5
- package/build/src/elements/autocomplete/internals/autocomplete.d.ts.map +1 -1
- package/build/src/elements/autocomplete/internals/autocomplete.js +156 -33
- package/build/src/elements/autocomplete/internals/autocomplete.js.map +1 -1
- package/build/src/elements/highlight/MarkedHighlight.d.ts +2 -1
- package/build/src/elements/highlight/MarkedHighlight.d.ts.map +1 -1
- package/build/src/elements/highlight/MarkedHighlight.js +14 -11
- package/build/src/elements/highlight/MarkedHighlight.js.map +1 -1
- package/demo/elements/autocomplete/index.html +40 -0
- package/demo/elements/autocomplete/index.ts +52 -4
- package/package.json +1 -1
- package/src/elements/autocomplete/internals/autocomplete.ts +136 -32
- package/src/elements/highlight/MarkedHighlight.ts +16 -13
- package/test/elements/autocomplete/autocomplete-input.spec.ts +210 -13
|
@@ -122,8 +122,8 @@ let MarkedHighlight = (() => {
|
|
|
122
122
|
const child = nodes.find((node) => node.nodeType === 1 && node.getAttribute('slot') === 'markdown-html');
|
|
123
123
|
return child || this.shadowRoot?.querySelector('#content');
|
|
124
124
|
}
|
|
125
|
-
|
|
126
|
-
super.
|
|
125
|
+
willUpdate(cp) {
|
|
126
|
+
super.willUpdate(cp);
|
|
127
127
|
if (cp.has('markdown') ||
|
|
128
128
|
cp.has('sanitizer') ||
|
|
129
129
|
cp.has('sanitize') ||
|
|
@@ -140,6 +140,18 @@ let MarkedHighlight = (() => {
|
|
|
140
140
|
this.renderMarkdown();
|
|
141
141
|
return;
|
|
142
142
|
}
|
|
143
|
+
}
|
|
144
|
+
connectedCallback() {
|
|
145
|
+
super.connectedCallback();
|
|
146
|
+
this.attachedValue = true;
|
|
147
|
+
this.processScript();
|
|
148
|
+
this.renderMarkdown();
|
|
149
|
+
}
|
|
150
|
+
disconnectedCallback() {
|
|
151
|
+
super.disconnectedCallback();
|
|
152
|
+
this.attachedValue = false;
|
|
153
|
+
}
|
|
154
|
+
async processScript() {
|
|
143
155
|
// Use the Markdown from the first `<script>` descendant whose MIME type
|
|
144
156
|
// starts with "text/markdown". Script elements beyond the first are
|
|
145
157
|
// ignored.
|
|
@@ -158,15 +170,6 @@ let MarkedHighlight = (() => {
|
|
|
158
170
|
const observer = new MutationObserver(this.scriptAttributeHandler.bind(this));
|
|
159
171
|
observer.observe(script, { attributes: true });
|
|
160
172
|
}
|
|
161
|
-
connectedCallback() {
|
|
162
|
-
super.connectedCallback();
|
|
163
|
-
this.attachedValue = true;
|
|
164
|
-
this.renderMarkdown();
|
|
165
|
-
}
|
|
166
|
-
disconnectedCallback() {
|
|
167
|
-
super.disconnectedCallback();
|
|
168
|
-
this.attachedValue = false;
|
|
169
|
-
}
|
|
170
173
|
/**
|
|
171
174
|
* Renders `markdown` into this element's DOM.
|
|
172
175
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MarkedHighlight.js","sourceRoot":"","sources":["../../../../src/elements/highlight/MarkedHighlight.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, LitElement, TemplateResult, PropertyValues } from 'lit'\nimport { property } from 'lit/decorators.js'\nimport DOMPurify from 'dompurify'\nimport { marked, MarkedOptions } from 'marked'\n// import { PrismHighlighter } from './PrismHighlighter.js';\n\n/**\nElement wrapper for the [marked](https://github.com/chjj/marked) library.\n\n`<marked-highlight>` accepts Markdown source and renders it to a child\nelement with the class `markdown-html`. This child element can be styled\nas you would a normal DOM element. If you do not provide a child element\nwith the `markdown-html` class, the Markdown source will still be rendered,\nbut to a shadow DOM child that cannot be styled.\n\n### Markdown Content\n\nThe Markdown source can be specified several ways:\n\n#### Use the `markdown` attribute to bind markdown\n\n```html\n<marked-highlight markdown=\"`Markdown` is _awesome_!\">\n <div slot=\"markdown-html\"></div>\n</marked-highlight>\n```\n#### Use `<script type=\"text/markdown\">` element child to inline markdown\n\n```html\n<marked-highlight>\n <div slot=\"markdown-html\"></div>\n <script type=\"text/markdown\">\n Check out my markdown!\n We can even embed elements without fear of the HTML parser mucking up their\n textual representation:\n </script>\n</marked-highlight>\n```\n#### Use `<script type=\"text/markdown\" src=\"URL\">` element child to specify remote markdown\n\n```html\n<marked-highlight>\n <div slot=\"markdown-html\"></div>\n <script type=\"text/markdown\" src=\"../guidelines.md\"></script>\n</marked-highlight>\n```\n\nNote that the `<script type=\"text/markdown\">` approach is *static*. Changes to\nthe script content will *not* update the rendered markdown!\n\nThough, you can data bind to the `src` attribute to change the markdown.\n\n```html\n<marked-highlight>\n <div slot=\"markdown-html\"></div>\n <script type=\"text/markdown\" src$=\"[[source]]\"></script>\n</marked-highlight>\n<script>\n ...\n this.source = '../guidelines.md';\n</script>\n```\n\n### Styling\n\nIf you are using a child with the `markdown-html` class, you can style it\nas you would a regular DOM element:\n\n```css\n[slot=\"markdown-html\"] p {\n color: red;\n}\n[slot=\"markdown-html\"] td:first-child {\n padding-left: 24px;\n}\n```\n\n@fires markedrendercomplete - Event dispatched when the element renders the output.\n@fires markedloaded - Event dispatched when the element loads the script from the URL.\n@fires markedloaderror - An event dispatched when an error ocurred when downloading\n the script content. The detail has the original error.\n */\nexport default class MarkedHighlight extends LitElement {\n /**\n * The markdown source to be rendered by this element.\n * @attribute\n */\n @property({ type: String }) accessor markdown: string | undefined\n\n /**\n * Enable GFM line breaks (regular newlines instead of two spaces for\n * breaks)\n * @attribute\n */\n @property({ type: Boolean, reflect: true }) accessor breaks: boolean | undefined\n\n /**\n * Conform to obscure parts of markdown.pl as much as possible. Don't fix\n * any of the original markdown bugs or poor behavior.\n * @attribute\n */\n @property({ type: Boolean, reflect: true }) accessor pedantic: boolean | undefined\n\n /**\n * Function used to customize a renderer based on the [API specified in the\n * Marked\n * library](https://github.com/chjj/marked#overriding-renderer-methods).\n * It takes one argument: a marked renderer object, which is mutated by the\n * function.\n */\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n @property({ type: Object }) accessor renderer: Function | undefined\n\n /**\n * Sanitize the output. Ignore any HTML that has been input.\n * @attribute\n */\n @property({ type: Boolean, reflect: true }) accessor sanitize: boolean | undefined\n\n /**\n * Function used to customize a sanitize behavior.\n * It takes one argument: element String without text Contents.\n *\n * e.g. `<div>` `<a href=\"/\">` `</p>'.\n * Note: To enable this function, must set `sanitize` to true.\n * WARNING: If you are using this option to un-trusted text, you must to\n * prevent XSS Attacks.\n */\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n @property({ type: Object }) accessor sanitizer: Function | undefined\n\n /**\n * If true, disables the default sanitization of any markdown received by\n * a request and allows fetched un-sanitized markdown\n *\n * e.g. fetching markdown via `src` that has HTML.\n * Note: this value overrides `sanitize` if a request is made.\n * @attribute\n */\n @property({ type: Boolean, reflect: true }) accessor disableRemoteSanitization: boolean | undefined\n\n /**\n * @returns A reference to the output element.\n */\n get outputElement(): HTMLElement | null {\n const slot = this.shadowRoot?.querySelector('slot[name=\"markdown-html\"]') as HTMLSlotElement | null\n if (!slot) {\n return null\n }\n const nodes = slot.assignedNodes()\n const child = nodes.find(\n (node) => node.nodeType === 1 && (node as HTMLElement).getAttribute('slot') === 'markdown-html'\n ) as HTMLElement | undefined\n return child || (this.shadowRoot?.querySelector('#content') as HTMLElement | null)\n }\n\n protected override updated(cp: PropertyValues<this>): void {\n super.updated(cp)\n if (\n cp.has('markdown') ||\n cp.has('sanitizer') ||\n cp.has('sanitize') ||\n cp.has('renderer') ||\n cp.has('pedantic') ||\n cp.has('breaks')\n ) {\n this.renderMarkdown()\n }\n }\n\n protected markdownElement?: HTMLScriptElement | null\n\n protected attachedValue = false\n\n override firstUpdated(): void {\n if (this.markdown) {\n this.renderMarkdown()\n return\n }\n\n // Use the Markdown from the first `<script>` descendant whose MIME type\n // starts with \"text/markdown\". Script elements beyond the first are\n // ignored.\n const script = this.querySelector('[type=\"text/markdown\"]') as HTMLScriptElement | null\n if (!script) {\n return\n }\n this.markdownElement = script\n\n if (script.src) {\n this.requestMarkdown(script.src)\n }\n const content = script.textContent\n if (content && content.trim() !== '') {\n this.markdown = this.unindent(content)\n }\n\n const observer = new MutationObserver(this.scriptAttributeHandler.bind(this))\n observer.observe(script, { attributes: true })\n }\n\n override connectedCallback(): void {\n super.connectedCallback()\n this.attachedValue = true\n this.renderMarkdown()\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback()\n this.attachedValue = false\n }\n\n /**\n * Renders `markdown` into this element's DOM.\n *\n * This is automatically called whenever the `markdown` property is changed.\n *\n * The only case where you should be calling this is if you are providing\n * markdown via `<script type=\"text/markdown\">` after this element has been\n * constructed (or updating that markdown).\n * @event markedrendercomplete\n */\n renderMarkdown(): void {\n if (!this.attachedValue) {\n return\n }\n const node = this.outputElement\n if (!node) {\n return\n }\n\n if (!this.markdown) {\n node.innerHTML = ''\n return\n }\n const renderer = new marked.Renderer()\n if (this.renderer) {\n this.renderer(renderer)\n }\n const data = this.markdown\n const opts: MarkedOptions = {\n renderer,\n breaks: this.breaks,\n pedantic: this.pedantic,\n gfm: true,\n }\n let out = marked(data, opts) as string\n if (this.sanitize) {\n if (this.sanitizer) {\n out = this.sanitizer(out)\n } else {\n const result = DOMPurify.sanitize(out)\n if (typeof result === 'string') {\n out = result\n } else {\n // @ts-expect-error Sometimes the result is an object!\n out = result.toString()\n }\n }\n }\n node.innerHTML = out\n this.dispatchEvent(new Event('markedrendercomplete'))\n }\n\n /**\n * Un-indents the markdown source that will be rendered.\n */\n unindent(text?: string): string {\n if (!text) {\n return ''\n }\n const lines = text.replace(/\\t/g, ' ').split('\\n')\n const indent = lines.reduce((prev: number | null, line: string): number | null => {\n if (/^\\s*$/.test(line)) {\n return prev // Completely ignore blank lines.\n }\n const match = line.match(/^(\\s*)/)\n if (!match) {\n return prev\n }\n\n const lineIndent = match[0].length\n if (prev === null) {\n return lineIndent\n }\n\n return lineIndent < prev ? lineIndent : prev\n }, null)\n\n if (indent === null) {\n return text\n }\n\n return lines.map((l) => l.substring(indent)).join('\\n')\n }\n\n /**\n * Fired when the XHR finishes loading\n */\n protected async requestMarkdown(url: string): Promise<void> {\n try {\n const response = await fetch(url, {\n headers: { accept: 'text/markdown' },\n })\n const { status } = response\n if (status === 0 || (status >= 200 && status < 300)) {\n this.sanitize = !this.disableRemoteSanitization\n this.markdown = await response.text()\n this.dispatchEvent(new Event('markedloaded'))\n } else {\n throw new Error('Unable to download the data')\n }\n } catch (e) {\n this.handleError(e as Error)\n }\n }\n\n /**\n * Fired when an error is received while fetching remote markdown content.\n */\n protected handleError(e: Error): void {\n const evt = new CustomEvent('markedloaderror', {\n composed: true,\n bubbles: true,\n cancelable: true,\n detail: e,\n })\n this.dispatchEvent(evt)\n if (!evt.defaultPrevented) {\n this.markdown = 'Failed loading markdown source'\n }\n }\n\n protected scriptAttributeHandler(mutation: MutationRecord[]): void {\n if (mutation[0].attributeName !== 'src') {\n return\n }\n const elm = this.markdownElement\n if (elm) {\n this.requestMarkdown(elm.src)\n }\n }\n\n override render(): TemplateResult {\n return html`<slot name=\"markdown-html\"><div id=\"content\"></div></slot>`\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"MarkedHighlight.js","sourceRoot":"","sources":["../../../../src/elements/highlight/MarkedHighlight.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, LitElement, TemplateResult, PropertyValues } from 'lit'\nimport { property } from 'lit/decorators.js'\nimport DOMPurify from 'dompurify'\nimport { marked, MarkedOptions } from 'marked'\n// import { PrismHighlighter } from './PrismHighlighter.js';\n\n/**\nElement wrapper for the [marked](https://github.com/chjj/marked) library.\n\n`<marked-highlight>` accepts Markdown source and renders it to a child\nelement with the class `markdown-html`. This child element can be styled\nas you would a normal DOM element. If you do not provide a child element\nwith the `markdown-html` class, the Markdown source will still be rendered,\nbut to a shadow DOM child that cannot be styled.\n\n### Markdown Content\n\nThe Markdown source can be specified several ways:\n\n#### Use the `markdown` attribute to bind markdown\n\n```html\n<marked-highlight markdown=\"`Markdown` is _awesome_!\">\n <div slot=\"markdown-html\"></div>\n</marked-highlight>\n```\n#### Use `<script type=\"text/markdown\">` element child to inline markdown\n\n```html\n<marked-highlight>\n <div slot=\"markdown-html\"></div>\n <script type=\"text/markdown\">\n Check out my markdown!\n We can even embed elements without fear of the HTML parser mucking up their\n textual representation:\n </script>\n</marked-highlight>\n```\n#### Use `<script type=\"text/markdown\" src=\"URL\">` element child to specify remote markdown\n\n```html\n<marked-highlight>\n <div slot=\"markdown-html\"></div>\n <script type=\"text/markdown\" src=\"../guidelines.md\"></script>\n</marked-highlight>\n```\n\nNote that the `<script type=\"text/markdown\">` approach is *static*. Changes to\nthe script content will *not* update the rendered markdown!\n\nThough, you can data bind to the `src` attribute to change the markdown.\n\n```html\n<marked-highlight>\n <div slot=\"markdown-html\"></div>\n <script type=\"text/markdown\" src$=\"[[source]]\"></script>\n</marked-highlight>\n<script>\n ...\n this.source = '../guidelines.md';\n</script>\n```\n\n### Styling\n\nIf you are using a child with the `markdown-html` class, you can style it\nas you would a regular DOM element:\n\n```css\n[slot=\"markdown-html\"] p {\n color: red;\n}\n[slot=\"markdown-html\"] td:first-child {\n padding-left: 24px;\n}\n```\n\n@fires markedrendercomplete - Event dispatched when the element renders the output.\n@fires markedloaded - Event dispatched when the element loads the script from the URL.\n@fires markedloaderror - An event dispatched when an error ocurred when downloading\n the script content. The detail has the original error.\n */\nexport default class MarkedHighlight extends LitElement {\n /**\n * The markdown source to be rendered by this element.\n * @attribute\n */\n @property({ type: String }) accessor markdown: string | undefined\n\n /**\n * Enable GFM line breaks (regular newlines instead of two spaces for\n * breaks)\n * @attribute\n */\n @property({ type: Boolean, reflect: true }) accessor breaks: boolean | undefined\n\n /**\n * Conform to obscure parts of markdown.pl as much as possible. Don't fix\n * any of the original markdown bugs or poor behavior.\n * @attribute\n */\n @property({ type: Boolean, reflect: true }) accessor pedantic: boolean | undefined\n\n /**\n * Function used to customize a renderer based on the [API specified in the\n * Marked\n * library](https://github.com/chjj/marked#overriding-renderer-methods).\n * It takes one argument: a marked renderer object, which is mutated by the\n * function.\n */\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n @property({ type: Object }) accessor renderer: Function | undefined\n\n /**\n * Sanitize the output. Ignore any HTML that has been input.\n * @attribute\n */\n @property({ type: Boolean, reflect: true }) accessor sanitize: boolean | undefined\n\n /**\n * Function used to customize a sanitize behavior.\n * It takes one argument: element String without text Contents.\n *\n * e.g. `<div>` `<a href=\"/\">` `</p>'.\n * Note: To enable this function, must set `sanitize` to true.\n * WARNING: If you are using this option to un-trusted text, you must to\n * prevent XSS Attacks.\n */\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n @property({ type: Object }) accessor sanitizer: Function | undefined\n\n /**\n * If true, disables the default sanitization of any markdown received by\n * a request and allows fetched un-sanitized markdown\n *\n * e.g. fetching markdown via `src` that has HTML.\n * Note: this value overrides `sanitize` if a request is made.\n * @attribute\n */\n @property({ type: Boolean, reflect: true }) accessor disableRemoteSanitization: boolean | undefined\n\n /**\n * @returns A reference to the output element.\n */\n get outputElement(): HTMLElement | null {\n const slot = this.shadowRoot?.querySelector('slot[name=\"markdown-html\"]') as HTMLSlotElement | null\n if (!slot) {\n return null\n }\n const nodes = slot.assignedNodes()\n const child = nodes.find(\n (node) => node.nodeType === 1 && (node as HTMLElement).getAttribute('slot') === 'markdown-html'\n ) as HTMLElement | undefined\n return child || (this.shadowRoot?.querySelector('#content') as HTMLElement | null)\n }\n\n protected override willUpdate(cp: PropertyValues<this>): void {\n super.willUpdate(cp)\n if (\n cp.has('markdown') ||\n cp.has('sanitizer') ||\n cp.has('sanitize') ||\n cp.has('renderer') ||\n cp.has('pedantic') ||\n cp.has('breaks')\n ) {\n this.renderMarkdown()\n }\n }\n\n protected markdownElement?: HTMLScriptElement | null\n\n protected attachedValue = false\n\n override firstUpdated(): void {\n if (this.markdown) {\n this.renderMarkdown()\n return\n }\n }\n\n override connectedCallback(): void {\n super.connectedCallback()\n this.attachedValue = true\n this.processScript()\n this.renderMarkdown()\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback()\n this.attachedValue = false\n }\n\n protected async processScript(): Promise<void> {\n // Use the Markdown from the first `<script>` descendant whose MIME type\n // starts with \"text/markdown\". Script elements beyond the first are\n // ignored.\n const script = this.querySelector('[type=\"text/markdown\"]') as HTMLScriptElement | null\n if (!script) {\n return\n }\n this.markdownElement = script\n\n if (script.src) {\n this.requestMarkdown(script.src)\n }\n const content = script.textContent\n if (content && content.trim() !== '') {\n this.markdown = this.unindent(content)\n }\n\n const observer = new MutationObserver(this.scriptAttributeHandler.bind(this))\n observer.observe(script, { attributes: true })\n }\n\n /**\n * Renders `markdown` into this element's DOM.\n *\n * This is automatically called whenever the `markdown` property is changed.\n *\n * The only case where you should be calling this is if you are providing\n * markdown via `<script type=\"text/markdown\">` after this element has been\n * constructed (or updating that markdown).\n * @event markedrendercomplete\n */\n renderMarkdown(): void {\n if (!this.attachedValue) {\n return\n }\n const node = this.outputElement\n if (!node) {\n return\n }\n\n if (!this.markdown) {\n node.innerHTML = ''\n return\n }\n const renderer = new marked.Renderer()\n if (this.renderer) {\n this.renderer(renderer)\n }\n const data = this.markdown\n const opts: MarkedOptions = {\n renderer,\n breaks: this.breaks,\n pedantic: this.pedantic,\n gfm: true,\n }\n let out = marked(data, opts) as string\n if (this.sanitize) {\n if (this.sanitizer) {\n out = this.sanitizer(out)\n } else {\n const result = DOMPurify.sanitize(out)\n if (typeof result === 'string') {\n out = result\n } else {\n // @ts-expect-error Sometimes the result is an object!\n out = result.toString()\n }\n }\n }\n node.innerHTML = out\n this.dispatchEvent(new Event('markedrendercomplete'))\n }\n\n /**\n * Un-indents the markdown source that will be rendered.\n */\n unindent(text?: string): string {\n if (!text) {\n return ''\n }\n const lines = text.replace(/\\t/g, ' ').split('\\n')\n const indent = lines.reduce((prev: number | null, line: string): number | null => {\n if (/^\\s*$/.test(line)) {\n return prev // Completely ignore blank lines.\n }\n const match = line.match(/^(\\s*)/)\n if (!match) {\n return prev\n }\n\n const lineIndent = match[0].length\n if (prev === null) {\n return lineIndent\n }\n\n return lineIndent < prev ? lineIndent : prev\n }, null)\n\n if (indent === null) {\n return text\n }\n\n return lines.map((l) => l.substring(indent)).join('\\n')\n }\n\n /**\n * Fired when the XHR finishes loading\n */\n protected async requestMarkdown(url: string): Promise<void> {\n try {\n const response = await fetch(url, {\n headers: { accept: 'text/markdown' },\n })\n const { status } = response\n if (status === 0 || (status >= 200 && status < 300)) {\n this.sanitize = !this.disableRemoteSanitization\n this.markdown = await response.text()\n this.dispatchEvent(new Event('markedloaded'))\n } else {\n throw new Error('Unable to download the data')\n }\n } catch (e) {\n this.handleError(e as Error)\n }\n }\n\n /**\n * Fired when an error is received while fetching remote markdown content.\n */\n protected handleError(e: Error): void {\n const evt = new CustomEvent('markedloaderror', {\n composed: true,\n bubbles: true,\n cancelable: true,\n detail: e,\n })\n this.dispatchEvent(evt)\n if (!evt.defaultPrevented) {\n this.markdown = 'Failed loading markdown source'\n }\n }\n\n protected scriptAttributeHandler(mutation: MutationRecord[]): void {\n if (mutation[0].attributeName !== 'src') {\n return\n }\n const elm = this.markdownElement\n if (elm) {\n this.requestMarkdown(elm.src)\n }\n }\n\n override render(): TemplateResult {\n return html`<slot name=\"markdown-html\"><div id=\"content\"></div></slot>`\n }\n}\n"]}
|
|
@@ -14,6 +14,46 @@
|
|
|
14
14
|
<link href="../../../src/styles/m3/tokens.css" rel="stylesheet" type="text/css" />
|
|
15
15
|
<link href="../../../src/styles/m3/theme.css" rel="stylesheet" type="text/css" />
|
|
16
16
|
<link href="../../page.css" rel="stylesheet" type="text/css" />
|
|
17
|
+
<style>
|
|
18
|
+
.share-input {
|
|
19
|
+
.input-container {
|
|
20
|
+
min-height: 3em;
|
|
21
|
+
border: none;
|
|
22
|
+
padding: 4px 12px;
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-wrap: wrap;
|
|
25
|
+
box-sizing: border-box;
|
|
26
|
+
gap: 4px;
|
|
27
|
+
border-radius: 4px;
|
|
28
|
+
background-color: var(--md-sys-color-surface-container-lowest);
|
|
29
|
+
|
|
30
|
+
outline-color: var(--md-sys-color-primary);
|
|
31
|
+
outline-style: solid;
|
|
32
|
+
outline-width: 1px;
|
|
33
|
+
outline-offset: -1px;
|
|
34
|
+
transition:
|
|
35
|
+
outline-width 0.14s ease-in-out,
|
|
36
|
+
outline-offset 0.14s ease-in-out;
|
|
37
|
+
|
|
38
|
+
&:focus-within {
|
|
39
|
+
outline-width: 2px;
|
|
40
|
+
outline-offset: -2px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.native-input {
|
|
44
|
+
flex: 1;
|
|
45
|
+
outline: none;
|
|
46
|
+
border: none;
|
|
47
|
+
background: transparent;
|
|
48
|
+
color: var(--md-sys-color-on-surface);
|
|
49
|
+
font-size: 1rem;
|
|
50
|
+
font-family: var(--md-sys-typescale-body-large-font-family);
|
|
51
|
+
line-height: var(--md-sys-typescale-body-large-line-height);
|
|
52
|
+
padding: 0;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
</style>
|
|
17
57
|
</head>
|
|
18
58
|
|
|
19
59
|
<body class="demo">
|
|
@@ -15,7 +15,9 @@ class ComponentDemoPage extends DemoPage {
|
|
|
15
15
|
|
|
16
16
|
@reactive() accessor fixedValue = ''
|
|
17
17
|
@reactive() accessor dynamicValue = ''
|
|
18
|
+
@reactive() accessor dynamicCustomValue = ''
|
|
18
19
|
@reactive() accessor users: IUser[] = []
|
|
20
|
+
@reactive() accessor customUsers: IUser[] = []
|
|
19
21
|
|
|
20
22
|
userMock = new User()
|
|
21
23
|
|
|
@@ -37,22 +39,33 @@ class ComponentDemoPage extends DemoPage {
|
|
|
37
39
|
handleDynamicInput(event: InputEvent): void {
|
|
38
40
|
const target = event.target as HTMLInputElement
|
|
39
41
|
this.dynamicValue = target.value
|
|
40
|
-
this.generateMockUsers(target.value)
|
|
42
|
+
this.generateMockUsers(target.value, 'users')
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
generateMockUsers(startsWith: string): void {
|
|
45
|
+
generateMockUsers(startsWith: string, prop: 'users' | 'customUsers'): void {
|
|
44
46
|
const timeout = this.userMock.types.number({ min: 100, max: 1000 })
|
|
45
47
|
const results = this.userMock.types.number({ min: 0, max: 30 })
|
|
46
48
|
setTimeout(() => {
|
|
47
|
-
this
|
|
49
|
+
this[prop] = Array.from({ length: results }, () => {
|
|
48
50
|
const item = this.userMock.user()
|
|
49
51
|
item.name = item.name.startsWith(startsWith) ? item.name : `${startsWith}${item.name}`
|
|
50
52
|
return item
|
|
51
53
|
})
|
|
52
|
-
console.log('Generated users:', this
|
|
54
|
+
console.log('Generated users:', this[prop])
|
|
53
55
|
}, timeout)
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
handleDynamicCustomInput(event: InputEvent): void {
|
|
59
|
+
const target = event.target as HTMLInputElement
|
|
60
|
+
this.dynamicCustomValue = target.value
|
|
61
|
+
this.generateMockUsers(target.value, 'customUsers')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
handleDynamicCustomAutocomplete(event: CustomEvent<{ item: HTMLElement }>): void {
|
|
65
|
+
const item = event.detail.item
|
|
66
|
+
this.dynamicCustomValue = item.dataset.name || ''
|
|
67
|
+
}
|
|
68
|
+
|
|
56
69
|
override contentTemplate(): TemplateResult {
|
|
57
70
|
return html`
|
|
58
71
|
<a href="../">Back</a>
|
|
@@ -116,6 +129,41 @@ class ComponentDemoPage extends DemoPage {
|
|
|
116
129
|
</autocomplete-input>
|
|
117
130
|
</div>
|
|
118
131
|
</section>
|
|
132
|
+
|
|
133
|
+
<section class="demo-section">
|
|
134
|
+
<h2 class="title-large">Custom Input</h2>
|
|
135
|
+
|
|
136
|
+
<div class="demo-row">
|
|
137
|
+
<autocomplete-input @autocomplete="${this.handleDynamicCustomAutocomplete}">
|
|
138
|
+
<div class="share-input" slot="anchor">
|
|
139
|
+
<div class="input-container">
|
|
140
|
+
<input
|
|
141
|
+
type="text"
|
|
142
|
+
slot="input"
|
|
143
|
+
autocomplete="off"
|
|
144
|
+
placeholder="Enter name or email"
|
|
145
|
+
name="dynamic-custom"
|
|
146
|
+
class="native-input"
|
|
147
|
+
.value="${live(this.dynamicCustomValue)}"
|
|
148
|
+
@input="${this.handleDynamicCustomInput}"
|
|
149
|
+
/>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
<ui-listbox slot="suggestions"
|
|
153
|
+
>${this.customUsers.map(
|
|
154
|
+
(user) =>
|
|
155
|
+
html`<ui-list-item
|
|
156
|
+
data-name="${user.name}"
|
|
157
|
+
data-email="${user.email[0].email}"
|
|
158
|
+
data-index="name email"
|
|
159
|
+
role="option"
|
|
160
|
+
>${user.name}<span slot="supporting-text">${user.email[0].email}</span></ui-list-item
|
|
161
|
+
>`
|
|
162
|
+
)}</ui-listbox
|
|
163
|
+
>
|
|
164
|
+
</autocomplete-input>
|
|
165
|
+
</div>
|
|
166
|
+
</section>
|
|
119
167
|
`
|
|
120
168
|
}
|
|
121
169
|
}
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { html, LitElement, PropertyValues, TemplateResult } from 'lit'
|
|
2
|
-
import { state } from 'lit/decorators.js'
|
|
2
|
+
import { property, state } from 'lit/decorators.js'
|
|
3
3
|
import type UiListbox from '../../../md/listbox/internals/Listbox.js'
|
|
4
4
|
import type UiListItem from '../../../md/list/internals/ListItem.js'
|
|
5
5
|
import { bound } from '../../../decorators/bound.js'
|
|
@@ -24,10 +24,20 @@ import { bound } from '../../../decorators/bound.js'
|
|
|
24
24
|
* - If a `ui-list-item` has a `data-index` attribute (e.g., `data-index="name email"`), filtering will search
|
|
25
25
|
* within the specified `data-*` attributes (e.g., `data-name`, `data-email`).
|
|
26
26
|
* - **Mutation Awareness**: Reacts to changes in slotted suggestions, re-filtering them if necessary.
|
|
27
|
-
* - **Event-Driven**: Notifies the
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
27
|
+
* - **Event-Driven**: Notifies the application via an `autocomplete` event when a suggestion is selected.
|
|
28
|
+
* The component itself does not modify the input's value, giving the application author full control.
|
|
29
|
+
*
|
|
30
|
+
* ### Accessibility
|
|
31
|
+
*
|
|
32
|
+
* The `autocomplete-input` component is designed with accessibility at its core:
|
|
33
|
+
* - **Keyboard Navigation**: Full keyboard support for navigating suggestions (ArrowUp, ArrowDown),
|
|
34
|
+
* selecting (Enter), and closing (Escape), all while focus remains on the input field.
|
|
35
|
+
* - **ARIA Attributes**: While the `autocomplete-input` manages ARIA attributes related to the popover's
|
|
36
|
+
* state, the slotted `ui-listbox` is responsible for its internal ARIA roles and states
|
|
37
|
+
* (e.g., `role="listbox"`, `aria-activedescendant`).
|
|
38
|
+
* - **Labeling Suggestions**: It is **crucial** for accessibility that the slotted `ui-listbox`
|
|
39
|
+
* has an `aria-label` attribute. This provides a descriptive name for the list of suggestions,
|
|
40
|
+
* which is announced by screen readers.
|
|
31
41
|
*
|
|
32
42
|
* The component uses CSS anchor positioning to place the suggestions popover relative to the input.
|
|
33
43
|
* Ensure your `ui-listbox` is styled appropriately for popover display (e.g., `popover="manual"`, `position-anchor`).
|
|
@@ -37,6 +47,10 @@ import { bound } from '../../../decorators/bound.js'
|
|
|
37
47
|
* or behave like one (have a `value` property and dispatch `input`, `focus`, `blur`, and `keydown`
|
|
38
48
|
* events).
|
|
39
49
|
* @slot suggestions - The `ui-listbox` element containing `ui-list-item` elements as suggestions.
|
|
50
|
+
* @slot anchor - An optional element that points to element that will be used as the anchor for the popover.
|
|
51
|
+
* This is useful if you want to position the suggestions relative to a different element than the input.
|
|
52
|
+
* If not provided, the input element will be used as the anchor.
|
|
53
|
+
* @slot Any additional content that should be rendered inside the component.
|
|
40
54
|
*
|
|
41
55
|
* @fires autocomplete - Dispatched when a suggestion is selected by the user (e.g., via click or Enter key).
|
|
42
56
|
* The `event.detail` object contains:
|
|
@@ -51,6 +65,12 @@ import { bound } from '../../../decorators/bound.js'
|
|
|
51
65
|
* <ui-list-item data-value="banana">Banana</ui-list-item>
|
|
52
66
|
* <ui-list-item data-value="cherry">Cherry</ui-list-item>
|
|
53
67
|
* </ui-listbox>
|
|
68
|
+
* <!-- With aria-label for accessibility -->
|
|
69
|
+
* <ui-listbox slot="suggestions" aria-label="Fruit suggestions">
|
|
70
|
+
* <ui-list-item data-value="apple">Apple</ui-list-item>
|
|
71
|
+
* <ui-list-item data-value="banana">Banana</ui-list-item>
|
|
72
|
+
* <ui-list-item data-value="cherry">Cherry</ui-list-item>
|
|
73
|
+
* </ui-listbox>
|
|
54
74
|
* </autocomplete-input>
|
|
55
75
|
*
|
|
56
76
|
* <script>
|
|
@@ -70,7 +90,7 @@ import { bound } from '../../../decorators/bound.js'
|
|
|
70
90
|
* ```html
|
|
71
91
|
* <autocomplete-input @autocomplete="${this.handleUserSelection}">
|
|
72
92
|
* <input slot="input" type="text" placeholder="Search users..." />
|
|
73
|
-
* <ui-listbox slot="suggestions">
|
|
93
|
+
* <ui-listbox slot="suggestions" aria-label="User suggestions">
|
|
74
94
|
* <ui-list-item data-id="1" data-name="Alice Wonderland" data-email="alice@example.com" data-index="name email">
|
|
75
95
|
* Alice Wonderland
|
|
76
96
|
* <span slot="supporting-text">alice@example.com</span>
|
|
@@ -97,6 +117,10 @@ import { bound } from '../../../decorators/bound.js'
|
|
|
97
117
|
* ```
|
|
98
118
|
*/
|
|
99
119
|
export default class Autocomplete extends LitElement {
|
|
120
|
+
protected override createRenderRoot(): HTMLElement | DocumentFragment {
|
|
121
|
+
return this
|
|
122
|
+
}
|
|
123
|
+
|
|
100
124
|
/**
|
|
101
125
|
* The MutationObserver instance used to watch for changes in slotted children.
|
|
102
126
|
*/
|
|
@@ -108,6 +132,20 @@ export default class Autocomplete extends LitElement {
|
|
|
108
132
|
*/
|
|
109
133
|
@state() protected accessor inputId = ''
|
|
110
134
|
|
|
135
|
+
/**
|
|
136
|
+
* The position area for the suggestions popover.
|
|
137
|
+
* This can be 'bottom' or 'top', depending on available space.
|
|
138
|
+
* Default is 'bottom'.
|
|
139
|
+
*
|
|
140
|
+
* Note, this is set dynamically based on available space
|
|
141
|
+
* and the position of the input element. This only sets the initial value.
|
|
142
|
+
*
|
|
143
|
+
* @attribute
|
|
144
|
+
* @type {'bottom' | 'top'}
|
|
145
|
+
* @default 'bottom'
|
|
146
|
+
*/
|
|
147
|
+
@property() accessor positionArea: 'bottom' | 'top' = 'bottom'
|
|
148
|
+
|
|
111
149
|
/**
|
|
112
150
|
* The reference to the slotted input element.
|
|
113
151
|
* This should be an `HTMLInputElement` or behave like one.
|
|
@@ -130,6 +168,10 @@ export default class Autocomplete extends LitElement {
|
|
|
130
168
|
super.connectedCallback()
|
|
131
169
|
this.observer = new MutationObserver(this.handleMutations.bind(this))
|
|
132
170
|
this.observer.observe(this, { childList: true })
|
|
171
|
+
if (!this.id) {
|
|
172
|
+
this.id = `autocomplete-${Math.random().toString(36).substring(2, 15)}`
|
|
173
|
+
}
|
|
174
|
+
this.classList.add(this.id)
|
|
133
175
|
}
|
|
134
176
|
|
|
135
177
|
override disconnectedCallback(): void {
|
|
@@ -151,20 +193,13 @@ export default class Autocomplete extends LitElement {
|
|
|
151
193
|
*/
|
|
152
194
|
async firstSetup(): Promise<void> {
|
|
153
195
|
await this.updateComplete
|
|
154
|
-
const input = this.
|
|
155
|
-
const suggestions = this.
|
|
196
|
+
const input = this.querySelector('[slot="input"]') as HTMLElement | null
|
|
197
|
+
const suggestions = this.querySelector('[slot="suggestions"]') as HTMLElement | null
|
|
156
198
|
if (input) {
|
|
157
|
-
|
|
158
|
-
if (elements.length > 0 && elements[0] instanceof HTMLElement) {
|
|
159
|
-
this.setupInput(elements[0])
|
|
160
|
-
}
|
|
199
|
+
this.setupInput(input)
|
|
161
200
|
}
|
|
162
201
|
if (suggestions) {
|
|
163
|
-
|
|
164
|
-
if (elements.length > 0 && elements[0] instanceof HTMLElement) {
|
|
165
|
-
// Assuming the slotted element is ui-listbox or compatible
|
|
166
|
-
this.setupSuggestions(elements[0] as UiListbox)
|
|
167
|
-
}
|
|
202
|
+
this.setupSuggestions(suggestions as UiListbox)
|
|
168
203
|
}
|
|
169
204
|
}
|
|
170
205
|
|
|
@@ -239,20 +274,16 @@ export default class Autocomplete extends LitElement {
|
|
|
239
274
|
} else {
|
|
240
275
|
this.inputId = input.id
|
|
241
276
|
}
|
|
277
|
+
const anchorElement = this.querySelector('[slot="anchor"]') as HTMLElement | null
|
|
242
278
|
// Ensure CSS anchor positioning can work
|
|
243
|
-
|
|
279
|
+
const anchor = anchorElement || input
|
|
280
|
+
anchor.style.setProperty('anchor-name', `--${this.inputId}`)
|
|
244
281
|
|
|
245
282
|
this.inputRef = input as HTMLInputElement // Assuming it behaves like an input
|
|
246
283
|
this.inputRef.addEventListener('focus', this.handleInputFocus)
|
|
247
284
|
this.inputRef.addEventListener('input', this.handleInput)
|
|
248
285
|
this.inputRef.addEventListener('keydown', this.handleKeydown as EventListener)
|
|
249
286
|
this.inputRef.addEventListener('blur', this.handleInputBlur)
|
|
250
|
-
|
|
251
|
-
// Apply initial filtering if suggestions are already present
|
|
252
|
-
if (this.suggestionsRef) {
|
|
253
|
-
this.suggestionsRef.style.setProperty('position-anchor', `--${this.inputId}`)
|
|
254
|
-
this.filterSuggestions((this.inputRef as HTMLInputElement).value || '')
|
|
255
|
-
}
|
|
256
287
|
}
|
|
257
288
|
|
|
258
289
|
/**
|
|
@@ -267,10 +298,6 @@ export default class Autocomplete extends LitElement {
|
|
|
267
298
|
this.suggestionsRef.popover = 'manual'
|
|
268
299
|
this.suggestionsRef.tabIndex = -1 // Prevent direct focus
|
|
269
300
|
|
|
270
|
-
if (this.inputId) {
|
|
271
|
-
this.suggestionsRef.style.setProperty('position-anchor', `--${this.inputId}`)
|
|
272
|
-
}
|
|
273
|
-
|
|
274
301
|
this.suggestionsRef.addEventListener('select', this.handleSuggestionSelect as EventListener)
|
|
275
302
|
// The `List` dispatches `itemschange` when the slot changes, so we can listen to it.
|
|
276
303
|
this.suggestionsRef.addEventListener('itemschange', this.handleSuggestionsSlotChange)
|
|
@@ -417,9 +444,53 @@ export default class Autocomplete extends LitElement {
|
|
|
417
444
|
* Opens the suggestions popover if it's not already open and there are visible items.
|
|
418
445
|
*/
|
|
419
446
|
protected openSuggestions(): void {
|
|
420
|
-
|
|
421
|
-
|
|
447
|
+
const popover = this.suggestionsRef
|
|
448
|
+
if (!popover || popover.matches(':popover-open')) {
|
|
449
|
+
return
|
|
450
|
+
}
|
|
451
|
+
// we need to open the popover first to make any measurements
|
|
452
|
+
popover.showPopover()
|
|
453
|
+
const anchor = (this.querySelector('[slot="anchor"]') as HTMLElement | null) || this.inputRef
|
|
454
|
+
if (!anchor) {
|
|
455
|
+
return
|
|
456
|
+
}
|
|
457
|
+
this.positionArea = this.decidePositionArea(popover, anchor)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Decides the position area for the popover based on available space.
|
|
462
|
+
* It checks if there is enough space below or above the anchor element and decides accordingly.
|
|
463
|
+
*
|
|
464
|
+
* We need to do this because we set the popover height to `100%`, `-webkit-fill-available`, or `-moz-available`
|
|
465
|
+
* and it makes the popover to always open at the bottom, even if there is no space. The `position-try-fallbacks`
|
|
466
|
+
* will not work in this case, because from its perspective there is always enough space, even if that will cause
|
|
467
|
+
* the popover to have a height of just a few pixels.
|
|
468
|
+
*
|
|
469
|
+
* @param popover The popover element to position.
|
|
470
|
+
* @param anchor The anchor element relative to which the popover will be positioned.
|
|
471
|
+
* @returns 'top' or 'bottom' based on available space.
|
|
472
|
+
*/
|
|
473
|
+
protected decidePositionArea(popover: HTMLElement, anchor: HTMLElement): 'top' | 'bottom' {
|
|
474
|
+
let newArea: 'top' | 'bottom' = 'bottom'
|
|
475
|
+
// Get bounding rectangles
|
|
476
|
+
const anchorRect = anchor.getBoundingClientRect()
|
|
477
|
+
const viewportHeight = window.innerHeight
|
|
478
|
+
|
|
479
|
+
const spaceBelow = viewportHeight - anchorRect.bottom
|
|
480
|
+
const spaceAbove = anchorRect.top
|
|
481
|
+
|
|
482
|
+
// Estimate a typical/minimum height the popover might need to be useful
|
|
483
|
+
const popoverThresholdHeight = popover.scrollHeight || 150
|
|
484
|
+
if (spaceBelow < popoverThresholdHeight && spaceAbove > spaceBelow && spaceAbove > popoverThresholdHeight) {
|
|
485
|
+
// Not enough space below, but more (and sufficient) space above
|
|
486
|
+
newArea = 'top'
|
|
487
|
+
} else if (spaceBelow < popoverThresholdHeight && spaceAbove > spaceBelow) {
|
|
488
|
+
// Not enough space below, and space above is more than space below (even if not "sufficient")
|
|
489
|
+
newArea = 'top'
|
|
490
|
+
} else {
|
|
491
|
+
newArea = 'bottom' // Default to bottom if enough space or if top is worse
|
|
422
492
|
}
|
|
493
|
+
return newArea
|
|
423
494
|
}
|
|
424
495
|
|
|
425
496
|
/**
|
|
@@ -482,9 +553,42 @@ export default class Autocomplete extends LitElement {
|
|
|
482
553
|
}
|
|
483
554
|
|
|
484
555
|
protected override render(): TemplateResult {
|
|
556
|
+
const { id, positionArea, inputId } = this
|
|
557
|
+
/*
|
|
558
|
+
position-try-fallbacks:
|
|
559
|
+
flip-block,
|
|
560
|
+
flip-inline,
|
|
561
|
+
flip-block flip-inline;
|
|
562
|
+
*/
|
|
485
563
|
return html`
|
|
486
|
-
<
|
|
487
|
-
|
|
564
|
+
<style>
|
|
565
|
+
.${id} {
|
|
566
|
+
display: inline-block;
|
|
567
|
+
|
|
568
|
+
[popover] {
|
|
569
|
+
border: none;
|
|
570
|
+
margin: 0;
|
|
571
|
+
|
|
572
|
+
box-shadow: var(--md-sys-elevation-1);
|
|
573
|
+
border-radius: var(--md-sys-shape-corner-medium);
|
|
574
|
+
|
|
575
|
+
position-area: ${positionArea};
|
|
576
|
+
position-anchor: --${inputId};
|
|
577
|
+
|
|
578
|
+
overflow: auto;
|
|
579
|
+
/* We try 100% and then vendor options which are more accurate */
|
|
580
|
+
height: 100%;
|
|
581
|
+
height: -webkit-fill-available;
|
|
582
|
+
height: -moz-available;
|
|
583
|
+
max-height: max-content;
|
|
584
|
+
width: anchor-size(width);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
[popover]:not(:popover-open) {
|
|
588
|
+
display: none;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
</style>
|
|
488
592
|
`
|
|
489
593
|
}
|
|
490
594
|
}
|
|
@@ -154,8 +154,8 @@ export default class MarkedHighlight extends LitElement {
|
|
|
154
154
|
return child || (this.shadowRoot?.querySelector('#content') as HTMLElement | null)
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
protected override
|
|
158
|
-
super.
|
|
157
|
+
protected override willUpdate(cp: PropertyValues<this>): void {
|
|
158
|
+
super.willUpdate(cp)
|
|
159
159
|
if (
|
|
160
160
|
cp.has('markdown') ||
|
|
161
161
|
cp.has('sanitizer') ||
|
|
@@ -177,7 +177,21 @@ export default class MarkedHighlight extends LitElement {
|
|
|
177
177
|
this.renderMarkdown()
|
|
178
178
|
return
|
|
179
179
|
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
override connectedCallback(): void {
|
|
183
|
+
super.connectedCallback()
|
|
184
|
+
this.attachedValue = true
|
|
185
|
+
this.processScript()
|
|
186
|
+
this.renderMarkdown()
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
override disconnectedCallback(): void {
|
|
190
|
+
super.disconnectedCallback()
|
|
191
|
+
this.attachedValue = false
|
|
192
|
+
}
|
|
180
193
|
|
|
194
|
+
protected async processScript(): Promise<void> {
|
|
181
195
|
// Use the Markdown from the first `<script>` descendant whose MIME type
|
|
182
196
|
// starts with "text/markdown". Script elements beyond the first are
|
|
183
197
|
// ignored.
|
|
@@ -199,17 +213,6 @@ export default class MarkedHighlight extends LitElement {
|
|
|
199
213
|
observer.observe(script, { attributes: true })
|
|
200
214
|
}
|
|
201
215
|
|
|
202
|
-
override connectedCallback(): void {
|
|
203
|
-
super.connectedCallback()
|
|
204
|
-
this.attachedValue = true
|
|
205
|
-
this.renderMarkdown()
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
override disconnectedCallback(): void {
|
|
209
|
-
super.disconnectedCallback()
|
|
210
|
-
this.attachedValue = false
|
|
211
|
-
}
|
|
212
|
-
|
|
213
216
|
/**
|
|
214
217
|
* Renders `markdown` into this element's DOM.
|
|
215
218
|
*
|