@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.
@@ -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
- updated(cp) {
126
- super.updated(cp);
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.users = Array.from({ length: results }, () => {
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.users)
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@api-client/ui",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Internal UI component library for the API Client ecosystem.",
5
5
  "license": "UNLICENSED",
6
6
  "main": "build/src/index.js",
@@ -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 parent application via an `autocomplete` event when a suggestion is selected,
28
- * without directly modifying the input's value. This allows the application author to control the input update logic.
29
- * - **Accessibility**: Designed with accessibility in mind, ensuring keyboard navigability and proper ARIA attribute
30
- * management (handled by `ui-listbox`).
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.shadowRoot?.querySelector('slot[name="input"]') as HTMLSlotElement | null
155
- const suggestions = this.shadowRoot?.querySelector('slot[name="suggestions"]') as HTMLSlotElement | null
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
- const elements = input.assignedElements()
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
- const elements = suggestions.assignedElements()
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
- input.style.setProperty('anchor-name', `--${this.inputId}`)
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
- if (this.suggestionsRef && !this.suggestionsRef.matches(':popover-open')) {
421
- this.suggestionsRef.showPopover()
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
- <slot name="input"></slot>
487
- <slot name="suggestions"></slot>
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 updated(cp: PropertyValues<this>): void {
158
- super.updated(cp)
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
  *