@adia-ai/web-components 0.6.36 → 0.6.38

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.
Files changed (159) hide show
  1. package/CHANGELOG.md +48 -1
  2. package/components/accordion/accordion-item.a2ui.json +3 -0
  3. package/components/accordion/accordion-item.yaml +5 -0
  4. package/components/action-list/action-item.a2ui.json +5 -1
  5. package/components/action-list/action-item.yaml +7 -0
  6. package/components/badge/badge.a2ui.json +10 -0
  7. package/components/badge/badge.css +70 -0
  8. package/components/badge/badge.yaml +20 -0
  9. package/components/blockquote/blockquote.a2ui.json +121 -0
  10. package/components/blockquote/blockquote.class.js +68 -0
  11. package/components/blockquote/blockquote.css +46 -0
  12. package/components/blockquote/blockquote.d.ts +31 -0
  13. package/components/blockquote/blockquote.js +17 -0
  14. package/components/blockquote/blockquote.yaml +124 -0
  15. package/components/button/button.css +11 -3
  16. package/components/calendar-picker/calendar-picker.a2ui.json +15 -0
  17. package/components/calendar-picker/calendar-picker.class.js +7 -1
  18. package/components/calendar-picker/calendar-picker.yaml +14 -0
  19. package/components/card/card.a2ui.json +17 -1
  20. package/components/card/card.yaml +24 -1
  21. package/components/color-input/color-input.a2ui.json +2 -2
  22. package/components/color-input/color-input.class.js +9 -2
  23. package/components/color-input/color-input.yaml +2 -2
  24. package/components/combobox/combobox.class.js +4 -0
  25. package/components/context-menu/context-menu.a2ui.json +159 -0
  26. package/components/context-menu/context-menu.class.js +275 -0
  27. package/components/context-menu/context-menu.css +56 -0
  28. package/components/context-menu/context-menu.d.ts +70 -0
  29. package/components/context-menu/context-menu.js +17 -0
  30. package/components/context-menu/context-menu.yaml +136 -0
  31. package/components/date-range-picker/date-range-picker.a2ui.json +15 -0
  32. package/components/date-range-picker/date-range-picker.class.js +2 -0
  33. package/components/date-range-picker/date-range-picker.yaml +14 -0
  34. package/components/datetime-picker/datetime-picker.a2ui.json +15 -0
  35. package/components/datetime-picker/datetime-picker.class.js +3 -1
  36. package/components/datetime-picker/datetime-picker.d.ts +2 -0
  37. package/components/datetime-picker/datetime-picker.yaml +14 -0
  38. package/components/empty-state/empty-state.a2ui.json +9 -0
  39. package/components/empty-state/empty-state.class.js +2 -0
  40. package/components/empty-state/empty-state.yaml +15 -0
  41. package/components/feed/feed-item.a2ui.json +5 -0
  42. package/components/feed/feed-item.yaml +10 -0
  43. package/components/feed/feed.class.js +13 -5
  44. package/components/feed/feed.css +14 -0
  45. package/components/field/field.a2ui.json +6 -0
  46. package/components/field/field.yaml +10 -0
  47. package/components/index.js +11 -0
  48. package/components/inline-edit/inline-edit.a2ui.json +159 -0
  49. package/components/inline-edit/inline-edit.class.js +184 -0
  50. package/components/inline-edit/inline-edit.css +62 -0
  51. package/components/inline-edit/inline-edit.d.ts +52 -0
  52. package/components/inline-edit/inline-edit.js +12 -0
  53. package/components/inline-edit/inline-edit.yaml +125 -0
  54. package/components/integration-card/integration-card.class.js +9 -0
  55. package/components/integration-card/integration-card.test.js +4 -3
  56. package/components/list/list-item.a2ui.json +8 -1
  57. package/components/list/list-item.yaml +12 -0
  58. package/components/list/list.css +36 -6
  59. package/components/mark/mark.a2ui.json +109 -0
  60. package/components/mark/mark.class.js +22 -0
  61. package/components/mark/mark.css +39 -0
  62. package/components/mark/mark.d.ts +27 -0
  63. package/components/mark/mark.js +12 -0
  64. package/components/mark/mark.yaml +87 -0
  65. package/components/modal/modal.a2ui.json +9 -0
  66. package/components/modal/modal.yaml +14 -0
  67. package/components/nav-group/nav-group.a2ui.json +3 -0
  68. package/components/nav-group/nav-group.css +7 -1
  69. package/components/nav-group/nav-group.yaml +5 -0
  70. package/components/nav-item/nav-item.a2ui.json +3 -0
  71. package/components/nav-item/nav-item.yaml +5 -0
  72. package/components/number-format/number-format.a2ui.json +180 -0
  73. package/components/number-format/number-format.class.js +96 -0
  74. package/components/number-format/number-format.css +18 -0
  75. package/components/number-format/number-format.d.ts +68 -0
  76. package/components/number-format/number-format.js +17 -0
  77. package/components/number-format/number-format.yaml +204 -0
  78. package/components/pagination/pagination.a2ui.json +19 -2
  79. package/components/pagination/pagination.class.js +90 -37
  80. package/components/pagination/pagination.css +32 -127
  81. package/components/pagination/pagination.d.ts +8 -2
  82. package/components/pagination/pagination.test.js +195 -0
  83. package/components/pagination/pagination.yaml +22 -1
  84. package/components/password-strength/password-strength.a2ui.json +152 -0
  85. package/components/password-strength/password-strength.class.js +157 -0
  86. package/components/password-strength/password-strength.css +80 -0
  87. package/components/password-strength/password-strength.d.ts +59 -0
  88. package/components/password-strength/password-strength.js +17 -0
  89. package/components/password-strength/password-strength.yaml +153 -0
  90. package/components/popover/popover.css +43 -23
  91. package/components/popover/popover.yaml +8 -4
  92. package/components/qr-code/QR-TEST.svg +4 -0
  93. package/components/qr-code/qr-code.a2ui.json +154 -0
  94. package/components/qr-code/qr-code.class.js +129 -0
  95. package/components/qr-code/qr-code.css +41 -0
  96. package/components/qr-code/qr-code.d.ts +83 -0
  97. package/components/qr-code/qr-code.js +17 -0
  98. package/components/qr-code/qr-code.yaml +203 -0
  99. package/components/qr-code/qr-encoder.js +633 -0
  100. package/components/relative-time/relative-time.a2ui.json +120 -0
  101. package/components/relative-time/relative-time.class.js +136 -0
  102. package/components/relative-time/relative-time.css +22 -0
  103. package/components/relative-time/relative-time.d.ts +51 -0
  104. package/components/relative-time/relative-time.js +17 -0
  105. package/components/relative-time/relative-time.yaml +133 -0
  106. package/components/segmented/segmented.class.js +15 -3
  107. package/components/select/select.a2ui.json +3 -0
  108. package/components/select/select.class.js +4 -0
  109. package/components/select/select.yaml +5 -0
  110. package/components/skip-nav/skip-nav.a2ui.json +92 -0
  111. package/components/skip-nav/skip-nav.class.js +45 -0
  112. package/components/skip-nav/skip-nav.css +54 -0
  113. package/components/skip-nav/skip-nav.d.ts +27 -0
  114. package/components/skip-nav/skip-nav.js +12 -0
  115. package/components/skip-nav/skip-nav.yaml +68 -0
  116. package/components/slider/slider.a2ui.json +22 -1
  117. package/components/slider/slider.class.js +264 -122
  118. package/components/slider/slider.css +82 -2
  119. package/components/slider/slider.d.ts +19 -3
  120. package/components/slider/slider.test.js +55 -0
  121. package/components/slider/slider.yaml +38 -6
  122. package/components/stat/stat.css +18 -14
  123. package/components/stepper/stepper-item.a2ui.json +3 -0
  124. package/components/stepper/stepper-item.yaml +5 -0
  125. package/components/table/table.class.js +29 -6
  126. package/components/table/table.css +31 -4
  127. package/components/table-toolbar/table-toolbar.class.js +3 -1
  128. package/components/tag/tag.a2ui.json +3 -2
  129. package/components/tag/tag.css +35 -11
  130. package/components/tag/tag.d.ts +14 -0
  131. package/components/tag/tag.test.js +35 -11
  132. package/components/tag/tag.yaml +13 -7
  133. package/components/timeline/timeline-item.a2ui.json +8 -1
  134. package/components/timeline/timeline-item.yaml +12 -0
  135. package/components/toast/toast.class.js +12 -4
  136. package/components/toc/toc.a2ui.json +159 -0
  137. package/components/toc/toc.class.js +222 -0
  138. package/components/toc/toc.css +92 -0
  139. package/components/toc/toc.d.ts +61 -0
  140. package/components/toc/toc.js +17 -0
  141. package/components/toc/toc.yaml +180 -0
  142. package/components/toolbar/toolbar.class.js +3 -0
  143. package/components/tree/tree-item.a2ui.json +5 -1
  144. package/components/tree/tree-item.yaml +7 -0
  145. package/components/tree/tree.a2ui.json +3 -0
  146. package/components/tree/tree.yaml +5 -0
  147. package/components/visually-hidden/visually-hidden.a2ui.json +71 -0
  148. package/components/visually-hidden/visually-hidden.class.js +14 -0
  149. package/components/visually-hidden/visually-hidden.css +25 -0
  150. package/components/visually-hidden/visually-hidden.d.ts +26 -0
  151. package/components/visually-hidden/visually-hidden.js +12 -0
  152. package/components/visually-hidden/visually-hidden.yaml +54 -0
  153. package/core/anchor.js +19 -3
  154. package/dist/web-components.min.css +1 -1
  155. package/dist/web-components.min.js +100 -89
  156. package/package.json +1 -1
  157. package/styles/colors/semantics.css +11 -2
  158. package/styles/components.css +11 -0
  159. package/styles/resets.css +10 -0
@@ -0,0 +1,159 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://adiaui.dev/a2ui/v0_9/components/InlineEdit.json",
4
+ "title": "InlineEdit",
5
+ "description": "Click-to-edit text in place. Renders as static text until clicked /\nfocused + Enter, then becomes editable. Enter or blur commits; Escape\ncancels and restores the original value. Form-participating.\n\nUse for editable titles, breadcrumb labels, table-cell text fields,\ndraft document names — any case where a user expects to edit text\nwithout opening a separate dialog or input. Distinct from `<input-ui>`\n(always-editable chrome) and from `<field-ui>` (stacked label + input\ncomposition). inline-edit reads as text in static state.\n",
6
+ "type": "object",
7
+ "allOf": [
8
+ {
9
+ "$ref": "common_types.json#/$defs/ComponentCommon"
10
+ },
11
+ {
12
+ "$ref": "common_types.json#/$defs/CatalogComponentCommon"
13
+ }
14
+ ],
15
+ "properties": {
16
+ "commit": {
17
+ "description": "When to commit pending edits. `blur` (default) — saves on focusout\nor Enter. `enter` — Enter saves, blur cancels (returns to original).\n`manual` — only programmatic `commitEdit()` saves.\n",
18
+ "type": "string",
19
+ "enum": [
20
+ "blur",
21
+ "enter",
22
+ "manual"
23
+ ],
24
+ "default": "blur"
25
+ },
26
+ "component": {
27
+ "const": "InlineEdit"
28
+ },
29
+ "editing": {
30
+ "description": "Reflected state — `true` when the element is actively being edited.\nToggles automatically on click / Enter / blur / Escape; rarely set\nby consumers. Listen to `edit-start` / `edit-end` events instead.\n",
31
+ "type": "boolean",
32
+ "default": false
33
+ },
34
+ "placeholder": {
35
+ "description": "Hint text shown when the value is empty (inline-edit reads this in the static state).",
36
+ "type": "string",
37
+ "default": "Click to edit"
38
+ }
39
+ },
40
+ "required": [
41
+ "component"
42
+ ],
43
+ "unevaluatedProperties": false,
44
+ "x-adiaui": {
45
+ "anti_patterns": [
46
+ {
47
+ "fix": "<inline-edit-ui value=\"Title\" name=\"title\"> — reads as text in static state; chrome only appears while editing.",
48
+ "why": "Ghost variant still renders input chrome (border on focus). Looks like a control, not text.",
49
+ "wrong": "<input-ui value=\"Title\" name=\"title\" variant=\"ghost\">"
50
+ },
51
+ {
52
+ "fix": "<inline-edit-ui value=\"Title\">",
53
+ "why": "No state machine — no commit/cancel semantics, no form participation, no a11y wiring.",
54
+ "wrong": "<text-ui contenteditable>Title</text-ui>"
55
+ }
56
+ ],
57
+ "category": "form",
58
+ "composes": [],
59
+ "events": {
60
+ "cancel": {
61
+ "description": "Fired when Escape (or blur with commit=enter) restores the original. Bubbles."
62
+ },
63
+ "change": {
64
+ "description": "Fired after a successful commit. detail = { value, oldValue }. Bubbles."
65
+ },
66
+ "edit-end": {
67
+ "description": "Fired when leaving edit mode. detail = { committed: boolean }. Bubbles."
68
+ },
69
+ "edit-start": {
70
+ "description": "Fired when entering edit mode. Bubbles."
71
+ }
72
+ },
73
+ "examples": [
74
+ {
75
+ "description": "A draft document title that becomes editable on click.",
76
+ "a2ui": "[{ \"id\": \"title\", \"component\": \"InlineEdit\", \"value\": \"Untitled draft\" }]\n",
77
+ "name": "editable-title"
78
+ }
79
+ ],
80
+ "keywords": [
81
+ "inline-edit",
82
+ "editable",
83
+ "click-to-edit",
84
+ "rename",
85
+ "edit-in-place"
86
+ ],
87
+ "name": "UIInlineEdit",
88
+ "related": [
89
+ "input",
90
+ "field",
91
+ "text"
92
+ ],
93
+ "slots": {},
94
+ "states": [
95
+ {
96
+ "description": "Static — text reads as plain text with a hover hint.",
97
+ "name": "idle"
98
+ },
99
+ {
100
+ "description": "Hover affordance — subtle background tint signals editability.",
101
+ "name": "hover"
102
+ },
103
+ {
104
+ "description": "contenteditable — host is the input surface; outline + caret.",
105
+ "name": "editing"
106
+ },
107
+ {
108
+ "description": "Value is empty and not editing — placeholder text renders.",
109
+ "name": "empty"
110
+ },
111
+ {
112
+ "description": "Locked; click + keyboard activation are no-ops.",
113
+ "name": "disabled"
114
+ }
115
+ ],
116
+ "status": "stable",
117
+ "synonyms": {
118
+ "inline-edit": [
119
+ "editable",
120
+ "click-to-edit",
121
+ "edit-in-place",
122
+ "rename-in-place"
123
+ ]
124
+ },
125
+ "tag": "inline-edit-ui",
126
+ "tokens": {
127
+ "--inline-edit-bg-edit": {
128
+ "description": "Background while editing.",
129
+ "default": "var(--a-bg)"
130
+ },
131
+ "--inline-edit-bg-hover": {
132
+ "description": "Background tint on hover (idle state).",
133
+ "default": "var(--a-bg-muted)"
134
+ },
135
+ "--inline-edit-outline": {
136
+ "description": "Outline color in the editing state.",
137
+ "default": "var(--a-accent-strong)"
138
+ },
139
+ "--inline-edit-placeholder": {
140
+ "description": "Placeholder text color in empty-static state.",
141
+ "default": "var(--a-fg-subtle)"
142
+ },
143
+ "--inline-edit-px": {
144
+ "description": "Horizontal padding (inner gutter so hover tint reads as a pill, not a flush block).",
145
+ "default": "var(--a-space-1)"
146
+ },
147
+ "--inline-edit-py": {
148
+ "description": "Vertical padding.",
149
+ "default": "var(--a-space-0-5)"
150
+ },
151
+ "--inline-edit-radius": {
152
+ "description": "Border-radius for the hover / editing chrome.",
153
+ "default": "var(--a-radius-sm)"
154
+ }
155
+ },
156
+ "traits": [],
157
+ "version": 1
158
+ }
159
+ }
@@ -0,0 +1,184 @@
1
+ /**
2
+ * `<inline-edit-ui>` — click-to-edit text in place.
3
+ *
4
+ * Static state: textContent reads as plain text with a hover affordance.
5
+ * Editing state: host becomes `contenteditable=plaintext-only`. Enter or
6
+ * blur commits (configurable via `commit`); Escape always cancels.
7
+ *
8
+ * Form-participating via UIFormElement (name/value/required/disabled/
9
+ * readonly inherited). Pair with a `<form>` + `name=` to submit edits
10
+ * as a field.
11
+ *
12
+ * Architecture: no template (stamp-nothing). The element manages its
13
+ * own DOM imperatively — textContent IS the value, contenteditable
14
+ * attribute IS the editing state. This keeps the element trivial and
15
+ * lets the inherited UIFormElement value sync stay authoritative.
16
+ */
17
+
18
+ import { UIFormElement } from '../../core/form.js';
19
+
20
+ export class UIInlineEdit extends UIFormElement {
21
+ static properties = {
22
+ ...UIFormElement.properties,
23
+ placeholder: { type: String, default: 'Click to edit', reflect: true },
24
+ editing: { type: Boolean, default: false, reflect: true },
25
+ commit: { type: String, default: 'blur', reflect: true }, // blur | enter | manual
26
+ };
27
+
28
+ static template = () => null;
29
+
30
+ #originalValue = '';
31
+ #bound = false;
32
+ #suppressBlur = false;
33
+
34
+ connected() {
35
+ super.connected();
36
+ if (!this.hasAttribute('role')) this.setAttribute('role', 'textbox');
37
+ if (!this.hasAttribute('tabindex') && !this.disabled) {
38
+ this.setAttribute('tabindex', '0');
39
+ }
40
+ // Reflect the initial value to textContent (the source of truth for
41
+ // display while not editing). Setting via property triggers the
42
+ // reflection but not the textContent sync — do it explicitly.
43
+ if (this.value && this.textContent.trim() === '') {
44
+ this.textContent = this.value;
45
+ } else if (!this.value && this.textContent.trim()) {
46
+ // Author supplied text in the slot — treat it as the initial value.
47
+ this.value = this.textContent.trim();
48
+ this.syncValue();
49
+ }
50
+
51
+ if (!this.#bound) {
52
+ this.#bound = true;
53
+ this.addEventListener('click', this.#onClick);
54
+ this.addEventListener('keydown', this.#onKeydown);
55
+ this.addEventListener('focus', this.#onFocus);
56
+ this.addEventListener('blur', this.#onBlur);
57
+ }
58
+ }
59
+
60
+ disconnected() {
61
+ super.disconnected();
62
+ this.removeEventListener('click', this.#onClick);
63
+ this.removeEventListener('keydown', this.#onKeydown);
64
+ this.removeEventListener('focus', this.#onFocus);
65
+ this.removeEventListener('blur', this.#onBlur);
66
+ this.#bound = false;
67
+ }
68
+
69
+ /* ── Public API ──────────────────────────────────────────────────── */
70
+
71
+ startEdit() {
72
+ if (this.editing || this.disabled || this.readonly) return;
73
+ this.#originalValue = this.value || '';
74
+ this.editing = true;
75
+ this.setAttribute('contenteditable', 'plaintext-only');
76
+ this.dispatchEvent(new CustomEvent('edit-start', { bubbles: true }));
77
+ // Defer focus/selection — setting contenteditable in the same tick
78
+ // as a click can race with the browser's own caret placement.
79
+ queueMicrotask(() => {
80
+ this.focus();
81
+ this.#selectAll();
82
+ });
83
+ }
84
+
85
+ commitEdit() {
86
+ if (!this.editing) return;
87
+ const newValue = (this.textContent || '').trim();
88
+ const oldValue = this.#originalValue;
89
+ this.editing = false;
90
+ this.removeAttribute('contenteditable');
91
+ // Keep the visible text in sync with the canonical value (normalizes
92
+ // trailing whitespace + restores the source-of-truth string).
93
+ if (this.textContent !== newValue) this.textContent = newValue;
94
+ if (newValue !== oldValue) {
95
+ this.value = newValue;
96
+ this.syncValue(newValue);
97
+ this.dispatchEvent(new CustomEvent('change', {
98
+ bubbles: true,
99
+ detail: { value: newValue, oldValue },
100
+ }));
101
+ }
102
+ this.dispatchEvent(new CustomEvent('edit-end', {
103
+ bubbles: true,
104
+ detail: { committed: true },
105
+ }));
106
+ }
107
+
108
+ cancelEdit() {
109
+ if (!this.editing) return;
110
+ this.editing = false;
111
+ this.removeAttribute('contenteditable');
112
+ this.textContent = this.#originalValue;
113
+ this.dispatchEvent(new CustomEvent('cancel', { bubbles: true }));
114
+ this.dispatchEvent(new CustomEvent('edit-end', {
115
+ bubbles: true,
116
+ detail: { committed: false },
117
+ }));
118
+ }
119
+
120
+ /* ── Event handlers ──────────────────────────────────────────────── */
121
+
122
+ #onClick = (e) => {
123
+ if (this.disabled || this.readonly) return;
124
+ if (this.editing) return; // already editing, let caret behavior pass through
125
+ e.preventDefault();
126
+ this.startEdit();
127
+ };
128
+
129
+ #onKeydown = (e) => {
130
+ if (this.editing) {
131
+ if (e.key === 'Enter') {
132
+ e.preventDefault();
133
+ this.#suppressBlur = true; // commitEdit blurs us; don't recurse
134
+ this.commitEdit();
135
+ this.blur();
136
+ } else if (e.key === 'Escape') {
137
+ e.preventDefault();
138
+ this.#suppressBlur = true;
139
+ this.cancelEdit();
140
+ this.blur();
141
+ }
142
+ } else {
143
+ // Activate edit from keyboard (Enter or Space)
144
+ if (e.key === 'Enter' || e.key === ' ') {
145
+ if (this.disabled || this.readonly) return;
146
+ e.preventDefault();
147
+ this.startEdit();
148
+ }
149
+ }
150
+ };
151
+
152
+ #onFocus = () => {
153
+ // No-op — keyboard activation handled in #onKeydown to distinguish
154
+ // focus-via-Tab (don't edit) from focus-via-click (also don't edit
155
+ // here; the click handler does that). Focus alone never starts an
156
+ // edit — that would surprise tab-traversers.
157
+ };
158
+
159
+ #onBlur = () => {
160
+ if (!this.editing) return;
161
+ if (this.#suppressBlur) {
162
+ this.#suppressBlur = false;
163
+ return;
164
+ }
165
+ if (this.commit === 'blur') {
166
+ this.commitEdit();
167
+ } else if (this.commit === 'enter') {
168
+ // blur cancels under `commit=enter` semantics
169
+ this.cancelEdit();
170
+ }
171
+ // commit=manual: do nothing on blur; consumer must call commitEdit()
172
+ };
173
+
174
+ /* ── Helpers ─────────────────────────────────────────────────────── */
175
+
176
+ #selectAll() {
177
+ const range = document.createRange();
178
+ range.selectNodeContents(this);
179
+ const sel = window.getSelection();
180
+ if (!sel) return;
181
+ sel.removeAllRanges();
182
+ sel.addRange(range);
183
+ }
184
+ }
@@ -0,0 +1,62 @@
1
+ @scope (inline-edit-ui) {
2
+ :where(:scope) {
3
+ --inline-edit-bg-hover-default: var(--a-bg-muted);
4
+ --inline-edit-bg-edit-default: var(--a-bg);
5
+ --inline-edit-outline-default: var(--a-accent-strong);
6
+ --inline-edit-placeholder-default: var(--a-fg-subtle);
7
+ --inline-edit-px-default: var(--a-space-1);
8
+ --inline-edit-py-default: var(--a-space-0-5);
9
+ --inline-edit-radius-default: var(--a-radius-sm);
10
+ }
11
+
12
+ :scope {
13
+ /* Inline-block so padding works without breaking text flow when
14
+ embedded inside a paragraph or heading. Falls back to inline for
15
+ table cells (where the cell already contains the box). */
16
+ display: inline-block;
17
+ box-sizing: border-box;
18
+ padding-inline: var(--inline-edit-px, var(--inline-edit-px-default));
19
+ padding-block: var(--inline-edit-py, var(--inline-edit-py-default));
20
+ margin-inline: calc(-1 * var(--inline-edit-px, var(--inline-edit-px-default)));
21
+ margin-block: calc(-1 * var(--inline-edit-py, var(--inline-edit-py-default)));
22
+ border-radius: var(--inline-edit-radius, var(--inline-edit-radius-default));
23
+ cursor: text;
24
+ color: inherit;
25
+ font: inherit;
26
+ transition: background-color var(--a-duration-fast) var(--a-easing-out);
27
+ /* Make sure focus-visible outline can land on us (we set tabindex=0) */
28
+ outline-offset: 2px;
29
+ }
30
+
31
+ /* Hover affordance — subtle bg tint so the user sees this IS editable.
32
+ Suppressed when disabled / readonly / already editing. */
33
+ :scope:not([editing]):not([disabled]):not([readonly]):hover {
34
+ background: var(--inline-edit-bg-hover, var(--inline-edit-bg-hover-default));
35
+ }
36
+
37
+ /* Editing state — flat background + accent outline for focus clarity */
38
+ :scope[editing] {
39
+ background: var(--inline-edit-bg-edit, var(--inline-edit-bg-edit-default));
40
+ outline: 2px solid var(--inline-edit-outline, var(--inline-edit-outline-default));
41
+ outline-offset: 0;
42
+ cursor: text;
43
+ }
44
+
45
+ /* Empty-value placeholder — show the [placeholder] attr as faint text
46
+ when the host has no text content + isn't being edited. */
47
+ :scope:empty:not([editing])::before {
48
+ content: attr(placeholder);
49
+ color: var(--inline-edit-placeholder, var(--inline-edit-placeholder-default));
50
+ font-style: italic;
51
+ }
52
+
53
+ :scope[disabled],
54
+ :scope[readonly] {
55
+ cursor: default;
56
+ opacity: 0.6;
57
+ }
58
+
59
+ :scope[disabled] {
60
+ pointer-events: none;
61
+ }
62
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * `<inline-edit-ui>` — Click-to-edit text in place. Renders as static text until clicked /
3
+ focused + Enter, then becomes editable. Enter or blur commits; Escape
4
+ cancels and restores the original value. Form-participating.
5
+
6
+ Use for editable titles, breadcrumb labels, table-cell text fields,
7
+ draft document names — any case where a user expects to edit text
8
+ without opening a separate dialog or input. Distinct from `<input-ui>`
9
+ (always-editable chrome) and from `<field-ui>` (stacked label + input
10
+ composition). inline-edit reads as text in static state.
11
+
12
+ *
13
+ * @see https://ui-kit.exe.xyz/site/components/inline-edit
14
+ *
15
+ * Type declarations generated by scripts/build/dts-codegen.mjs from
16
+ * the component's `.a2ui.json` sidecar(s). Edit the source `.yaml`,
17
+ * run `npm run build:components`, then `npm run codegen:dts` to
18
+ * regenerate; or hand-author this file fully if rich event types are
19
+ * needed beyond what the yaml `events:` block can express.
20
+ */
21
+
22
+ import { UIElement } from '../../core/element.js';
23
+
24
+ export type InlineEditCancelEvent = CustomEvent<unknown>;
25
+ export type InlineEditChangeEvent = CustomEvent<unknown>;
26
+ export type InlineEditEditEndEvent = CustomEvent<unknown>;
27
+ export type InlineEditEditStartEvent = CustomEvent<unknown>;
28
+
29
+ export class UIInlineEdit extends UIElement {
30
+ /** When to commit pending edits. `blur` (default) — saves on focusout
31
+ or Enter. `enter` — Enter saves, blur cancels (returns to original).
32
+ `manual` — only programmatic `commitEdit()` saves.
33
+ */
34
+ commit: 'blur' | 'enter' | 'manual';
35
+ /** Reflected state — `true` when the element is actively being edited.
36
+ Toggles automatically on click / Enter / blur / Escape; rarely set
37
+ by consumers. Listen to `edit-start` / `edit-end` events instead.
38
+ */
39
+ editing: boolean;
40
+ /** Hint text shown when the value is empty (inline-edit reads this in the static state). */
41
+ placeholder: string;
42
+
43
+ addEventListener<K extends keyof HTMLElementEventMap>(
44
+ type: K,
45
+ listener: (this: UIInlineEdit, ev: HTMLElementEventMap[K]) => unknown,
46
+ options?: boolean | AddEventListenerOptions,
47
+ ): void;
48
+ addEventListener(type: 'cancel', listener: (ev: InlineEditCancelEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
49
+ addEventListener(type: 'change', listener: (ev: InlineEditChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
50
+ addEventListener(type: 'edit-end', listener: (ev: InlineEditEditEndEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
51
+ addEventListener(type: 'edit-start', listener: (ev: InlineEditEditStartEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
52
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `<inline-edit-ui>` — auto-registers the tag on import.
3
+ *
4
+ * @see ../../USAGE.md#registration--auto-vs-explicit
5
+ */
6
+
7
+ import { defineIfFree } from '../../core/register.js';
8
+ import { UIInlineEdit } from './inline-edit.class.js';
9
+
10
+ defineIfFree('inline-edit-ui', UIInlineEdit);
11
+
12
+ export { UIInlineEdit };
@@ -0,0 +1,125 @@
1
+ $schema: ../../../../scripts/schemas/component.yaml.schema.json
2
+ name: UIInlineEdit
3
+ tag: inline-edit-ui
4
+ status: stable
5
+ component: InlineEdit
6
+ category: form
7
+ version: 1
8
+ description: |
9
+ Click-to-edit text in place. Renders as static text until clicked /
10
+ focused + Enter, then becomes editable. Enter or blur commits; Escape
11
+ cancels and restores the original value. Form-participating.
12
+
13
+ Use for editable titles, breadcrumb labels, table-cell text fields,
14
+ draft document names — any case where a user expects to edit text
15
+ without opening a separate dialog or input. Distinct from `<input-ui>`
16
+ (always-editable chrome) and from `<field-ui>` (stacked label + input
17
+ composition). inline-edit reads as text in static state.
18
+ props:
19
+ placeholder:
20
+ description: Hint text shown when the value is empty (inline-edit reads this in the static state).
21
+ type: string
22
+ default: Click to edit
23
+ reflect: true
24
+ editing:
25
+ description: |
26
+ Reflected state — `true` when the element is actively being edited.
27
+ Toggles automatically on click / Enter / blur / Escape; rarely set
28
+ by consumers. Listen to `edit-start` / `edit-end` events instead.
29
+ type: boolean
30
+ default: false
31
+ reflect: true
32
+ commit:
33
+ description: |
34
+ When to commit pending edits. `blur` (default) — saves on focusout
35
+ or Enter. `enter` — Enter saves, blur cancels (returns to original).
36
+ `manual` — only programmatic `commitEdit()` saves.
37
+ type: string
38
+ default: blur
39
+ enum:
40
+ - blur
41
+ - enter
42
+ - manual
43
+ reflect: true
44
+ events:
45
+ change:
46
+ description: 'Fired after a successful commit. detail = { value, oldValue }. Bubbles.'
47
+ cancel:
48
+ description: 'Fired when Escape (or blur with commit=enter) restores the original. Bubbles.'
49
+ edit-start:
50
+ description: 'Fired when entering edit mode. Bubbles.'
51
+ edit-end:
52
+ description: 'Fired when leaving edit mode. detail = { committed: boolean }. Bubbles.'
53
+ slots: {}
54
+ states:
55
+ - name: idle
56
+ description: Static — text reads as plain text with a hover hint.
57
+ - name: hover
58
+ description: Hover affordance — subtle background tint signals editability.
59
+ - name: editing
60
+ description: contenteditable — host is the input surface; outline + caret.
61
+ - name: empty
62
+ description: Value is empty and not editing — placeholder text renders.
63
+ - name: disabled
64
+ description: Locked; click + keyboard activation are no-ops.
65
+ traits: []
66
+ tokens:
67
+ --inline-edit-bg-hover:
68
+ description: Background tint on hover (idle state).
69
+ default: var(--a-bg-muted)
70
+ --inline-edit-bg-edit:
71
+ description: Background while editing.
72
+ default: var(--a-bg)
73
+ --inline-edit-outline:
74
+ description: Outline color in the editing state.
75
+ default: var(--a-accent-strong)
76
+ --inline-edit-placeholder:
77
+ description: Placeholder text color in empty-static state.
78
+ default: var(--a-fg-subtle)
79
+ --inline-edit-px:
80
+ description: Horizontal padding (inner gutter so hover tint reads as a pill, not a flush block).
81
+ default: var(--a-space-1)
82
+ --inline-edit-py:
83
+ description: Vertical padding.
84
+ default: var(--a-space-0-5)
85
+ --inline-edit-radius:
86
+ description: Border-radius for the hover / editing chrome.
87
+ default: var(--a-radius-sm)
88
+ a2ui:
89
+ rules:
90
+ - rule: 'Use inline-edit-ui for click-to-edit titles, draft names, table-cell text, breadcrumb labels — anywhere the user expects to edit text without opening a dialog.'
91
+ reason: 'Primary use case — inline rename/edit.'
92
+ - rule: 'inline-edit-ui IS form-participating (extends UIFormElement). Pair with a hidden <form> + name="..." to submit edits as a field.'
93
+ reason: 'Form participation contract.'
94
+ - rule: 'Distinct from <input-ui> (always shows input chrome) and from <field-ui> (stacked label + input composition). inline-edit reads as text in the static state.'
95
+ reason: 'Sibling-component boundary.'
96
+ - rule: 'Listen to `change` event (detail.value, detail.oldValue) for commit; `cancel` for Escape. Default commit=blur saves on focusout + Enter.'
97
+ reason: 'Event-handling contract.'
98
+ anti_patterns:
99
+ - wrong: '<input-ui value="Title" name="title" variant="ghost">'
100
+ why: 'Ghost variant still renders input chrome (border on focus). Looks like a control, not text.'
101
+ fix: '<inline-edit-ui value="Title" name="title"> — reads as text in static state; chrome only appears while editing.'
102
+ - wrong: '<text-ui contenteditable>Title</text-ui>'
103
+ why: 'No state machine — no commit/cancel semantics, no form participation, no a11y wiring.'
104
+ fix: '<inline-edit-ui value="Title">'
105
+ examples:
106
+ - name: editable-title
107
+ description: A draft document title that becomes editable on click.
108
+ a2ui: |
109
+ [{ "id": "title", "component": "InlineEdit", "value": "Untitled draft" }]
110
+ keywords:
111
+ - inline-edit
112
+ - editable
113
+ - click-to-edit
114
+ - rename
115
+ - edit-in-place
116
+ synonyms:
117
+ inline-edit:
118
+ - editable
119
+ - click-to-edit
120
+ - edit-in-place
121
+ - rename-in-place
122
+ related:
123
+ - input
124
+ - field
125
+ - text
@@ -229,6 +229,15 @@ export class UIIntegrationCard extends UIElement {
229
229
  if (!this.#logoEl) return;
230
230
  const logo = (this.logo || '').trim();
231
231
 
232
+ // Guard against unresolved AdiaUI template binding descriptors
233
+ // ({{p:N}} placeholders). The integration-card's connected() + render()
234
+ // fires synchronously when the parent template stamps the element, BEFORE
235
+ // the parent's reconciliation pass replaces {{p:N}} with the real value.
236
+ // Skipping here lets the second render (triggered by the reconciliation's
237
+ // property set) render the real logo. Without this guard, icon-ui fires
238
+ // a "not found" warn for every {{p:4}} it receives on first connect.
239
+ if (logo.startsWith('{{p:')) return;
240
+
232
241
  // No logo → strip any prior content and hide.
233
242
  if (!logo) {
234
243
  this.#logoEl.replaceChildren();
@@ -275,9 +275,10 @@ describe('integration-card-ui — CSS contract (source-grep)', () => {
275
275
 
276
276
  expect(CSS).toMatch(/@scope\s*\(\s*integration-card-ui\s*\)/);
277
277
  expect(CSS).toMatch(/:where\(:scope\)\s*\{/);
278
- expect(CSS).toMatch(/--integration-card-bg:/);
279
- expect(CSS).toMatch(/--integration-card-border:/);
280
- expect(CSS).toMatch(/--integration-card-radius:/);
278
+ // OD-5 sweep: token declarations use -default suffix per component-token-contract.md
279
+ expect(CSS).toMatch(/--integration-card-bg-default:/);
280
+ expect(CSS).toMatch(/--integration-card-border-default:/);
281
+ expect(CSS).toMatch(/--integration-card-radius-default:/);
281
282
  });
282
283
 
283
284
  it('keeps status-driven border tint (status="connected") inside @scope', async () => {
@@ -53,7 +53,14 @@
53
53
  "MenuItem",
54
54
  "TreeItem"
55
55
  ],
56
- "slots": {},
56
+ "slots": {
57
+ "description": {
58
+ "description": "Override slot for richer description markup than the plain [description] attribute string (inline links, code spans, multiple lines). Renders beneath the primary text at body-subtle typography."
59
+ },
60
+ "icon": {
61
+ "description": "Override the [icon] glyph with a custom slotted element (e.g. a colored <icon-ui>, an image, or an avatar-ui). Mutually exclusive with the [icon] attribute — slot child wins if both are present."
62
+ }
63
+ },
57
64
  "states": [],
58
65
  "status": "stable",
59
66
  "synonyms": {
@@ -29,6 +29,18 @@ props:
29
29
  description: Secondary line below the primary text. Subtle color.
30
30
  type: string
31
31
 
32
+ slots:
33
+ icon:
34
+ description: >-
35
+ Override the [icon] glyph with a custom slotted element (e.g. a colored
36
+ <icon-ui>, an image, or an avatar-ui). Mutually exclusive with the
37
+ [icon] attribute — slot child wins if both are present.
38
+ description:
39
+ description: >-
40
+ Override slot for richer description markup than the plain [description]
41
+ attribute string (inline links, code spans, multiple lines). Renders
42
+ beneath the primary text at body-subtle typography.
43
+
32
44
  keywords:
33
45
  - list-item
34
46
  - list-row