jamdesk 1.1.202 → 1.1.203
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jamdesk",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.203",
|
|
4
4
|
"description": "CLI for Jamdesk — build, preview, and deploy documentation sites from MDX. Dev server with hot reload, 50+ components, OpenAPI support, AI search, and Mintlify migration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jamdesk",
|
package/vendored/app/globals.css
CHANGED
|
@@ -42,6 +42,44 @@
|
|
|
42
42
|
*/
|
|
43
43
|
@import "../themes/jam/variables.css";
|
|
44
44
|
|
|
45
|
+
/*
|
|
46
|
+
* MDX wraps a button's label in a paragraph, and our prose <p> styling then
|
|
47
|
+
* recolours it — defeating the colour the author put on the button itself.
|
|
48
|
+
*
|
|
49
|
+
* Observed in production: a customer wrote
|
|
50
|
+
* <button className="bg-[#ED8200] text-black">Submit Feedback</button>
|
|
51
|
+
* and remark turned the label into `<p class="text-theme-text-secondary
|
|
52
|
+
* leading-7 mb-4">`. That class sets colour directly, so it beats the
|
|
53
|
+
* `text-black` the <p> would otherwise have INHERITED from the button:
|
|
54
|
+
* 1.40:1 against the orange in dark mode, 3.79:1 in light. Both fail WCAG AA;
|
|
55
|
+
* the author's own intent was 7.79:1 and correct. `mb-4` also made the button
|
|
56
|
+
* 65px tall with the label sitting above centre.
|
|
57
|
+
*
|
|
58
|
+
* Scoped to `> p:only-child` — the exact "MDX wrapped my label" shape. A
|
|
59
|
+
* button that deliberately contains a styled paragraph alongside other content
|
|
60
|
+
* is untouched, so this cannot silently override a real authoring choice.
|
|
61
|
+
*
|
|
62
|
+
* Must be UNLAYERED, and that is load-bearing — it has to outrank two
|
|
63
|
+
* different things at once:
|
|
64
|
+
* - `text-theme-text-secondary`, a utility in `@layer utilities`
|
|
65
|
+
* - `.prose p { margin: 0.5rem 0 0.8rem }` in themes/base.css, which the
|
|
66
|
+
* @import above pulls in UNLAYERED
|
|
67
|
+
* Nothing inside a layer can beat that second one — an unlayered rule wins
|
|
68
|
+
* over every layer regardless of specificity, which is exactly why an earlier
|
|
69
|
+
* version of this block sat in `@layer utilities`, fixed the colour, and left
|
|
70
|
+
* the button still 63px tall with 12.8px of dead space under the label.
|
|
71
|
+
* Unlayered, `button > p:only-child` (0,1,2) also outranks `.prose p` (0,1,1)
|
|
72
|
+
* on its own terms, so no `!important` is needed.
|
|
73
|
+
*
|
|
74
|
+
* Verified that no platform component renders a <button> whose only child is
|
|
75
|
+
* a <p>, so the unlayered reach costs us nothing.
|
|
76
|
+
*/
|
|
77
|
+
button > p:only-child {
|
|
78
|
+
color: inherit;
|
|
79
|
+
margin: 0;
|
|
80
|
+
line-height: inherit;
|
|
81
|
+
}
|
|
82
|
+
|
|
45
83
|
/*
|
|
46
84
|
* Light/Dark mode image utilities
|
|
47
85
|
* These utilities enable showing different images based on theme.
|
|
@@ -79,6 +79,44 @@ export function isEventHandlerProp(name: string): boolean {
|
|
|
79
79
|
return EVENT_HANDLER_PROP.test(name);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Attributes that only mean something on a `<form>`. When a form is downgraded
|
|
84
|
+
* to a `<div>` (see `neutralizeDeadForm`) these would become invalid DOM
|
|
85
|
+
* attributes and draw React warnings, so they go with it.
|
|
86
|
+
*/
|
|
87
|
+
const FORM_ONLY_PROPS = new Set([
|
|
88
|
+
'method',
|
|
89
|
+
'encType',
|
|
90
|
+
'target',
|
|
91
|
+
'noValidate',
|
|
92
|
+
'acceptCharset',
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* A `<form>` whose only submit path was an `on*` handler we just stripped, and
|
|
97
|
+
* which has no `action`, is downgraded to a `<div>`.
|
|
98
|
+
*
|
|
99
|
+
* WHY, and why the tag rather than the submit button:
|
|
100
|
+
* with the handler gone the browser falls back to NATIVE submission, and with
|
|
101
|
+
* no `action` that targets the current URL — so a reader who fills the form in
|
|
102
|
+
* is navigated to `?<field>=<whatever they typed>`, the page reloads, their
|
|
103
|
+
* input goes nowhere, and their free text is left in the URL, in history, and
|
|
104
|
+
* in CDN logs. That is strictly worse than the form doing nothing.
|
|
105
|
+
*
|
|
106
|
+
* Observed in production: a customer's feedback snippet POSTed JSON to their
|
|
107
|
+
* own Lambda from `onSubmit`, whose FIRST statement was `e.preventDefault()`.
|
|
108
|
+
* Stripping the handler removed the very thing suppressing the native submit.
|
|
109
|
+
*
|
|
110
|
+
* Neutralizing the submit BUTTON would not close it. HTML implicit submission
|
|
111
|
+
* fires on Enter in a form with a single text field whether or not a submit
|
|
112
|
+
* button exists, so the form element itself has to go. Children render exactly
|
|
113
|
+
* as before; only the submission path disappears.
|
|
114
|
+
*
|
|
115
|
+
* Deliberately narrow: it fires only when a handler was actually removed AND
|
|
116
|
+
* there is no `action`. A form with an `action` still works natively and is
|
|
117
|
+
* left alone, and a form that never had a handler is not this bug.
|
|
118
|
+
*/
|
|
119
|
+
|
|
82
120
|
/** JSX factory callees emitted by the MDX/React compilers. */
|
|
83
121
|
const JSX_CALLEES = new Set(['_jsx', '_jsxs', '_jsxDEV']);
|
|
84
122
|
|
|
@@ -114,14 +152,34 @@ export function recmaStripEventHandlers() {
|
|
|
114
152
|
const props = node.arguments[1];
|
|
115
153
|
if (!props || props.type !== 'ObjectExpression') return;
|
|
116
154
|
|
|
155
|
+
const tag = node.arguments[0];
|
|
156
|
+
const isForm =
|
|
157
|
+
tag && tag.type === 'Literal' && tag.value === 'form';
|
|
158
|
+
let strippedHandler = false;
|
|
159
|
+
let hasAction = false;
|
|
160
|
+
|
|
117
161
|
props.properties = props.properties.filter((prop) => {
|
|
118
162
|
// A SpreadElement here is an identifier spread (`{...handlers}`) — an
|
|
119
163
|
// object-literal spread was already flattened into this same properties
|
|
120
164
|
// list by the MDX compiler. Structural, so keep it (RESIDUAL SCOPE 1).
|
|
121
165
|
if (prop.type !== 'Property') return true;
|
|
122
166
|
const name = propKeyName(prop);
|
|
123
|
-
|
|
167
|
+
if (!name) return true;
|
|
168
|
+
if (name === 'action') hasAction = true;
|
|
169
|
+
if (!isEventHandlerProp(name)) return true;
|
|
170
|
+
strippedHandler = true;
|
|
171
|
+
return false;
|
|
124
172
|
});
|
|
173
|
+
|
|
174
|
+
if (isForm && strippedHandler && !hasAction) {
|
|
175
|
+
(tag as { value: string; raw?: string }).value = 'div';
|
|
176
|
+
delete (tag as { raw?: string }).raw;
|
|
177
|
+
props.properties = props.properties.filter((prop) => {
|
|
178
|
+
if (prop.type !== 'Property') return true;
|
|
179
|
+
const name = propKeyName(prop);
|
|
180
|
+
return !name || !FORM_ONLY_PROPS.has(name);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
125
183
|
});
|
|
126
184
|
};
|
|
127
185
|
}
|
|
@@ -144,13 +202,46 @@ export function babelStripEventHandlers() {
|
|
|
144
202
|
visitor: {
|
|
145
203
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
146
204
|
JSXOpeningElement(path: any) {
|
|
205
|
+
const isForm =
|
|
206
|
+
path.node.name?.type === 'JSXIdentifier' &&
|
|
207
|
+
path.node.name.name === 'form';
|
|
208
|
+
let strippedHandler = false;
|
|
209
|
+
let hasAction = false;
|
|
210
|
+
|
|
211
|
+
path.node.attributes = path.node.attributes.filter(
|
|
212
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
213
|
+
(attr: any) => {
|
|
214
|
+
if (
|
|
215
|
+
attr.type !== 'JSXAttribute' ||
|
|
216
|
+
attr.name?.type !== 'JSXIdentifier'
|
|
217
|
+
) {
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
if (attr.name.name === 'action') hasAction = true;
|
|
221
|
+
if (!isEventHandlerProp(attr.name.name)) return true;
|
|
222
|
+
strippedHandler = true;
|
|
223
|
+
return false;
|
|
224
|
+
},
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
if (!isForm || !strippedHandler || hasAction) return;
|
|
228
|
+
|
|
229
|
+
// Downgrade to <div> — see neutralizeDeadForm above. The closing tag
|
|
230
|
+
// is renamed too. Under the `react` preset that lowers JSX to `_jsx`
|
|
231
|
+
// calls this is unobservable (the closing element is discarded), so it
|
|
232
|
+
// reads like dead code — but leaving a mismatched AST would emit
|
|
233
|
+
// `<div>…</form>` for any consumer that preserves JSX, and a test
|
|
234
|
+
// transpiles with JSX preserved specifically to pin it.
|
|
235
|
+
path.node.name.name = 'div';
|
|
236
|
+
const closing = path.parent?.closingElement;
|
|
237
|
+
if (closing?.name?.type === 'JSXIdentifier') closing.name.name = 'div';
|
|
147
238
|
path.node.attributes = path.node.attributes.filter(
|
|
148
239
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
149
240
|
(attr: any) =>
|
|
150
241
|
!(
|
|
151
242
|
attr.type === 'JSXAttribute' &&
|
|
152
243
|
attr.name?.type === 'JSXIdentifier' &&
|
|
153
|
-
|
|
244
|
+
FORM_ONLY_PROPS.has(attr.name.name)
|
|
154
245
|
),
|
|
155
246
|
);
|
|
156
247
|
},
|
|
@@ -2925,9 +2925,9 @@
|
|
|
2925
2925
|
"license": "MIT"
|
|
2926
2926
|
},
|
|
2927
2927
|
"node_modules/electron-to-chromium": {
|
|
2928
|
-
"version": "1.5.
|
|
2929
|
-
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.
|
|
2930
|
-
"integrity": "sha512-
|
|
2928
|
+
"version": "1.5.418",
|
|
2929
|
+
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.418.tgz",
|
|
2930
|
+
"integrity": "sha512-UzS26r3AEbG5wSoGVpJKqwHIU9zwQN7LHdVIThDrJpS0I5KdlXFMEb8543fhc9dVnIIAST6ar8rhwa00AL5MlA==",
|
|
2931
2931
|
"license": "ISC"
|
|
2932
2932
|
},
|
|
2933
2933
|
"node_modules/enhanced-resolve": {
|