@aglyn/plugins-mui 1.0.0-beta.143
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/LICENSE +201 -0
- package/README.md +22 -0
- package/package.json +47 -0
- package/src/index.d.ts +21 -0
- package/src/index.js +22 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/accordion.d.ts +96 -0
- package/src/lib/components/accordion.js +392 -0
- package/src/lib/components/accordion.js.map +1 -0
- package/src/lib/components/app-bar.d.ts +32 -0
- package/src/lib/components/app-bar.js +86 -0
- package/src/lib/components/app-bar.js.map +1 -0
- package/src/lib/components/blocks.d.ts +46 -0
- package/src/lib/components/blocks.js +1007 -0
- package/src/lib/components/blocks.js.map +1 -0
- package/src/lib/components/box.d.ts +47 -0
- package/src/lib/components/box.js +125 -0
- package/src/lib/components/box.js.map +1 -0
- package/src/lib/components/breadcrumbs.d.ts +44 -0
- package/src/lib/components/breadcrumbs.js +172 -0
- package/src/lib/components/breadcrumbs.js.map +1 -0
- package/src/lib/components/button.d.ts +89 -0
- package/src/lib/components/button.js +272 -0
- package/src/lib/components/button.js.map +1 -0
- package/src/lib/components/card.d.ts +61 -0
- package/src/lib/components/card.js +336 -0
- package/src/lib/components/card.js.map +1 -0
- package/src/lib/components/collection.d.ts +514 -0
- package/src/lib/components/collection.js +2999 -0
- package/src/lib/components/collection.js.map +1 -0
- package/src/lib/components/container.d.ts +42 -0
- package/src/lib/components/container.js +169 -0
- package/src/lib/components/container.js.map +1 -0
- package/src/lib/components/custom-html.d.ts +78 -0
- package/src/lib/components/custom-html.js +219 -0
- package/src/lib/components/custom-html.js.map +1 -0
- package/src/lib/components/data-table.d.ts +57 -0
- package/src/lib/components/data-table.js +216 -0
- package/src/lib/components/data-table.js.map +1 -0
- package/src/lib/components/document-root.d.ts +77 -0
- package/src/lib/components/document-root.js +118 -0
- package/src/lib/components/document-root.js.map +1 -0
- package/src/lib/components/drawer.d.ts +93 -0
- package/src/lib/components/drawer.js +566 -0
- package/src/lib/components/drawer.js.map +1 -0
- package/src/lib/components/function-controls.d.ts +66 -0
- package/src/lib/components/function-controls.js +207 -0
- package/src/lib/components/function-controls.js.map +1 -0
- package/src/lib/components/function-scope.d.ts +110 -0
- package/src/lib/components/function-scope.js +478 -0
- package/src/lib/components/function-scope.js.map +1 -0
- package/src/lib/components/function-widget.d.ts +75 -0
- package/src/lib/components/function-widget.js +307 -0
- package/src/lib/components/function-widget.js.map +1 -0
- package/src/lib/components/grid.d.ts +91 -0
- package/src/lib/components/grid.js +322 -0
- package/src/lib/components/grid.js.map +1 -0
- package/src/lib/components/icon.d.ts +54 -0
- package/src/lib/components/icon.js +163 -0
- package/src/lib/components/icon.js.map +1 -0
- package/src/lib/components/image-list.d.ts +58 -0
- package/src/lib/components/image-list.js +336 -0
- package/src/lib/components/image-list.js.map +1 -0
- package/src/lib/components/image.d.ts +154 -0
- package/src/lib/components/image.js +451 -0
- package/src/lib/components/image.js.map +1 -0
- package/src/lib/components/inline-text.d.ts +74 -0
- package/src/lib/components/inline-text.js +407 -0
- package/src/lib/components/inline-text.js.map +1 -0
- package/src/lib/components/language-switcher.d.ts +35 -0
- package/src/lib/components/language-switcher.js +179 -0
- package/src/lib/components/language-switcher.js.map +1 -0
- package/src/lib/components/layout-slot.d.ts +68 -0
- package/src/lib/components/layout-slot.js +192 -0
- package/src/lib/components/layout-slot.js.map +1 -0
- package/src/lib/components/link-box-accessible-name.d.ts +79 -0
- package/src/lib/components/link-box-accessible-name.js +158 -0
- package/src/lib/components/link-box-accessible-name.js.map +1 -0
- package/src/lib/components/link-box.d.ts +66 -0
- package/src/lib/components/link-box.js +212 -0
- package/src/lib/components/link-box.js.map +1 -0
- package/src/lib/components/list-item-text.d.ts +22 -0
- package/src/lib/components/list-item-text.js +86 -0
- package/src/lib/components/list-item-text.js.map +1 -0
- package/src/lib/components/list-item.d.ts +32 -0
- package/src/lib/components/list-item.js +127 -0
- package/src/lib/components/list-item.js.map +1 -0
- package/src/lib/components/list.d.ts +34 -0
- package/src/lib/components/list.js +108 -0
- package/src/lib/components/list.js.map +1 -0
- package/src/lib/components/markdown.d.ts +129 -0
- package/src/lib/components/markdown.js +853 -0
- package/src/lib/components/markdown.js.map +1 -0
- package/src/lib/components/nav-menu.d.ts +68 -0
- package/src/lib/components/nav-menu.js +683 -0
- package/src/lib/components/nav-menu.js.map +1 -0
- package/src/lib/components/pagination.d.ts +59 -0
- package/src/lib/components/pagination.js +243 -0
- package/src/lib/components/pagination.js.map +1 -0
- package/src/lib/components/paper.d.ts +47 -0
- package/src/lib/components/paper.js +202 -0
- package/src/lib/components/paper.js.map +1 -0
- package/src/lib/components/plugin-frame.d.ts +64 -0
- package/src/lib/components/plugin-frame.js +335 -0
- package/src/lib/components/plugin-frame.js.map +1 -0
- package/src/lib/components/plugin.d.ts +95 -0
- package/src/lib/components/plugin.js +304 -0
- package/src/lib/components/plugin.js.map +1 -0
- package/src/lib/components/product.d.ts +40 -0
- package/src/lib/components/product.js +287 -0
- package/src/lib/components/product.js.map +1 -0
- package/src/lib/components/reusable-instance.d.ts +57 -0
- package/src/lib/components/reusable-instance.js +126 -0
- package/src/lib/components/reusable-instance.js.map +1 -0
- package/src/lib/components/screen-link.d.ts +85 -0
- package/src/lib/components/screen-link.js +324 -0
- package/src/lib/components/screen-link.js.map +1 -0
- package/src/lib/components/search-box.d.ts +41 -0
- package/src/lib/components/search-box.js +125 -0
- package/src/lib/components/search-box.js.map +1 -0
- package/src/lib/components/section.d.ts +56 -0
- package/src/lib/components/section.js +132 -0
- package/src/lib/components/section.js.map +1 -0
- package/src/lib/components/stack.d.ts +55 -0
- package/src/lib/components/stack.js +309 -0
- package/src/lib/components/stack.js.map +1 -0
- package/src/lib/components/tabs.d.ts +288 -0
- package/src/lib/components/tabs.js +788 -0
- package/src/lib/components/tabs.js.map +1 -0
- package/src/lib/components/theme-mode-switcher.d.ts +38 -0
- package/src/lib/components/theme-mode-switcher.js +168 -0
- package/src/lib/components/theme-mode-switcher.js.map +1 -0
- package/src/lib/components/toolbar.d.ts +37 -0
- package/src/lib/components/toolbar.js +117 -0
- package/src/lib/components/toolbar.js.map +1 -0
- package/src/lib/components/typography.d.ts +33 -0
- package/src/lib/components/typography.js +433 -0
- package/src/lib/components/typography.js.map +1 -0
- package/src/lib/components/video-lightbox-controls.d.ts +131 -0
- package/src/lib/components/video-lightbox-controls.js +536 -0
- package/src/lib/components/video-lightbox-controls.js.map +1 -0
- package/src/lib/components/video-lightbox.d.ts +60 -0
- package/src/lib/components/video-lightbox.js +247 -0
- package/src/lib/components/video-lightbox.js.map +1 -0
- package/src/lib/components/video-playback-beacon.d.ts +80 -0
- package/src/lib/components/video-playback-beacon.js +128 -0
- package/src/lib/components/video-playback-beacon.js.map +1 -0
- package/src/lib/components/video-player-frame.d.ts +58 -0
- package/src/lib/components/video-player-frame.js +51 -0
- package/src/lib/components/video-player-frame.js.map +1 -0
- package/src/lib/components/video.d.ts +220 -0
- package/src/lib/components/video.js +874 -0
- package/src/lib/components/video.js.map +1 -0
- package/src/lib/constants/bundle-common.d.ts +17 -0
- package/src/lib/constants/bundle-common.js +18 -0
- package/src/lib/constants/bundle-common.js.map +1 -0
- package/src/lib/constants/field-presets.d.ts +93 -0
- package/src/lib/constants/field-presets.js +248 -0
- package/src/lib/constants/field-presets.js.map +1 -0
- package/src/lib/plugin.d.ts +80 -0
- package/src/lib/plugin.js +599 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/utils/author-html-label.d.ts +73 -0
- package/src/lib/utils/author-html-label.js +89 -0
- package/src/lib/utils/author-html-label.js.map +1 -0
- package/src/lib/utils/drop-cleared-props.d.ts +18 -0
- package/src/lib/utils/drop-cleared-props.js +74 -0
- package/src/lib/utils/drop-cleared-props.js.map +1 -0
- package/src/lib/utils/element-picker.d.ts +99 -0
- package/src/lib/utils/element-picker.js +140 -0
- package/src/lib/utils/element-picker.js.map +1 -0
- package/src/lib/utils/generate-preset-id.d.ts +19 -0
- package/src/lib/utils/generate-preset-id.js +25 -0
- package/src/lib/utils/generate-preset-id.js.map +1 -0
- package/src/lib/utils/link-target-props.d.ts +53 -0
- package/src/lib/utils/link-target-props.js +53 -0
- package/src/lib/utils/link-target-props.js.map +1 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import * as Aglyn from "@aglyn/aglyn";
|
|
18
|
+
import { mdiTextBoxOutline, mdiViewArrayOutline } from "@aglyn/shared-data-mdi";
|
|
19
|
+
import MuiContainer from "@mui/material/Container";
|
|
20
|
+
import { createElement, forwardRef } from "react";
|
|
21
|
+
import { BUNDLE_ID } from "../constants/bundle-common.js";
|
|
22
|
+
import { applySemanticElement, semanticElementAttribute, semanticElementLabelAttribute } from "../utils/element-picker.js";
|
|
23
|
+
import { dropClearedProps } from "../utils/drop-cleared-props.js";
|
|
24
|
+
import { generatePresetId } from "../utils/generate-preset-id.js";
|
|
25
|
+
/**
|
|
26
|
+
* MUI's Container behind the cleared-prop guard (AGL-1435).
|
|
27
|
+
*
|
|
28
|
+
* `maxWidth` is the one authorable attribute in this plugin where a cleared
|
|
29
|
+
* value changes PAGE LAYOUT instead of showing an obviously wrong value, and
|
|
30
|
+
* it satisfied neither branch of MUI's own logic:
|
|
31
|
+
*
|
|
32
|
+
* - `''` / `null` is not `undefined`, so the `maxWidth = 'lg'` destructuring
|
|
33
|
+
* default in `@mui/system/Container/createContainer.js` never fires;
|
|
34
|
+
* - `''` / `null` is falsy, so the `ownerState.maxWidth &&` guard skips both
|
|
35
|
+
* the branch that emits the `max-width` rule and the one that emits the
|
|
36
|
+
* matching utility class.
|
|
37
|
+
*
|
|
38
|
+
* The result is a section with NO width constraint at all — full-bleed, with
|
|
39
|
+
* nothing thrown and nothing visibly wrong in the editor. Clearing the field
|
|
40
|
+
* persists `null` (the AGL-1226 shape; 106 cleared prop values live in the
|
|
41
|
+
* corpus today across other components), so this was reachable, not
|
|
42
|
+
* theoretical. Dropping the key instead lets MUI's own `lg` apply.
|
|
43
|
+
*/ const Container = forwardRef((props, ref)=>createElement(MuiContainer, _extends({}, applySemanticElement(dropClearedProps(props)), {
|
|
44
|
+
ref
|
|
45
|
+
})));
|
|
46
|
+
Container.displayName = 'AglynContainer';
|
|
47
|
+
// Component ids are persisted in screen documents; keep the legacy ids.
|
|
48
|
+
export const ID = 'muiContainer';
|
|
49
|
+
export const schema = {
|
|
50
|
+
$id: ID,
|
|
51
|
+
pluginId: BUNDLE_ID,
|
|
52
|
+
displayName: 'Container',
|
|
53
|
+
description: 'Centers your page content and caps how wide it gets.',
|
|
54
|
+
category: Aglyn.ComponentCategory.LAYOUT,
|
|
55
|
+
icon: {
|
|
56
|
+
path: mdiViewArrayOutline.path,
|
|
57
|
+
sx: {
|
|
58
|
+
color: '#2196f3'
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
attributes: [
|
|
62
|
+
semanticElementAttribute('this container'),
|
|
63
|
+
semanticElementLabelAttribute(),
|
|
64
|
+
{
|
|
65
|
+
name: 'fixed',
|
|
66
|
+
description: "If true, set the max-width to match the min-width of the current breakpoint. This is useful if you'd prefer to design for a fixed set of sizes instead of trying to accommodate a fully fluid viewport. It's fluid by default.",
|
|
67
|
+
component: Aglyn.FieldComponentType.SWITCH,
|
|
68
|
+
label: 'Fixed Breakpoints'
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'disableGutters',
|
|
72
|
+
description: 'Removes the container’s side padding, so its content runs to the ' + 'full width it is centered in. Use it for a full-bleed band; leave ' + 'it off for text, which needs the margin on a phone.',
|
|
73
|
+
component: Aglyn.FieldComponentType.SWITCH,
|
|
74
|
+
label: 'Disable Gutters?'
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'maxWidth',
|
|
78
|
+
description: 'The width this section stops growing at. New containers start at ' + 'XL (1536px), the standard for full-width page sections. Long-form ' + 'prose — legal pages, docs, blog and changelog bodies — uses MD ' + '(900px), which lands near the 65–75 characters a line that reads ' + 'comfortably; the "Prose Container" preset starts there. LG (1200px) ' + 'is the deliberate middle case for a wide but text-led section, not ' + 'a default to spread. Clearing the field falls back to the MUI ' + 'default of LG. There is no "Default" option: "Fluid Responsive" is ' + 'the only way to render edge-to-edge, and it has to be chosen on ' + 'purpose.',
|
|
79
|
+
component: Aglyn.FieldComponentType.SELECT,
|
|
80
|
+
label: 'Max Width',
|
|
81
|
+
// Every value here is a real, persistable one. The list used to open
|
|
82
|
+
// with `{ value: '', label: 'Default' }`, which was unpersistable by
|
|
83
|
+
// construction — the attributes form strips `''` on change (AGL-1191)
|
|
84
|
+
// — and, on any path that DID land it, rendered full-bleed rather than
|
|
85
|
+
// any default at all (AGL-1435).
|
|
86
|
+
options: [
|
|
87
|
+
{
|
|
88
|
+
value: 'xs',
|
|
89
|
+
label: 'XS - Mobile (444px)'
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
value: 'sm',
|
|
93
|
+
label: 'SM - Tablet (600px)'
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
value: 'md',
|
|
97
|
+
label: 'MD - Laptop (900px)'
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
value: 'lg',
|
|
101
|
+
label: 'LG - Desktop (1200px)'
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
value: 'xl',
|
|
105
|
+
label: 'XL - Widescreen (1536px, section standard)'
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
value: false,
|
|
109
|
+
label: 'Fluid Responsive (no max width)'
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
};
|
|
115
|
+
export const presets = [
|
|
116
|
+
{
|
|
117
|
+
$id: generatePresetId(ID),
|
|
118
|
+
type: Aglyn.NodeType.PRESET,
|
|
119
|
+
displayName: 'Container',
|
|
120
|
+
icon: {
|
|
121
|
+
path: mdiViewArrayOutline.path,
|
|
122
|
+
sx: {
|
|
123
|
+
color: '#2196f3'
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
category: Aglyn.ComponentCategory.LAYOUT,
|
|
127
|
+
data: {
|
|
128
|
+
$id: null,
|
|
129
|
+
componentId: ID,
|
|
130
|
+
pluginId: BUNDLE_ID,
|
|
131
|
+
// Explicit, not implicit: a dropped Container is a page section, and
|
|
132
|
+
// sections are XL here (245 of the 248 authored on the marketing host).
|
|
133
|
+
// Relying on MUI's implicit `lg` is what made "what does no value mean"
|
|
134
|
+
// an open question in the first place (AGL-1435).
|
|
135
|
+
props: {
|
|
136
|
+
maxWidth: 'xl'
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
// Case is MD on READING grounds, not as "one step down" — at XL a
|
|
142
|
+
// paragraph runs 110–120 characters a line, and even LG (1200px) is far
|
|
143
|
+
// past the 65–75 the eye tracks without losing its place. MD is the
|
|
144
|
+
// stock breakpoint that lands in that band, so the standard grows a
|
|
145
|
+
// third case without growing a bespoke number: the 1328px ban holds
|
|
146
|
+
// because 1328 is a content width, not a breakpoint.
|
|
147
|
+
$id: generatePresetId(ID, 'prose'),
|
|
148
|
+
type: Aglyn.NodeType.PRESET,
|
|
149
|
+
displayName: 'Prose Container',
|
|
150
|
+
icon: {
|
|
151
|
+
path: mdiTextBoxOutline.path,
|
|
152
|
+
sx: {
|
|
153
|
+
color: '#2196f3'
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
category: Aglyn.ComponentCategory.LAYOUT,
|
|
157
|
+
data: {
|
|
158
|
+
$id: null,
|
|
159
|
+
componentId: ID,
|
|
160
|
+
pluginId: BUNDLE_ID,
|
|
161
|
+
props: {
|
|
162
|
+
maxWidth: 'md'
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
];
|
|
167
|
+
export default Container;
|
|
168
|
+
|
|
169
|
+
//# sourceMappingURL=container.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/container.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport {\n mdiTextBoxOutline,\n mdiViewArrayOutline,\n} from '@aglyn/shared-data-mdi'\nimport MuiContainer, { type ContainerProps } from '@mui/material/Container'\nimport { createElement, forwardRef } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport {\n applySemanticElement,\n semanticElementAttribute,\n semanticElementLabelAttribute,\n} from '../utils/element-picker'\nimport { dropClearedProps } from '../utils/drop-cleared-props'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n/**\n * MUI's Container behind the cleared-prop guard (AGL-1435).\n *\n * `maxWidth` is the one authorable attribute in this plugin where a cleared\n * value changes PAGE LAYOUT instead of showing an obviously wrong value, and\n * it satisfied neither branch of MUI's own logic:\n *\n * - `''` / `null` is not `undefined`, so the `maxWidth = 'lg'` destructuring\n * default in `@mui/system/Container/createContainer.js` never fires;\n * - `''` / `null` is falsy, so the `ownerState.maxWidth &&` guard skips both\n * the branch that emits the `max-width` rule and the one that emits the\n * matching utility class.\n *\n * The result is a section with NO width constraint at all — full-bleed, with\n * nothing thrown and nothing visibly wrong in the editor. Clearing the field\n * persists `null` (the AGL-1226 shape; 106 cleared prop values live in the\n * corpus today across other components), so this was reachable, not\n * theoretical. Dropping the key instead lets MUI's own `lg` apply.\n */\nconst Container = forwardRef<HTMLDivElement, ContainerProps>((props, ref) =>\n createElement(MuiContainer, {\n ...applySemanticElement(dropClearedProps(props) as Record<string, unknown>),\n ref,\n }),\n)\nContainer.displayName = 'AglynContainer'\n\n// Component ids are persisted in screen documents; keep the legacy ids.\nexport const ID: Aglyn.ComponentId = 'muiContainer'\n\nexport const schema: Aglyn.ComponentSchema<ContainerProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Container',\n description: 'Centers your page content and caps how wide it gets.',\n category: Aglyn.ComponentCategory.LAYOUT,\n icon: {\n path: mdiViewArrayOutline.path,\n sx: { color: '#2196f3' },\n },\n attributes: [\n semanticElementAttribute('this container'),\n semanticElementLabelAttribute(),\n {\n name: 'fixed',\n description:\n \"If true, set the max-width to match the min-width of the current breakpoint. This is useful if you'd prefer to design for a fixed set of sizes instead of trying to accommodate a fully fluid viewport. It's fluid by default.\",\n component: Aglyn.FieldComponentType.SWITCH,\n label: 'Fixed Breakpoints',\n },\n {\n name: 'disableGutters',\n description:\n 'Removes the container’s side padding, so its content runs to the ' +\n 'full width it is centered in. Use it for a full-bleed band; leave ' +\n 'it off for text, which needs the margin on a phone.',\n component: Aglyn.FieldComponentType.SWITCH,\n label: 'Disable Gutters?',\n },\n {\n name: 'maxWidth',\n description:\n 'The width this section stops growing at. New containers start at ' +\n 'XL (1536px), the standard for full-width page sections. Long-form ' +\n 'prose — legal pages, docs, blog and changelog bodies — uses MD ' +\n '(900px), which lands near the 65–75 characters a line that reads ' +\n 'comfortably; the \"Prose Container\" preset starts there. LG (1200px) ' +\n 'is the deliberate middle case for a wide but text-led section, not ' +\n 'a default to spread. Clearing the field falls back to the MUI ' +\n 'default of LG. There is no \"Default\" option: \"Fluid Responsive\" is ' +\n 'the only way to render edge-to-edge, and it has to be chosen on ' +\n 'purpose.',\n component: Aglyn.FieldComponentType.SELECT,\n label: 'Max Width',\n // Every value here is a real, persistable one. The list used to open\n // with `{ value: '', label: 'Default' }`, which was unpersistable by\n // construction — the attributes form strips `''` on change (AGL-1191)\n // — and, on any path that DID land it, rendered full-bleed rather than\n // any default at all (AGL-1435).\n options: [\n { value: 'xs', label: 'XS - Mobile (444px)' },\n { value: 'sm', label: 'SM - Tablet (600px)' },\n { value: 'md', label: 'MD - Laptop (900px)' },\n { value: 'lg', label: 'LG - Desktop (1200px)' },\n { value: 'xl', label: 'XL - Widescreen (1536px, section standard)' },\n { value: false, label: 'Fluid Responsive (no max width)' },\n ],\n },\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Container',\n icon: {\n path: mdiViewArrayOutline.path,\n sx: { color: '#2196f3' },\n },\n category: Aglyn.ComponentCategory.LAYOUT,\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n // Explicit, not implicit: a dropped Container is a page section, and\n // sections are XL here (245 of the 248 authored on the marketing host).\n // Relying on MUI's implicit `lg` is what made \"what does no value mean\"\n // an open question in the first place (AGL-1435).\n props: { maxWidth: 'xl' },\n },\n },\n {\n // Case is MD on READING grounds, not as \"one step down\" — at XL a\n // paragraph runs 110–120 characters a line, and even LG (1200px) is far\n // past the 65–75 the eye tracks without losing its place. MD is the\n // stock breakpoint that lands in that band, so the standard grows a\n // third case without growing a bespoke number: the 1328px ban holds\n // because 1328 is a content width, not a breakpoint.\n $id: generatePresetId(ID, 'prose'),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Prose Container',\n icon: {\n path: mdiTextBoxOutline.path,\n sx: { color: '#2196f3' },\n },\n category: Aglyn.ComponentCategory.LAYOUT,\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: { maxWidth: 'md' },\n },\n },\n]\n\nexport default Container\n"],"names":["Aglyn","mdiTextBoxOutline","mdiViewArrayOutline","MuiContainer","createElement","forwardRef","BUNDLE_ID","applySemanticElement","semanticElementAttribute","semanticElementLabelAttribute","dropClearedProps","generatePresetId","Container","props","ref","displayName","ID","schema","$id","pluginId","description","category","ComponentCategory","LAYOUT","icon","path","sx","color","attributes","name","component","FieldComponentType","SWITCH","label","SELECT","options","value","presets","type","NodeType","PRESET","data","componentId","maxWidth"],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import * as Aglyn from '@aglyn/aglyn';
|
|
18
|
+
import type { SxProps } from '@mui/material/styles';
|
|
19
|
+
export declare const ID: Aglyn.ComponentId;
|
|
20
|
+
export interface CustomHtmlProps {
|
|
21
|
+
/** Author-provided markup, sanitized before every render. */
|
|
22
|
+
html?: string;
|
|
23
|
+
/** Scoped styles injected as a <style> tag (selectors are the
|
|
24
|
+
* author's responsibility; keep them specific). */
|
|
25
|
+
css?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Embed mode (AGL-320): renders the RAW snippet inside a sandboxed
|
|
28
|
+
* iframe (scripts allowed, same-origin denied) for third-party
|
|
29
|
+
* widgets. Business-plan gated by the contentGating entitlement's
|
|
30
|
+
* sibling `commerce` checks server-side; the besigner surfaces the
|
|
31
|
+
* gate in the attribute description.
|
|
32
|
+
*/
|
|
33
|
+
embedMode?: boolean;
|
|
34
|
+
/** Iframe height (px) in embed mode. */
|
|
35
|
+
embedHeight?: number;
|
|
36
|
+
/**
|
|
37
|
+
* Authored node styles, handed over by the renderer rather than typed
|
|
38
|
+
* into an attribute — recomposed below so the Styles panel's values are
|
|
39
|
+
* merged rather than replaced (AGL-1284).
|
|
40
|
+
*/
|
|
41
|
+
sx?: SxProps;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Sanitization policy (AGL-320): an allowlist — no scripts, iframes,
|
|
45
|
+
* objects, or event-handler attributes. Applied at EVERY render (server,
|
|
46
|
+
* client, and besigner canvas), so stored markup can never execute even if a
|
|
47
|
+
* write bypassed the console. Embed mode never touches the DOM — the raw
|
|
48
|
+
* snippet only exists inside `sandbox="allow-scripts"` (no
|
|
49
|
+
* allow-same-origin), so it cannot reach cookies, storage, or the parent
|
|
50
|
+
* page.
|
|
51
|
+
*
|
|
52
|
+
* The engine is `Aglyn.sanitizeAuthorHtml` (AGL-1901), which replaced a
|
|
53
|
+
* per-instance DOMPurify. DOMPurify needs a real DOM, so this component
|
|
54
|
+
* could only sanitize in an effect, and every Custom HTML block was
|
|
55
|
+
* therefore absent from the server response — the SSR/SEO half AGL-1268 left
|
|
56
|
+
* open. The new sanitizer is a pure function of the string, so the same call
|
|
57
|
+
* runs on the server and in the browser and both produce the same bytes.
|
|
58
|
+
*
|
|
59
|
+
* What carried over unchanged:
|
|
60
|
+
*
|
|
61
|
+
* - the forbidden set (`script`, `iframe`, `object`, `embed`, `form`,
|
|
62
|
+
* `base`), `srcdoc`/`formaction`, and `ALLOW_DATA_ATTR: false`;
|
|
63
|
+
* - AGL-1725's `style`-attribute rule. DOMPurify performs NO CSS filtering,
|
|
64
|
+
* which is why that hook existed at all; `sanitizeAuthorHtml` applies
|
|
65
|
+
* `sanitizeAuthorCss` to every `style` attribute itself, so the rule is
|
|
66
|
+
* part of the sanitizer rather than a hook a future caller could forget to
|
|
67
|
+
* register.
|
|
68
|
+
* - the `<style>` element being dropped from the `html` attribute, so the
|
|
69
|
+
* `css` attribute below stays the ONLY route to a style block. That was
|
|
70
|
+
* DOMPurify's behaviour and `custom-html-author-css.spec.tsx` pins it;
|
|
71
|
+
* a first pass at AGL-1901 mistook it for a gap and let the tag through,
|
|
72
|
+
* which that spec caught.
|
|
73
|
+
*/
|
|
74
|
+
export declare function sanitizeCustomHtml(html: string): string;
|
|
75
|
+
declare const CustomHtml: import("react").ForwardRefExoticComponent<CustomHtmlProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
76
|
+
export declare const schema: Aglyn.ComponentSchema<CustomHtmlProps>;
|
|
77
|
+
export declare const presets: Aglyn.PresetSchema[];
|
|
78
|
+
export default CustomHtml;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
|
|
3
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
4
|
+
/**
|
|
5
|
+
* @license
|
|
6
|
+
* Copyright 2026 Aglyn LLC
|
|
7
|
+
*
|
|
8
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
9
|
+
* you may not use this file except in compliance with the License.
|
|
10
|
+
* You may obtain a copy of the License at
|
|
11
|
+
*
|
|
12
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
13
|
+
*
|
|
14
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
15
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
16
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
17
|
+
* See the License for the specific language governing permissions and
|
|
18
|
+
* limitations under the License.
|
|
19
|
+
*/ import * as Aglyn from "@aglyn/aglyn";
|
|
20
|
+
import { mdiCodeTags } from "@aglyn/shared-data-mdi";
|
|
21
|
+
import Box from "@mui/material/Box";
|
|
22
|
+
import Typography from "@mui/material/Typography";
|
|
23
|
+
import { forwardRef, useContext } from "react";
|
|
24
|
+
import { BUNDLE_ID } from "../constants/bundle-common.js";
|
|
25
|
+
import { generatePresetId } from "../utils/generate-preset-id.js";
|
|
26
|
+
// Component ids are persisted in screen documents; never rename.
|
|
27
|
+
export const ID = 'custom-html';
|
|
28
|
+
/**
|
|
29
|
+
* Sanitization policy (AGL-320): an allowlist — no scripts, iframes,
|
|
30
|
+
* objects, or event-handler attributes. Applied at EVERY render (server,
|
|
31
|
+
* client, and besigner canvas), so stored markup can never execute even if a
|
|
32
|
+
* write bypassed the console. Embed mode never touches the DOM — the raw
|
|
33
|
+
* snippet only exists inside `sandbox="allow-scripts"` (no
|
|
34
|
+
* allow-same-origin), so it cannot reach cookies, storage, or the parent
|
|
35
|
+
* page.
|
|
36
|
+
*
|
|
37
|
+
* The engine is `Aglyn.sanitizeAuthorHtml` (AGL-1901), which replaced a
|
|
38
|
+
* per-instance DOMPurify. DOMPurify needs a real DOM, so this component
|
|
39
|
+
* could only sanitize in an effect, and every Custom HTML block was
|
|
40
|
+
* therefore absent from the server response — the SSR/SEO half AGL-1268 left
|
|
41
|
+
* open. The new sanitizer is a pure function of the string, so the same call
|
|
42
|
+
* runs on the server and in the browser and both produce the same bytes.
|
|
43
|
+
*
|
|
44
|
+
* What carried over unchanged:
|
|
45
|
+
*
|
|
46
|
+
* - the forbidden set (`script`, `iframe`, `object`, `embed`, `form`,
|
|
47
|
+
* `base`), `srcdoc`/`formaction`, and `ALLOW_DATA_ATTR: false`;
|
|
48
|
+
* - AGL-1725's `style`-attribute rule. DOMPurify performs NO CSS filtering,
|
|
49
|
+
* which is why that hook existed at all; `sanitizeAuthorHtml` applies
|
|
50
|
+
* `sanitizeAuthorCss` to every `style` attribute itself, so the rule is
|
|
51
|
+
* part of the sanitizer rather than a hook a future caller could forget to
|
|
52
|
+
* register.
|
|
53
|
+
* - the `<style>` element being dropped from the `html` attribute, so the
|
|
54
|
+
* `css` attribute below stays the ONLY route to a style block. That was
|
|
55
|
+
* DOMPurify's behaviour and `custom-html-author-css.spec.tsx` pins it;
|
|
56
|
+
* a first pass at AGL-1901 mistook it for a gap and let the tag through,
|
|
57
|
+
* which that spec caught.
|
|
58
|
+
*/ export function sanitizeCustomHtml(html) {
|
|
59
|
+
return Aglyn.sanitizeAuthorHtml(html);
|
|
60
|
+
}
|
|
61
|
+
const CustomHtml = /*#__PURE__*/ forwardRef((props, ref)=>{
|
|
62
|
+
const { html, css, embedMode, embedHeight } = props, rest = _object_without_properties_loose(props, [
|
|
63
|
+
"html",
|
|
64
|
+
"css",
|
|
65
|
+
"embedMode",
|
|
66
|
+
"embedHeight"
|
|
67
|
+
]);
|
|
68
|
+
// Node styles ride the renderer-merged sx; recompose (stack.ts pattern).
|
|
69
|
+
const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [
|
|
70
|
+
props['sx']
|
|
71
|
+
];
|
|
72
|
+
const { hostId } = Aglyn.useSite();
|
|
73
|
+
const { suppressNavigation } = useContext(Aglyn.ScreenLinkContext);
|
|
74
|
+
// Same shape as typography's rich text, and fixed the same way
|
|
75
|
+
// (AGL-1901): sanitized DURING render rather than in an effect. The
|
|
76
|
+
// effect existed because DOMPurify needs a DOM — the server rendered
|
|
77
|
+
// nothing, so the first client render had to render nothing too or
|
|
78
|
+
// hydration would mismatch (AGL-1268). `sanitizeCustomHtml` is now a
|
|
79
|
+
// pure function of the string, so both sides compute the same markup and
|
|
80
|
+
// the block is in the server response where a crawler can read it.
|
|
81
|
+
const sanitized = !html || embedMode ? '' : sanitizeCustomHtml(html);
|
|
82
|
+
if (!(html == null ? void 0 : html.trim())) {
|
|
83
|
+
// The hint is for the author, so only editing surfaces draw it; a
|
|
84
|
+
// published page renders the bare element.
|
|
85
|
+
if (!suppressNavigation) return /*#__PURE__*/ _jsx(Box, _extends({
|
|
86
|
+
ref: ref
|
|
87
|
+
}, rest));
|
|
88
|
+
return /*#__PURE__*/ _jsx(Box, _extends({
|
|
89
|
+
ref: ref
|
|
90
|
+
}, rest, {
|
|
91
|
+
// MERGE, never replace (AGL-1284): the placeholder's own look goes
|
|
92
|
+
// first so an author who styles the empty state can override it.
|
|
93
|
+
sx: [
|
|
94
|
+
{
|
|
95
|
+
p: 3,
|
|
96
|
+
border: '1px dashed',
|
|
97
|
+
borderColor: 'divider',
|
|
98
|
+
borderRadius: 1,
|
|
99
|
+
color: 'text.secondary',
|
|
100
|
+
fontSize: 13,
|
|
101
|
+
fontFamily: 'system-ui, sans-serif'
|
|
102
|
+
},
|
|
103
|
+
...nodeSx
|
|
104
|
+
],
|
|
105
|
+
children: '</> Custom HTML — add markup in the attributes panel'
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
if (embedMode) {
|
|
109
|
+
return /*#__PURE__*/ _jsxs(Box, _extends({
|
|
110
|
+
ref: ref
|
|
111
|
+
}, rest, {
|
|
112
|
+
children: [
|
|
113
|
+
!hostId ? /*#__PURE__*/ _jsx(Typography, {
|
|
114
|
+
variant: "caption",
|
|
115
|
+
color: "text.secondary",
|
|
116
|
+
children: 'Embed (sandboxed iframe) — runs on the live site'
|
|
117
|
+
}) : null,
|
|
118
|
+
/*#__PURE__*/ _jsx("iframe", {
|
|
119
|
+
title: "Embedded content",
|
|
120
|
+
sandbox: "allow-scripts allow-popups",
|
|
121
|
+
srcDoc: html,
|
|
122
|
+
style: {
|
|
123
|
+
width: '100%',
|
|
124
|
+
height: `${Math.max(40, embedHeight != null ? embedHeight : 320)}px`,
|
|
125
|
+
border: 0
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
]
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
return /*#__PURE__*/ _jsxs(Box, _extends({
|
|
132
|
+
ref: ref
|
|
133
|
+
}, rest, {
|
|
134
|
+
children: [
|
|
135
|
+
css ? /*#__PURE__*/ _jsx("style", {
|
|
136
|
+
children: Aglyn.sanitizeAuthorCss(css)
|
|
137
|
+
}) : null,
|
|
138
|
+
/*#__PURE__*/ _jsx("div", {
|
|
139
|
+
dangerouslySetInnerHTML: {
|
|
140
|
+
__html: sanitized
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
]
|
|
144
|
+
}));
|
|
145
|
+
});
|
|
146
|
+
CustomHtml.displayName = 'AglynCustomHtml';
|
|
147
|
+
export const schema = {
|
|
148
|
+
$id: ID,
|
|
149
|
+
pluginId: BUNDLE_ID,
|
|
150
|
+
displayName: 'Custom HTML',
|
|
151
|
+
description: 'Your own markup, sanitised on render, or isolated in a sandboxed iframe.',
|
|
152
|
+
category: Aglyn.ComponentCategory.DATA_DISPLAY,
|
|
153
|
+
icon: {
|
|
154
|
+
path: mdiCodeTags.path,
|
|
155
|
+
sx: {
|
|
156
|
+
color: '#7b1fa2'
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
flags: {
|
|
160
|
+
selfClosing: Aglyn.FEATURE_FLAG.ENABLED
|
|
161
|
+
},
|
|
162
|
+
attributes: [
|
|
163
|
+
{
|
|
164
|
+
name: 'html',
|
|
165
|
+
label: 'HTML',
|
|
166
|
+
description: 'Sanitized on every render: scripts, iframes, and on* handlers ' + 'are stripped. Use Embed mode for third-party widgets.',
|
|
167
|
+
component: Aglyn.FieldComponentType.TEXT_FIELD
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: 'css',
|
|
171
|
+
label: 'CSS',
|
|
172
|
+
// AGL-1725 — the "warn" half of warn-and-disclose. An author can only
|
|
173
|
+
// weigh this egress if they are told it exists, and the attributes
|
|
174
|
+
// panel is where they are typing the url().
|
|
175
|
+
description: 'Injected as a style tag — keep selectors specific. A url() ' + 'pointing off your site makes every visitor’s browser contact that ' + 'host, which sees their IP address and which page they are on. ' + 'Insecure http:// URLs are not loaded.',
|
|
176
|
+
component: Aglyn.FieldComponentType.TEXT_FIELD
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: 'embedMode',
|
|
180
|
+
label: 'Embed mode (sandboxed)',
|
|
181
|
+
description: 'Runs the raw snippet in a sandboxed iframe (scripts allowed, no ' + 'access to your site or visitor data). For third-party widgets.',
|
|
182
|
+
component: Aglyn.FieldComponentType.CHECKBOX
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: 'embedHeight',
|
|
186
|
+
label: 'Embed height (px)',
|
|
187
|
+
description: 'Iframe height in embed mode (default 320).',
|
|
188
|
+
component: Aglyn.FieldComponentType.TEXT_FIELD,
|
|
189
|
+
type: 'number'
|
|
190
|
+
}
|
|
191
|
+
]
|
|
192
|
+
};
|
|
193
|
+
export const presets = [
|
|
194
|
+
{
|
|
195
|
+
$id: generatePresetId(ID),
|
|
196
|
+
type: Aglyn.NodeType.PRESET,
|
|
197
|
+
displayName: 'Custom HTML',
|
|
198
|
+
pluginId: BUNDLE_ID,
|
|
199
|
+
description: 'Sanitized markup block, with a sandboxed embed mode',
|
|
200
|
+
category: Aglyn.ComponentCategory.DATA_DISPLAY,
|
|
201
|
+
icon: {
|
|
202
|
+
path: mdiCodeTags.path,
|
|
203
|
+
sx: {
|
|
204
|
+
color: '#7b1fa2'
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
data: {
|
|
208
|
+
$id: null,
|
|
209
|
+
componentId: ID,
|
|
210
|
+
pluginId: BUNDLE_ID,
|
|
211
|
+
props: {
|
|
212
|
+
html: '<p>Hello from custom HTML</p>'
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
];
|
|
217
|
+
export default CustomHtml;
|
|
218
|
+
|
|
219
|
+
//# sourceMappingURL=custom-html.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/custom-html.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { mdiCodeTags } from '@aglyn/shared-data-mdi'\nimport Box from '@mui/material/Box'\nimport type { SxProps } from '@mui/material/styles'\nimport Typography from '@mui/material/Typography'\nimport { forwardRef, useContext } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n// Component ids are persisted in screen documents; never rename.\nexport const ID: Aglyn.ComponentId = 'custom-html'\n\nexport interface CustomHtmlProps {\n /** Author-provided markup, sanitized before every render. */\n html?: string\n /** Scoped styles injected as a <style> tag (selectors are the\n * author's responsibility; keep them specific). */\n css?: string\n /**\n * Embed mode (AGL-320): renders the RAW snippet inside a sandboxed\n * iframe (scripts allowed, same-origin denied) for third-party\n * widgets. Business-plan gated by the contentGating entitlement's\n * sibling `commerce` checks server-side; the besigner surfaces the\n * gate in the attribute description.\n */\n embedMode?: boolean\n /** Iframe height (px) in embed mode. */\n embedHeight?: number\n /**\n * Authored node styles, handed over by the renderer rather than typed\n * into an attribute — recomposed below so the Styles panel's values are\n * merged rather than replaced (AGL-1284).\n */\n sx?: SxProps\n}\n\n/**\n * Sanitization policy (AGL-320): an allowlist — no scripts, iframes,\n * objects, or event-handler attributes. Applied at EVERY render (server,\n * client, and besigner canvas), so stored markup can never execute even if a\n * write bypassed the console. Embed mode never touches the DOM — the raw\n * snippet only exists inside `sandbox=\"allow-scripts\"` (no\n * allow-same-origin), so it cannot reach cookies, storage, or the parent\n * page.\n *\n * The engine is `Aglyn.sanitizeAuthorHtml` (AGL-1901), which replaced a\n * per-instance DOMPurify. DOMPurify needs a real DOM, so this component\n * could only sanitize in an effect, and every Custom HTML block was\n * therefore absent from the server response — the SSR/SEO half AGL-1268 left\n * open. The new sanitizer is a pure function of the string, so the same call\n * runs on the server and in the browser and both produce the same bytes.\n *\n * What carried over unchanged:\n *\n * - the forbidden set (`script`, `iframe`, `object`, `embed`, `form`,\n * `base`), `srcdoc`/`formaction`, and `ALLOW_DATA_ATTR: false`;\n * - AGL-1725's `style`-attribute rule. DOMPurify performs NO CSS filtering,\n * which is why that hook existed at all; `sanitizeAuthorHtml` applies\n * `sanitizeAuthorCss` to every `style` attribute itself, so the rule is\n * part of the sanitizer rather than a hook a future caller could forget to\n * register.\n * - the `<style>` element being dropped from the `html` attribute, so the\n * `css` attribute below stays the ONLY route to a style block. That was\n * DOMPurify's behaviour and `custom-html-author-css.spec.tsx` pins it;\n * a first pass at AGL-1901 mistook it for a gap and let the tag through,\n * which that spec caught.\n */\nexport function sanitizeCustomHtml(html: string): string {\n return Aglyn.sanitizeAuthorHtml(html)\n}\n\nconst CustomHtml = forwardRef<HTMLDivElement, CustomHtmlProps>(\n (props, ref) => {\n const { html, css, embedMode, embedHeight, ...rest } = props\n // Node styles ride the renderer-merged sx; recompose (stack.ts pattern).\n const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [props['sx']]\n const { hostId } = Aglyn.useSite()\n const { suppressNavigation } = useContext(Aglyn.ScreenLinkContext)\n\n // Same shape as typography's rich text, and fixed the same way\n // (AGL-1901): sanitized DURING render rather than in an effect. The\n // effect existed because DOMPurify needs a DOM — the server rendered\n // nothing, so the first client render had to render nothing too or\n // hydration would mismatch (AGL-1268). `sanitizeCustomHtml` is now a\n // pure function of the string, so both sides compute the same markup and\n // the block is in the server response where a crawler can read it.\n const sanitized = !html || embedMode ? '' : sanitizeCustomHtml(html)\n\n if (!html?.trim()) {\n // The hint is for the author, so only editing surfaces draw it; a\n // published page renders the bare element.\n if (!suppressNavigation) return <Box ref={ref} {...rest} />\n return (\n <Box\n ref={ref}\n {...rest}\n // MERGE, never replace (AGL-1284): the placeholder's own look goes\n // first so an author who styles the empty state can override it.\n sx={[\n {\n p: 3,\n border: '1px dashed',\n borderColor: 'divider',\n borderRadius: 1,\n color: 'text.secondary',\n fontSize: 13,\n fontFamily: 'system-ui, sans-serif',\n },\n ...nodeSx,\n ]}\n >\n {'</> Custom HTML — add markup in the attributes panel'}\n </Box>\n )\n }\n\n if (embedMode) {\n return (\n <Box ref={ref} {...rest}>\n {!hostId ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Embed (sandboxed iframe) — runs on the live site'}\n </Typography>\n ) : null}\n <iframe\n title=\"Embedded content\"\n sandbox=\"allow-scripts allow-popups\"\n srcDoc={html}\n style={{\n width: '100%',\n height: `${Math.max(40, embedHeight ?? 320)}px`,\n border: 0,\n }}\n />\n </Box>\n )\n }\n\n return (\n <Box ref={ref} {...rest}>\n {/* AGL-1725: the `css` attribute never reached DOMPurify — that\n policy covers `html` only — so this <style> block was the one\n surface on a published site where an author's raw CSS shipped to\n every visitor unexamined. `sanitizeAuthorCss` refuses schemes\n that cannot be defended (http:, and anything not https/data/blob)\n and deliberately leaves the HOST alone; see the module header on\n why AGL-1701's input restriction does not transfer to a site\n owner styling their own site. */}\n {css ? <style>{Aglyn.sanitizeAuthorCss(css)}</style> : null}\n {/* Sanitized above on every render — see sanitizeCustomHtml. */}\n <div dangerouslySetInnerHTML={{ __html: sanitized }} />\n </Box>\n )\n },\n)\nCustomHtml.displayName = 'AglynCustomHtml'\n\nexport const schema: Aglyn.ComponentSchema<CustomHtmlProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Custom HTML',\n description:\n 'Your own markup, sanitised on render, or isolated in a sandboxed iframe.',\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: { path: mdiCodeTags.path, sx: { color: '#7b1fa2' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'html',\n label: 'HTML',\n description:\n 'Sanitized on every render: scripts, iframes, and on* handlers ' +\n 'are stripped. Use Embed mode for third-party widgets.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'css',\n label: 'CSS',\n // AGL-1725 — the \"warn\" half of warn-and-disclose. An author can only\n // weigh this egress if they are told it exists, and the attributes\n // panel is where they are typing the url().\n description:\n 'Injected as a style tag — keep selectors specific. A url() ' +\n 'pointing off your site makes every visitor’s browser contact that ' +\n 'host, which sees their IP address and which page they are on. ' +\n 'Insecure http:// URLs are not loaded.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'embedMode',\n label: 'Embed mode (sandboxed)',\n description:\n 'Runs the raw snippet in a sandboxed iframe (scripts allowed, no ' +\n 'access to your site or visitor data). For third-party widgets.',\n component: Aglyn.FieldComponentType.CHECKBOX,\n },\n {\n name: 'embedHeight',\n label: 'Embed height (px)',\n description: 'Iframe height in embed mode (default 320).',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Custom HTML',\n pluginId: BUNDLE_ID,\n description: 'Sanitized markup block, with a sandboxed embed mode',\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: { path: mdiCodeTags.path, sx: { color: '#7b1fa2' } },\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: { html: '<p>Hello from custom HTML</p>' },\n },\n },\n]\n\nexport default CustomHtml\n"],"names":["Aglyn","mdiCodeTags","Box","Typography","forwardRef","useContext","BUNDLE_ID","generatePresetId","ID","sanitizeCustomHtml","html","sanitizeAuthorHtml","CustomHtml","props","ref","css","embedMode","embedHeight","rest","nodeSx","Array","isArray","hostId","useSite","suppressNavigation","ScreenLinkContext","sanitized","trim","sx","p","border","borderColor","borderRadius","color","fontSize","fontFamily","variant","iframe","title","sandbox","srcDoc","style","width","height","Math","max","sanitizeAuthorCss","div","dangerouslySetInnerHTML","__html","displayName","schema","$id","pluginId","description","category","ComponentCategory","DATA_DISPLAY","icon","path","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","name","label","component","FieldComponentType","TEXT_FIELD","CHECKBOX","type","presets","NodeType","PRESET","data","componentId"],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import * as Aglyn from '@aglyn/aglyn';
|
|
18
|
+
export declare const DATA_TABLE_ID: Aglyn.ComponentId;
|
|
19
|
+
export interface DataTableProps {
|
|
20
|
+
/**
|
|
21
|
+
* The grid, pipe-delimited, one row per line — and its column alignment,
|
|
22
|
+
* as a markdown divider row. See `@aglyn/shared-data-enums`'s `data-table` utilities.
|
|
23
|
+
*/
|
|
24
|
+
rows?: string;
|
|
25
|
+
/** Render the first row as a header. */
|
|
26
|
+
headerRow?: boolean;
|
|
27
|
+
/** 1-based column to emphasise, or 0/absent for none. */
|
|
28
|
+
emphasizeColumn?: number | string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A comparison table (AGL-2543).
|
|
32
|
+
*
|
|
33
|
+
* The feature matrix is the load-bearing block on a competitor comparison
|
|
34
|
+
* page, and the palette had no way to build one: searching `table` returned
|
|
35
|
+
* **Table of Contents** and **Pricing Table**, neither of which is a general
|
|
36
|
+
* grid. The only route was to author markdown pipe syntax inside a Markdown
|
|
37
|
+
* element — which renders correctly and is exactly the audience the besigner
|
|
38
|
+
* exists to spare.
|
|
39
|
+
*
|
|
40
|
+
* Three things the Markdown route could not give, and the reason this is an
|
|
41
|
+
* element rather than a docs note:
|
|
42
|
+
*
|
|
43
|
+
* - **Header emphasis**, without asking an author to remember that the first
|
|
44
|
+
* row is special.
|
|
45
|
+
* - **A highlighted column** — the "ours" column on a comparison page is the
|
|
46
|
+
* entire point of the block, and markdown has no way to say it.
|
|
47
|
+
* - **A grid to edit**, rather than pipe syntax in a text box — see the
|
|
48
|
+
* `DATA_TABLE` field this element's `rows` attribute asks for.
|
|
49
|
+
*
|
|
50
|
+
* Narrow-screen behaviour is NOT one of them (AGL-2568). Both routes draw the
|
|
51
|
+
* same block, so both take the same treatment from `scroll-overflow`: the
|
|
52
|
+
* grid scrolls inside its own box once it is too wide to read, and says so.
|
|
53
|
+
*/
|
|
54
|
+
declare const DataTable: import("react").ForwardRefExoticComponent<DataTableProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
55
|
+
export default DataTable;
|
|
56
|
+
export declare const dataTableSchema: Aglyn.ComponentSchema<DataTableProps>;
|
|
57
|
+
export declare const dataTablePresets: Aglyn.PresetSchema[];
|