@lushly-dev/fast-element 3.0.1-preview.0 → 3.0.1-preview.2

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 (250) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/command/command.api.json +7857 -0
  3. package/dist/context/context.api.json +1084 -0
  4. package/dist/declarative/declarative.api.json +8130 -0
  5. package/dist/definition-schema/definition-schema.api.json +2689 -0
  6. package/dist/di/di.api.json +4945 -0
  7. package/dist/dts/arrays.d.ts +2 -0
  8. package/dist/dts/binding/binding.d.ts +59 -0
  9. package/dist/dts/binding/exports.d.ts +9 -0
  10. package/dist/dts/binding/normalize.d.ts +9 -0
  11. package/dist/dts/binding/one-time.d.ts +11 -0
  12. package/dist/dts/binding/one-way.d.ts +20 -0
  13. package/dist/dts/binding/signal.d.ts +36 -0
  14. package/dist/dts/binding/two-way.d.ts +44 -0
  15. package/dist/dts/command/behavior.d.ts +94 -0
  16. package/dist/dts/command/behaviors/confirm-behavior.d.ts +139 -0
  17. package/dist/dts/command/behaviors/undo-behavior.d.ts +178 -0
  18. package/dist/dts/command/command.d.ts +79 -0
  19. package/dist/dts/command/component-registry.d.ts +50 -0
  20. package/dist/dts/command/components/confirm-dialog.d.ts +117 -0
  21. package/dist/dts/command/index.d.ts +21 -0
  22. package/dist/dts/command/registry.d.ts +201 -0
  23. package/dist/dts/command/schema-validation.d.ts +45 -0
  24. package/dist/dts/command/types.d.ts +332 -0
  25. package/dist/dts/command/validation.d.ts +116 -0
  26. package/dist/dts/command/weak-ref.d.ts +11 -0
  27. package/dist/dts/components/attributes.d.ts +153 -0
  28. package/dist/dts/components/definition-schema-transforms.d.ts +9 -0
  29. package/dist/dts/components/element-controller.d.ts +349 -0
  30. package/dist/dts/components/element-hydration.d.ts +2 -0
  31. package/dist/dts/components/enable-hydration.d.ts +66 -0
  32. package/dist/dts/components/fast-definitions.d.ts +211 -0
  33. package/dist/dts/components/fast-element-registry.d.ts +23 -0
  34. package/dist/dts/components/fast-element.d.ts +91 -0
  35. package/dist/dts/components/hydration-tracker.d.ts +110 -0
  36. package/dist/dts/components/hydration.d.ts +52 -0
  37. package/dist/dts/components/schema.d.ts +205 -0
  38. package/dist/dts/context.d.ts +171 -0
  39. package/dist/dts/debug.d.ts +5 -0
  40. package/dist/dts/declarative/attribute-map.d.ts +58 -0
  41. package/dist/dts/declarative/debug.d.ts +4 -0
  42. package/dist/dts/declarative/index.d.ts +14 -0
  43. package/dist/dts/declarative/interfaces.d.ts +8 -0
  44. package/dist/dts/declarative/observer-map-utilities.d.ts +58 -0
  45. package/dist/dts/declarative/observer-map.d.ts +89 -0
  46. package/dist/dts/declarative/runtime.d.ts +5 -0
  47. package/dist/dts/declarative/syntax.d.ts +21 -0
  48. package/dist/dts/declarative/template-bridge.d.ts +33 -0
  49. package/dist/dts/declarative/template-parser.d.ts +98 -0
  50. package/dist/dts/declarative/template.d.ts +9 -0
  51. package/dist/dts/declarative/utilities.d.ts +358 -0
  52. package/dist/dts/definition-schema/api.d.ts +42 -0
  53. package/dist/dts/definition-schema/cem.d.ts +78 -0
  54. package/dist/dts/definition-schema/extension.d.ts +18 -0
  55. package/dist/dts/definition-schema/extract.d.ts +38 -0
  56. package/dist/dts/definition-schema/index.d.ts +9 -0
  57. package/dist/dts/definition-schema/storage.d.ts +35 -0
  58. package/dist/dts/definition-schema/types.d.ts +176 -0
  59. package/dist/dts/dev-mode.d.ts +13 -0
  60. package/dist/dts/di/di.d.ts +894 -0
  61. package/dist/dts/dom-policy.d.ts +101 -0
  62. package/dist/dts/dom.d.ts +88 -0
  63. package/dist/dts/hydration/diagnostics.d.ts +93 -0
  64. package/dist/dts/hydration/exports.d.ts +18 -0
  65. package/dist/dts/hydration/hydration-debugger.d.ts +35 -0
  66. package/dist/dts/hydration/messages.d.ts +62 -0
  67. package/dist/dts/hydration/runtime.d.ts +7 -0
  68. package/dist/dts/hydration/target-builder.d.ts +107 -0
  69. package/dist/dts/index.d.ts +51 -0
  70. package/dist/dts/index.debug.d.ts +1 -0
  71. package/dist/dts/index.rollup.d.ts +1 -0
  72. package/dist/dts/index.rollup.debug.d.ts +1 -0
  73. package/dist/dts/interfaces.d.ts +106 -0
  74. package/dist/dts/metadata.d.ts +26 -0
  75. package/dist/dts/observation/arrays.d.ts +248 -0
  76. package/dist/dts/observation/notifier.d.ts +127 -0
  77. package/dist/dts/observation/observable.d.ts +276 -0
  78. package/dist/dts/observation/update-queue.d.ts +40 -0
  79. package/dist/dts/observation/volatile.d.ts +9 -0
  80. package/dist/dts/platform.d.ts +87 -0
  81. package/dist/dts/registry.d.ts +2 -0
  82. package/dist/dts/render.d.ts +7 -0
  83. package/dist/dts/schema.d.ts +1 -0
  84. package/dist/dts/scoped/detection.d.ts +55 -0
  85. package/dist/dts/scoped/index.d.ts +68 -0
  86. package/dist/dts/scoped/mixin.d.ts +118 -0
  87. package/dist/dts/scoped/registry.d.ts +76 -0
  88. package/dist/dts/scoped/types.d.ts +48 -0
  89. package/dist/dts/state/exports.d.ts +3 -0
  90. package/dist/dts/state/reactive.d.ts +8 -0
  91. package/dist/dts/state/state.d.ts +141 -0
  92. package/dist/dts/state/visitor.d.ts +6 -0
  93. package/dist/dts/state/watch.d.ts +10 -0
  94. package/dist/dts/styles/css-directive.d.ts +50 -0
  95. package/dist/dts/styles/css.d.ts +34 -0
  96. package/dist/dts/styles/element-styles.d.ts +61 -0
  97. package/dist/dts/styles/host.d.ts +65 -0
  98. package/dist/dts/styles/style-strategy.d.ts +42 -0
  99. package/dist/dts/templating/children.d.ts +62 -0
  100. package/dist/dts/templating/compiler.d.ts +54 -0
  101. package/dist/dts/templating/html-binding-directive.d.ts +155 -0
  102. package/dist/dts/templating/html-directive.d.ts +218 -0
  103. package/dist/dts/templating/html.d.ts +1 -0
  104. package/dist/dts/templating/hydration-view.d.ts +130 -0
  105. package/dist/dts/templating/markup.d.ts +48 -0
  106. package/dist/dts/templating/node-observation.d.ts +99 -0
  107. package/dist/dts/templating/ref.d.ts +23 -0
  108. package/dist/dts/templating/render.d.ts +318 -0
  109. package/dist/dts/templating/repeat.d.ts +131 -0
  110. package/dist/dts/templating/slotted.d.ts +38 -0
  111. package/dist/dts/templating/template.d.ts +221 -0
  112. package/dist/dts/templating/view.d.ts +268 -0
  113. package/dist/dts/templating/when.d.ts +12 -0
  114. package/dist/dts/testing/exports.d.ts +3 -0
  115. package/dist/dts/testing/fakes.d.ts +16 -0
  116. package/dist/dts/testing/fixture.d.ts +84 -0
  117. package/dist/dts/testing/models.d.ts +20 -0
  118. package/dist/dts/testing/timeout.d.ts +7 -0
  119. package/dist/dts/trust/index.d.ts +8 -0
  120. package/dist/dts/trust/types.d.ts +48 -0
  121. package/dist/dts/trust/utils.d.ts +26 -0
  122. package/dist/dts/tsdoc-metadata.json +11 -0
  123. package/dist/dts/utilities.d.ts +76 -0
  124. package/dist/esm/arrays.js +1 -0
  125. package/dist/esm/binding/binding.js +18 -0
  126. package/dist/esm/binding/exports.js +6 -0
  127. package/dist/esm/binding/normalize.js +17 -0
  128. package/dist/esm/binding/one-time.js +21 -0
  129. package/dist/esm/binding/one-way.js +30 -0
  130. package/dist/esm/binding/signal.js +99 -0
  131. package/dist/esm/binding/two-way.js +109 -0
  132. package/dist/esm/command/behavior.js +155 -0
  133. package/dist/esm/command/behaviors/confirm-behavior.js +225 -0
  134. package/dist/esm/command/behaviors/undo-behavior.js +313 -0
  135. package/dist/esm/command/command.js +104 -0
  136. package/dist/esm/command/component-registry.js +107 -0
  137. package/dist/esm/command/components/confirm-dialog.js +370 -0
  138. package/dist/esm/command/index.js +22 -0
  139. package/dist/esm/command/registry.js +755 -0
  140. package/dist/esm/command/schema-validation.js +194 -0
  141. package/dist/esm/command/types.js +86 -0
  142. package/dist/esm/command/validation.js +508 -0
  143. package/dist/esm/command/weak-ref.js +5 -0
  144. package/dist/esm/components/attributes.js +219 -0
  145. package/dist/esm/components/definition-schema-transforms.js +23 -0
  146. package/dist/esm/components/element-controller.js +840 -0
  147. package/dist/esm/components/element-hydration.js +2 -0
  148. package/dist/esm/components/enable-hydration.js +123 -0
  149. package/dist/esm/components/fast-definitions.js +294 -0
  150. package/dist/esm/components/fast-element-registry.js +67 -0
  151. package/dist/esm/components/fast-element.js +82 -0
  152. package/dist/esm/components/hydration-tracker.js +211 -0
  153. package/dist/esm/components/hydration.js +187 -0
  154. package/dist/esm/components/schema.js +253 -0
  155. package/dist/esm/context.js +185 -0
  156. package/dist/esm/debug.js +71 -0
  157. package/dist/esm/declarative/attribute-map.js +122 -0
  158. package/dist/esm/declarative/debug.js +4 -0
  159. package/dist/esm/declarative/index.js +10 -0
  160. package/dist/esm/declarative/interfaces.js +9 -0
  161. package/dist/esm/declarative/observer-map-utilities.js +565 -0
  162. package/dist/esm/declarative/observer-map.js +216 -0
  163. package/dist/esm/declarative/runtime.js +14 -0
  164. package/dist/esm/declarative/syntax.js +36 -0
  165. package/dist/esm/declarative/template-bridge.js +160 -0
  166. package/dist/esm/declarative/template-parser.js +306 -0
  167. package/dist/esm/declarative/template.js +116 -0
  168. package/dist/esm/declarative/utilities.js +1069 -0
  169. package/dist/esm/definition-schema/api.js +129 -0
  170. package/dist/esm/definition-schema/cem.js +89 -0
  171. package/dist/esm/definition-schema/extension.js +53 -0
  172. package/dist/esm/definition-schema/extract.js +167 -0
  173. package/dist/esm/definition-schema/index.js +8 -0
  174. package/dist/esm/definition-schema/storage.js +58 -0
  175. package/dist/esm/definition-schema/types.js +8 -0
  176. package/dist/esm/dev-mode.js +22 -0
  177. package/dist/esm/di/di.js +1441 -0
  178. package/dist/esm/dom-policy.js +374 -0
  179. package/dist/esm/dom.js +101 -0
  180. package/dist/esm/hydration/diagnostics.js +50 -0
  181. package/dist/esm/hydration/exports.js +6 -0
  182. package/dist/esm/hydration/hydration-debugger.js +112 -0
  183. package/dist/esm/hydration/messages.js +84 -0
  184. package/dist/esm/hydration/runtime.js +33 -0
  185. package/dist/esm/hydration/target-builder.js +264 -0
  186. package/dist/esm/index.debug.js +3 -0
  187. package/dist/esm/index.js +49 -0
  188. package/dist/esm/index.rollup.debug.js +6 -0
  189. package/dist/esm/index.rollup.js +4 -0
  190. package/dist/esm/interfaces.js +55 -0
  191. package/dist/esm/metadata.js +57 -0
  192. package/dist/esm/observation/arrays.js +661 -0
  193. package/dist/esm/observation/notifier.js +162 -0
  194. package/dist/esm/observation/observable.js +324 -0
  195. package/dist/esm/observation/update-queue.js +56 -0
  196. package/dist/esm/observation/volatile.js +16 -0
  197. package/dist/esm/platform.js +98 -0
  198. package/dist/esm/registry.js +2 -0
  199. package/dist/esm/render.js +1 -0
  200. package/dist/esm/schema.js +1 -0
  201. package/dist/esm/scoped/detection.js +102 -0
  202. package/dist/esm/scoped/index.js +69 -0
  203. package/dist/esm/scoped/mixin.js +127 -0
  204. package/dist/esm/scoped/registry.js +125 -0
  205. package/dist/esm/scoped/types.js +4 -0
  206. package/dist/esm/state/exports.js +3 -0
  207. package/dist/esm/state/reactive.js +34 -0
  208. package/dist/esm/state/state.js +148 -0
  209. package/dist/esm/state/visitor.js +28 -0
  210. package/dist/esm/state/watch.js +36 -0
  211. package/dist/esm/styles/css-directive.js +36 -0
  212. package/dist/esm/styles/css.js +56 -0
  213. package/dist/esm/styles/element-styles.js +144 -0
  214. package/dist/esm/styles/host.js +1 -0
  215. package/dist/esm/styles/style-strategy.js +1 -0
  216. package/dist/esm/templating/children.js +67 -0
  217. package/dist/esm/templating/compiler.js +359 -0
  218. package/dist/esm/templating/html-binding-directive.js +288 -0
  219. package/dist/esm/templating/html-directive.js +113 -0
  220. package/dist/esm/templating/html.js +1 -0
  221. package/dist/esm/templating/hydration-view.js +228 -0
  222. package/dist/esm/templating/markup.js +83 -0
  223. package/dist/esm/templating/node-observation.js +84 -0
  224. package/dist/esm/templating/ref.js +21 -0
  225. package/dist/esm/templating/render.js +468 -0
  226. package/dist/esm/templating/repeat.js +434 -0
  227. package/dist/esm/templating/slotted.js +49 -0
  228. package/dist/esm/templating/template.js +206 -0
  229. package/dist/esm/templating/view.js +298 -0
  230. package/dist/esm/templating/when.js +22 -0
  231. package/dist/esm/testing/exports.js +3 -0
  232. package/dist/esm/testing/fakes.js +107 -0
  233. package/dist/esm/testing/fixture.js +86 -0
  234. package/dist/esm/testing/models.js +58 -0
  235. package/dist/esm/testing/timeout.js +24 -0
  236. package/dist/esm/trust/index.js +7 -0
  237. package/dist/esm/trust/types.js +6 -0
  238. package/dist/esm/trust/utils.js +126 -0
  239. package/dist/esm/utilities.js +140 -0
  240. package/dist/fast-element.api.json +26798 -0
  241. package/dist/fast-element.d.ts +3985 -0
  242. package/dist/fast-element.debug.js +7604 -0
  243. package/dist/fast-element.debug.min.js +3 -0
  244. package/dist/fast-element.js +7603 -0
  245. package/dist/fast-element.min.js +3 -0
  246. package/dist/fast-element.untrimmed.d.ts +4103 -0
  247. package/dist/observation/arrays/arrays.api.json +2621 -0
  248. package/dist/scoped/scoped.api.json +891 -0
  249. package/dist/trust/trust.api.json +629 -0
  250. package/package.json +21 -23
@@ -0,0 +1,225 @@
1
+ /**
2
+ * ConfirmBehavior - Intercepts destructive commands and prompts for confirmation.
3
+ *
4
+ * @remarks
5
+ * Uses the CommandRegistry interceptor API to intercept commands marked with
6
+ * `destructive: true`. Exposes pending confirmation state for UI rendering.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { ConfirmBehavior } from '@lushly-dev/fast-element/command.js';
11
+ *
12
+ * class MyApp extends FASTElement {
13
+ * private confirmBehavior = new ConfirmBehavior();
14
+ *
15
+ * connectedCallback() {
16
+ * super.connectedCallback();
17
+ * this.$fastController.addBehavior(this.confirmBehavior);
18
+ * }
19
+ *
20
+ * // In template (pendingConfirmation is observable, so the template updates):
21
+ * // <fast-af-confirm-dialog
22
+ * // :open="${x => x.confirmBehavior.pendingConfirmation !== null}"
23
+ * // :prompt="${x => x.confirmBehavior.pendingConfirmation?.prompt}"
24
+ * // @confirm="${x => x.confirmBehavior.executeConfirmed()}"
25
+ * // @cancel="${x => x.confirmBehavior.cancelPending()}">
26
+ * // </fast-af-confirm-dialog>
27
+ * }
28
+ * ```
29
+ *
30
+ * @public
31
+ */
32
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
33
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
34
+ return new (P || (P = Promise))(function (resolve, reject) {
35
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
36
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
37
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
38
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
39
+ });
40
+ };
41
+ import { Observable } from "../../observation/observable.js";
42
+ import { CommandRegistry } from "../registry.js";
43
+ import { failure } from "../types.js";
44
+ /**
45
+ * Error code for user-cancelled operations.
46
+ * @public
47
+ */
48
+ export const USER_CANCELLED = "USER_CANCELLED";
49
+ /**
50
+ * Behavior that intercepts destructive commands and prompts for user confirmation.
51
+ *
52
+ * @remarks
53
+ * - Installs a shared `confirm` interceptor with the CommandRegistry while at
54
+ * least one instance is connected (reference counted: the interceptor is added
55
+ * by the first instance and removed when the last one disconnects)
56
+ * - Intercepts commands where `destructive: true`
57
+ * - When several instances are connected, the most recently connected one handles
58
+ * confirmations; if it disconnects, the previous one takes over
59
+ * - Concurrent confirmations are queued: `pendingConfirmation` is the oldest
60
+ * unsettled one, and each `executeConfirmed()`/`cancelPending()` settles it and
61
+ * exposes the next
62
+ * - `pendingConfirmation` is observable, so templates binding to it update
63
+ * - Cancels any pending confirmation on disconnect
64
+ *
65
+ * @public
66
+ */
67
+ export class ConfirmBehavior {
68
+ constructor() {
69
+ /** Pending confirmations, oldest first */
70
+ this.queue = [];
71
+ /** Whether this instance is currently connected */
72
+ this.connected = false;
73
+ }
74
+ /**
75
+ * Called when the host element connects to the DOM.
76
+ * Registers this instance and, for the first one, the shared interceptor.
77
+ *
78
+ * @param controller - The host controller managing this behavior
79
+ */
80
+ connectedCallback(controller) {
81
+ if (this.connected) {
82
+ return;
83
+ }
84
+ this.connected = true;
85
+ const instances = ConfirmBehavior.instances;
86
+ instances.push(this);
87
+ if (instances.length === 1) {
88
+ CommandRegistry.addInterceptor({
89
+ name: ConfirmBehavior.INTERCEPTOR_NAME,
90
+ confirms: true,
91
+ priority: 10, // Run early to block before other interceptors
92
+ intercept: (command, args, next) => __awaiter(this, void 0, void 0, function* () {
93
+ var _a;
94
+ // Only intercept destructive commands
95
+ if (!command.destructive) {
96
+ return next();
97
+ }
98
+ const active = ConfirmBehavior.instances;
99
+ const handler = active[active.length - 1];
100
+ // Show confirmation and wait for user response; with nobody left
101
+ // to ask, fail closed.
102
+ const confirmed = handler !== void 0 &&
103
+ (yield handler.showConfirmation(command.id, command.label, (_a = command.confirmPrompt) !== null && _a !== void 0 ? _a : "Are you sure you want to proceed?"));
104
+ if (!confirmed) {
105
+ return failure({
106
+ code: USER_CANCELLED,
107
+ message: "User cancelled the operation",
108
+ retryable: false,
109
+ });
110
+ }
111
+ return next();
112
+ }),
113
+ });
114
+ }
115
+ }
116
+ /**
117
+ * Called when the host element disconnects from the DOM.
118
+ * Cancels pending confirmations, and removes the shared interceptor when this
119
+ * was the last connected instance.
120
+ *
121
+ * @param controller - The host controller managing this behavior
122
+ */
123
+ disconnectedCallback(controller) {
124
+ if (!this.connected) {
125
+ return;
126
+ }
127
+ this.connected = false;
128
+ // Cancel any pending confirmation
129
+ this.settleAll(false);
130
+ const instances = ConfirmBehavior.instances;
131
+ const index = instances.indexOf(this);
132
+ if (index !== -1) {
133
+ instances.splice(index, 1);
134
+ }
135
+ // Remove interceptor to prevent memory leaks once nobody can confirm
136
+ if (instances.length === 0) {
137
+ CommandRegistry.removeInterceptor(ConfirmBehavior.INTERCEPTOR_NAME);
138
+ }
139
+ }
140
+ /**
141
+ * Gets the oldest pending confirmation, if any.
142
+ *
143
+ * @remarks
144
+ * Observable: use this in templates to show a confirmation dialog and to get the
145
+ * prompt text. When several confirmations are queued, the next one becomes
146
+ * current after the present one is settled.
147
+ *
148
+ * @returns The pending confirmation or null
149
+ */
150
+ get pendingConfirmation() {
151
+ var _a;
152
+ Observable.track(this, "pendingConfirmation");
153
+ return (_a = this.queue[0]) !== null && _a !== void 0 ? _a : null;
154
+ }
155
+ /**
156
+ * Gets the number of unsettled confirmations, including the current one.
157
+ * Observable.
158
+ */
159
+ get pendingCount() {
160
+ Observable.track(this, "pendingConfirmation");
161
+ return this.queue.length;
162
+ }
163
+ /**
164
+ * Confirms the current pending operation and allows it to proceed.
165
+ *
166
+ * @remarks
167
+ * Call this when the user clicks "Confirm" in your confirmation UI.
168
+ * The intercepted command will then execute normally.
169
+ */
170
+ executeConfirmed() {
171
+ this.settleCurrent(true);
172
+ }
173
+ /**
174
+ * Cancels the current pending operation.
175
+ *
176
+ * @remarks
177
+ * Call this when the user clicks "Cancel" in your confirmation UI.
178
+ * The intercepted command will return a USER_CANCELLED error.
179
+ */
180
+ cancelPending() {
181
+ this.settleCurrent(false);
182
+ }
183
+ settleCurrent(confirmed) {
184
+ const current = this.queue.shift();
185
+ if (current !== void 0) {
186
+ Observable.notify(this, "pendingConfirmation");
187
+ current.resolve(confirmed);
188
+ }
189
+ }
190
+ settleAll(confirmed) {
191
+ const settled = this.queue;
192
+ if (settled.length > 0) {
193
+ this.queue = [];
194
+ Observable.notify(this, "pendingConfirmation");
195
+ for (const pending of settled) {
196
+ pending.resolve(confirmed);
197
+ }
198
+ }
199
+ }
200
+ /**
201
+ * Queues a confirmation prompt and waits for user response.
202
+ *
203
+ * @param commandId - The command ID being confirmed
204
+ * @param label - The command label for display
205
+ * @param prompt - The confirmation prompt text
206
+ * @returns Promise that resolves to true if confirmed, false if cancelled
207
+ *
208
+ * @internal
209
+ */
210
+ showConfirmation(commandId, label, prompt) {
211
+ return new Promise(resolve => {
212
+ this.queue.push({
213
+ commandId,
214
+ label,
215
+ prompt,
216
+ resolve,
217
+ });
218
+ Observable.notify(this, "pendingConfirmation");
219
+ });
220
+ }
221
+ }
222
+ /** Name of the interceptor for registration/removal */
223
+ ConfirmBehavior.INTERCEPTOR_NAME = "confirm";
224
+ /** Connected instances, oldest first. The last one is the active handler. */
225
+ ConfirmBehavior.instances = [];
@@ -0,0 +1,313 @@
1
+ /**
2
+ * UndoBehavior - Manages an undo stack for undoable commands.
3
+ *
4
+ * @remarks
5
+ * Uses the CommandRegistry PostHook API to observe command execution and
6
+ * capture undo functions. Provides keyboard shortcut support (Ctrl+Z).
7
+ *
8
+ * Commands are undoable if the source element has a matching `${methodName}Undo()`
9
+ * method that returns a function to reverse the operation. `methodName` is the name
10
+ * of the `@command`-decorated method (not the last segment of the command ID).
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * import { UndoBehavior } from '@lushly-dev/fast-element/command.js';
15
+ *
16
+ * class MyApp extends FASTElement {
17
+ * private undoBehavior = new UndoBehavior({ limit: 20 });
18
+ *
19
+ * connectedCallback() {
20
+ * super.connectedCallback();
21
+ * this.$fastController.addBehavior(this.undoBehavior);
22
+ * }
23
+ *
24
+ * // Command with undo support
25
+ * @command("editor.delete")
26
+ * delete() { ... }
27
+ *
28
+ * // Undo function - returns the reverse operation
29
+ * deleteUndo() {
30
+ * return () => { ... restore deleted content ... };
31
+ * }
32
+ * }
33
+ * ```
34
+ *
35
+ * @public
36
+ */
37
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
38
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
39
+ return new (P || (P = Promise))(function (resolve, reject) {
40
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
41
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
42
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
43
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
44
+ });
45
+ };
46
+ import { CommandRegistry } from "../registry.js";
47
+ /**
48
+ * Whether a keyboard event originates from an element whose own undo history the
49
+ * browser manages (inputs, textareas, selects, contenteditable).
50
+ * @internal
51
+ */
52
+ function isEditableTarget(event) {
53
+ var _a;
54
+ var _b;
55
+ // composedPath()[0] sees through shadow roots, where event.target is retargeted
56
+ const target = ((_b = (_a = event.composedPath) === null || _a === void 0 ? void 0 : _a.call(event)[0]) !== null && _b !== void 0 ? _b : event.target);
57
+ if (!target || typeof target.tagName !== "string") {
58
+ return false;
59
+ }
60
+ const tag = target.tagName;
61
+ return (tag === "INPUT" ||
62
+ tag === "TEXTAREA" ||
63
+ tag === "SELECT" ||
64
+ target.isContentEditable === true);
65
+ }
66
+ /**
67
+ * Behavior that manages an undo stack for undoable commands.
68
+ *
69
+ * @remarks
70
+ * - Installs a shared PostHook with the CommandRegistry while at least one instance
71
+ * is connected (reference counted: added by the first instance, removed when the
72
+ * last one disconnects)
73
+ * - When several instances are connected, the most recently connected one records
74
+ * undo entries and handles the keyboard shortcut
75
+ * - Captures undo functions from `${methodName}Undo()` convention
76
+ * - Maintains a limited stack of undo entries
77
+ * - Provides `undo()` method and Ctrl/Cmd+Z keyboard shortcut; the shortcut is left
78
+ * to the browser when the event target is an editable element
79
+ * - Cleans up on disconnect
80
+ *
81
+ * @public
82
+ */
83
+ export class UndoBehavior {
84
+ /**
85
+ * Creates a new UndoBehavior instance.
86
+ *
87
+ * @param options - Configuration options
88
+ */
89
+ constructor(options = {}) {
90
+ var _a, _b;
91
+ /** Undo stack (most recent at the end) */
92
+ this.stack = [];
93
+ /** Bound keyboard handler for cleanup */
94
+ this.boundKeydownHandler = null;
95
+ /** Whether this instance is currently connected */
96
+ this.connected = false;
97
+ this.limit = (_a = options.limit) !== null && _a !== void 0 ? _a : 10;
98
+ this.enableKeyboardShortcut = (_b = options.enableKeyboardShortcut) !== null && _b !== void 0 ? _b : true;
99
+ }
100
+ /**
101
+ * Called when the host element connects to the DOM.
102
+ * Registers the undo post-hook with CommandRegistry.
103
+ *
104
+ * @param controller - The host controller managing this behavior
105
+ */
106
+ connectedCallback(controller) {
107
+ if (this.connected) {
108
+ return;
109
+ }
110
+ this.connected = true;
111
+ const instances = UndoBehavior.instances;
112
+ instances.push(this);
113
+ // Register the shared post-hook to capture undo functions
114
+ if (instances.length === 1) {
115
+ CommandRegistry.addPostHook({
116
+ name: UndoBehavior.POST_HOOK_NAME,
117
+ afterExecute: (command, args, result) => {
118
+ var _a;
119
+ const active = UndoBehavior.instances;
120
+ (_a = active[active.length - 1]) === null || _a === void 0 ? void 0 : _a.handleCommandExecuted(command, args, result);
121
+ },
122
+ });
123
+ }
124
+ // Register keyboard shortcut
125
+ if (this.enableKeyboardShortcut && typeof document !== "undefined") {
126
+ const handler = this.handleKeydown.bind(this);
127
+ this.boundKeydownHandler = handler;
128
+ document.addEventListener("keydown", handler);
129
+ }
130
+ }
131
+ /**
132
+ * Called when the host element disconnects from the DOM.
133
+ * Removes the post-hook (when this was the last instance) and cleans up state.
134
+ *
135
+ * @param controller - The host controller managing this behavior
136
+ */
137
+ disconnectedCallback(controller) {
138
+ if (!this.connected) {
139
+ return;
140
+ }
141
+ this.connected = false;
142
+ const instances = UndoBehavior.instances;
143
+ const index = instances.indexOf(this);
144
+ if (index !== -1) {
145
+ instances.splice(index, 1);
146
+ }
147
+ // Remove post-hook once no instance needs it
148
+ if (instances.length === 0) {
149
+ CommandRegistry.removePostHook(UndoBehavior.POST_HOOK_NAME);
150
+ }
151
+ // Remove keyboard handler
152
+ const handler = this.boundKeydownHandler;
153
+ if (typeof document !== "undefined" && handler !== null) {
154
+ document.removeEventListener("keydown", handler);
155
+ this.boundKeydownHandler = null;
156
+ }
157
+ // Clear state
158
+ this.stack = [];
159
+ }
160
+ /**
161
+ * Gets the current undo stack.
162
+ *
163
+ * @remarks
164
+ * Returns a copy of the stack to prevent external mutation.
165
+ *
166
+ * @returns Array of undo entries (most recent last)
167
+ */
168
+ getStack() {
169
+ return [...this.stack];
170
+ }
171
+ /**
172
+ * Gets the number of entries in the undo stack.
173
+ *
174
+ * @returns The stack size
175
+ */
176
+ get stackSize() {
177
+ return this.stack.length;
178
+ }
179
+ /**
180
+ * Whether there are any operations to undo.
181
+ *
182
+ * @returns True if the stack is not empty
183
+ */
184
+ get canUndo() {
185
+ return this.stack.length > 0;
186
+ }
187
+ /**
188
+ * Gets the most recent undo entry without removing it.
189
+ *
190
+ * @returns The most recent entry, or undefined if stack is empty
191
+ */
192
+ peek() {
193
+ return this.stack[this.stack.length - 1];
194
+ }
195
+ /**
196
+ * Undoes the most recent operation.
197
+ *
198
+ * @remarks
199
+ * Pops the most recent entry from the stack and executes its undo function.
200
+ * If the undo function is async, this returns a Promise.
201
+ *
202
+ * @returns Promise that resolves when the undo is complete
203
+ */
204
+ undo() {
205
+ return __awaiter(this, void 0, void 0, function* () {
206
+ const entry = this.stack.pop();
207
+ if (!entry) {
208
+ return false;
209
+ }
210
+ try {
211
+ yield entry.undoFn();
212
+ return true;
213
+ }
214
+ catch (error) {
215
+ // Re-push on failure so user can retry
216
+ this.stack.push(entry);
217
+ throw error;
218
+ }
219
+ });
220
+ }
221
+ /**
222
+ * Clears the entire undo stack.
223
+ */
224
+ clear() {
225
+ this.stack = [];
226
+ }
227
+ /**
228
+ * Handles a command execution and potentially adds to the undo stack.
229
+ *
230
+ * @internal
231
+ */
232
+ handleCommandExecuted(command, args, result) {
233
+ var _a;
234
+ // Only capture successful commands
235
+ if (!result.success) {
236
+ return;
237
+ }
238
+ // Get the source element
239
+ const source = command.source.deref();
240
+ if (!source) {
241
+ return;
242
+ }
243
+ // Prefer the decorated method's name; fall back to the last segment of the
244
+ // command ID (e.g., "editor.save" -> "save") for hand-registered entries.
245
+ const methodName = (_a = command.methodName) !== null && _a !== void 0 ? _a : this.extractMethodName(command.id);
246
+ if (!methodName) {
247
+ return;
248
+ }
249
+ // Look for ${methodName}Undo() convention
250
+ const undoMethodName = `${methodName}Undo`;
251
+ const undoMethod = source[undoMethodName];
252
+ if (typeof undoMethod !== "function") {
253
+ return;
254
+ }
255
+ // Call the undo method to get the undo function
256
+ // The undo method should return a function that reverses the operation
257
+ const undoFn = undoMethod.call(source);
258
+ if (typeof undoFn !== "function") {
259
+ return;
260
+ }
261
+ // Add to stack
262
+ this.stack.push({
263
+ commandId: command.id,
264
+ label: command.label,
265
+ args,
266
+ undoFn,
267
+ timestamp: Date.now(),
268
+ });
269
+ // Trim to limit
270
+ while (this.stack.length > this.limit) {
271
+ this.stack.shift();
272
+ }
273
+ }
274
+ /**
275
+ * Extracts the method name from a command ID.
276
+ *
277
+ * @param commandId - The command ID (e.g., "editor.save")
278
+ * @returns The method name (e.g., "save")
279
+ *
280
+ * @internal
281
+ */
282
+ extractMethodName(commandId) {
283
+ // Command IDs are in format "namespace.method" or "@scope/namespace.method"
284
+ const lastDotIndex = commandId.lastIndexOf(".");
285
+ if (lastDotIndex === -1) {
286
+ return null;
287
+ }
288
+ return commandId.slice(lastDotIndex + 1);
289
+ }
290
+ /**
291
+ * Handles keyboard events for Ctrl+Z shortcut.
292
+ *
293
+ * @internal
294
+ */
295
+ handleKeydown(event) {
296
+ // Check for Ctrl+Z (Windows/Linux) or Cmd+Z (Mac)
297
+ const isUndo = (event.ctrlKey || event.metaKey) && event.key === "z" && !event.shiftKey;
298
+ if (isUndo &&
299
+ this.canUndo &&
300
+ UndoBehavior.instances[UndoBehavior.instances.length - 1] === this &&
301
+ !event.defaultPrevented &&
302
+ !isEditableTarget(event)) {
303
+ event.preventDefault();
304
+ this.undo().catch(error => {
305
+ console.error("[fast-af] Undo failed:", error);
306
+ });
307
+ }
308
+ }
309
+ }
310
+ /** Name of the post-hook for registration/removal */
311
+ UndoBehavior.POST_HOOK_NAME = "undo";
312
+ /** Connected instances, oldest first. The last one is the active handler. */
313
+ UndoBehavior.instances = [];
@@ -0,0 +1,104 @@
1
+ /**
2
+ * The `command` decorator for FASTElement methods.
3
+ * Binds methods to the Command Registry for AI agent interaction.
4
+ *
5
+ * @remarks
6
+ * Uses FAST's createMetadataLocator with push() pattern for metadata storage.
7
+ * Metadata is discovered at connect-time by CommandBehavior.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { FASTElement } from '@lushly-dev/fast-element';
12
+ * import { command, success } from '@lushly-dev/fast-element/command.js';
13
+ *
14
+ * class MyEditor extends FASTElement {
15
+ * @command('editor.save', { label: 'Save', keybinding: 'Ctrl+S' })
16
+ * save(): CommandResult {
17
+ * // Save logic here
18
+ * return success({ saved: true });
19
+ * }
20
+ * }
21
+ * ```
22
+ */
23
+ import { registerElementControllerBehaviorFactory } from "../components/element-controller.js";
24
+ import { createMetadataLocator } from "../platform.js";
25
+ import { CommandBehavior } from "./behavior.js";
26
+ /**
27
+ * Locates command metadata associated with a class.
28
+ * @remarks
29
+ * Uses FAST's createMetadataLocator pattern with WeakMap storage.
30
+ * Traverses prototype chain for inherited commands.
31
+ * @internal
32
+ */
33
+ const commandMetadata = createMetadataLocator();
34
+ registerElementControllerBehaviorFactory(element => getCommandMetadata(element.constructor).length > 0 ? new CommandBehavior() : void 0);
35
+ /**
36
+ * Retrieves all command metadata for a given class.
37
+ *
38
+ * @param target - The class constructor to retrieve metadata for
39
+ * @returns Array of CommandMetadata for all `command` decorators on the class
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * const metadata = getCommandMetadata(MyEditor);
44
+ * console.log(metadata); // [{ id: 'editor.save', methodName: 'save', ... }]
45
+ * ```
46
+ *
47
+ * @public
48
+ */
49
+ export function getCommandMetadata(target) {
50
+ return commandMetadata(target);
51
+ }
52
+ /**
53
+ * Decorator that registers a method as a command.
54
+ * Commands are discoverable by AI agents and command palettes.
55
+ *
56
+ * @param id - Namespace-prefixed command identifier (e.g., "editor.save")
57
+ * @param options - Optional command configuration
58
+ * @returns A method decorator
59
+ *
60
+ * @remarks
61
+ * - Command IDs must include a namespace prefix (e.g., "app.action")
62
+ * - Runtime warning is emitted if namespace is missing
63
+ * - Metadata is stored using FAST's push() pattern
64
+ * - Commands are registered with CommandRegistry when element connects
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * class TextEditor extends FASTElement {
69
+ * // Basic command
70
+ * @command('editor.selectAll')
71
+ * selectAll(): CommandResult {
72
+ * return success({ selected: true });
73
+ * }
74
+ *
75
+ * // Command with full options
76
+ * @command('editor.save', {
77
+ * label: 'Save Document',
78
+ * icon: 'save',
79
+ * description: 'Save the current document',
80
+ * category: 'File',
81
+ * keybinding: 'Ctrl+S',
82
+ * when: 'editorFocus'
83
+ * })
84
+ * save(): CommandResult {
85
+ * return success({ saved: true });
86
+ * }
87
+ * }
88
+ * ```
89
+ *
90
+ * @public
91
+ */
92
+ export function command(id, options) {
93
+ return function (target, propertyKey, descriptor) {
94
+ // Runtime validation for namespace prefix
95
+ if (!id.includes(".")) {
96
+ console.warn(`[fast-af] Command "${id}" missing namespace prefix. ` +
97
+ `Use format: "namespace.action" (e.g., "editor.save")`);
98
+ }
99
+ // Store metadata using FAST's push() pattern
100
+ // target is the prototype, target.constructor is the class
101
+ commandMetadata(target.constructor).push(Object.assign({ id, methodName: propertyKey }, options));
102
+ return descriptor;
103
+ };
104
+ }