@ai-composer/web-component 0.0.0-stage → 0.1.0

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.
@@ -0,0 +1,22 @@
1
+ import type { StorybookConfig } from '@storybook/web-components-vite';
2
+ import { resolve } from 'node:path';
3
+
4
+ const config: StorybookConfig = {
5
+ framework: { name: '@storybook/web-components-vite', options: {} },
6
+ stories: ['../src/**/*.stories.@(ts|tsx)'],
7
+ addons: ['@storybook/addon-a11y'],
8
+ viteFinal: (config) => {
9
+ config.resolve ??= {};
10
+ config.resolve.alias = {
11
+ ...config.resolve.alias,
12
+ '@ai-composer/core': resolve(__dirname, '../../../packages/core/src/index.ts'),
13
+ '@ai-composer/dom': resolve(__dirname, '../../dom/src/index.ts'),
14
+ '@ai-composer/web-component': resolve(__dirname, '../src/index.ts'),
15
+ '@ai-composer/plugin-mention': resolve(__dirname, '../../../plugins/mention/src/index.ts'),
16
+ '@ai-composer/plugin-command': resolve(__dirname, '../../../plugins/command/src/index.ts'),
17
+ };
18
+ return config;
19
+ },
20
+ };
21
+
22
+ export default config;
@@ -0,0 +1,20 @@
1
+ import type { Preview } from '@storybook/web-components-vite';
2
+ import '@ai-composer/themes/css/tokens.css';
3
+ import '@ai-composer/themes/css/default.css';
4
+ import '@ai-composer/themes/css/dark.css';
5
+ // Register <ai-composer-editor> for every story.
6
+ import '@ai-composer/web-component';
7
+
8
+ const preview: Preview = {
9
+ parameters: {
10
+ layout: 'padded',
11
+ docs: {
12
+ description: {
13
+ component:
14
+ 'AI Composer — **universal `<ai-composer-editor>` custom element** (`@ai-composer/web-component`). Works in React, Angular, Vue, Svelte or plain HTML/JS.',
15
+ },
16
+ },
17
+ },
18
+ };
19
+
20
+ export default preview;
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Deepak Chopkar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,6 @@
1
- # Temporary Holding Version
1
+ # @ai-composer/web-component
2
+
3
+ Universal <ai-composer-editor> custom element.
4
+
5
+ See [`docs/`](../../docs/README.md) for guides and API reference.
2
6
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @ai-composer/web-component — universal `<ai-composer-editor>` custom element.
3
+ *
4
+ * ```html
5
+ * <ai-composer-editor mode="chat" placeholder="Ask anything…"></ai-composer-editor>
6
+ *
7
+ * <script type="module">
8
+ * import { defineAiComposerEditor } from '@ai-composer/web-component';
9
+ * defineAiComposerEditor();
10
+ * </script>
11
+ * ```
12
+ *
13
+ * Attributes: mode, placeholder, disabled, readonly, max-height (CSS length).
14
+ * Properties: editor (external AIComposer), plugins, value.
15
+ * Events: aic-change, aic-input, aic-submit, aic-focus, aic-blur (CustomEvent, detail = payload).
16
+ */
17
+ import { type AIComposer, type AIComposerPlugin } from '@ai-composer/core';
18
+ declare const TAG_NAME = "ai-composer-editor";
19
+ export declare class AiComposerEditorElement extends HTMLElement {
20
+ static get observedAttributes(): string[];
21
+ private _plugins;
22
+ private _editor;
23
+ private mounted;
24
+ private unsubscribes;
25
+ /**
26
+ * Plugins for the internally created editor. May be set before or after
27
+ * connection — after connection the internal editor is rebuilt.
28
+ */
29
+ get plugins(): AIComposerPlugin[];
30
+ set plugins(value: AIComposerPlugin[]);
31
+ /** Use an externally owned editor (takes precedence over internal creation). */
32
+ get editor(): AIComposer | null;
33
+ set editor(value: AIComposer | null);
34
+ get value(): string;
35
+ set value(json: string);
36
+ connectedCallback(): void;
37
+ disconnectedCallback(): void;
38
+ attributeChangedCallback(name: string, _old: string | null, next: string | null): void;
39
+ private remount;
40
+ private teardown;
41
+ }
42
+ /** Register <ai-composer-editor>. Idempotent. */
43
+ export declare function defineAiComposerEditor(): void;
44
+ export { TAG_NAME };
45
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAGL,KAAK,UAAU,EAGf,KAAK,gBAAgB,EACtB,MAAM,mBAAmB,CAAC;AAG3B,QAAA,MAAM,QAAQ,uBAAuB,CAAC;AAsBtC,qBAAa,uBAAwB,SAAQ,WAAW;IACtD,MAAM,KAAK,kBAAkB,IAAI,MAAM,EAAE,CAExC;IAED,OAAO,CAAC,QAAQ,CAA0B;IAC1C,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,YAAY,CAAyB;IAE7C;;;OAGG;IACH,IAAI,OAAO,IAAI,gBAAgB,EAAE,CAEhC;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,gBAAgB,EAAE,EAOpC;IAED,gFAAgF;IAChF,IAAI,MAAM,IAAI,UAAU,GAAG,IAAI,CAE9B;IACD,IAAI,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,EAIlC;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;IACD,IAAI,KAAK,CAAC,IAAI,EAAE,MAAM,EAOrB;IAED,iBAAiB,IAAI,IAAI;IAIzB,oBAAoB,IAAI,IAAI;IAI5B,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAiBtF,OAAO,CAAC,OAAO;IA4Bf,OAAO,CAAC,QAAQ;CAKjB;AAED,iDAAiD;AACjD,wBAAgB,sBAAsB,IAAI,IAAI,CAK7C;AAED,OAAO,EAAE,QAAQ,EAAE,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,153 @@
1
+ /**
2
+ * @ai-composer/web-component — universal `<ai-composer-editor>` custom element.
3
+ *
4
+ * ```html
5
+ * <ai-composer-editor mode="chat" placeholder="Ask anything…"></ai-composer-editor>
6
+ *
7
+ * <script type="module">
8
+ * import { defineAiComposerEditor } from '@ai-composer/web-component';
9
+ * defineAiComposerEditor();
10
+ * </script>
11
+ * ```
12
+ *
13
+ * Attributes: mode, placeholder, disabled, readonly, max-height (CSS length).
14
+ * Properties: editor (external AIComposer), plugins, value.
15
+ * Events: aic-change, aic-input, aic-submit, aic-focus, aic-blur (CustomEvent, detail = payload).
16
+ */
17
+ import { createAIComposer, isAIComposer, } from '@ai-composer/core';
18
+ import { mountAIComposer } from '@ai-composer/dom';
19
+ const TAG_NAME = 'ai-composer-editor';
20
+ const ATTRIBUTE_MAP = {
21
+ mode: 'mode',
22
+ placeholder: 'placeholder',
23
+ disabled: 'disabled',
24
+ readonly: 'readonly',
25
+ 'max-height': 'maxHeight',
26
+ };
27
+ const FORWARDED_EVENTS = [
28
+ 'change',
29
+ 'input',
30
+ 'submit',
31
+ 'focus',
32
+ 'blur',
33
+ 'triggerOpen',
34
+ 'triggerClose',
35
+ 'commandExecute',
36
+ 'error',
37
+ ];
38
+ export class AiComposerEditorElement extends HTMLElement {
39
+ static get observedAttributes() {
40
+ return Object.keys(ATTRIBUTE_MAP);
41
+ }
42
+ _plugins = [];
43
+ _editor = null;
44
+ mounted = null;
45
+ unsubscribes = [];
46
+ /**
47
+ * Plugins for the internally created editor. May be set before or after
48
+ * connection — after connection the internal editor is rebuilt.
49
+ */
50
+ get plugins() {
51
+ return this._plugins;
52
+ }
53
+ set plugins(value) {
54
+ this._plugins = value;
55
+ if (!this.isConnected)
56
+ return;
57
+ this.teardown();
58
+ this._editor?.destroy();
59
+ this._editor = null;
60
+ this.remount();
61
+ }
62
+ /** Use an externally owned editor (takes precedence over internal creation). */
63
+ get editor() {
64
+ return this._editor;
65
+ }
66
+ set editor(value) {
67
+ if (value && !isAIComposer(value))
68
+ return;
69
+ this._editor = value;
70
+ if (this.isConnected)
71
+ this.remount();
72
+ }
73
+ get value() {
74
+ return this._editor ? this._editor.serialize('json') : '';
75
+ }
76
+ set value(json) {
77
+ if (!this._editor)
78
+ return;
79
+ try {
80
+ this._editor.setValue(JSON.parse(json));
81
+ }
82
+ catch {
83
+ this._editor.setValue(json);
84
+ }
85
+ }
86
+ connectedCallback() {
87
+ this.remount();
88
+ }
89
+ disconnectedCallback() {
90
+ this.teardown();
91
+ }
92
+ attributeChangedCallback(name, _old, next) {
93
+ const key = ATTRIBUTE_MAP[name];
94
+ if (!key)
95
+ return;
96
+ // Auto-height ceiling is a pure style concern (token override).
97
+ if (key === 'maxHeight') {
98
+ if (next === null)
99
+ this.style.removeProperty('--aic-input-max-height');
100
+ else
101
+ this.style.setProperty('--aic-input-max-height', next);
102
+ return;
103
+ }
104
+ if (!this._editor)
105
+ return;
106
+ if (key === 'disabled' || key === 'readonly') {
107
+ this._editor.configure({ [key]: next !== null });
108
+ }
109
+ else if (next !== null) {
110
+ this._editor.configure({ [key]: next });
111
+ }
112
+ }
113
+ remount() {
114
+ this.teardown();
115
+ if (!this._editor)
116
+ this._editor = createAIComposer({ plugins: this._plugins });
117
+ this.mounted = mountAIComposer(this, this._editor, {
118
+ mode: this.getAttribute('mode') ?? undefined,
119
+ });
120
+ if (this.getAttribute('placeholder')) {
121
+ this._editor.configure({ placeholder: this.getAttribute('placeholder') });
122
+ }
123
+ if (this.hasAttribute('disabled'))
124
+ this._editor.configure({ disabled: true });
125
+ if (this.hasAttribute('readonly'))
126
+ this._editor.configure({ readonly: true });
127
+ for (const type of FORWARDED_EVENTS) {
128
+ this.unsubscribes.push(this._editor.on(type, (payload) => {
129
+ this.dispatchEvent(new CustomEvent(`aic-${type}`, {
130
+ detail: payload,
131
+ bubbles: type === 'submit' || type === 'change',
132
+ composed: true,
133
+ }));
134
+ }));
135
+ }
136
+ }
137
+ teardown() {
138
+ for (const unsubscribe of this.unsubscribes.splice(0))
139
+ unsubscribe();
140
+ this.mounted?.destroy();
141
+ this.mounted = null;
142
+ }
143
+ }
144
+ /** Register <ai-composer-editor>. Idempotent. */
145
+ export function defineAiComposerEditor() {
146
+ if (typeof customElements === 'undefined')
147
+ return;
148
+ if (!customElements.get(TAG_NAME)) {
149
+ customElements.define(TAG_NAME, AiComposerEditorElement);
150
+ }
151
+ }
152
+ export { TAG_NAME };
153
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@ai-composer/web-component",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "sideEffects": false,
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./package.json": "./package.json"
15
+ },
16
+ "peerDependencies": {
17
+ "@ai-composer/core": "^0.1.0",
18
+ "@ai-composer/dom": "^0.1.0"
19
+ },
20
+ "devDependencies": {
21
+ "@ai-composer/core": "workspace:*",
22
+ "@ai-composer/dom": "workspace:*",
23
+ "vitest": "^2.1.8",
24
+ "jsdom": "^25.0.1",
25
+ "@ai-composer/testing": "workspace:*",
26
+ "@ai-composer/plugin-mention": "workspace:*"
27
+ },
28
+ "module": "./index.js"
29
+ }
package/package.json CHANGED
@@ -1,6 +1,28 @@
1
1
  {
2
2
  "name": "@ai-composer/web-component",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "sideEffects": false,
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./package.json": "./package.json"
15
+ },
16
+ "peerDependencies": {
17
+ "@ai-composer/core": "^0.1.0",
18
+ "@ai-composer/dom": "^0.1.0"
19
+ },
20
+ "devDependencies": {
21
+ "vitest": "^2.1.8",
22
+ "jsdom": "^25.0.1",
23
+ "@ai-composer/dom": "0.1.0",
24
+ "@ai-composer/core": "0.1.0",
25
+ "@ai-composer/testing": "0.1.0",
26
+ "@ai-composer/plugin-mention": "0.1.0"
27
+ }
6
28
  }
package/project.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "web-component",
3
+ "$schema": "../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "packages/web-component/src",
5
+ "projectType": "library",
6
+ "tags": [
7
+ "scope:adapter",
8
+ "type:lib"
9
+ ],
10
+ "targets": {
11
+ "build": {
12
+ "executor": "@nx/js:tsc",
13
+ "outputs": [
14
+ "{options.outputPath}"
15
+ ],
16
+ "options": {
17
+ "outputPath": "packages/web-component/dist",
18
+ "main": "packages/web-component/src/index.ts",
19
+ "tsConfig": "packages/web-component/tsconfig.lib.json",
20
+ "rootDir": "packages/web-component/src",
21
+ "assets": [
22
+ {
23
+ "input": "../packages/web-component",
24
+ "glob": "*.md",
25
+ "output": "."
26
+ }
27
+ ],
28
+ "generateExportsIfAbsent": false
29
+ }
30
+ },
31
+ "test": {
32
+ "executor": "nx:run-commands",
33
+ "options": {
34
+ "command": "vitest run --project web-component"
35
+ }
36
+ },
37
+ "lint": {
38
+ "executor": "@nx/eslint:lint",
39
+ "outputs": [
40
+ "{options.outputFile}"
41
+ ],
42
+ "options": {
43
+ "lintFilePatterns": [
44
+ "packages/web-component/**/*.ts",
45
+ "packages/web-component/**/*.tsx"
46
+ ]
47
+ }
48
+ },
49
+ "typecheck": {
50
+ "executor": "nx:run-commands",
51
+ "options": {
52
+ "command": "tsc -p packages/web-component/tsconfig.spec.json --noEmit"
53
+ }
54
+ },
55
+ "storybook": {
56
+ "executor": "nx:run-commands",
57
+ "options": {
58
+ "command": "storybook dev --port 6009 --no-open -c .storybook",
59
+ "cwd": "packages/web-component"
60
+ }
61
+ },
62
+ "build-storybook": {
63
+ "executor": "nx:run-commands",
64
+ "options": {
65
+ "command": "storybook build -c .storybook --output-dir ../../dist/storybook/web-component",
66
+ "cwd": "packages/web-component"
67
+ },
68
+ "outputs": [
69
+ "{workspaceRoot}/dist/storybook/web-component"
70
+ ]
71
+ }
72
+ }
73
+ }
@@ -0,0 +1,82 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import { createAIComposer } from '@ai-composer/core';
3
+ import { mentionPlugin } from '@ai-composer/plugin-mention';
4
+ import { defineAIComposerContractSuite } from '@ai-composer/testing';
5
+ import { defineAiComposerEditor } from './index';
6
+
7
+ defineAiComposerEditor();
8
+
9
+ let host: HTMLDivElement | null = null;
10
+
11
+ function attach(html: string): HTMLElement {
12
+ host = document.createElement('div');
13
+ host.innerHTML = html;
14
+ document.body.appendChild(host);
15
+ return host.firstElementChild as HTMLElement;
16
+ }
17
+
18
+ async function attachUpgraded(html: string): Promise<HTMLElement> {
19
+ const element = attach(html);
20
+ // mount() promotes the element itself to .aic-root
21
+ await vi.waitFor(() => expect(element.classList.contains('aic-root')).toBe(true));
22
+ return element;
23
+ }
24
+
25
+ afterEach(() => {
26
+ host?.remove();
27
+ host = null;
28
+ });
29
+
30
+ describe('<ai-composer-editor>', () => {
31
+ it('defines and upgrades the custom element', async () => {
32
+ const element = await attachUpgraded('<ai-composer-editor mode="chat"></ai-composer-editor>');
33
+ expect(element.getAttribute('data-aic-mode')).toBe('chat');
34
+ expect(element.querySelector('[data-aic-input]')).not.toBeNull();
35
+ });
36
+
37
+ it('attributes map to editor config', async () => {
38
+ const element = await attachUpgraded(
39
+ '<ai-composer-editor placeholder="Hi" disabled></ai-composer-editor>',
40
+ );
41
+ const editor = (element as unknown as { editor: ReturnType<typeof createAIComposer> }).editor;
42
+ expect(editor.getState().placeholder).toBe('Hi');
43
+ expect(editor.getState().disabled).toBe(true);
44
+ });
45
+
46
+ it('typing syncs and aic-change/aic-submit events fire', async () => {
47
+ const element = await attachUpgraded('<ai-composer-editor></ai-composer-editor>');
48
+ const editor = (element as unknown as { editor: ReturnType<typeof createAIComposer> }).editor;
49
+ const onChange = vi.fn();
50
+ element.addEventListener('aic-change', onChange);
51
+
52
+ const input = element.querySelector<HTMLElement>('[data-aic-input]');
53
+ input!.textContent = 'from web component';
54
+ input!.dispatchEvent(new Event('input', { bubbles: true }));
55
+
56
+ expect(editor.serialize('text')).toBe('from web component');
57
+ expect(onChange).toHaveBeenCalled();
58
+ });
59
+
60
+ it('works with plugins set before connection', async () => {
61
+ const hostDiv = document.createElement('div');
62
+ document.body.appendChild(hostDiv);
63
+ host = hostDiv;
64
+ const element = document.createElement('ai-composer-editor') as HTMLElement & {
65
+ plugins: unknown[];
66
+ };
67
+ element.plugins = [
68
+ mentionPlugin({ items: [{ id: 'u1', label: 'Ada Lovelace' }] }),
69
+ ];
70
+ hostDiv.appendChild(element); // triggers connectedCallback
71
+
72
+ const editor = (element as unknown as { editor: ReturnType<typeof createAIComposer> }).editor;
73
+ editor.insertText('@ad');
74
+ await vi.waitFor(() => expect(editor.getState().suggestions.length).toBe(1));
75
+ await editor.acceptSuggestion();
76
+ expect(editor.serialize('text')).toBe('@Ada Lovelace ');
77
+ });
78
+ });
79
+
80
+ defineAIComposerContractSuite((options) => createAIComposer(options), {
81
+ title: 'AIComposer contract (web-component package, headless reference)',
82
+ });
package/src/index.ts ADDED
@@ -0,0 +1,166 @@
1
+ /**
2
+ * @ai-composer/web-component — universal `<ai-composer-editor>` custom element.
3
+ *
4
+ * ```html
5
+ * <ai-composer-editor mode="chat" placeholder="Ask anything…"></ai-composer-editor>
6
+ *
7
+ * <script type="module">
8
+ * import { defineAiComposerEditor } from '@ai-composer/web-component';
9
+ * defineAiComposerEditor();
10
+ * </script>
11
+ * ```
12
+ *
13
+ * Attributes: mode, placeholder, disabled, readonly, max-height (CSS length).
14
+ * Properties: editor (external AIComposer), plugins, value.
15
+ * Events: aic-change, aic-input, aic-submit, aic-focus, aic-blur (CustomEvent, detail = payload).
16
+ */
17
+
18
+ import {
19
+ createAIComposer,
20
+ isAIComposer,
21
+ type AIComposer,
22
+ type AIComposerEventMap,
23
+ type AIComposerEventType,
24
+ type AIComposerPlugin,
25
+ } from '@ai-composer/core';
26
+ import { mountAIComposer, type MountedEditor } from '@ai-composer/dom';
27
+
28
+ const TAG_NAME = 'ai-composer-editor';
29
+
30
+ const ATTRIBUTE_MAP: Record<string, 'mode' | 'placeholder' | 'disabled' | 'readonly' | 'maxHeight'> = {
31
+ mode: 'mode',
32
+ placeholder: 'placeholder',
33
+ disabled: 'disabled',
34
+ readonly: 'readonly',
35
+ 'max-height': 'maxHeight',
36
+ };
37
+
38
+ const FORWARDED_EVENTS: AIComposerEventType[] = [
39
+ 'change',
40
+ 'input',
41
+ 'submit',
42
+ 'focus',
43
+ 'blur',
44
+ 'triggerOpen',
45
+ 'triggerClose',
46
+ 'commandExecute',
47
+ 'error',
48
+ ];
49
+
50
+ export class AiComposerEditorElement extends HTMLElement {
51
+ static get observedAttributes(): string[] {
52
+ return Object.keys(ATTRIBUTE_MAP);
53
+ }
54
+
55
+ private _plugins: AIComposerPlugin[] = [];
56
+ private _editor: AIComposer | null = null;
57
+ private mounted: MountedEditor | null = null;
58
+ private unsubscribes: Array<() => void> = [];
59
+
60
+ /**
61
+ * Plugins for the internally created editor. May be set before or after
62
+ * connection — after connection the internal editor is rebuilt.
63
+ */
64
+ get plugins(): AIComposerPlugin[] {
65
+ return this._plugins;
66
+ }
67
+ set plugins(value: AIComposerPlugin[]) {
68
+ this._plugins = value;
69
+ if (!this.isConnected) return;
70
+ this.teardown();
71
+ this._editor?.destroy();
72
+ this._editor = null;
73
+ this.remount();
74
+ }
75
+
76
+ /** Use an externally owned editor (takes precedence over internal creation). */
77
+ get editor(): AIComposer | null {
78
+ return this._editor;
79
+ }
80
+ set editor(value: AIComposer | null) {
81
+ if (value && !isAIComposer(value)) return;
82
+ this._editor = value;
83
+ if (this.isConnected) this.remount();
84
+ }
85
+
86
+ get value(): string {
87
+ return this._editor ? (this._editor.serialize('json') as unknown as string) : '';
88
+ }
89
+ set value(json: string) {
90
+ if (!this._editor) return;
91
+ try {
92
+ this._editor.setValue(JSON.parse(json));
93
+ } catch {
94
+ this._editor.setValue(json);
95
+ }
96
+ }
97
+
98
+ connectedCallback(): void {
99
+ this.remount();
100
+ }
101
+
102
+ disconnectedCallback(): void {
103
+ this.teardown();
104
+ }
105
+
106
+ attributeChangedCallback(name: string, _old: string | null, next: string | null): void {
107
+ const key = ATTRIBUTE_MAP[name];
108
+ if (!key) return;
109
+ // Auto-height ceiling is a pure style concern (token override).
110
+ if (key === 'maxHeight') {
111
+ if (next === null) this.style.removeProperty('--aic-input-max-height');
112
+ else this.style.setProperty('--aic-input-max-height', next);
113
+ return;
114
+ }
115
+ if (!this._editor) return;
116
+ if (key === 'disabled' || key === 'readonly') {
117
+ this._editor.configure({ [key]: next !== null });
118
+ } else if (next !== null) {
119
+ this._editor.configure({ [key]: next });
120
+ }
121
+ }
122
+
123
+ private remount(): void {
124
+ this.teardown();
125
+ if (!this._editor) this._editor = createAIComposer({ plugins: this._plugins });
126
+
127
+ this.mounted = mountAIComposer(this, this._editor, {
128
+ mode: this.getAttribute('mode') ?? undefined,
129
+ });
130
+ if (this.getAttribute('placeholder')) {
131
+ this._editor.configure({ placeholder: this.getAttribute('placeholder')! });
132
+ }
133
+ if (this.hasAttribute('disabled')) this._editor.configure({ disabled: true });
134
+ if (this.hasAttribute('readonly')) this._editor.configure({ readonly: true });
135
+
136
+ for (const type of FORWARDED_EVENTS) {
137
+ this.unsubscribes.push(
138
+ this._editor.on(type, (payload) => {
139
+ this.dispatchEvent(
140
+ new CustomEvent(`aic-${type}`, {
141
+ detail: payload as AIComposerEventMap[typeof type],
142
+ bubbles: type === 'submit' || type === 'change',
143
+ composed: true,
144
+ }),
145
+ );
146
+ }),
147
+ );
148
+ }
149
+ }
150
+
151
+ private teardown(): void {
152
+ for (const unsubscribe of this.unsubscribes.splice(0)) unsubscribe();
153
+ this.mounted?.destroy();
154
+ this.mounted = null;
155
+ }
156
+ }
157
+
158
+ /** Register <ai-composer-editor>. Idempotent. */
159
+ export function defineAiComposerEditor(): void {
160
+ if (typeof customElements === 'undefined') return;
161
+ if (!customElements.get(TAG_NAME)) {
162
+ customElements.define(TAG_NAME, AiComposerEditorElement);
163
+ }
164
+ }
165
+
166
+ export { TAG_NAME };
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Basic usage — the universal custom element, plain HTML.
3
+ */
4
+
5
+ import type { Meta, StoryObj } from '@storybook/web-components-vite';
6
+ import { defineAiComposerEditor } from '../index';
7
+
8
+ defineAiComposerEditor();
9
+
10
+ const meta: Meta = {
11
+ title: 'AI Composer/Web Component/Basic',
12
+ tags: ['autodocs'],
13
+ parameters: {
14
+ docs: {
15
+ description: {
16
+ component:
17
+ '`<ai-composer-editor>` is a universal custom element — works in React, Angular, Vue, Svelte or plain HTML. Attributes: `mode`, `placeholder`, `disabled`, `readonly`. Properties: `editor`, `plugins`. Events: `aic-change`, `aic-submit`.',
18
+ },
19
+ },
20
+ },
21
+ argTypes: {
22
+ mode: { control: 'select', options: ['compact', 'chat', 'expanded'] },
23
+ placeholder: { control: 'text' },
24
+ disabled: { control: 'boolean' },
25
+ readonly: { control: 'boolean' },
26
+ },
27
+ args: { mode: 'chat', placeholder: 'Ask anything…', disabled: false, readonly: false },
28
+ render: (args) => {
29
+ const element = document.createElement('ai-composer-editor');
30
+ element.setAttribute('mode', args.mode);
31
+ element.setAttribute('placeholder', args.placeholder);
32
+ if (args.disabled) element.setAttribute('disabled', '');
33
+ if (args.readonly) element.setAttribute('readonly', '');
34
+ return element;
35
+ },
36
+ };
37
+
38
+ export default meta;
39
+ type Story = StoryObj<typeof meta>;
40
+
41
+ export const Default: Story = {
42
+ parameters: {
43
+ docs: {
44
+ source: {
45
+ code: `<ai-composer-editor mode="chat" placeholder="Ask anything…"></ai-composer-editor>`,
46
+ },
47
+ description: { story: 'Zero configuration — import the package once (`import "@ai-composer/web-component"`), then use plain HTML.' },
48
+ },
49
+ },
50
+ };
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Events & API — custom events, element API, and the vanilla-JS mount story
3
+ * (core + dom with zero adapters — the independence proof).
4
+ */
5
+
6
+ import type { Meta, StoryObj } from '@storybook/web-components-vite';
7
+ import { createAIComposer } from '@ai-composer/core';
8
+ import { mountAIComposer } from '@ai-composer/dom';
9
+ import { mentionPlugin } from '@ai-composer/plugin-mention';
10
+ import { defineAiComposerEditor } from '../index';
11
+
12
+ defineAiComposerEditor();
13
+
14
+ const meta: Meta = {
15
+ title: 'AI Composer/Web Component/Events & Vanilla JS',
16
+ tags: ['autodocs'],
17
+ parameters: {
18
+ docs: {
19
+ description: {
20
+ component:
21
+ 'The element emits `aic-change` / `aic-submit` custom events; the underlying editor is reachable via `element.editor`. The last story uses **no adapter at all** — `@ai-composer/core` + `@ai-composer/dom` in plain JS.',
22
+ },
23
+ },
24
+ },
25
+ };
26
+
27
+ export default meta;
28
+ type Story = StoryObj<typeof meta>;
29
+
30
+ export const Events: Story = {
31
+ name: 'Events · aic-change / aic-submit',
32
+ render: () => {
33
+ const root = document.createElement('div');
34
+ root.innerHTML = `
35
+ <ai-composer-editor mode="chat" placeholder="Type and press Enter…"></ai-composer-editor>
36
+ <pre style="background:#f6f7f9;padding:12px;border-radius:8px;font-size:13px">—</pre>
37
+ `;
38
+ const element = root.querySelector('ai-composer-editor')!;
39
+ const log = root.querySelector('pre')!;
40
+ const write = (line: string): void => {
41
+ log.textContent = `${line}\n${log.textContent}`;
42
+ };
43
+ element.addEventListener('aic-change', () => {
44
+ const editor = (element as unknown as { editor: ReturnType<typeof createAIComposer> }).editor;
45
+ write(`change → ${editor.serialize('text')}`);
46
+ });
47
+ element.addEventListener('aic-submit', (event) => {
48
+ write(`submit → ${JSON.stringify((event as CustomEvent).detail)}`);
49
+ });
50
+ return root;
51
+ },
52
+ parameters: {
53
+ docs: {
54
+ source: {
55
+ code: `const el = document.querySelector('ai-composer-editor');
56
+ el.addEventListener('aic-change', () => console.log(el.editor.serialize('text')));
57
+ el.addEventListener('aic-submit', (e) => console.log(e.detail));`,
58
+ },
59
+ },
60
+ },
61
+ };
62
+
63
+ export const VanillaJsMount: Story = {
64
+ name: 'Vanilla JS · mountAIComposer()',
65
+ render: () => {
66
+ const root = document.createElement('div');
67
+ root.innerHTML = `
68
+ <div style="max-width:640px"></div>
69
+ <pre style="background:#f6f7f9;padding:12px;border-radius:8px;font-size:13px">—</pre>
70
+ `;
71
+ const container = root.querySelector('div')!;
72
+ const output = root.querySelector('pre')!;
73
+ const editor = createAIComposer({
74
+ mode: 'chat',
75
+ placeholder: 'Vanilla JS — no framework at all',
76
+ plugins: [
77
+ mentionPlugin({ items: [{ id: 'u1', label: 'Ada Lovelace', description: 'Engineering' }] }),
78
+ ],
79
+ });
80
+ mountAIComposer(container, editor, { mode: 'chat' });
81
+ editor.subscribe((state) => {
82
+ output.textContent = JSON.stringify(
83
+ { text: editor.serialize('text'), canUndo: state.canUndo, mode: state.mode },
84
+ null,
85
+ 2,
86
+ );
87
+ });
88
+ return root;
89
+ },
90
+ parameters: {
91
+ docs: {
92
+ source: {
93
+ code: `import { createAIComposer } from '@ai-composer/core';
94
+ import { mountAIComposer } from '@ai-composer/dom';
95
+ import '@ai-composer/themes/css/tokens.css';
96
+ import '@ai-composer/themes/css/default.css';
97
+
98
+ const editor = createAIComposer({
99
+ mode: 'chat',
100
+ plugins: [mentionPlugin({ items: people })],
101
+ });
102
+
103
+ mountAIComposer(document.querySelector('#composer'), editor, { mode: 'chat' });`,
104
+ },
105
+ description: { story: 'core + dom only — no adapter, no framework. Live state panel shows the model-first document.' },
106
+ },
107
+ },
108
+ };
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Mentions & commands — plugins set as an element property.
3
+ */
4
+
5
+ import type { Meta, StoryObj } from '@storybook/web-components-vite';
6
+ import { expect, userEvent, waitFor, within } from 'storybook/test';
7
+ import { mentionPlugin } from '@ai-composer/plugin-mention';
8
+ import { commandPlugin } from '@ai-composer/plugin-command';
9
+ import { defineAiComposerEditor } from '../index';
10
+
11
+ defineAiComposerEditor();
12
+
13
+ const meta: Meta = {
14
+ title: 'AI Composer/Web Component/Mentions & Commands',
15
+ tags: ['autodocs'],
16
+ parameters: {
17
+ docs: {
18
+ description: {
19
+ component:
20
+ 'Plugins are set as the `plugins` element property before the element connects (or re-set — the element re-mounts its editor view). The popup opens at the caret, clamps into the viewport, flips when there is no room above.',
21
+ },
22
+ },
23
+ },
24
+ };
25
+
26
+ export default meta;
27
+ type Story = StoryObj<typeof meta>;
28
+
29
+ export const MentionFlow: Story = {
30
+ name: 'Interaction · type @, pick from popup',
31
+ render: () => {
32
+ const element = document.createElement('ai-composer-editor') as HTMLElement & { plugins: unknown[] };
33
+ element.setAttribute('mode', 'chat');
34
+ element.setAttribute('placeholder', 'Try typing @ad…');
35
+ element.plugins = [
36
+ mentionPlugin({
37
+ items: [
38
+ { id: 'u1', label: 'Ada Lovelace', description: 'Engineering' },
39
+ { id: 'u2', label: 'Grace Hopper', description: 'Engineering' },
40
+ ],
41
+ }),
42
+ ];
43
+ return element;
44
+ },
45
+ parameters: {
46
+ docs: {
47
+ source: {
48
+ code: `const el = document.querySelector('ai-composer-editor');
49
+ el.plugins = [mentionPlugin({ items: people })];`,
50
+ },
51
+ },
52
+ },
53
+ play: async ({ canvasElement }) => {
54
+ const canvas = within(canvasElement);
55
+ const input = canvas.getByRole('textbox');
56
+ await userEvent.click(input);
57
+ await userEvent.type(input, '@ad');
58
+ const option = await canvas.findByRole('option');
59
+ await expect(option.textContent).toContain('Ada Lovelace');
60
+ await userEvent.keyboard('{Enter}');
61
+ await waitFor(() => expect(canvas.getByRole('textbox').textContent).toContain('@Ada Lovelace'));
62
+ },
63
+ };
64
+
65
+ export const CommandMenu: Story = {
66
+ name: 'Interaction · slash commands',
67
+ render: () => {
68
+ const element = document.createElement('ai-composer-editor') as HTMLElement & { plugins: unknown[] };
69
+ element.setAttribute('mode', 'chat');
70
+ element.setAttribute('placeholder', 'Try typing /…');
71
+ element.plugins = [
72
+ commandPlugin({
73
+ commands: [
74
+ { id: 'summarize', label: 'Summarize', description: 'Summarize the conversation' },
75
+ { id: 'translate', label: 'Translate', description: 'Translate the draft' },
76
+ ],
77
+ }),
78
+ ];
79
+ return element;
80
+ },
81
+ parameters: {
82
+ docs: {
83
+ source: {
84
+ code: `el.plugins = [
85
+ commandPlugin({ commands: [{ id: 'summarize', label: 'Summarize' }] }),
86
+ ];`,
87
+ },
88
+ },
89
+ },
90
+ play: async ({ canvasElement }) => {
91
+ const canvas = within(canvasElement);
92
+ const input = canvas.getByRole('textbox');
93
+ await userEvent.click(input);
94
+ await userEvent.type(input, '/');
95
+ const option = await canvas.findByRole('option');
96
+ await expect(option.textContent).toContain('Summarize');
97
+ await userEvent.keyboard('{ArrowDown}');
98
+ await userEvent.keyboard('{Enter}');
99
+ await waitFor(() => expect(canvas.getByRole('textbox').textContent).toContain('/Translate'));
100
+ },
101
+ };
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Modes — compact pill, chat box, expanded canvas.
3
+ */
4
+
5
+ import type { Meta, StoryObj } from '@storybook/web-components-vite';
6
+ import { defineAiComposerEditor } from '../index';
7
+
8
+ defineAiComposerEditor();
9
+
10
+ const meta: Meta = {
11
+ title: 'AI Composer/Web Component/Modes',
12
+ tags: ['autodocs'],
13
+ parameters: {
14
+ docs: {
15
+ description: {
16
+ component:
17
+ '`mode` is a structural preset: **compact** = single-line pill, **chat** = auto-growing Copilot box, **expanded** = tall canvas. Change it via the attribute (or `element.editor.setMode()` for live switching with the draft preserved).',
18
+ },
19
+ },
20
+ },
21
+ argTypes: {
22
+ mode: { control: 'select', options: ['compact', 'chat', 'expanded'] },
23
+ placeholder: { control: 'text' },
24
+ },
25
+ args: { placeholder: 'Ask anything…' },
26
+ render: (args) => {
27
+ const element = document.createElement('ai-composer-editor');
28
+ element.setAttribute('mode', args.mode);
29
+ element.setAttribute('placeholder', args.placeholder);
30
+ return element;
31
+ },
32
+ };
33
+
34
+ export default meta;
35
+ type Story = StoryObj<typeof meta>;
36
+
37
+ export const Compact: Story = {
38
+ args: { mode: 'compact', placeholder: 'Search or ask…' },
39
+ parameters: {
40
+ docs: {
41
+ source: { code: `<ai-composer-editor mode="compact" placeholder="Search…"></ai-composer-editor>` },
42
+ description: { story: 'Inline pill while single-line; wrapped/multiline content morphs it into a rounded auto-growing box (never clips).' },
43
+ },
44
+ },
45
+ };
46
+
47
+ export const MaxHeight: Story = {
48
+ name: 'Max height · custom cap',
49
+ render: () => {
50
+ const element = document.createElement('ai-composer-editor');
51
+ element.setAttribute('mode', 'chat');
52
+ element.setAttribute('max-height', '96px');
53
+ element.setAttribute('placeholder', 'Type several lines — the box stops at 96px and scrolls…');
54
+ return element;
55
+ },
56
+ parameters: {
57
+ docs: {
58
+ source: {
59
+ code: `<!-- attribute (any CSS length) -->
60
+ <ai-composer-editor mode="chat" max-height="96px"></ai-composer-editor>
61
+
62
+ <!-- or the underlying token -->
63
+ <div style="--aic-input-max-height: 40vh">
64
+ <ai-composer-editor mode="chat"></ai-composer-editor>
65
+ </div>`,
66
+ },
67
+ },
68
+ },
69
+ };
70
+
71
+ export const Chat: Story = {
72
+ args: { mode: 'chat' },
73
+ parameters: {
74
+ docs: { source: { code: `<ai-composer-editor mode="chat"></ai-composer-editor>` } },
75
+ },
76
+ };
77
+
78
+ export const Expanded: Story = {
79
+ args: { mode: 'expanded', placeholder: 'Write a detailed draft…' },
80
+ parameters: {
81
+ docs: { source: { code: `<ai-composer-editor mode="expanded"></ai-composer-editor>` } },
82
+ },
83
+ };
@@ -0,0 +1,79 @@
1
+ /**
2
+ * States — disabled, readonly, dark theme.
3
+ */
4
+
5
+ import type { Meta, StoryObj } from '@storybook/web-components-vite';
6
+ import { createAIComposer } from '@ai-composer/core';
7
+ import { defineAiComposerEditor } from '../index';
8
+
9
+ defineAiComposerEditor();
10
+
11
+ const meta: Meta = {
12
+ title: 'AI Composer/Web Component/States',
13
+ tags: ['autodocs'],
14
+ parameters: {
15
+ docs: {
16
+ description: {
17
+ component: 'Boolean attributes for state; dark tokens via `data-aic-theme="dark"` on any ancestor.',
18
+ },
19
+ },
20
+ },
21
+ };
22
+
23
+ export default meta;
24
+ type Story = StoryObj<typeof meta>;
25
+
26
+ export const Disabled: Story = {
27
+ render: () => {
28
+ const element = document.createElement('ai-composer-editor') as HTMLElement & {
29
+ editor: ReturnType<typeof createAIComposer>;
30
+ };
31
+ element.setAttribute('mode', 'chat');
32
+ element.setAttribute('placeholder', 'Disabled');
33
+ element.setAttribute('disabled', '');
34
+ element.editor = createAIComposer({ value: 'You cannot edit me', disabled: true });
35
+ return element;
36
+ },
37
+ parameters: {
38
+ docs: {
39
+ source: { code: `<ai-composer-editor mode="chat" disabled></ai-composer-editor>` },
40
+ },
41
+ },
42
+ };
43
+
44
+ export const Readonly: Story = {
45
+ render: () => {
46
+ const element = document.createElement('ai-composer-editor');
47
+ element.setAttribute('mode', 'chat');
48
+ element.setAttribute('placeholder', 'Readonly');
49
+ element.setAttribute('readonly', '');
50
+ return element;
51
+ },
52
+ parameters: {
53
+ docs: {
54
+ source: { code: `<ai-composer-editor mode="chat" readonly></ai-composer-editor>` },
55
+ },
56
+ },
57
+ };
58
+
59
+ export const DarkTheme: Story = {
60
+ render: () => {
61
+ const root = document.createElement('div');
62
+ root.innerHTML = `
63
+ <div data-aic-theme="dark" style="background:#0d0e12;padding:24px;border-radius:12px">
64
+ <ai-composer-editor mode="chat" placeholder="Dark tokens via data-aic-theme"></ai-composer-editor>
65
+ </div>
66
+ `;
67
+ return root;
68
+ },
69
+ parameters: {
70
+ backgrounds: { default: 'dark' },
71
+ docs: {
72
+ source: {
73
+ code: `<div data-aic-theme="dark">
74
+ <ai-composer-editor mode="chat"></ai-composer-editor>
75
+ </div>`,
76
+ },
77
+ },
78
+ },
79
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "types": []
5
+ },
6
+ "include": [
7
+ "src/**/*.ts",
8
+ "src/**/*.tsx",
9
+ "vitest.config.ts"
10
+ ],
11
+ "exclude": [
12
+ "node_modules",
13
+ "dist"
14
+ ]
15
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "declaration": true,
5
+ "declarationMap": true,
6
+ "stripInternal": true,
7
+ "outDir": "../../dist/packages/web-component"
8
+ },
9
+ "exclude": [
10
+ "**/*.test.ts",
11
+ "**/*.test.tsx",
12
+ "vitest.config.ts",
13
+ "src/test-setup.ts",
14
+ "**/*.stories.ts",
15
+ "**/*.stories.tsx"
16
+ ]
17
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "noEmit": true,
5
+ "types": [
6
+ "vitest"
7
+ ]
8
+ },
9
+ "include": [
10
+ "src/**/*.ts",
11
+ "src/**/*.tsx"
12
+ ],
13
+ "exclude": [
14
+ "**/*.stories.ts",
15
+ "**/*.stories.tsx",
16
+ "vitest.config.ts"
17
+ ]
18
+ }
@@ -0,0 +1,19 @@
1
+ import { defineConfig } from 'vitest/config';
2
+ import { resolve } from 'node:path';
3
+
4
+ export default defineConfig({
5
+ resolve: {
6
+ alias: {
7
+ '@ai-composer/core': resolve(__dirname, '../core/src/index.ts'),
8
+ '@ai-composer/dom': resolve(__dirname, '../dom/src/index.ts'),
9
+ '@ai-composer/testing': resolve(__dirname, '../testing/src/index.ts'),
10
+ '@ai-composer/plugin-mention': resolve(__dirname, '../../plugins/mention/src/index.ts'),
11
+ },
12
+ },
13
+ test: {
14
+ name: 'web-component',
15
+ environment: 'jsdom',
16
+ include: ['src/**/*.test.{ts,tsx}'],
17
+ },
18
+ });
19
+