@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.
- package/CHANGELOG.md +12 -0
- package/dist/command/command.api.json +7857 -0
- package/dist/context/context.api.json +1084 -0
- package/dist/declarative/declarative.api.json +8130 -0
- package/dist/definition-schema/definition-schema.api.json +2689 -0
- package/dist/di/di.api.json +4945 -0
- package/dist/dts/arrays.d.ts +2 -0
- package/dist/dts/binding/binding.d.ts +59 -0
- package/dist/dts/binding/exports.d.ts +9 -0
- package/dist/dts/binding/normalize.d.ts +9 -0
- package/dist/dts/binding/one-time.d.ts +11 -0
- package/dist/dts/binding/one-way.d.ts +20 -0
- package/dist/dts/binding/signal.d.ts +36 -0
- package/dist/dts/binding/two-way.d.ts +44 -0
- package/dist/dts/command/behavior.d.ts +94 -0
- package/dist/dts/command/behaviors/confirm-behavior.d.ts +139 -0
- package/dist/dts/command/behaviors/undo-behavior.d.ts +178 -0
- package/dist/dts/command/command.d.ts +79 -0
- package/dist/dts/command/component-registry.d.ts +50 -0
- package/dist/dts/command/components/confirm-dialog.d.ts +117 -0
- package/dist/dts/command/index.d.ts +21 -0
- package/dist/dts/command/registry.d.ts +201 -0
- package/dist/dts/command/schema-validation.d.ts +45 -0
- package/dist/dts/command/types.d.ts +332 -0
- package/dist/dts/command/validation.d.ts +116 -0
- package/dist/dts/command/weak-ref.d.ts +11 -0
- package/dist/dts/components/attributes.d.ts +153 -0
- package/dist/dts/components/definition-schema-transforms.d.ts +9 -0
- package/dist/dts/components/element-controller.d.ts +349 -0
- package/dist/dts/components/element-hydration.d.ts +2 -0
- package/dist/dts/components/enable-hydration.d.ts +66 -0
- package/dist/dts/components/fast-definitions.d.ts +211 -0
- package/dist/dts/components/fast-element-registry.d.ts +23 -0
- package/dist/dts/components/fast-element.d.ts +91 -0
- package/dist/dts/components/hydration-tracker.d.ts +110 -0
- package/dist/dts/components/hydration.d.ts +52 -0
- package/dist/dts/components/schema.d.ts +205 -0
- package/dist/dts/context.d.ts +171 -0
- package/dist/dts/debug.d.ts +5 -0
- package/dist/dts/declarative/attribute-map.d.ts +58 -0
- package/dist/dts/declarative/debug.d.ts +4 -0
- package/dist/dts/declarative/index.d.ts +14 -0
- package/dist/dts/declarative/interfaces.d.ts +8 -0
- package/dist/dts/declarative/observer-map-utilities.d.ts +58 -0
- package/dist/dts/declarative/observer-map.d.ts +89 -0
- package/dist/dts/declarative/runtime.d.ts +5 -0
- package/dist/dts/declarative/syntax.d.ts +21 -0
- package/dist/dts/declarative/template-bridge.d.ts +33 -0
- package/dist/dts/declarative/template-parser.d.ts +98 -0
- package/dist/dts/declarative/template.d.ts +9 -0
- package/dist/dts/declarative/utilities.d.ts +358 -0
- package/dist/dts/definition-schema/api.d.ts +42 -0
- package/dist/dts/definition-schema/cem.d.ts +78 -0
- package/dist/dts/definition-schema/extension.d.ts +18 -0
- package/dist/dts/definition-schema/extract.d.ts +38 -0
- package/dist/dts/definition-schema/index.d.ts +9 -0
- package/dist/dts/definition-schema/storage.d.ts +35 -0
- package/dist/dts/definition-schema/types.d.ts +176 -0
- package/dist/dts/dev-mode.d.ts +13 -0
- package/dist/dts/di/di.d.ts +894 -0
- package/dist/dts/dom-policy.d.ts +101 -0
- package/dist/dts/dom.d.ts +88 -0
- package/dist/dts/hydration/diagnostics.d.ts +93 -0
- package/dist/dts/hydration/exports.d.ts +18 -0
- package/dist/dts/hydration/hydration-debugger.d.ts +35 -0
- package/dist/dts/hydration/messages.d.ts +62 -0
- package/dist/dts/hydration/runtime.d.ts +7 -0
- package/dist/dts/hydration/target-builder.d.ts +107 -0
- package/dist/dts/index.d.ts +51 -0
- package/dist/dts/index.debug.d.ts +1 -0
- package/dist/dts/index.rollup.d.ts +1 -0
- package/dist/dts/index.rollup.debug.d.ts +1 -0
- package/dist/dts/interfaces.d.ts +106 -0
- package/dist/dts/metadata.d.ts +26 -0
- package/dist/dts/observation/arrays.d.ts +248 -0
- package/dist/dts/observation/notifier.d.ts +127 -0
- package/dist/dts/observation/observable.d.ts +276 -0
- package/dist/dts/observation/update-queue.d.ts +40 -0
- package/dist/dts/observation/volatile.d.ts +9 -0
- package/dist/dts/platform.d.ts +87 -0
- package/dist/dts/registry.d.ts +2 -0
- package/dist/dts/render.d.ts +7 -0
- package/dist/dts/schema.d.ts +1 -0
- package/dist/dts/scoped/detection.d.ts +55 -0
- package/dist/dts/scoped/index.d.ts +68 -0
- package/dist/dts/scoped/mixin.d.ts +118 -0
- package/dist/dts/scoped/registry.d.ts +76 -0
- package/dist/dts/scoped/types.d.ts +48 -0
- package/dist/dts/state/exports.d.ts +3 -0
- package/dist/dts/state/reactive.d.ts +8 -0
- package/dist/dts/state/state.d.ts +141 -0
- package/dist/dts/state/visitor.d.ts +6 -0
- package/dist/dts/state/watch.d.ts +10 -0
- package/dist/dts/styles/css-directive.d.ts +50 -0
- package/dist/dts/styles/css.d.ts +34 -0
- package/dist/dts/styles/element-styles.d.ts +61 -0
- package/dist/dts/styles/host.d.ts +65 -0
- package/dist/dts/styles/style-strategy.d.ts +42 -0
- package/dist/dts/templating/children.d.ts +62 -0
- package/dist/dts/templating/compiler.d.ts +54 -0
- package/dist/dts/templating/html-binding-directive.d.ts +155 -0
- package/dist/dts/templating/html-directive.d.ts +218 -0
- package/dist/dts/templating/html.d.ts +1 -0
- package/dist/dts/templating/hydration-view.d.ts +130 -0
- package/dist/dts/templating/markup.d.ts +48 -0
- package/dist/dts/templating/node-observation.d.ts +99 -0
- package/dist/dts/templating/ref.d.ts +23 -0
- package/dist/dts/templating/render.d.ts +318 -0
- package/dist/dts/templating/repeat.d.ts +131 -0
- package/dist/dts/templating/slotted.d.ts +38 -0
- package/dist/dts/templating/template.d.ts +221 -0
- package/dist/dts/templating/view.d.ts +268 -0
- package/dist/dts/templating/when.d.ts +12 -0
- package/dist/dts/testing/exports.d.ts +3 -0
- package/dist/dts/testing/fakes.d.ts +16 -0
- package/dist/dts/testing/fixture.d.ts +84 -0
- package/dist/dts/testing/models.d.ts +20 -0
- package/dist/dts/testing/timeout.d.ts +7 -0
- package/dist/dts/trust/index.d.ts +8 -0
- package/dist/dts/trust/types.d.ts +48 -0
- package/dist/dts/trust/utils.d.ts +26 -0
- package/dist/dts/tsdoc-metadata.json +11 -0
- package/dist/dts/utilities.d.ts +76 -0
- package/dist/esm/arrays.js +1 -0
- package/dist/esm/binding/binding.js +18 -0
- package/dist/esm/binding/exports.js +6 -0
- package/dist/esm/binding/normalize.js +17 -0
- package/dist/esm/binding/one-time.js +21 -0
- package/dist/esm/binding/one-way.js +30 -0
- package/dist/esm/binding/signal.js +99 -0
- package/dist/esm/binding/two-way.js +109 -0
- package/dist/esm/command/behavior.js +155 -0
- package/dist/esm/command/behaviors/confirm-behavior.js +225 -0
- package/dist/esm/command/behaviors/undo-behavior.js +313 -0
- package/dist/esm/command/command.js +104 -0
- package/dist/esm/command/component-registry.js +107 -0
- package/dist/esm/command/components/confirm-dialog.js +370 -0
- package/dist/esm/command/index.js +22 -0
- package/dist/esm/command/registry.js +755 -0
- package/dist/esm/command/schema-validation.js +194 -0
- package/dist/esm/command/types.js +86 -0
- package/dist/esm/command/validation.js +508 -0
- package/dist/esm/command/weak-ref.js +5 -0
- package/dist/esm/components/attributes.js +219 -0
- package/dist/esm/components/definition-schema-transforms.js +23 -0
- package/dist/esm/components/element-controller.js +840 -0
- package/dist/esm/components/element-hydration.js +2 -0
- package/dist/esm/components/enable-hydration.js +123 -0
- package/dist/esm/components/fast-definitions.js +294 -0
- package/dist/esm/components/fast-element-registry.js +67 -0
- package/dist/esm/components/fast-element.js +82 -0
- package/dist/esm/components/hydration-tracker.js +211 -0
- package/dist/esm/components/hydration.js +187 -0
- package/dist/esm/components/schema.js +253 -0
- package/dist/esm/context.js +185 -0
- package/dist/esm/debug.js +71 -0
- package/dist/esm/declarative/attribute-map.js +122 -0
- package/dist/esm/declarative/debug.js +4 -0
- package/dist/esm/declarative/index.js +10 -0
- package/dist/esm/declarative/interfaces.js +9 -0
- package/dist/esm/declarative/observer-map-utilities.js +565 -0
- package/dist/esm/declarative/observer-map.js +216 -0
- package/dist/esm/declarative/runtime.js +14 -0
- package/dist/esm/declarative/syntax.js +36 -0
- package/dist/esm/declarative/template-bridge.js +160 -0
- package/dist/esm/declarative/template-parser.js +306 -0
- package/dist/esm/declarative/template.js +116 -0
- package/dist/esm/declarative/utilities.js +1069 -0
- package/dist/esm/definition-schema/api.js +129 -0
- package/dist/esm/definition-schema/cem.js +89 -0
- package/dist/esm/definition-schema/extension.js +53 -0
- package/dist/esm/definition-schema/extract.js +167 -0
- package/dist/esm/definition-schema/index.js +8 -0
- package/dist/esm/definition-schema/storage.js +58 -0
- package/dist/esm/definition-schema/types.js +8 -0
- package/dist/esm/dev-mode.js +22 -0
- package/dist/esm/di/di.js +1441 -0
- package/dist/esm/dom-policy.js +374 -0
- package/dist/esm/dom.js +101 -0
- package/dist/esm/hydration/diagnostics.js +50 -0
- package/dist/esm/hydration/exports.js +6 -0
- package/dist/esm/hydration/hydration-debugger.js +112 -0
- package/dist/esm/hydration/messages.js +84 -0
- package/dist/esm/hydration/runtime.js +33 -0
- package/dist/esm/hydration/target-builder.js +264 -0
- package/dist/esm/index.debug.js +3 -0
- package/dist/esm/index.js +49 -0
- package/dist/esm/index.rollup.debug.js +6 -0
- package/dist/esm/index.rollup.js +4 -0
- package/dist/esm/interfaces.js +55 -0
- package/dist/esm/metadata.js +57 -0
- package/dist/esm/observation/arrays.js +661 -0
- package/dist/esm/observation/notifier.js +162 -0
- package/dist/esm/observation/observable.js +324 -0
- package/dist/esm/observation/update-queue.js +56 -0
- package/dist/esm/observation/volatile.js +16 -0
- package/dist/esm/platform.js +98 -0
- package/dist/esm/registry.js +2 -0
- package/dist/esm/render.js +1 -0
- package/dist/esm/schema.js +1 -0
- package/dist/esm/scoped/detection.js +102 -0
- package/dist/esm/scoped/index.js +69 -0
- package/dist/esm/scoped/mixin.js +127 -0
- package/dist/esm/scoped/registry.js +125 -0
- package/dist/esm/scoped/types.js +4 -0
- package/dist/esm/state/exports.js +3 -0
- package/dist/esm/state/reactive.js +34 -0
- package/dist/esm/state/state.js +148 -0
- package/dist/esm/state/visitor.js +28 -0
- package/dist/esm/state/watch.js +36 -0
- package/dist/esm/styles/css-directive.js +36 -0
- package/dist/esm/styles/css.js +56 -0
- package/dist/esm/styles/element-styles.js +144 -0
- package/dist/esm/styles/host.js +1 -0
- package/dist/esm/styles/style-strategy.js +1 -0
- package/dist/esm/templating/children.js +67 -0
- package/dist/esm/templating/compiler.js +359 -0
- package/dist/esm/templating/html-binding-directive.js +288 -0
- package/dist/esm/templating/html-directive.js +113 -0
- package/dist/esm/templating/html.js +1 -0
- package/dist/esm/templating/hydration-view.js +228 -0
- package/dist/esm/templating/markup.js +83 -0
- package/dist/esm/templating/node-observation.js +84 -0
- package/dist/esm/templating/ref.js +21 -0
- package/dist/esm/templating/render.js +468 -0
- package/dist/esm/templating/repeat.js +434 -0
- package/dist/esm/templating/slotted.js +49 -0
- package/dist/esm/templating/template.js +206 -0
- package/dist/esm/templating/view.js +298 -0
- package/dist/esm/templating/when.js +22 -0
- package/dist/esm/testing/exports.js +3 -0
- package/dist/esm/testing/fakes.js +107 -0
- package/dist/esm/testing/fixture.js +86 -0
- package/dist/esm/testing/models.js +58 -0
- package/dist/esm/testing/timeout.js +24 -0
- package/dist/esm/trust/index.js +7 -0
- package/dist/esm/trust/types.js +6 -0
- package/dist/esm/trust/utils.js +126 -0
- package/dist/esm/utilities.js +140 -0
- package/dist/fast-element.api.json +26798 -0
- package/dist/fast-element.d.ts +3985 -0
- package/dist/fast-element.debug.js +7604 -0
- package/dist/fast-element.debug.min.js +3 -0
- package/dist/fast-element.js +7603 -0
- package/dist/fast-element.min.js +3 -0
- package/dist/fast-element.untrimmed.d.ts +4103 -0
- package/dist/observation/arrays/arrays.api.json +2621 -0
- package/dist/scoped/scoped.api.json +891 -0
- package/dist/trust/trust.api.json +629 -0
- 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
|
+
}
|