@zohodesk/library-platform 1.2.0-exp.45 → 1.2.2-exp.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/es/.DS_Store +0 -0
- package/es/cc/table-connected/SdkContract.js +28 -3
- package/es/cc/table-list/Properties.js +9 -8
- package/es/cc/table-list/row/Properties.js +19 -6
- package/es/library/custom-component/frameworks/json-schema-validator/Validator.js +57 -44
- package/es/library/custom-component/frameworks/json-schema-validator/__generated__/registry.js +92 -0
- package/es/library/custom-component/frameworks/json-schema-validator/__generated__/validators.js +1 -0
- package/es/library/custom-component/frameworks/json-schema-validator/__tests__/Validator.test.js +478 -0
- package/es/library/custom-component/frameworks/ui/DependencyFactory.js +16 -0
- package/es/{flex-layout → platform}/.DS_Store +0 -0
- package/es/platform/client-actions/cc/action-event-mediator/Properties.js +18 -4
- package/package.json +6 -4
- package/es/bc/sort-by/SortOrderEnum.js +0 -8
- package/es/cc/flex-container/Properties.js +0 -79
- package/es/cc/flex-container/Types.js +0 -39
- package/es/cc/flex-container/index.js +0 -2
- package/es/cc/tags/model/TagModel.js +0 -26
- package/es/library/behaviours/sort-by/adapters/controllers/SortBy.js +0 -28
- package/es/library/behaviours/sort-by/applications/usecases/SortBy.js +0 -28
- package/es/library/behaviours/sort-by/frameworks/ui/DemoBehaviour.js +0 -20
- package/es/library/custom-component/domain/entities/SlotValidator.js +0 -84
- package/es/library/custom-component/frameworks/ui/CustomComponentFactory.js +0 -56
- package/es/library/dot/components/part-wrapper/POC_DOCUMENT.md +0 -648
- package/es/library/dot/components/part-wrapper/applications/interfaces/State.js +0 -1
- package/es/library/dot/components/part-wrapper/domain/entities/interfaces/Properties.js +0 -52
- package/es/library/dot/components/part-wrapper/frameworks/ui/PartWrapper.js +0 -24
- package/es/library/dot/components/part-wrapper/frameworks/ui/PartWrapperFactory.js +0 -27
- package/es/library/dot/components/part-wrapper/frameworks/ui/PartWrapperView.js +0 -45
- package/es/library/dot/components/part-wrapper/frameworks/ui/css/PartWrapper.module.css +0 -16
- package/es/library/dot/components/part-wrapper/poc/index.js +0 -9
- package/es/library/dot/components/part-wrapper/poc/layouts/POCFormLayout.js +0 -130
- package/es/library/dot/components/part-wrapper/poc/layouts/POCMixedLayout.js +0 -128
- package/es/library/dot/components/part-wrapper/poc/layouts/POCTableCellLayout.js +0 -105
- package/es/library/dot/components/part-wrapper/poc/layouts/css/poc-form-layout.module.css +0 -46
- package/es/library/dot/components/part-wrapper/poc/layouts/css/poc-mixed-layout.module.css +0 -89
- package/es/library/dot/components/part-wrapper/poc/layouts/css/poc-table-cell-layout.module.css +0 -70
- package/es/library/dot/components/table-list/adapters/controllers/SortByController.js +0 -25
- package/es/library/dot/components/table-list/adapters/controllers/SortedController.js +0 -18
- package/es/library/dot/components/table-list/frameworks/ui/handlers/HandleSortClick.js +0 -12
- package/es/library/dot/legacy-to-new-arch/flex-container/frameworks/ui/FlexContainer.js +0 -11
- package/es/library/dot/legacy-to-new-arch/flex-container/frameworks/ui/FlexContainerView.js +0 -37
- package/es/library/poc-slot-usage/employee-card/adapters/presenters/EmployeeCardStateMapper.js +0 -14
- package/es/library/poc-slot-usage/employee-card/applications/usecases/BuildEmployeeCardUseCase.js +0 -25
- package/es/library/poc-slot-usage/employee-card/domain/entities/EmployeeCardSlots.js +0 -68
- package/es/library/poc-slot-usage/employee-card/frameworks/ui/EmployeeCard.js +0 -13
- package/es/library/poc-slot-usage/employee-card/frameworks/ui/EmployeeCardView.js +0 -42
- package/es/library/poc-slot-usage/flex-container/adapters/presenters/FlexContainerStateMapper.js +0 -8
- package/es/library/poc-slot-usage/flex-container/applications/usecases/BuildFlexContainerUseCase.js +0 -44
- package/es/library/poc-slot-usage/flex-container/domain/entities/FlexContainerSlots.js +0 -18
- package/es/library/poc-slot-usage/flex-container/frameworks/ui/FlexContainer.js +0 -5
- package/es/library/poc-slot-usage/flex-container/frameworks/ui/FlexContainerView.js +0 -40
- package/es/library/poc-slot-usage/index.js +0 -7
- package/es/library/poc-slot-usage/mismatched-card/applications/usecases/BuildMismatchedCardUseCase.js +0 -23
- package/es/library/poc-slot-usage/mismatched-card/domain/entities/MismatchedCardSlots.js +0 -39
- package/es/library/poc-slot-usage/mismatched-card/frameworks/ui/MismatchedCard.js +0 -9
- package/es/library/poc-slot-usage/mismatched-card/frameworks/ui/MismatchedCardView.js +0 -39
- package/es/library/poc-slot-usage/product-action/applications/usecases/BuildProductActionUseCase.js +0 -21
- package/es/library/poc-slot-usage/product-action/frameworks/ui/ProductAction.js +0 -5
- package/es/library/poc-slot-usage/product-action/frameworks/ui/ProductActionView.js +0 -24
- package/es/library/poc-slot-usage/product-body/applications/usecases/BuildProductBodyUseCase.js +0 -21
- package/es/library/poc-slot-usage/product-body/frameworks/ui/ProductBody.js +0 -5
- package/es/library/poc-slot-usage/product-body/frameworks/ui/ProductBodyView.js +0 -20
- package/es/library/poc-slot-usage/product-card/adapters/presenters/ProductCardStateMapper.js +0 -8
- package/es/library/poc-slot-usage/product-card/applications/usecases/BuildProductCardUseCase.js +0 -34
- package/es/library/poc-slot-usage/product-card/domain/entities/ProductCardSlots.js +0 -70
- package/es/library/poc-slot-usage/product-card/frameworks/ui/ProductCard.js +0 -5
- package/es/library/poc-slot-usage/product-card/frameworks/ui/ProductCardView.js +0 -40
- package/es/library/poc-slot-usage/product-title/applications/usecases/BuildProductTitleUseCase.js +0 -21
- package/es/library/poc-slot-usage/product-title/frameworks/ui/ProductTitle.js +0 -5
- package/es/library/poc-slot-usage/product-title/frameworks/ui/ProductTitleView.js +0 -20
- package/es/platform/components/table-connected/adapters/controllers/ColumnChooserOpenedController.js +0 -28
- package/es/platform/components/table-connected/adapters/controllers/ColumnChooserUpdateController.js +0 -31
- package/es/platform/zlist/adapters/gateways/SortBy.js +0 -38
- package/es/platform/zlist/applications/interfaces/gateways/ISortBy.js +0 -1
- package/es/platform/zlist/domain/entities/SortBy.js +0 -58
- package/es/platform/zlist/domain/entities/interfaces/ISortBy.js +0 -1
- package/es/to-do-app/ToDo.js +0 -10
- package/es/to-do-app/cc/button/Constants.js +0 -0
- package/es/to-do-app/cc/button/Events.js +0 -0
- package/es/to-do-app/cc/button/Properties.js +0 -4
- package/es/to-do-app/cc/button/index.js +0 -0
- package/es/to-do-app/cc/textbox/Constants.js +0 -7
- package/es/to-do-app/cc/textbox/Events.js +0 -20
- package/es/to-do-app/cc/textbox/Properties.js +0 -51
- package/es/to-do-app/cc/textbox/index.js +0 -3
- package/es/to-do-app/component/textbox/framework/TextBox.js +0 -30
- package/es/to-do-app/component/textbox/framework/TextBoxView.js +0 -42
- /package/es/library/{behaviours/sort-by/applications/interfaces/output/SortByOutputModel.js → custom-component/frameworks/json-schema-validator/__generated__/registry.d.js} +0 -0
|
@@ -1,648 +0,0 @@
|
|
|
1
|
-
# POC Document: PartWrapper — Styling Boundaries for ACA Layouts
|
|
2
|
-
|
|
3
|
-
**Reference:** ADR UI-STYLE-003
|
|
4
|
-
**Date:** February 2026
|
|
5
|
-
**Status:** POC Complete
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## 1. Agenda
|
|
10
|
-
|
|
11
|
-
### What is the problem?
|
|
12
|
-
|
|
13
|
-
In our ACA (Adaptive Component Architecture), layout components use inner components like `TextBox`, `Button`, `Text`, etc. These inner components are **legacy** — they were built before ACA.
|
|
14
|
-
|
|
15
|
-
Developers need to **style around** these inner components (add padding, hover effects, borders) **without modifying** the inner components themselves.
|
|
16
|
-
|
|
17
|
-
**Example problem:**
|
|
18
|
-
|
|
19
|
-
```
|
|
20
|
-
┌─ Form Layout ────────────────────────────┐
|
|
21
|
-
│ │
|
|
22
|
-
│ ┌─ TextBox (legacy) ─────────────────┐ │
|
|
23
|
-
│ │ How do I add hover background │ │ ← Can't modify TextBox
|
|
24
|
-
│ │ around this field? │ │
|
|
25
|
-
│ └────────────────────────────────────┘ │
|
|
26
|
-
│ │
|
|
27
|
-
│ ┌─ Email (legacy) ───────────────────┐ │
|
|
28
|
-
│ │ How do I add bottom border │ │ ← Can't modify Email
|
|
29
|
-
│ │ below this field? │ │
|
|
30
|
-
│ └────────────────────────────────────┘ │
|
|
31
|
-
│ │
|
|
32
|
-
└──────────────────────────────────────────┘
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
### What does this POC prove?
|
|
36
|
-
|
|
37
|
-
We can wrap any inner component with a **PartWrapper** that adds `part` / `data-part` attributes as CSS hooks — **without changing any existing component code**.
|
|
38
|
-
|
|
39
|
-
```
|
|
40
|
-
┌─ Form Layout ────────────────────────────┐
|
|
41
|
-
│ │
|
|
42
|
-
│ ┌─ PartWrapper [data-part="field"] ──┐ │ ← CSS target!
|
|
43
|
-
│ │ ┌─ TextBox (untouched) ────────┐ │ │
|
|
44
|
-
│ │ │ ... │ │ │
|
|
45
|
-
│ │ └──────────────────────────────┘ │ │
|
|
46
|
-
│ └────────────────────────────────────┘ │
|
|
47
|
-
│ │
|
|
48
|
-
└──────────────────────────────────────────┘
|
|
49
|
-
|
|
50
|
-
CSS: [data-part="field"]:hover { background: #f8f9fa; }
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
## 2. Master Plan
|
|
56
|
-
|
|
57
|
-
### High-Level Flow
|
|
58
|
-
|
|
59
|
-
```
|
|
60
|
-
┌──────────────────────────────────────────────────────────────────────┐
|
|
61
|
-
│ ACA LAYOUT VIEW │
|
|
62
|
-
│ │
|
|
63
|
-
│ Layout CSS (.module.css) │
|
|
64
|
-
│ ┌────────────────────────────────────────────────────────────────┐ │
|
|
65
|
-
│ │ [data-part="field-container"] { padding: 12px; } │ │
|
|
66
|
-
│ │ [data-part="field-container"]:hover { background: #f8f9fa; } │ │
|
|
67
|
-
│ │ [data-part="action-bar"] { display: flex; gap: 8px; } │ │
|
|
68
|
-
│ └───────────────────────────┬────────────────────────────────────┘ │
|
|
69
|
-
│ │ targets │
|
|
70
|
-
│ ▼ │
|
|
71
|
-
│ Layout JSX (View.tsx) │
|
|
72
|
-
│ ┌────────────────────────────────────────────────────────────────┐ │
|
|
73
|
-
│ │ │ │
|
|
74
|
-
│ │ <PartWrapper part="field-container" content={ │ │
|
|
75
|
-
│ │ <TextBox ... /> ← legacy, untouched │ │
|
|
76
|
-
│ │ } /> │ │
|
|
77
|
-
│ │ │ │
|
|
78
|
-
│ │ <PartWrapper part="field-container" content={ │ │
|
|
79
|
-
│ │ <Email ... /> ← legacy, untouched │ │
|
|
80
|
-
│ │ } /> │ │
|
|
81
|
-
│ │ │ │
|
|
82
|
-
│ └────────────────────────────────────────────────────────────────┘ │
|
|
83
|
-
└──────────────────────────────────────────────────────────────────────┘
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
### Rendered DOM Output
|
|
87
|
-
|
|
88
|
-
```
|
|
89
|
-
<div data-part="form-layout"> ← Layout root
|
|
90
|
-
|
|
91
|
-
<div part="field-container" ← PartWrapper (display: contents)
|
|
92
|
-
data-part="field-container">
|
|
93
|
-
<div class="fieldItem"> ← TextBox renders here (untouched)
|
|
94
|
-
<label>Name</label>
|
|
95
|
-
<input type="text" />
|
|
96
|
-
</div>
|
|
97
|
-
</div>
|
|
98
|
-
|
|
99
|
-
<div part="field-container" ← PartWrapper (display: contents)
|
|
100
|
-
data-part="field-container">
|
|
101
|
-
<div class="fieldItem"> ← Email renders here (untouched)
|
|
102
|
-
<label>Email</label>
|
|
103
|
-
<input type="email" />
|
|
104
|
-
</div>
|
|
105
|
-
</div>
|
|
106
|
-
|
|
107
|
-
</div>
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
### Component Architecture (ACA Layers)
|
|
111
|
-
|
|
112
|
-
```
|
|
113
|
-
PartWrapper follows the SmartActionBand ACA pattern:
|
|
114
|
-
|
|
115
|
-
┌─────────────────────────────────────────────────────────┐
|
|
116
|
-
│ DOMAIN LAYER │
|
|
117
|
-
│ domain/entities/interfaces/Properties.ts │
|
|
118
|
-
│ ┌───────────────────────────────────────────────────┐ │
|
|
119
|
-
│ │ part: string ← required, CSS hook name │ │
|
|
120
|
-
│ │ tagName: string ← default 'div' │ │
|
|
121
|
-
│ │ className: string ← extra CSS classes │ │
|
|
122
|
-
│ │ content: ReactNode ← inner component(s) │ │
|
|
123
|
-
│ └───────────────────────────────────────────────────┘ │
|
|
124
|
-
├─────────────────────────────────────────────────────────┤
|
|
125
|
-
│ APPLICATION LAYER │
|
|
126
|
-
│ applications/interfaces/State.ts │
|
|
127
|
-
│ ┌───────────────────────────────────────────────────┐ │
|
|
128
|
-
│ │ { properties: PartWrapperProperties } │ │
|
|
129
|
-
│ └───────────────────────────────────────────────────┘ │
|
|
130
|
-
├─────────────────────────────────────────────────────────┤
|
|
131
|
-
│ FRAMEWORK LAYER │
|
|
132
|
-
│ frameworks/ui/ │
|
|
133
|
-
│ ┌───────────────────────────────────────────────────┐ │
|
|
134
|
-
│ │ PartWrapper.tsx ← entry point │ │
|
|
135
|
-
│ │ │ │ │
|
|
136
|
-
│ │ ▼ │ │
|
|
137
|
-
│ │ PartWrapperFactory.ts ← createCustomComponent │ │
|
|
138
|
-
│ │ │ │ │
|
|
139
|
-
│ │ ▼ │ │
|
|
140
|
-
│ │ PartWrapperView.tsx ← renders <div part=..> │ │
|
|
141
|
-
│ │ │ │ │
|
|
142
|
-
│ │ ▼ │ │
|
|
143
|
-
│ │ css/PartWrapper.module.css ← display: contents │ │
|
|
144
|
-
│ └───────────────────────────────────────────────────┘ │
|
|
145
|
-
└─────────────────────────────────────────────────────────┘
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### Data Flow Through `createCustomComponent`
|
|
149
|
-
|
|
150
|
-
```
|
|
151
|
-
Developer writes:
|
|
152
|
-
<PartWrapper part="field" content={<TextBox />} />
|
|
153
|
-
|
|
154
|
-
│
|
|
155
|
-
▼
|
|
156
|
-
┌─ createCustomComponent ────────────────────────────────┐
|
|
157
|
-
│ │
|
|
158
|
-
│ props = { part: "field", content: <TextBox /> } │
|
|
159
|
-
│ │ │
|
|
160
|
-
│ ▼ │
|
|
161
|
-
│ discardChildren(props) │
|
|
162
|
-
│ → looks for props.children → NOT FOUND (no-op) │
|
|
163
|
-
│ → content is untouched ✅ │
|
|
164
|
-
│ │ │
|
|
165
|
-
│ ▼ │
|
|
166
|
-
│ controller.updateProperties({ part, content, ... }) │
|
|
167
|
-
│ │ │
|
|
168
|
-
│ ▼ │
|
|
169
|
-
│ state.properties = { part: "field", │
|
|
170
|
-
│ content: <TextBox /> } │
|
|
171
|
-
│ │ │
|
|
172
|
-
│ ▼ │
|
|
173
|
-
│ <PartWrapperView state={state} /> │
|
|
174
|
-
│ │ │
|
|
175
|
-
│ ▼ │
|
|
176
|
-
│ Renders: │
|
|
177
|
-
│ <div part="field" data-part="field" │
|
|
178
|
-
│ class="partWrapper"> │
|
|
179
|
-
│ <TextBox /> │
|
|
180
|
-
│ </div> │
|
|
181
|
-
│ │
|
|
182
|
-
└────────────────────────────────────────────────────────┘
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
### Component Categories Handled
|
|
186
|
-
|
|
187
|
-
```
|
|
188
|
-
80+ View files analysed across the codebase:
|
|
189
|
-
|
|
190
|
-
Category A — HTML Root (~8 components)
|
|
191
|
-
┌──────────────────────────────────────────┐
|
|
192
|
-
│ Component │ Root Element │
|
|
193
|
-
│──────────────────┼───────────────────────│
|
|
194
|
-
│ TableListView │ <div> │
|
|
195
|
-
│ BreadcrumbView │ <div> │
|
|
196
|
-
│ ErrorStateView │ <div> │
|
|
197
|
-
│ DateView │ <div> │
|
|
198
|
-
│ EmptyStateView │ <span> │
|
|
199
|
-
└──────────────────────────────────────────┘
|
|
200
|
-
|
|
201
|
-
Category B — Custom Component Root (~72+ components)
|
|
202
|
-
┌──────────────────────────────────────────┐
|
|
203
|
-
│ Component │ Root Element │
|
|
204
|
-
│──────────────────┼───────────────────────│
|
|
205
|
-
│ ButtonView │ <Button> │
|
|
206
|
-
│ TextView │ <Typography> │
|
|
207
|
-
│ SectionView │ <Section> │
|
|
208
|
-
│ CheckboxView │ <FieldItem> │
|
|
209
|
-
│ SwitchView │ <Switch> │
|
|
210
|
-
│ TagsView │ <TagList> │
|
|
211
|
-
│ LinkView │ <TableLink> │
|
|
212
|
-
└──────────────────────────────────────────┘
|
|
213
|
-
|
|
214
|
-
PartWrapper wraps BOTH categories identically:
|
|
215
|
-
<PartWrapper part="x" content={<AnyCategoryComponent />} />
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
### CSS Targeting Strategy
|
|
219
|
-
|
|
220
|
-
```
|
|
221
|
-
TODAY (Light DOM):
|
|
222
|
-
|
|
223
|
-
Layout CSS DOM
|
|
224
|
-
───────────── ───
|
|
225
|
-
[data-part="field"] { → <div data-part="field">
|
|
226
|
-
padding: 12px; <TextBox />
|
|
227
|
-
} </div>
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
FUTURE (Shadow DOM):
|
|
231
|
-
|
|
232
|
-
Layout CSS Shadow DOM
|
|
233
|
-
───────────── ──────────
|
|
234
|
-
my-form::part(field) { → #shadow-root
|
|
235
|
-
padding: 12px; <div part="field">
|
|
236
|
-
} <TextBox />
|
|
237
|
-
</div>
|
|
238
|
-
|
|
239
|
-
Switch CSS selectors only — zero component code changes.
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
---
|
|
243
|
-
|
|
244
|
-
## 3. Approaches
|
|
245
|
-
|
|
246
|
-
### Approach 1: Modify Legacy Components Directly
|
|
247
|
-
|
|
248
|
-
Add a `part` prop to each legacy component so it renders `part="..."` on its root element.
|
|
249
|
-
|
|
250
|
-
```tsx
|
|
251
|
-
// ❌ Would need to change ButtonView.tsx, TextView.tsx, etc.
|
|
252
|
-
function ButtonView({ state }, ref) {
|
|
253
|
-
return <Button part={state.properties.part} ... />;
|
|
254
|
-
}
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
**Usage:**
|
|
258
|
-
```tsx
|
|
259
|
-
<Button part="action-button" text="Save" />
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
**Why rejected:**
|
|
263
|
-
- Violates ADR UI-STYLE-003 — must NOT modify legacy components
|
|
264
|
-
- Every legacy component (80+) needs changes
|
|
265
|
-
- High risk — legacy components are reused across the entire app
|
|
266
|
-
|
|
267
|
-
---
|
|
268
|
-
|
|
269
|
-
### Approach 2: Higher-Order Component (HOC)
|
|
270
|
-
|
|
271
|
-
Create a function that wraps a component at **definition time**.
|
|
272
|
-
|
|
273
|
-
```tsx
|
|
274
|
-
// Create a wrapped version of Button
|
|
275
|
-
const WrappedButton = withPartWrapper(Button, 'action-button');
|
|
276
|
-
|
|
277
|
-
function withPartWrapper(Component, partName) {
|
|
278
|
-
return function Wrapper(props) {
|
|
279
|
-
return (
|
|
280
|
-
<div part={partName} data-part={partName}>
|
|
281
|
-
<Component {...props} />
|
|
282
|
-
</div>
|
|
283
|
-
);
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
**Usage:**
|
|
289
|
-
```tsx
|
|
290
|
-
const WrappedButton = withPartWrapper(Button, 'action-button');
|
|
291
|
-
const WrappedText = withPartWrapper(Text, 'label');
|
|
292
|
-
|
|
293
|
-
// In layout:
|
|
294
|
-
<WrappedButton text="Save" />
|
|
295
|
-
<WrappedText text="Hello" />
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
**Why rejected:**
|
|
299
|
-
- Does not follow ACA folder structure (domain/applications/frameworks)
|
|
300
|
-
- Part name is fixed at definition time — not flexible in layout Views
|
|
301
|
-
- Need to create a wrapped version for every component you use
|
|
302
|
-
|
|
303
|
-
---
|
|
304
|
-
|
|
305
|
-
### Approach 3: Simple `forwardRef` Component
|
|
306
|
-
|
|
307
|
-
Create a lightweight React component using `forwardRef` (same pattern as existing `FieldItem` component).
|
|
308
|
-
|
|
309
|
-
```tsx
|
|
310
|
-
import React, { forwardRef } from 'react';
|
|
311
|
-
|
|
312
|
-
const PartWrapper = forwardRef(({ part, tagName = 'div', className, children }, ref) => {
|
|
313
|
-
const Element = tagName;
|
|
314
|
-
return (
|
|
315
|
-
<Element ref={ref} part={part} data-part={part} className={className}>
|
|
316
|
-
{children}
|
|
317
|
-
</Element>
|
|
318
|
-
);
|
|
319
|
-
});
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
**Usage:**
|
|
323
|
-
```tsx
|
|
324
|
-
<PartWrapper part="field-container">
|
|
325
|
-
<TextBox id="name" label="Name" />
|
|
326
|
-
</PartWrapper>
|
|
327
|
-
|
|
328
|
-
<PartWrapper part="action-button">
|
|
329
|
-
<Button text="Save" />
|
|
330
|
-
</PartWrapper>
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
**Pros:**
|
|
334
|
-
- Simple and easy to understand
|
|
335
|
-
- Uses natural React `children` pattern (`<Wrapper>...</Wrapper>`)
|
|
336
|
-
- Zero changes to existing components
|
|
337
|
-
|
|
338
|
-
**Why rejected:**
|
|
339
|
-
- Does NOT use `createCustomComponent`
|
|
340
|
-
- Bypasses the ACA lifecycle (no controller, presenter, state management)
|
|
341
|
-
- Not strictly ACA-compliant
|
|
342
|
-
|
|
343
|
-
---
|
|
344
|
-
|
|
345
|
-
### Approach 4: `createCustomComponent` + `content` Property — ✅ Selected
|
|
346
|
-
|
|
347
|
-
Create a proper ACA component using `createCustomComponent` with the Factory pattern (same pattern as `SmartActionBand`). Inner components are passed via a **`content` property** instead of React `children`.
|
|
348
|
-
|
|
349
|
-
```tsx
|
|
350
|
-
// Entry point — PartWrapper.tsx
|
|
351
|
-
import PartWrapperFactory from './PartWrapperFactory';
|
|
352
|
-
const PartWrapper = PartWrapperFactory.create({ name: 'PartWrapper' });
|
|
353
|
-
export default PartWrapper;
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
```tsx
|
|
357
|
-
// Factory — PartWrapperFactory.ts
|
|
358
|
-
import { createCustomComponent } from '@library/custom-component';
|
|
359
|
-
import PartWrapperPropertiesSchema from '../../domain/entities/interfaces/Properties';
|
|
360
|
-
import PartWrapperView from './PartWrapperView';
|
|
361
|
-
|
|
362
|
-
export default class PartWrapperFactory {
|
|
363
|
-
static create({ name = 'PartWrapper', View = PartWrapperView } = {}) {
|
|
364
|
-
return createCustomComponent({
|
|
365
|
-
name,
|
|
366
|
-
View,
|
|
367
|
-
properties: PartWrapperPropertiesSchema,
|
|
368
|
-
eventHandlers: {}
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
```tsx
|
|
375
|
-
// View — PartWrapperView.tsx
|
|
376
|
-
import React from 'react';
|
|
377
|
-
import style from './css/PartWrapper.module.css';
|
|
378
|
-
|
|
379
|
-
function PartWrapperView({ state }: any, ref: any) {
|
|
380
|
-
const { part, tagName = 'div', className, content } = state.properties;
|
|
381
|
-
const Element = tagName;
|
|
382
|
-
const combinedClassName = className
|
|
383
|
-
? `${style.partWrapper} ${className}`
|
|
384
|
-
: style.partWrapper;
|
|
385
|
-
|
|
386
|
-
return (
|
|
387
|
-
<Element ref={ref} part={part} data-part={part} className={combinedClassName}>
|
|
388
|
-
{content}
|
|
389
|
-
</Element>
|
|
390
|
-
);
|
|
391
|
-
}
|
|
392
|
-
export default PartWrapperView;
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
```css
|
|
396
|
-
/* PartWrapper.module.css */
|
|
397
|
-
.partWrapper {
|
|
398
|
-
display: contents; /* Makes the wrapper invisible to layout */
|
|
399
|
-
}
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
**Usage:**
|
|
403
|
-
```tsx
|
|
404
|
-
{/* Wrap a form field */}
|
|
405
|
-
<PartWrapper part="field-container" content={<TextBox id="name" label="Name" />} />
|
|
406
|
-
|
|
407
|
-
{/* Wrap a button */}
|
|
408
|
-
<PartWrapper part="primary-action" content={<Button text="Save" />} />
|
|
409
|
-
|
|
410
|
-
{/* Nesting wrappers */}
|
|
411
|
-
<PartWrapper part="action-bar" content={
|
|
412
|
-
<div className={style.actions}>
|
|
413
|
-
<PartWrapper part="primary-action" content={<Button text="Save" />} />
|
|
414
|
-
<PartWrapper part="secondary-action" content={<Button text="Cancel" />} />
|
|
415
|
-
</div>
|
|
416
|
-
} />
|
|
417
|
-
|
|
418
|
-
{/* Custom HTML element for the wrapper */}
|
|
419
|
-
<PartWrapper part="page-footer" tagName="footer" content={<Text text="Footer" />} />
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
**CSS targeting:**
|
|
423
|
-
```css
|
|
424
|
-
[data-part="field-container"] {
|
|
425
|
-
padding: 12px 16px;
|
|
426
|
-
border-bottom: 1px solid #f0f0f0;
|
|
427
|
-
}
|
|
428
|
-
[data-part="field-container"]:hover {
|
|
429
|
-
background-color: #f8f9fa;
|
|
430
|
-
}
|
|
431
|
-
[data-part="field-container"]:focus-within {
|
|
432
|
-
outline: 2px solid #4a90d9;
|
|
433
|
-
}
|
|
434
|
-
```
|
|
435
|
-
|
|
436
|
-
---
|
|
437
|
-
|
|
438
|
-
## 4. Why `content` Prop Instead of `children`?
|
|
439
|
-
|
|
440
|
-
This is the most important technical detail to understand.
|
|
441
|
-
|
|
442
|
-
**The problem:** `createCustomComponent` internally calls a function called `discardChildren()`:
|
|
443
|
-
|
|
444
|
-
```typescript
|
|
445
|
-
// Inside CreateCustomComponent.tsx
|
|
446
|
-
function discardChildren(props) {
|
|
447
|
-
const newObject = { ...props };
|
|
448
|
-
delete newObject.children; // ← removes children!
|
|
449
|
-
return newObject;
|
|
450
|
-
}
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
If we write `<PartWrapper>children here</PartWrapper>`, React puts `children here` into `props.children`. But `discardChildren()` **deletes** it before it reaches the View.
|
|
454
|
-
|
|
455
|
-
The deleted children are sent to a **slot system** which requires each child to have `slotName` and `displayName` properties — which legacy components (TextBox, Button, etc.) don't have.
|
|
456
|
-
|
|
457
|
-
**The solution:** Use a property called `content` instead. Since it's not named `children`, `discardChildren()` leaves it alone:
|
|
458
|
-
|
|
459
|
-
```
|
|
460
|
-
<PartWrapper content={<TextBox />} />
|
|
461
|
-
|
|
462
|
-
props = { part: "field", content: <TextBox /> }
|
|
463
|
-
↓
|
|
464
|
-
discardChildren(props) → deletes props.children (doesn't exist, no-op)
|
|
465
|
-
↓
|
|
466
|
-
state.properties = { part: "field", content: <TextBox /> } ← content survives!
|
|
467
|
-
↓
|
|
468
|
-
View receives state.properties.content ← renders correctly!
|
|
469
|
-
```
|
|
470
|
-
|
|
471
|
-
---
|
|
472
|
-
|
|
473
|
-
## 5. Approach Comparison Table
|
|
474
|
-
|
|
475
|
-
| Criteria | Approach 1 (Modify Legacy) | Approach 2 (HOC) | Approach 3 (forwardRef) | Approach 4 (createCustomComponent) ✅ |
|
|
476
|
-
|---|---|---|---|---|
|
|
477
|
-
| Modifies existing code | ❌ Yes | ✅ No | ✅ No | ✅ No |
|
|
478
|
-
| ACA structure | N/A | ❌ No | ⚠️ Partial | ✅ Full |
|
|
479
|
-
| Uses `createCustomComponent` | N/A | ❌ No | ❌ No | ✅ Yes |
|
|
480
|
-
| Dynamic part names | ✅ Yes | ❌ Fixed at definition | ✅ Yes | ✅ Yes |
|
|
481
|
-
| Natural API | ✅ Built-in prop | ⚠️ Pre-wrapped | ✅ Children | ⚠️ `content` prop |
|
|
482
|
-
| Shadow DOM ready | ⚠️ Partial | ✅ Yes | ✅ Yes | ✅ Yes |
|
|
483
|
-
| Risk level | 🔴 High | 🟡 Medium | 🟢 Low | 🟢 Low |
|
|
484
|
-
|
|
485
|
-
---
|
|
486
|
-
|
|
487
|
-
## 6. POC Examples
|
|
488
|
-
|
|
489
|
-
### Example 1: Form Layout (Category B components)
|
|
490
|
-
|
|
491
|
-
Wraps form field components that have custom component roots (`FieldItem`).
|
|
492
|
-
|
|
493
|
-
```tsx
|
|
494
|
-
// POCFormLayout.tsx
|
|
495
|
-
<div className={style.formLayout}>
|
|
496
|
-
<PartWrapper
|
|
497
|
-
part="field-container"
|
|
498
|
-
content={<TextBox id="name" label="Name" value="" placeholder="Enter name" />}
|
|
499
|
-
/>
|
|
500
|
-
<PartWrapper
|
|
501
|
-
part="field-container"
|
|
502
|
-
content={<Email id="email" label="Email Address" value="" />}
|
|
503
|
-
/>
|
|
504
|
-
<PartWrapper
|
|
505
|
-
part="field-container"
|
|
506
|
-
content={<PickList id="status" label="Status" value="" options={[]} />}
|
|
507
|
-
/>
|
|
508
|
-
</div>
|
|
509
|
-
```
|
|
510
|
-
|
|
511
|
-
```css
|
|
512
|
-
/* poc-form-layout.module.css */
|
|
513
|
-
.formLayout [data-part="field-container"] {
|
|
514
|
-
padding: 12px 16px;
|
|
515
|
-
border-bottom: 1px solid #f0f0f0;
|
|
516
|
-
}
|
|
517
|
-
.formLayout [data-part="field-container"]:hover {
|
|
518
|
-
background-color: #f8f9fa;
|
|
519
|
-
}
|
|
520
|
-
.formLayout [data-part="field-container"]:focus-within {
|
|
521
|
-
outline: 2px solid #4a90d9;
|
|
522
|
-
}
|
|
523
|
-
```
|
|
524
|
-
|
|
525
|
-
### Example 2: Table Cells (Category B components)
|
|
526
|
-
|
|
527
|
-
Wraps different cell types with unique part names for granular styling.
|
|
528
|
-
|
|
529
|
-
```tsx
|
|
530
|
-
// POCTableCellLayout.tsx
|
|
531
|
-
<div className={style.tableRow}>
|
|
532
|
-
<PartWrapper part="cell-text" content={<Text text="Sample" />} />
|
|
533
|
-
<PartWrapper part="cell-tags" content={<Tags tags={[{ id: '1', label: 'Tag A' }]} />} />
|
|
534
|
-
<PartWrapper part="cell-switch" content={<Switch checked={false} />} />
|
|
535
|
-
<PartWrapper part="cell-link" content={<Link text="View" href="#" />} />
|
|
536
|
-
<PartWrapper part="cell-email" content={<Email email="user@example.com" />} />
|
|
537
|
-
</div>
|
|
538
|
-
```
|
|
539
|
-
|
|
540
|
-
```css
|
|
541
|
-
/* poc-table-cell-layout.module.css */
|
|
542
|
-
.tableRow [data-part^="cell-"] { padding: 8px 12px; flex: 1; }
|
|
543
|
-
.tableRow [data-part="cell-text"] { text-align: left; flex: 2; }
|
|
544
|
-
.tableRow [data-part="cell-tags"] { max-width: 220px; overflow: hidden; }
|
|
545
|
-
.tableRow [data-part="cell-switch"] { display: flex; justify-content: center; flex: 0 0 80px; }
|
|
546
|
-
```
|
|
547
|
-
|
|
548
|
-
### Example 3: Mixed Layout (Category A + B components)
|
|
549
|
-
|
|
550
|
-
Wraps both HTML-root (Category A) and custom-root (Category B) components — proving the wrapper is agnostic to inner component type.
|
|
551
|
-
|
|
552
|
-
```tsx
|
|
553
|
-
// POCMixedLayout.tsx
|
|
554
|
-
<div className={style.pageLayout}>
|
|
555
|
-
|
|
556
|
-
{/* Category B: Text component (custom root) */}
|
|
557
|
-
<PartWrapper part="page-header" content={
|
|
558
|
-
<div className={style.headerContent}>
|
|
559
|
-
<PartWrapper part="page-title" content={<Text text="Page Title" size="24" />} />
|
|
560
|
-
<PartWrapper part="page-description" content={<Text text="Description" size="14" />} />
|
|
561
|
-
</div>
|
|
562
|
-
} />
|
|
563
|
-
|
|
564
|
-
{/* Category B: Button + ActionIcon (custom roots) */}
|
|
565
|
-
<PartWrapper part="action-bar" content={
|
|
566
|
-
<div className={style.actionBarContent}>
|
|
567
|
-
<PartWrapper part="primary-action" content={<Button text="Create" />} />
|
|
568
|
-
<PartWrapper part="secondary-action" content={<Button text="Export" />} />
|
|
569
|
-
<PartWrapper part="icon-action" content={<ActionIcon name="filter" />} />
|
|
570
|
-
</div>
|
|
571
|
-
} />
|
|
572
|
-
|
|
573
|
-
{/* Content area */}
|
|
574
|
-
<PartWrapper part="content-area" content={<Text text="Content area" />} />
|
|
575
|
-
|
|
576
|
-
{/* Category A: raw HTML (tagName="footer") */}
|
|
577
|
-
<PartWrapper part="page-footer" tagName="footer" content={
|
|
578
|
-
<div><Text text="Footer content" size="12" /></div>
|
|
579
|
-
} />
|
|
580
|
-
|
|
581
|
-
</div>
|
|
582
|
-
```
|
|
583
|
-
|
|
584
|
-
```css
|
|
585
|
-
/* poc-mixed-layout.module.css */
|
|
586
|
-
.pageLayout [data-part="page-header"] { padding: 24px 32px; background: #fff; }
|
|
587
|
-
.pageLayout [data-part="action-bar"] { padding: 12px 32px; }
|
|
588
|
-
.pageLayout [data-part="content-area"] { flex: 1; padding: 24px 32px; }
|
|
589
|
-
.pageLayout [data-part="page-footer"] { padding: 12px 32px; border-top: 1px solid #e5e5e5; }
|
|
590
|
-
.pageLayout [data-part="icon-action"] { margin-left: auto; }
|
|
591
|
-
```
|
|
592
|
-
|
|
593
|
-
---
|
|
594
|
-
|
|
595
|
-
## 7. What `display: contents` Does
|
|
596
|
-
|
|
597
|
-
The PartWrapper uses `display: contents` so it doesn't break existing layouts.
|
|
598
|
-
|
|
599
|
-
**Without `display: contents`:**
|
|
600
|
-
```
|
|
601
|
-
Parent (display: flex)
|
|
602
|
-
└─ PartWrapper <div> ← This div breaks the flex layout!
|
|
603
|
-
└─ TextBox
|
|
604
|
-
```
|
|
605
|
-
|
|
606
|
-
**With `display: contents`:**
|
|
607
|
-
```
|
|
608
|
-
Parent (display: flex)
|
|
609
|
-
└─ PartWrapper <div> ← Invisible to layout — like it's not there
|
|
610
|
-
└─ TextBox ← Behaves as direct child of Parent
|
|
611
|
-
```
|
|
612
|
-
|
|
613
|
-
The wrapper DOM element still exists (so `data-part` works for CSS targeting), but it has **no box** in the layout — children render as if the wrapper isn't there.
|
|
614
|
-
|
|
615
|
-
---
|
|
616
|
-
|
|
617
|
-
## 8. Dual Attribute Strategy (Future-Proofing)
|
|
618
|
-
|
|
619
|
-
Both `part` and `data-part` are set on the wrapper element:
|
|
620
|
-
|
|
621
|
-
```html
|
|
622
|
-
<div part="field-container" data-part="field-container">
|
|
623
|
-
<TextBox ... />
|
|
624
|
-
</div>
|
|
625
|
-
```
|
|
626
|
-
|
|
627
|
-
| Attribute | CSS Selector | When to use |
|
|
628
|
-
|---|---|---|
|
|
629
|
-
| `data-part` | `[data-part="field-container"] { }` | **Now** — works in Light DOM |
|
|
630
|
-
| `part` | `::part(field-container) { }` | **Future** — when we adopt Shadow DOM |
|
|
631
|
-
|
|
632
|
-
When we migrate to Shadow DOM, we switch CSS selectors from `[data-part="x"]` to `::part(x)` — no component code changes needed.
|
|
633
|
-
|
|
634
|
-
---
|
|
635
|
-
|
|
636
|
-
## 9. Known Limitations
|
|
637
|
-
|
|
638
|
-
| Limitation | Why it exists | Workaround |
|
|
639
|
-
|---|---|---|
|
|
640
|
-
| Uses `content` prop instead of `children` | `createCustomComponent` deletes `children` internally | This is the only way with `createCustomComponent` — document clearly |
|
|
641
|
-
| `display: contents` may affect screen readers | Some assistive tech skips elements with `display: contents` | Add `role="presentation"` if needed; requires a11y testing |
|
|
642
|
-
| Can't style inner sub-elements of wrapped components | ADR UI-STYLE-003 says: boundary styling only | By design — keeps wrapper simple and safe |
|
|
643
|
-
| Each wrapper creates an ACA lifecycle instance | Part of using `createCustomComponent` | Overhead is minimal — no behaviours, SDK, or event handlers |
|
|
644
|
-
| Extra DOM node per wrapper | A `<div>` (or custom `tagName`) is added | `display: contents` makes it layout-transparent |
|
|
645
|
-
|
|
646
|
-
---
|
|
647
|
-
|
|
648
|
-
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|