@deneb-ui/cli 2.0.50 → 2.0.51
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/package.json +2 -2
- package/src/arc/__tests__/arc.test.cjs +125 -1
- package/src/arc/adapters.cjs +19 -0
- package/src/arc/field-paths.cjs +4 -0
- package/src/arc/index.cjs +2 -2
- package/src/arc/learning.cjs +1 -0
- package/src/arc/planner.cjs +8 -5
- package/src/arc/semantic.cjs +39 -4
- package/src/arc/style-candidates.cjs +2 -1
- package/src/arc/transformer.cjs +22 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deneb-ui/cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.51",
|
|
4
4
|
"description": "Official DENEB CLI — scaffold, convert, validate, and package Fivora-ready storefront templates.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"deneb": "bin/index.js",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"license": "MIT",
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@babel/parser": "^7.28.0",
|
|
52
|
-
"@deneb-ui/core": "^2.0.
|
|
52
|
+
"@deneb-ui/core": "^2.0.51",
|
|
53
53
|
"@octokit/rest": "^22.0.1",
|
|
54
54
|
"adm-zip": "^0.6.0",
|
|
55
55
|
"dotenv": "^17.4.2",
|
|
@@ -729,7 +729,12 @@ test('classifyActionIntent identifies action keywords and assigns correct fallba
|
|
|
729
729
|
assert.equal(shop?.defaultUrl, '/shop');
|
|
730
730
|
assert.equal(shop?.external, false);
|
|
731
731
|
|
|
732
|
-
const
|
|
732
|
+
const formSubmit = classifyActionIntent('Submit Form', null);
|
|
733
|
+
assert.equal(formSubmit?.action, 'form-submit');
|
|
734
|
+
assert.equal(formSubmit?.defaultUrl, 'https://wa.me/1234567890');
|
|
735
|
+
assert.equal(formSubmit?.external, true);
|
|
736
|
+
|
|
737
|
+
const nonAction = classifyActionIntent('Random Content Text', null);
|
|
733
738
|
assert.equal(nonAction, null);
|
|
734
739
|
});
|
|
735
740
|
|
|
@@ -1215,3 +1220,122 @@ export function CartDrawer() {
|
|
|
1215
1220
|
assert.match(splitAction.urlField, /whatsapp/i);
|
|
1216
1221
|
});
|
|
1217
1222
|
|
|
1223
|
+
test('classifyActionIntent recognizes form submit action keywords', () => {
|
|
1224
|
+
const submit = classifyActionIntent('Submit', null);
|
|
1225
|
+
assert.equal(submit?.action, 'form-submit');
|
|
1226
|
+
assert.equal(submit?.defaultUrl, 'https://wa.me/1234567890');
|
|
1227
|
+
|
|
1228
|
+
const sendMessage = classifyActionIntent('Send Message', null);
|
|
1229
|
+
assert.equal(sendMessage?.action, 'form-submit');
|
|
1230
|
+
|
|
1231
|
+
const confirmBooking = classifyActionIntent('Confirm Booking', null);
|
|
1232
|
+
assert.equal(confirmBooking?.action, 'form-submit');
|
|
1233
|
+
|
|
1234
|
+
const getQuote = classifyActionIntent('Get Quote', null);
|
|
1235
|
+
assert.equal(getQuote?.action, 'form-submit');
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
test('semantic engine recognizes form inputs, labels, placeholders, and form-submit-action buttons', () => {
|
|
1239
|
+
const code = `
|
|
1240
|
+
export function BookingForm() {
|
|
1241
|
+
return (
|
|
1242
|
+
<form className="booking-form">
|
|
1243
|
+
<h2>Book Appointment</h2>
|
|
1244
|
+
<label>Full Name</label>
|
|
1245
|
+
<input type="text" placeholder="Enter your full name" />
|
|
1246
|
+
<label>Email Address</label>
|
|
1247
|
+
<input type="email" placeholder="you@example.com" />
|
|
1248
|
+
<label>Special Instructions</label>
|
|
1249
|
+
<textarea placeholder="Describe your request..." />
|
|
1250
|
+
<button type="submit">Confirm Booking</button>
|
|
1251
|
+
</form>
|
|
1252
|
+
);
|
|
1253
|
+
}
|
|
1254
|
+
`;
|
|
1255
|
+
const profile = {
|
|
1256
|
+
root: os.tmpdir(),
|
|
1257
|
+
framework: 'nextjs',
|
|
1258
|
+
router: 'next-app',
|
|
1259
|
+
language: 'typescript',
|
|
1260
|
+
cssSystems: ['tailwind'],
|
|
1261
|
+
hasSrc: true,
|
|
1262
|
+
};
|
|
1263
|
+
|
|
1264
|
+
const analysis = analyzeFile({
|
|
1265
|
+
code,
|
|
1266
|
+
relativeFile: 'src/components/BookingForm.tsx',
|
|
1267
|
+
profile,
|
|
1268
|
+
graph: { sharedFiles: [] },
|
|
1269
|
+
ownerScope: 'home',
|
|
1270
|
+
componentMeta: { name: 'BookingForm', role: 'form' },
|
|
1271
|
+
});
|
|
1272
|
+
|
|
1273
|
+
const placeholders = analysis.candidates.filter((c) => c.kind === 'placeholder');
|
|
1274
|
+
assert.equal(placeholders.length, 3, 'expected 3 placeholder candidates');
|
|
1275
|
+
|
|
1276
|
+
const labels = analysis.candidates.filter((c) => c.kind === 'text' && c.tag === 'label');
|
|
1277
|
+
assert.equal(labels.length, 3, 'expected 3 label candidates');
|
|
1278
|
+
|
|
1279
|
+
const submitAction = analysis.candidates.find((c) => c.kind === 'form-submit-action');
|
|
1280
|
+
assert.ok(submitAction, 'expected form-submit-action candidate');
|
|
1281
|
+
assert.equal(submitAction.label, 'Confirm Booking');
|
|
1282
|
+
assert.equal(submitAction.extra.action, 'form-submit');
|
|
1283
|
+
assert.equal(submitAction.extra.insideForm, true);
|
|
1284
|
+
});
|
|
1285
|
+
|
|
1286
|
+
test('planner and transformer make entire form editable with submit button and WhatsApp URL binding', () => {
|
|
1287
|
+
const code = `
|
|
1288
|
+
import React from 'react';
|
|
1289
|
+
|
|
1290
|
+
export function ContactSection() {
|
|
1291
|
+
return (
|
|
1292
|
+
<form className="contact-form">
|
|
1293
|
+
<label>Your Name</label>
|
|
1294
|
+
<input type="text" placeholder="John Doe" />
|
|
1295
|
+
<button type="submit">Send Message</button>
|
|
1296
|
+
</form>
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
1299
|
+
`;
|
|
1300
|
+
const profile = {
|
|
1301
|
+
root: os.tmpdir(),
|
|
1302
|
+
framework: 'nextjs',
|
|
1303
|
+
router: 'next-app',
|
|
1304
|
+
language: 'typescript',
|
|
1305
|
+
cssSystems: ['tailwind'],
|
|
1306
|
+
hasSrc: true,
|
|
1307
|
+
aliasMap: { '@/*': ['src/*'] },
|
|
1308
|
+
};
|
|
1309
|
+
|
|
1310
|
+
const analysis = analyzeFile({
|
|
1311
|
+
code,
|
|
1312
|
+
relativeFile: 'src/components/ContactSection.tsx',
|
|
1313
|
+
profile,
|
|
1314
|
+
graph: { sharedFiles: [] },
|
|
1315
|
+
ownerScope: 'home',
|
|
1316
|
+
componentMeta: { name: 'ContactSection', role: 'contact' },
|
|
1317
|
+
});
|
|
1318
|
+
analysis.code = code;
|
|
1319
|
+
analysis.relativeFile = 'src/components/ContactSection.tsx';
|
|
1320
|
+
|
|
1321
|
+
const plan = planTransformations({
|
|
1322
|
+
profile,
|
|
1323
|
+
analyses: [analysis],
|
|
1324
|
+
});
|
|
1325
|
+
|
|
1326
|
+
const filePlan = plan.files[0];
|
|
1327
|
+
const formSubmit = filePlan.transformations.find((t) => t.operation === 'form-submit-action');
|
|
1328
|
+
assert.ok(formSubmit, 'form-submit-action must be planned');
|
|
1329
|
+
assert.match(formSubmit.urlField, /formWhatsappUrl/i);
|
|
1330
|
+
assert.match(formSubmit.labelField, /formSubmitLabel/i);
|
|
1331
|
+
|
|
1332
|
+
const transformed = applyFilePlan(filePlan, profile);
|
|
1333
|
+
assert.ok(transformed.changed, 'file must be changed');
|
|
1334
|
+
assert.match(transformed.code, /data-preview-field-path/);
|
|
1335
|
+
assert.match(transformed.code, /formSubmitLabel/);
|
|
1336
|
+
assert.match(transformed.code, /formWhatsappUrl/);
|
|
1337
|
+
assert.match(transformed.code, /type="submit"/);
|
|
1338
|
+
assert.match(transformed.code, /hidden/);
|
|
1339
|
+
});
|
|
1340
|
+
|
|
1341
|
+
|
package/src/arc/adapters.cjs
CHANGED
|
@@ -23,6 +23,8 @@ const HEADING_TAGS = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'Heading', 'Ti
|
|
|
23
23
|
const TEXT_TAGS = new Set(['p', 'span', 'li', 'blockquote', 'figcaption', 'label', 'CardDescription', 'Description', 'Subtitle', 'Typography', 'Text', 'Badge', 'badge']);
|
|
24
24
|
const ACTION_TAGS = new Set(['a', 'Link', 'NavLink', 'Button', 'button', 'IconButton', 'NavbarBrand']);
|
|
25
25
|
const IMAGE_TAGS = new Set(['img', 'Image', 'Img', 'BackgroundImage']);
|
|
26
|
+
const FORM_INPUT_TAGS = new Set(['input', 'Input', 'textarea', 'Textarea', 'select', 'Select']);
|
|
27
|
+
const FORM_CONTAINER_TAGS = new Set(['form', 'Form']);
|
|
26
28
|
|
|
27
29
|
const shadcn = createAdapter('shadcn', {
|
|
28
30
|
detect: (project) => detectFromProfile(project, 'shadcn') || project.shadcn,
|
|
@@ -106,6 +108,9 @@ const deneb = createAdapter('deneb', {
|
|
|
106
108
|
Boolean(project.dependencies && project.dependencies['@deneb-ui/ui']),
|
|
107
109
|
recognizeNode(node) {
|
|
108
110
|
const name = getJsxName(node);
|
|
111
|
+
if (['EditableContactForm', 'ContactForm'].includes(name)) {
|
|
112
|
+
return { library: 'deneb', kind: 'form', tag: name };
|
|
113
|
+
}
|
|
109
114
|
if (['EditableGoogleFeedback', 'EditableCustomerReviews', 'CustomerReviews', 'GoogleFeedback'].includes(name)) {
|
|
110
115
|
return { library: 'deneb', kind: 'feedback', tag: name };
|
|
111
116
|
}
|
|
@@ -192,6 +197,18 @@ function classifyActionIntent(text, href) {
|
|
|
192
197
|
const t = String(text || '').trim().toLowerCase();
|
|
193
198
|
const h = String(href || '').trim().toLowerCase();
|
|
194
199
|
|
|
200
|
+
// 0. Form Submit / WhatsApp Form Action
|
|
201
|
+
if (
|
|
202
|
+
/^(submit|send\s*message|send\s*inquiry|confirm\s*booking|place\s*order|book\s*now|confirm|get\s*quote|request\s*quote|send\s*request|schedule|register|sign\s*up|subscribe|apply|enroll)$/i.test(t) ||
|
|
203
|
+
/\b(?:send\s*message|confirm\s*booking|submit\s*form|submit\s*inquiry|send\s*inquiry|request\s*quote|get\s*quote|send\s*request)\b/i.test(t)
|
|
204
|
+
) {
|
|
205
|
+
return {
|
|
206
|
+
action: 'form-submit',
|
|
207
|
+
defaultUrl: /^https?:\/\//i.test(h) && !h.includes('#') ? href : 'https://wa.me/1234567890',
|
|
208
|
+
external: true,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
195
212
|
// 1. WhatsApp
|
|
196
213
|
if (
|
|
197
214
|
/wa\.me|whatsapp/i.test(h) ||
|
|
@@ -280,6 +297,8 @@ module.exports = {
|
|
|
280
297
|
TEXT_TAGS,
|
|
281
298
|
ACTION_TAGS,
|
|
282
299
|
IMAGE_TAGS,
|
|
300
|
+
FORM_INPUT_TAGS,
|
|
301
|
+
FORM_CONTAINER_TAGS,
|
|
283
302
|
collectJsxText,
|
|
284
303
|
getJsxAttributeLiteral,
|
|
285
304
|
};
|
package/src/arc/field-paths.cjs
CHANGED
|
@@ -47,6 +47,7 @@ function inferSection(context) {
|
|
|
47
47
|
['testimonials', /testimonial/],
|
|
48
48
|
['map', /map\b|location-map|google-map/],
|
|
49
49
|
['faq', /faq|accordion/],
|
|
50
|
+
['form', /form|booking|inquiry|registration/],
|
|
50
51
|
['contact', /contact|whatsapp|mailto/],
|
|
51
52
|
['featuredProducts', /featured|product-grid|collection/],
|
|
52
53
|
['newsletter', /newsletter|subscribe/],
|
|
@@ -65,6 +66,7 @@ function inferSection(context) {
|
|
|
65
66
|
|
|
66
67
|
function inferFieldName(kind, tag, text, extra = {}) {
|
|
67
68
|
if (kind === 'url') {
|
|
69
|
+
if (extra.action === 'form-submit') return 'formWhatsappUrl';
|
|
68
70
|
if (extra.platform) return `${extra.platform}Url`;
|
|
69
71
|
if (extra.action === 'whatsapp') return 'whatsappUrl';
|
|
70
72
|
if (extra.action === 'phone') return 'phoneUrl';
|
|
@@ -74,6 +76,7 @@ function inferFieldName(kind, tag, text, extra = {}) {
|
|
|
74
76
|
return fromText || (extra.action ? `${extra.action}Url` : 'ctaUrl');
|
|
75
77
|
}
|
|
76
78
|
if (kind === 'label' && extra.paired) {
|
|
79
|
+
if (extra.action === 'form-submit') return 'formSubmitLabel';
|
|
77
80
|
if (extra.action === 'whatsapp') return 'whatsappLabel';
|
|
78
81
|
if (extra.action === 'phone') return 'phoneLabel';
|
|
79
82
|
if (extra.action === 'email') return 'emailLabel';
|
|
@@ -100,6 +103,7 @@ function inferFieldName(kind, tag, text, extra = {}) {
|
|
|
100
103
|
Description: 'description',
|
|
101
104
|
Typography: 'text',
|
|
102
105
|
Badge: 'badge',
|
|
106
|
+
label: 'label',
|
|
103
107
|
button: 'label',
|
|
104
108
|
Button: 'label',
|
|
105
109
|
span: 'label',
|
package/src/arc/index.cjs
CHANGED
|
@@ -256,7 +256,7 @@ async function runDenebArcAsync(projectDir, projectName, options = {}) {
|
|
|
256
256
|
0
|
|
257
257
|
);
|
|
258
258
|
const actionCount = analyses.reduce(
|
|
259
|
-
(n, a) => n + (a.candidates || []).filter((c) => c.operation === 'split-action-contract' || c.kind === 'url').length,
|
|
259
|
+
(n, a) => n + (a.candidates || []).filter((c) => c.operation === 'split-action-contract' || c.operation === 'form-submit-action' || c.kind === 'url').length,
|
|
260
260
|
0
|
|
261
261
|
);
|
|
262
262
|
printer.printScan(profile, graph, candidateCount, actionCount);
|
|
@@ -397,7 +397,7 @@ function runDenebArcSync(projectDir, projectName, options = {}) {
|
|
|
397
397
|
0
|
|
398
398
|
);
|
|
399
399
|
const actionCount = analyses.reduce(
|
|
400
|
-
(n, a) => n + (a.candidates || []).filter((c) => c.operation === 'split-action-contract' || c.kind === 'url').length,
|
|
400
|
+
(n, a) => n + (a.candidates || []).filter((c) => c.operation === 'split-action-contract' || c.operation === 'form-submit-action' || c.kind === 'url').length,
|
|
401
401
|
0
|
|
402
402
|
);
|
|
403
403
|
printer.printScan(profile, graph, candidateCount, actionCount);
|
package/src/arc/learning.cjs
CHANGED
package/src/arc/planner.cjs
CHANGED
|
@@ -9,7 +9,7 @@ const { appendStyleBindTransforms } = require('./style-candidates.cjs');
|
|
|
9
9
|
function recipeBoost(candidate, recipe) {
|
|
10
10
|
if (!recipe) return 0;
|
|
11
11
|
let boost = 0;
|
|
12
|
-
if (recipe.actionRules?.splitActionAndLabel && candidate.operation === 'split-action-contract') boost += 0.03;
|
|
12
|
+
if (recipe.actionRules?.splitActionAndLabel && (candidate.operation === 'split-action-contract' || candidate.operation === 'form-submit-action')) boost += 0.03;
|
|
13
13
|
const keywords = recipe.signatures?.keywords || [];
|
|
14
14
|
const hay = `${candidate.tag} ${candidate.value || ''} ${candidate.label || ''} ${candidate.file || ''}`.toLowerCase();
|
|
15
15
|
if (keywords.some((kw) => hay.includes(String(kw).toLowerCase()))) boost += 0.02;
|
|
@@ -66,8 +66,8 @@ function planTransformations({ profile, analyses, recipe }) {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
const extra = { ...(candidate.extra || {}) };
|
|
69
|
-
if (candidate.operation === 'split-action-contract') {
|
|
70
|
-
extra.action = extra.action || classifyHref(candidate.value);
|
|
69
|
+
if (candidate.operation === 'split-action-contract' || candidate.operation === 'form-submit-action') {
|
|
70
|
+
extra.action = extra.action || (candidate.operation === 'form-submit-action' ? 'form-submit' : classifyHref(candidate.value));
|
|
71
71
|
extra.paired = true;
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -107,7 +107,7 @@ function planTransformations({ profile, analyses, recipe }) {
|
|
|
107
107
|
continue;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
if (candidate.operation === 'split-action-contract') {
|
|
110
|
+
if (candidate.operation === 'split-action-contract' || candidate.operation === 'form-submit-action') {
|
|
111
111
|
const urlName = inferFieldName('url', candidate.tag, candidate.label, extra);
|
|
112
112
|
const labelName = inferFieldName('label', candidate.tag, candidate.label, { ...extra, paired: true });
|
|
113
113
|
const actionSection = sectionForAction(scope, section, extra);
|
|
@@ -117,6 +117,9 @@ function planTransformations({ profile, analyses, recipe }) {
|
|
|
117
117
|
transform.labelField = uniquePath(usedPaths, sibling.join('.'));
|
|
118
118
|
transform.fieldType = 'url';
|
|
119
119
|
transform.labelFieldType = 'text';
|
|
120
|
+
if (candidate.operation === 'form-submit-action') {
|
|
121
|
+
transform.formContext = true;
|
|
122
|
+
}
|
|
120
123
|
} else if (candidate.operation === 'extract-url') {
|
|
121
124
|
transform.field = buildFieldPath({
|
|
122
125
|
scope,
|
|
@@ -238,7 +241,7 @@ function sectionForAction(scope, section, extra) {
|
|
|
238
241
|
if (scope === 'common' && (extra.social || ['instagram', 'facebook', 'twitter', 'tiktok', 'youtube', 'linkedin'].includes(extra.action))) {
|
|
239
242
|
return 'footer';
|
|
240
243
|
}
|
|
241
|
-
if (['whatsapp', 'phone', 'email', 'directions', 'location'].includes(extra.action)) {
|
|
244
|
+
if (['whatsapp', 'phone', 'email', 'directions', 'location', 'form-submit'].includes(extra.action)) {
|
|
242
245
|
return section || 'contact';
|
|
243
246
|
}
|
|
244
247
|
if (extra.action === 'shop') {
|
package/src/arc/semantic.cjs
CHANGED
|
@@ -23,6 +23,8 @@ const {
|
|
|
23
23
|
TEXT_TAGS,
|
|
24
24
|
ACTION_TAGS,
|
|
25
25
|
IMAGE_TAGS,
|
|
26
|
+
FORM_INPUT_TAGS,
|
|
27
|
+
FORM_CONTAINER_TAGS,
|
|
26
28
|
DECORATIVE_TAGS,
|
|
27
29
|
} = require('./adapters.cjs');
|
|
28
30
|
const { shortHash } = require('./fs-utils.cjs');
|
|
@@ -392,7 +394,7 @@ function confidenceFor(kind, extras = {}) {
|
|
|
392
394
|
if (extras.icon) return 0.1;
|
|
393
395
|
if (kind === 'url' && extras.action === 'whatsapp') return 0.96;
|
|
394
396
|
if (kind === 'url' && extras.social) return 0.93;
|
|
395
|
-
if (kind === 'split-action-contract') return 0.94;
|
|
397
|
+
if (kind === 'split-action-contract' || kind === 'form-submit-action') return 0.94;
|
|
396
398
|
if (kind === 'text' && HEADING_TAGS.has(extras.tag)) return 0.95;
|
|
397
399
|
if (kind === 'text' && extras.tag === 'p') return 0.9;
|
|
398
400
|
if (kind === 'image') return 0.88;
|
|
@@ -562,14 +564,47 @@ function analyzeFile({ code, relativeFile, profile, graph, ownerScope, component
|
|
|
562
564
|
});
|
|
563
565
|
}
|
|
564
566
|
|
|
567
|
+
const insideForm = parents.some((p) => FORM_CONTAINER_TAGS.has(p));
|
|
568
|
+
|
|
565
569
|
const actionableHref = href || (name === 'Button' ? getJsxAttributeLiteral(node, 'href') : null);
|
|
566
570
|
const innerText = textInfo.dynamic ? '' : textInfo.text;
|
|
567
571
|
const typeAttr = getJsxAttributeLiteral(node, 'type');
|
|
568
572
|
const isSubmit = typeAttr === 'submit';
|
|
569
573
|
|
|
570
|
-
// Check for action intent via classifyActionIntent (covers WhatsApp, Call/Phone, Directions, Location, Shop, Email)
|
|
571
|
-
const actionIntent = !
|
|
572
|
-
|
|
574
|
+
// Check for action intent via classifyActionIntent (covers Form Submit, WhatsApp, Call/Phone, Directions, Location, Shop, Email)
|
|
575
|
+
const actionIntent = !apiOwned ? classifyActionIntent(innerText, actionableHref) : null;
|
|
576
|
+
|
|
577
|
+
const isFormSubmit =
|
|
578
|
+
!apiOwned &&
|
|
579
|
+
(ACTION_TAGS.has(name) || isLikelyCtaClass(className)) &&
|
|
580
|
+
(isSubmit ||
|
|
581
|
+
(insideForm && (actionIntent?.action === 'form-submit' || (innerText && !actionableHref))) ||
|
|
582
|
+
actionIntent?.action === 'form-submit');
|
|
583
|
+
|
|
584
|
+
if (isFormSubmit && innerText && !textInfo.dynamic) {
|
|
585
|
+
usedLocs.add(loc);
|
|
586
|
+
const resolvedHref = actionableHref || (actionIntent ? actionIntent.defaultUrl : 'https://wa.me/1234567890');
|
|
587
|
+
candidates.push({
|
|
588
|
+
...baseMeta,
|
|
589
|
+
kind: 'form-submit-action',
|
|
590
|
+
operation: 'form-submit-action',
|
|
591
|
+
value: resolvedHref,
|
|
592
|
+
label: innerText,
|
|
593
|
+
extra: {
|
|
594
|
+
action: 'form-submit',
|
|
595
|
+
external: true,
|
|
596
|
+
insideForm,
|
|
597
|
+
isSubmit,
|
|
598
|
+
},
|
|
599
|
+
confidence: confidenceFor('form-submit-action'),
|
|
600
|
+
reason: 'form-submit-whatsapp-dispatch',
|
|
601
|
+
fingerprint: fingerprintCandidate({ tag: name, kind: 'form-submit-action', action: 'form-submit' }),
|
|
602
|
+
});
|
|
603
|
+
this.traverse(pathNode);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const isAction = !isSubmit && (Boolean(actionableHref) || Boolean(actionIntent)) && (ACTION_TAGS.has(name) || isLikelyCtaClass(className));
|
|
573
608
|
|
|
574
609
|
if (isAction && (actionableHref || actionIntent)) {
|
|
575
610
|
const action = actionIntent ? actionIntent.action : classifyHref(actionableHref);
|
|
@@ -5,6 +5,7 @@ const BUTTON_TAGS = new Set(['button', 'Button', 'CTAButton']);
|
|
|
5
5
|
|
|
6
6
|
function inferStyleKind(transform) {
|
|
7
7
|
if (transform.operation === 'collection-conversion') return 'grid';
|
|
8
|
+
if (transform.operation === 'form-submit-action') return 'button';
|
|
8
9
|
if (transform.operation === 'split-action-contract') return 'text';
|
|
9
10
|
if (BUTTON_TAGS.has(transform.tag) && transform.operation === 'extract-text') return 'button';
|
|
10
11
|
if (TEXT_OPS.has(transform.operation)) return 'text';
|
|
@@ -14,7 +15,7 @@ function inferStyleKind(transform) {
|
|
|
14
15
|
function stylePathFor(transform, kind) {
|
|
15
16
|
if (kind === 'grid' && transform.listField) return `${transform.listField}.grid`;
|
|
16
17
|
if (kind === 'card' && transform.listField) return `${transform.listField}[*].card`;
|
|
17
|
-
if (transform.operation === 'split-action-contract') return transform.labelField;
|
|
18
|
+
if (transform.operation === 'split-action-contract' || transform.operation === 'form-submit-action') return transform.labelField;
|
|
18
19
|
return transform.field || transform.labelField || null;
|
|
19
20
|
}
|
|
20
21
|
|
package/src/arc/transformer.cjs
CHANGED
|
@@ -152,6 +152,27 @@ function wrapHiddenUrlSibling(pathNode, urlField, fallback) {
|
|
|
152
152
|
|
|
153
153
|
function applyTransformToElement(pathNode, transform) {
|
|
154
154
|
const node = pathNode.node;
|
|
155
|
+
if (transform.operation === 'form-submit-action') {
|
|
156
|
+
const tagName = getJsxName(node);
|
|
157
|
+
if (!hasJsxAttribute(node, 'type') && tagName === 'button') {
|
|
158
|
+
node.openingElement.attributes.push(
|
|
159
|
+
b.jsxAttribute(b.jsxIdentifier('type'), b.stringLiteral('submit'))
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
if (!hasJsxAttribute(node, 'data-preview-static')) {
|
|
163
|
+
node.openingElement.attributes.push(
|
|
164
|
+
b.jsxAttribute(b.jsxIdentifier('data-preview-static'), b.stringLiteral('action-button'))
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
if (hasJsxAttribute(node, 'data-preview-field-path')) {
|
|
168
|
+
node.openingElement.attributes = node.openingElement.attributes.filter(
|
|
169
|
+
(attr) => !(attr.type === 'JSXAttribute' && attr.name && attr.name.name === 'data-preview-field-path')
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
splitActionChildren(node, transform.labelField, transform.labelFallback || '');
|
|
173
|
+
wrapHiddenUrlSibling(pathNode, transform.urlField, transform.fallback);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
155
176
|
if (transform.operation === 'split-action-contract') {
|
|
156
177
|
const tagName = getJsxName(node);
|
|
157
178
|
if (tagName === 'button') {
|
|
@@ -625,6 +646,7 @@ function applyFilePlan(filePlan, profile) {
|
|
|
625
646
|
|
|
626
647
|
const supported = new Set([
|
|
627
648
|
'split-action-contract',
|
|
649
|
+
'form-submit-action',
|
|
628
650
|
'extract-url',
|
|
629
651
|
'extract-image',
|
|
630
652
|
'extract-alt',
|