@dusted/anqst 0.1.3 → 1.0.1

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/dist/src/app.js CHANGED
@@ -19,7 +19,7 @@ const project_1 = require("./project");
19
19
  const layout_1 = require("./layout");
20
20
  const parser_1 = require("./parser");
21
21
  const verify_1 = require("./verify");
22
- const build_stamp_1 = require("./build-stamp");
22
+ const ANQSTGEN_ACTIVE_STAMP_FILE = ".anqstgen-version-active.json";
23
23
  function renderHelp() {
24
24
  const version = readActiveBuildStamp();
25
25
  return [
@@ -135,8 +135,27 @@ function runTest(cwd) {
135
135
  message: verification.message
136
136
  };
137
137
  }
138
+ function resolveAnQstGenRoot() {
139
+ return node_path_1.default.resolve(__dirname, "..", "..");
140
+ }
138
141
  function readActiveBuildStamp() {
139
- return build_stamp_1.ANQST_BUILD_STAMP;
142
+ if (process.env.ANQST_BUILD_STAMP && process.env.ANQST_BUILD_STAMP.trim().length > 0) {
143
+ return process.env.ANQST_BUILD_STAMP.trim();
144
+ }
145
+ const activePath = node_path_1.default.join(resolveAnQstGenRoot(), ANQSTGEN_ACTIVE_STAMP_FILE);
146
+ if (!node_fs_1.default.existsSync(activePath)) {
147
+ return "unknown_build_0";
148
+ }
149
+ try {
150
+ const parsed = JSON.parse(node_fs_1.default.readFileSync(activePath, "utf8"));
151
+ if (typeof parsed.active === "string" && parsed.active.trim().length > 0) {
152
+ return parsed.active.trim();
153
+ }
154
+ }
155
+ catch {
156
+ // Fallback to deterministic unknown stamp.
157
+ }
158
+ return "unknown_build_0";
140
159
  }
141
160
  function runDesignerPluginBuild(cwd, widgetName) {
142
161
  const layout = (0, layout_1.resolveGeneratedLayoutPaths)(cwd, widgetName);
@@ -176,94 +195,100 @@ function runDesignerPluginBuild(cwd, widgetName) {
176
195
  }
177
196
  function runBuild(cwd, designerPlugin = false) {
178
197
  const buildVersion = readActiveBuildStamp();
179
- const specPath = (0, project_1.resolveAnQstSpecPath)(cwd);
180
- const configuredWidgetName = (0, project_1.resolveAnQstWidgetName)(cwd);
181
- const generationTargets = resolveGenerationTargetsFromCwd(cwd, true);
182
- const parsed = (0, parser_1.parseSpecFile)(specPath);
183
- (0, verify_1.verifySpec)(parsed);
184
- if (parsed.widgetName !== configuredWidgetName) {
185
- throw new errors_1.VerifyError(`Settings widgetName '${configuredWidgetName}' does not match spec namespace '${parsed.widgetName}'.`);
186
- }
187
- resetGeneratedTargets(cwd, parsed.widgetName, generationTargets);
188
- const outputs = (0, emit_1.generateOutputs)(parsed, generationTargets);
189
- (0, emit_1.writeGeneratedOutputs)(cwd, outputs);
190
- if (generationTargets.emitQWidget) {
191
- (0, emit_1.installQtIntegrationCMake)(cwd, parsed.widgetName);
192
- }
193
- const hasAngularProject = generationTargets.emitQWidget && node_fs_1.default.existsSync(node_path_1.default.join(cwd, "angular.json"));
194
- if (hasAngularProject) {
195
- const angularBuild = (0, node_child_process_1.spawnSync)("npx", ["ng", "build", "--configuration", "production"], {
196
- cwd,
197
- stdio: "inherit",
198
- shell: process.platform === "win32"
199
- });
200
- if (angularBuild.status !== 0) {
201
- throw new errors_1.VerifyError("Angular build failed while preparing embedded widget assets.");
198
+ process.env.ANQST_BUILD_STAMP = buildVersion;
199
+ try {
200
+ const specPath = (0, project_1.resolveAnQstSpecPath)(cwd);
201
+ const configuredWidgetName = (0, project_1.resolveAnQstWidgetName)(cwd);
202
+ const generationTargets = resolveGenerationTargetsFromCwd(cwd, true);
203
+ const parsed = (0, parser_1.parseSpecFile)(specPath);
204
+ (0, verify_1.verifySpec)(parsed);
205
+ if (parsed.widgetName !== configuredWidgetName) {
206
+ throw new errors_1.VerifyError(`Settings widgetName '${configuredWidgetName}' does not match spec namespace '${parsed.widgetName}'.`);
202
207
  }
203
- }
204
- if (generationTargets.emitQWidget) {
205
- const embedded = (0, emit_1.installEmbeddedWebBundle)(cwd, parsed.widgetName);
206
- if (hasAngularProject && !embedded) {
207
- throw new errors_1.VerifyError("Unable to embed Angular output. Ensure ng build produced a dist bundle with index.html.");
208
+ resetGeneratedTargets(cwd, parsed.widgetName, generationTargets);
209
+ const outputs = (0, emit_1.generateOutputs)(parsed, generationTargets);
210
+ (0, emit_1.writeGeneratedOutputs)(cwd, outputs);
211
+ if (generationTargets.emitQWidget) {
212
+ (0, emit_1.installQtIntegrationCMake)(cwd, parsed.widgetName);
208
213
  }
209
- }
210
- let designerPluginBuilt = false;
211
- if (designerPlugin) {
212
- if (!generationTargets.emitQWidget) {
213
- console.warn("[AnQst] --designerplugin requested but QWidget target is not enabled. Skipping designer plugin build.");
214
+ const hasAngularProject = generationTargets.emitQWidget && node_fs_1.default.existsSync(node_path_1.default.join(cwd, "angular.json"));
215
+ if (hasAngularProject) {
216
+ const angularBuild = (0, node_child_process_1.spawnSync)("npx", ["ng", "build", "--configuration", "production"], {
217
+ cwd,
218
+ stdio: "inherit",
219
+ shell: process.platform === "win32"
220
+ });
221
+ if (angularBuild.status !== 0) {
222
+ throw new errors_1.VerifyError("Angular build failed while preparing embedded widget assets.");
223
+ }
214
224
  }
215
- else {
216
- const widgetCategory = (0, project_1.resolveAnQstWidgetCategory)(cwd);
217
- (0, emit_1.installQtDesignerPluginCMake)(cwd, parsed.widgetName, { widgetCategory });
218
- runDesignerPluginBuild(cwd, parsed.widgetName);
219
- designerPluginBuilt = true;
225
+ if (generationTargets.emitQWidget) {
226
+ const embedded = (0, emit_1.installEmbeddedWebBundle)(cwd, parsed.widgetName);
227
+ if (hasAngularProject && !embedded) {
228
+ throw new errors_1.VerifyError("Unable to embed Angular output. Ensure ng build produced a dist bundle with index.html.");
229
+ }
230
+ }
231
+ let designerPluginBuilt = false;
232
+ if (designerPlugin) {
233
+ if (!generationTargets.emitQWidget) {
234
+ console.warn("[AnQst] --designerplugin requested but QWidget target is not enabled. Skipping designer plugin build.");
235
+ }
236
+ else {
237
+ const widgetCategory = (0, project_1.resolveAnQstWidgetCategory)(cwd);
238
+ (0, emit_1.installQtDesignerPluginCMake)(cwd, parsed.widgetName, { widgetCategory });
239
+ runDesignerPluginBuild(cwd, parsed.widgetName);
240
+ designerPluginBuilt = true;
241
+ }
242
+ }
243
+ if (!generationTargets.emitAngularService && !generationTargets.emitQWidget && !generationTargets.emitNodeExpressWs) {
244
+ return {
245
+ success: true,
246
+ message: [
247
+ "Build completed.",
248
+ ` anqst version ${buildVersion}`,
249
+ " No outputs selected by AnQst.generate."
250
+ ].join("\n")
251
+ };
252
+ }
253
+ const layout = (0, layout_1.resolveGeneratedLayoutPaths)(cwd, parsed.widgetName);
254
+ const detailLines = [];
255
+ if (generationTargets.emitAngularService) {
256
+ detailLines.push(" Target AngularService:");
257
+ detailLines.push(` - Services output: ${(0, layout_1.toProjectRelative)(cwd, node_path_1.default.join(layout.frontendRoot, "services"))}`);
258
+ detailLines.push(` - Types output: ${(0, layout_1.toProjectRelative)(cwd, node_path_1.default.join(layout.frontendRoot, "types"))}`);
259
+ }
260
+ if (generationTargets.emitQWidget) {
261
+ detailLines.push(" Target QWidget:");
262
+ detailLines.push(` - Qt integration CMake: ${(0, layout_1.toProjectRelative)(cwd, node_path_1.default.join(layout.cppCmakeRoot, "CMakeLists.txt"))}`);
263
+ detailLines.push(` - Widget output root: ${(0, layout_1.toProjectRelative)(cwd, layout.cppQtWidgetRoot)}`);
264
+ detailLines.push(" - Embedded web assets refreshed from Angular build");
265
+ }
266
+ if (generationTargets.emitNodeExpressWs) {
267
+ detailLines.push(" Target node_express_ws:");
268
+ detailLines.push(` - Module output root: ${(0, layout_1.toProjectRelative)(cwd, layout.nodeExpressRoot)}`);
269
+ }
270
+ if (designerPluginBuilt) {
271
+ const pluginBinaryPath = (0, layout_1.normalizeSlashes)(node_path_1.default.join((0, layout_1.toProjectRelative)(cwd, layout.designerPluginBuildRoot), designerPluginBinaryName(parsed.widgetName)));
272
+ detailLines.push(" Target QtDesignerPlugin:");
273
+ detailLines.push(` - Build output: ${(0, layout_1.toProjectRelative)(cwd, layout.designerPluginBuildRoot)}`);
274
+ detailLines.push(` - Plugin binary: ${pluginBinaryPath}`);
275
+ detailLines.push(" - Install target dir: <QT_INSTALL_PLUGINS>/designer");
276
+ detailLines.push(" - Discover QT_INSTALL_PLUGINS: qmake -query QT_INSTALL_PLUGINS");
277
+ detailLines.push(` - Example install: cp ${pluginBinaryPath} \"$(qmake -query QT_INSTALL_PLUGINS)/designer/\"`);
278
+ detailLines.push(` - User-local install: mkdir -p \"$HOME/.local/lib/qt5/plugins/designer\" && cp ${pluginBinaryPath} \"$HOME/.local/lib/qt5/plugins/designer/\"`);
220
279
  }
221
- }
222
- if (!generationTargets.emitAngularService && !generationTargets.emitQWidget && !generationTargets.emitNodeExpressWs) {
223
280
  return {
224
281
  success: true,
225
282
  message: [
226
283
  "Build completed.",
227
284
  ` anqst version ${buildVersion}`,
228
- " No outputs selected by AnQst.generate."
285
+ ...detailLines
229
286
  ].join("\n")
230
287
  };
231
288
  }
232
- const layout = (0, layout_1.resolveGeneratedLayoutPaths)(cwd, parsed.widgetName);
233
- const detailLines = [];
234
- if (generationTargets.emitAngularService) {
235
- detailLines.push(" Target AngularService:");
236
- detailLines.push(` - Services output: ${(0, layout_1.toProjectRelative)(cwd, node_path_1.default.join(layout.frontendRoot, "services"))}`);
237
- detailLines.push(` - Types output: ${(0, layout_1.toProjectRelative)(cwd, node_path_1.default.join(layout.frontendRoot, "types"))}`);
238
- }
239
- if (generationTargets.emitQWidget) {
240
- detailLines.push(" Target QWidget:");
241
- detailLines.push(` - Qt integration CMake: ${(0, layout_1.toProjectRelative)(cwd, node_path_1.default.join(layout.cppCmakeRoot, "CMakeLists.txt"))}`);
242
- detailLines.push(` - Widget output root: ${(0, layout_1.toProjectRelative)(cwd, layout.cppQtWidgetRoot)}`);
243
- detailLines.push(" - Embedded web assets refreshed from Angular build");
244
- }
245
- if (generationTargets.emitNodeExpressWs) {
246
- detailLines.push(" Target node_express_ws:");
247
- detailLines.push(` - Module output root: ${(0, layout_1.toProjectRelative)(cwd, layout.nodeExpressRoot)}`);
248
- }
249
- if (designerPluginBuilt) {
250
- const pluginBinaryPath = (0, layout_1.normalizeSlashes)(node_path_1.default.join((0, layout_1.toProjectRelative)(cwd, layout.designerPluginBuildRoot), designerPluginBinaryName(parsed.widgetName)));
251
- detailLines.push(" Target QtDesignerPlugin:");
252
- detailLines.push(` - Build output: ${(0, layout_1.toProjectRelative)(cwd, layout.designerPluginBuildRoot)}`);
253
- detailLines.push(` - Plugin binary: ${pluginBinaryPath}`);
254
- detailLines.push(" - Install target dir: <QT_INSTALL_PLUGINS>/designer");
255
- detailLines.push(" - Discover QT_INSTALL_PLUGINS: qmake -query QT_INSTALL_PLUGINS");
256
- detailLines.push(` - Example install: cp ${pluginBinaryPath} \"$(qmake -query QT_INSTALL_PLUGINS)/designer/\"`);
257
- detailLines.push(` - User-local install: mkdir -p \"$HOME/.local/lib/qt5/plugins/designer\" && cp ${pluginBinaryPath} \"$HOME/.local/lib/qt5/plugins/designer/\"`);
289
+ finally {
290
+ delete process.env.ANQST_BUILD_STAMP;
258
291
  }
259
- return {
260
- success: true,
261
- message: [
262
- "Build completed.",
263
- ` anqst version ${buildVersion}`,
264
- ...detailLines
265
- ].join("\n")
266
- };
267
292
  }
268
293
  function parseSpecCommandArg(commandName, specArg, extraArgs) {
269
294
  const allArgs = [specArg, ...extraArgs].filter((arg) => typeof arg === "string" && arg.length > 0);
@@ -2,4 +2,4 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ANQST_BUILD_STAMP = void 0;
4
4
  // Generated by scripts/build-with-stamp.js. Do not edit by hand.
5
- exports.ANQST_BUILD_STAMP = "0.1.3/da5d2ee_clean_build_1/2026-03-11";
5
+ exports.ANQST_BUILD_STAMP = "unknown/unknown_build_0/1970-01-01";
package/dist/src/emit.js CHANGED
@@ -12,7 +12,6 @@ const node_fs_1 = __importDefault(require("node:fs"));
12
12
  const node_path_1 = __importDefault(require("node:path"));
13
13
  const typescript_1 = __importDefault(require("typescript"));
14
14
  const pngjs_1 = require("pngjs");
15
- const build_stamp_1 = require("./build-stamp");
16
15
  const layout_1 = require("./layout");
17
16
  function stripAnQstType(typeText) {
18
17
  return typeText
@@ -627,6 +626,27 @@ function buildCppTypeContext(spec) {
627
626
  }
628
627
  return normalizer.buildContext();
629
628
  }
629
+ function collectDragDropMimeConstants(spec) {
630
+ const seen = new Set();
631
+ const constants = [];
632
+ for (const service of spec.services) {
633
+ for (const member of service.members) {
634
+ if ((member.kind === "DropTarget" || member.kind === "HoverTarget") && member.payloadTypeText) {
635
+ const typeName = member.payloadTypeText.replace(/\s/g, "");
636
+ const key = `${service.name}-${typeName}`;
637
+ if (seen.has(key))
638
+ continue;
639
+ seen.add(key);
640
+ constants.push({
641
+ typeName,
642
+ serviceName: service.name,
643
+ mimeType: `application/anqst-dragdropevent_${service.name}-${typeName}`
644
+ });
645
+ }
646
+ }
647
+ }
648
+ return constants;
649
+ }
630
650
  function renderTypesHeader(spec, cppTypes) {
631
651
  const decls = cppTypes.orderedDecls.map(renderCppDecl).join("\n\n");
632
652
  const metatypes = cppTypes.structNames
@@ -635,6 +655,11 @@ function renderTypesHeader(spec, cppTypes) {
635
655
  `Q_DECLARE_METATYPE(QList<${spec.widgetName}::${name}>)`
636
656
  ])
637
657
  .join("\n");
658
+ const mimeConstants = collectDragDropMimeConstants(spec);
659
+ const mimeConstantLines = mimeConstants
660
+ .map((c) => `static constexpr const char* kDragDropMime_${c.typeName} = "${c.mimeType}";`)
661
+ .join("\n");
662
+ const mimeBlock = mimeConstantLines.length > 0 ? `\n${mimeConstantLines}\n` : "";
638
663
  return `#pragma once
639
664
  #include <QString>
640
665
  #include <QStringList>
@@ -647,7 +672,7 @@ function renderTypesHeader(spec, cppTypes) {
647
672
  namespace ${spec.widgetName} {
648
673
 
649
674
  ${decls}
650
-
675
+ ${mimeBlock}
651
676
  } // namespace ${spec.widgetName}
652
677
 
653
678
  ${metatypes}
@@ -710,6 +735,15 @@ function renderWidgetHeader(spec, cppTypes) {
710
735
  publicSlots.push(`void ${member.name}Slot(const ${cppType}& value);`);
711
736
  }
712
737
  }
738
+ else if (member.kind === "DropTarget" && member.payloadTypeText) {
739
+ const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
740
+ signals.push(`void ${member.name}(const ${cppType}& payload, double x, double y);`);
741
+ }
742
+ else if (member.kind === "HoverTarget" && member.payloadTypeText) {
743
+ const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
744
+ signals.push(`void ${member.name}(const ${cppType}& payload, double x, double y);`);
745
+ signals.push(`void ${member.name}Left();`);
746
+ }
713
747
  }
714
748
  }
715
749
  return `#pragma once
@@ -860,6 +894,45 @@ function renderCppStub(spec, cppTypes) {
860
894
  lines.push(` Q_UNUSED(kResourcesInitialized);`);
861
895
  lines.push(` registerGeneratedMetaTypes();`);
862
896
  lines.push(` installBridgeBindings();`);
897
+ for (const service of spec.services) {
898
+ for (const member of service.members) {
899
+ if (member.kind === "DropTarget" && member.payloadTypeText) {
900
+ const typeName = member.payloadTypeText.replace(/\s/g, "");
901
+ const mimeConst = `${spec.widgetName}::kDragDropMime_${typeName}`;
902
+ lines.push(` registerDropTarget(QStringLiteral("${service.name}"), QStringLiteral("${member.name}"), QString::fromUtf8(${mimeConst}));`);
903
+ }
904
+ else if (member.kind === "HoverTarget" && member.payloadTypeText) {
905
+ const typeName = member.payloadTypeText.replace(/\s/g, "");
906
+ const mimeConst = `${spec.widgetName}::kDragDropMime_${typeName}`;
907
+ lines.push(` registerHoverTarget(QStringLiteral("${service.name}"), QStringLiteral("${member.name}"), QString::fromUtf8(${mimeConst}), ${member.hoverThrottleMs});`);
908
+ }
909
+ }
910
+ }
911
+ for (const service of spec.services) {
912
+ for (const member of service.members) {
913
+ if (member.kind === "DropTarget" && member.payloadTypeText) {
914
+ const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
915
+ lines.push(` QObject::connect(this, &AnQstWebHostBase::anQstBridge_dropReceived, this, [this](const QString& service, const QString& member, const QVariant& payload, double x, double y) {`);
916
+ lines.push(` if (service == QStringLiteral("${service.name}") && member == QStringLiteral("${member.name}")) {`);
917
+ lines.push(` emit ${member.name}(payload.value<${cppType}>(), x, y);`);
918
+ lines.push(` }`);
919
+ lines.push(` });`);
920
+ }
921
+ else if (member.kind === "HoverTarget" && member.payloadTypeText) {
922
+ const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
923
+ lines.push(` QObject::connect(this, &AnQstWebHostBase::anQstBridge_hoverUpdated, this, [this](const QString& service, const QString& member, const QVariant& payload, double x, double y) {`);
924
+ lines.push(` if (service == QStringLiteral("${service.name}") && member == QStringLiteral("${member.name}")) {`);
925
+ lines.push(` emit ${member.name}(payload.value<${cppType}>(), x, y);`);
926
+ lines.push(` }`);
927
+ lines.push(` });`);
928
+ lines.push(` QObject::connect(this, &AnQstWebHostBase::anQstBridge_hoverLeft, this, [this](const QString& service, const QString& member) {`);
929
+ lines.push(` if (service == QStringLiteral("${service.name}") && member == QStringLiteral("${member.name}")) {`);
930
+ lines.push(` emit ${member.name}Left();`);
931
+ lines.push(` }`);
932
+ lines.push(` });`);
933
+ }
934
+ }
935
+ }
863
936
  lines.push(` QObject::connect(this, &AnQstWebHostBase::onHostError, this, &${widgetClassName}::diagnosticsForwarded);`);
864
937
  lines.push(` const bool rootOk = setContentRoot(QString::fromUtf8(kBootstrapContentRoot));`);
865
938
  lines.push(` const bool bridgeOk = setBridgeObject(this, QString::fromUtf8(kBootstrapBridgeObject));`);
@@ -1172,7 +1245,24 @@ function normalizeSlashes(value) {
1172
1245
  return value.split(node_path_1.default.sep).join("/");
1173
1246
  }
1174
1247
  function resolveActiveBuildStamp() {
1175
- return build_stamp_1.ANQST_BUILD_STAMP.trim();
1248
+ const fromEnv = process.env.ANQST_BUILD_STAMP?.trim();
1249
+ if (fromEnv && fromEnv.length > 0) {
1250
+ return fromEnv;
1251
+ }
1252
+ const activePath = node_path_1.default.resolve(__dirname, "..", "..", ".anqstgen-version-active.json");
1253
+ if (!node_fs_1.default.existsSync(activePath)) {
1254
+ return "";
1255
+ }
1256
+ try {
1257
+ const parsed = JSON.parse(node_fs_1.default.readFileSync(activePath, "utf8"));
1258
+ if (typeof parsed.active === "string" && parsed.active.trim().length > 0) {
1259
+ return parsed.active.trim();
1260
+ }
1261
+ }
1262
+ catch {
1263
+ return "";
1264
+ }
1265
+ return "";
1176
1266
  }
1177
1267
  function withBuildStamp(relativePath, content) {
1178
1268
  const stamp = resolveActiveBuildStamp();
@@ -1315,6 +1405,19 @@ function renderTsService(spec, serviceName) {
1315
1405
  constructorBodyLines.push(` this._bridge.onOutput("${serviceName}", "${m.name}", (value) => this._${m.name}.set(value as ${tsType}));`);
1316
1406
  }
1317
1407
  }
1408
+ if (m.kind === "DropTarget" && m.payloadTypeText) {
1409
+ const tsType = mapTypeTextToTs(m.payloadTypeText);
1410
+ fieldLines.push(` private readonly _${m.name} = signal<{ payload: ${tsType}; x: number; y: number } | null>(null);`);
1411
+ methodLines.push(` ${m.name}(): { payload: ${tsType}; x: number; y: number } | null { return this._${m.name}(); }`);
1412
+ constructorBodyLines.push(` this._bridge.onDrop("${serviceName}", "${m.name}", (payload, x, y) => this._${m.name}.set({ payload: payload as ${tsType}, x, y }));`);
1413
+ }
1414
+ if (m.kind === "HoverTarget" && m.payloadTypeText) {
1415
+ const tsType = mapTypeTextToTs(m.payloadTypeText);
1416
+ fieldLines.push(` private readonly _${m.name} = signal<{ payload: ${tsType}; x: number; y: number } | null>(null);`);
1417
+ methodLines.push(` ${m.name}(): { payload: ${tsType}; x: number; y: number } | null { return this._${m.name}(); }`);
1418
+ constructorBodyLines.push(` this._bridge.onHover("${serviceName}", "${m.name}", (payload, x, y) => this._${m.name}.set({ payload: payload as ${tsType}, x, y }));`);
1419
+ constructorBodyLines.push(` this._bridge.onHoverLeft("${serviceName}", "${m.name}", () => this._${m.name}.set(null));`);
1420
+ }
1318
1421
  }
1319
1422
  const constructorLines = [
1320
1423
  " constructor() {",
@@ -1366,6 +1469,14 @@ function renderTsServiceDts(spec, serviceName) {
1366
1469
  setMembers.push(` ${m.name}(value: ${tsType}): void;`);
1367
1470
  }
1368
1471
  }
1472
+ if (m.kind === "DropTarget" && m.payloadTypeText) {
1473
+ const tsType = mapTypeTextToTs(m.payloadTypeText);
1474
+ classMembers.push(` ${m.name}(): { payload: ${tsType}; x: number; y: number } | null;`);
1475
+ }
1476
+ if (m.kind === "HoverTarget" && m.payloadTypeText) {
1477
+ const tsType = mapTypeTextToTs(m.payloadTypeText);
1478
+ classMembers.push(` ${m.name}(): { payload: ${tsType}; x: number; y: number } | null;`);
1479
+ }
1369
1480
  }
1370
1481
  const setInterfaceDecl = setMembers.length > 0
1371
1482
  ? `export interface ${setInterfaceName} {\n${setMembers.join("\n")}\n}`
@@ -1396,6 +1507,9 @@ type SlotHandler = (...args: unknown[]) => unknown;
1396
1507
  type OutputHandler = (value: unknown) => void;
1397
1508
  type SlotInvocationListener = (requestId: string, service: string, member: string, args: unknown[]) => void;
1398
1509
  type OutputListener = (service: string, member: string, value: unknown) => void;
1510
+ type DropListener = (service: string, member: string, payload: unknown, x: number, y: number) => void;
1511
+ type HoverListener = (service: string, member: string, payload: unknown, x: number, y: number) => void;
1512
+ type HoverLeftListener = (service: string, member: string) => void;
1399
1513
 
1400
1514
  interface HostBridgeApi {
1401
1515
  anQstBridge_call(service: string, member: string, args: unknown[], callback: (result: unknown) => void): void;
@@ -1407,6 +1521,9 @@ interface HostBridgeApi {
1407
1521
  anQstBridge_slotInvocationRequested: {
1408
1522
  connect: (cb: (requestId: string, service: string, member: string, args: unknown[]) => void) => void;
1409
1523
  };
1524
+ anQstBridge_dropReceived: { connect: (cb: (service: string, member: string, payload: unknown, x: number, y: number) => void) => void };
1525
+ anQstBridge_hoverUpdated: { connect: (cb: (service: string, member: string, payload: unknown, x: number, y: number) => void) => void };
1526
+ anQstBridge_hoverLeft: { connect: (cb: (service: string, member: string) => void) => void };
1410
1527
  }
1411
1528
 
1412
1529
  interface QWebChannelCtor {
@@ -1424,6 +1541,9 @@ interface BridgeAdapter {
1424
1541
  resolveSlot(requestId: string, ok: boolean, payload: unknown, error: string): void;
1425
1542
  onOutput(handler: OutputListener): void;
1426
1543
  onSlotInvocation(handler: SlotInvocationListener): void;
1544
+ onDrop(handler: DropListener): void;
1545
+ onHover(handler: HoverListener): void;
1546
+ onHoverLeft(handler: HoverLeftListener): void;
1427
1547
  }
1428
1548
 
1429
1549
  function isBridgeCallError(value: unknown): value is {
@@ -1511,6 +1631,18 @@ class QtWebChannelAdapter implements BridgeAdapter {
1511
1631
  onSlotInvocation(handler: SlotInvocationListener): void {
1512
1632
  this.host.anQstBridge_slotInvocationRequested.connect(handler);
1513
1633
  }
1634
+
1635
+ onDrop(handler: DropListener): void {
1636
+ this.host.anQstBridge_dropReceived.connect(handler);
1637
+ }
1638
+
1639
+ onHover(handler: HoverListener): void {
1640
+ this.host.anQstBridge_hoverUpdated.connect(handler);
1641
+ }
1642
+
1643
+ onHoverLeft(handler: HoverLeftListener): void {
1644
+ this.host.anQstBridge_hoverLeft.connect(handler);
1645
+ }
1514
1646
  }
1515
1647
 
1516
1648
  class WebSocketBridgeAdapter implements BridgeAdapter {
@@ -1523,6 +1655,9 @@ class WebSocketBridgeAdapter implements BridgeAdapter {
1523
1655
  }>();
1524
1656
  private readonly outputListeners: OutputListener[] = [];
1525
1657
  private readonly slotListeners: SlotInvocationListener[] = [];
1658
+ private readonly dropListeners: DropListener[] = [];
1659
+ private readonly hoverListeners: HoverListener[] = [];
1660
+ private readonly hoverLeftListeners: HoverLeftListener[] = [];
1526
1661
  private requestCounter = 0;
1527
1662
 
1528
1663
  private constructor(private readonly socket: WebSocket) {
@@ -1562,6 +1697,34 @@ class WebSocketBridgeAdapter implements BridgeAdapter {
1562
1697
  }
1563
1698
  return;
1564
1699
  }
1700
+ if (type === "dropReceived") {
1701
+ const service = String(message["service"] ?? "");
1702
+ const member = String(message["member"] ?? "");
1703
+ const x = Number(message["x"] ?? 0);
1704
+ const y = Number(message["y"] ?? 0);
1705
+ for (const listener of this.dropListeners) {
1706
+ listener(service, member, message["payload"], x, y);
1707
+ }
1708
+ return;
1709
+ }
1710
+ if (type === "hoverUpdated") {
1711
+ const service = String(message["service"] ?? "");
1712
+ const member = String(message["member"] ?? "");
1713
+ const x = Number(message["x"] ?? 0);
1714
+ const y = Number(message["y"] ?? 0);
1715
+ for (const listener of this.hoverListeners) {
1716
+ listener(service, member, message["payload"], x, y);
1717
+ }
1718
+ return;
1719
+ }
1720
+ if (type === "hoverLeft") {
1721
+ const service = String(message["service"] ?? "");
1722
+ const member = String(message["member"] ?? "");
1723
+ for (const listener of this.hoverLeftListeners) {
1724
+ listener(service, member);
1725
+ }
1726
+ return;
1727
+ }
1565
1728
  if (type === "hostError") {
1566
1729
  console.error("AnQst host error:", message["payload"]);
1567
1730
  return;
@@ -1649,6 +1812,18 @@ class WebSocketBridgeAdapter implements BridgeAdapter {
1649
1812
  onSlotInvocation(handler: SlotInvocationListener): void {
1650
1813
  this.slotListeners.push(handler);
1651
1814
  }
1815
+
1816
+ onDrop(handler: DropListener): void {
1817
+ this.dropListeners.push(handler);
1818
+ }
1819
+
1820
+ onHover(handler: HoverListener): void {
1821
+ this.hoverListeners.push(handler);
1822
+ }
1823
+
1824
+ onHoverLeft(handler: HoverLeftListener): void {
1825
+ this.hoverLeftListeners.push(handler);
1826
+ }
1652
1827
  }
1653
1828
 
1654
1829
  @Injectable({ providedIn: "root" })
@@ -1656,6 +1831,9 @@ class AnQstBridgeRuntime {
1656
1831
  private adapter: BridgeAdapter | null = null;
1657
1832
  private readonly slotHandlers = new Map<string, SlotHandler>();
1658
1833
  private readonly outputHandlers = new Map<string, OutputHandler[]>();
1834
+ private readonly dropHandlers = new Map<string, ((payload: unknown, x: number, y: number) => void)[]>();
1835
+ private readonly hoverHandlers = new Map<string, ((payload: unknown, x: number, y: number) => void)[]>();
1836
+ private readonly hoverLeftHandlers = new Map<string, (() => void)[]>();
1659
1837
  private readonly startup = this.init();
1660
1838
 
1661
1839
  async ready(): Promise<void> {
@@ -1706,6 +1884,27 @@ class AnQstBridgeRuntime {
1706
1884
  this.outputHandlers.set(key, existing);
1707
1885
  }
1708
1886
 
1887
+ onDrop(service: string, member: string, handler: (payload: unknown, x: number, y: number) => void): void {
1888
+ const key = this.key(service, member);
1889
+ const existing = this.dropHandlers.get(key) ?? [];
1890
+ existing.push(handler);
1891
+ this.dropHandlers.set(key, existing);
1892
+ }
1893
+
1894
+ onHover(service: string, member: string, handler: (payload: unknown, x: number, y: number) => void): void {
1895
+ const key = this.key(service, member);
1896
+ const existing = this.hoverHandlers.get(key) ?? [];
1897
+ existing.push(handler);
1898
+ this.hoverHandlers.set(key, existing);
1899
+ }
1900
+
1901
+ onHoverLeft(service: string, member: string, handler: () => void): void {
1902
+ const key = this.key(service, member);
1903
+ const existing = this.hoverLeftHandlers.get(key) ?? [];
1904
+ existing.push(handler);
1905
+ this.hoverLeftHandlers.set(key, existing);
1906
+ }
1907
+
1709
1908
  private requireAdapterSync(): BridgeAdapter {
1710
1909
  if (this.adapter === null) {
1711
1910
  throw new Error("AnQst bridge is not ready.");
@@ -1751,6 +1950,24 @@ class AnQstBridgeRuntime {
1751
1950
  this.adapter!.resolveSlot(requestId, false, undefined, message);
1752
1951
  }
1753
1952
  });
1953
+ this.adapter.onDrop((service, member, payload, x, y) => {
1954
+ const key = this.key(service, member);
1955
+ for (const handler of this.dropHandlers.get(key) ?? []) {
1956
+ handler(payload, x, y);
1957
+ }
1958
+ });
1959
+ this.adapter.onHover((service, member, payload, x, y) => {
1960
+ const key = this.key(service, member);
1961
+ for (const handler of this.hoverHandlers.get(key) ?? []) {
1962
+ handler(payload, x, y);
1963
+ }
1964
+ });
1965
+ this.adapter.onHoverLeft((service, member) => {
1966
+ const key = this.key(service, member);
1967
+ for (const handler of this.hoverLeftHandlers.get(key) ?? []) {
1968
+ handler();
1969
+ }
1970
+ });
1754
1971
  for (const key of this.slotHandlers.keys()) {
1755
1972
  const parts = key.split("::");
1756
1973
  if (parts.length === 2) {
@@ -2790,6 +3007,7 @@ set(CMAKE_AUTOUIC ON)
2790
3007
  set(CMAKE_AUTORCC ON)
2791
3008
 
2792
3009
  find_program(ANQST_NPM_EXECUTABLE npm REQUIRED)
3010
+ find_program(ANQST_NPX_EXECUTABLE npx REQUIRED)
2793
3011
 
2794
3012
  add_custom_command(
2795
3013
  OUTPUT
@@ -2801,7 +3019,7 @@ add_custom_command(
2801
3019
  "\${${generatedIncludeVar}}/${widgetName}Types.h"
2802
3020
  "\${${generatedRootVar}}/webapp/index.html"
2803
3021
  COMMAND "\${ANQST_NPM_EXECUTABLE}" install
2804
- COMMAND "\${ANQST_NPM_EXECUTABLE}" run anqst:build
3022
+ COMMAND "\${ANQST_NPX_EXECUTABLE}" anqst build
2805
3023
  WORKING_DIRECTORY "\${${projectRootVar}}"
2806
3024
  COMMENT "Generating AnQst widget library (${widgetTarget}) from Angular project"
2807
3025
  VERBATIM
@@ -3029,7 +3247,7 @@ function renderQtDesignerPluginCpp(widgetName, widgetCategory, hasIcon) {
3029
3247
  #include <QObject>
3030
3248
  #include <QString>
3031
3249
  #include <QWidget>
3032
- #include "include/${widgetName}.h"
3250
+ #include "${widgetName}.h"
3033
3251
 
3034
3252
  class ${pluginClass} final : public QObject, public QDesignerCustomWidgetInterface {
3035
3253
  Q_OBJECT
@@ -3045,9 +3263,10 @@ public:
3045
3263
  QString toolTip() const override { return QStringLiteral("${widgetName} generated by AnQst."); }
3046
3264
  QString whatsThis() const override { return QStringLiteral("${widgetName} generated by AnQst."); }
3047
3265
  bool isContainer() const override { return false; }
3048
- QString includeFile() const override { return QStringLiteral("include/${widgetName}.h"); }
3266
+ QString includeFile() const override { return QStringLiteral("${widgetName}.h"); }
3049
3267
  QWidget* createWidget(QWidget* parent) override {
3050
3268
  auto* widget = new ${widgetClass}(parent);
3269
+ widget->setMinimumHeight(128);
3051
3270
  widget->setProperty("anqstDesignerContext", true);
3052
3271
  return widget;
3053
3272
  }
@@ -3058,6 +3277,12 @@ public:
3058
3277
  return QStringLiteral(
3059
3278
  "<ui language=\\"c++\\">\\n"
3060
3279
  " <widget class=\\"${widgetClass}\\" name=\\"${widgetName.toLowerCase()}\\">\\n"
3280
+ " <property name=\\"minimumSize\\">\\n"
3281
+ " <size>\\n"
3282
+ " <width>0</width>\\n"
3283
+ " <height>128</height>\\n"
3284
+ " </size>\\n"
3285
+ " </property>\\n"
3061
3286
  " </widget>\\n"
3062
3287
  "</ui>\\n");
3063
3288
  }