@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.
Files changed (88) hide show
  1. package/es/.DS_Store +0 -0
  2. package/es/cc/table-connected/SdkContract.js +28 -3
  3. package/es/cc/table-list/Properties.js +9 -8
  4. package/es/cc/table-list/row/Properties.js +19 -6
  5. package/es/library/custom-component/frameworks/json-schema-validator/Validator.js +57 -44
  6. package/es/library/custom-component/frameworks/json-schema-validator/__generated__/registry.js +92 -0
  7. package/es/library/custom-component/frameworks/json-schema-validator/__generated__/validators.js +1 -0
  8. package/es/library/custom-component/frameworks/json-schema-validator/__tests__/Validator.test.js +478 -0
  9. package/es/library/custom-component/frameworks/ui/DependencyFactory.js +16 -0
  10. package/es/{flex-layout → platform}/.DS_Store +0 -0
  11. package/es/platform/client-actions/cc/action-event-mediator/Properties.js +18 -4
  12. package/package.json +6 -4
  13. package/es/bc/sort-by/SortOrderEnum.js +0 -8
  14. package/es/cc/flex-container/Properties.js +0 -79
  15. package/es/cc/flex-container/Types.js +0 -39
  16. package/es/cc/flex-container/index.js +0 -2
  17. package/es/cc/tags/model/TagModel.js +0 -26
  18. package/es/library/behaviours/sort-by/adapters/controllers/SortBy.js +0 -28
  19. package/es/library/behaviours/sort-by/applications/usecases/SortBy.js +0 -28
  20. package/es/library/behaviours/sort-by/frameworks/ui/DemoBehaviour.js +0 -20
  21. package/es/library/custom-component/domain/entities/SlotValidator.js +0 -84
  22. package/es/library/custom-component/frameworks/ui/CustomComponentFactory.js +0 -56
  23. package/es/library/dot/components/part-wrapper/POC_DOCUMENT.md +0 -648
  24. package/es/library/dot/components/part-wrapper/applications/interfaces/State.js +0 -1
  25. package/es/library/dot/components/part-wrapper/domain/entities/interfaces/Properties.js +0 -52
  26. package/es/library/dot/components/part-wrapper/frameworks/ui/PartWrapper.js +0 -24
  27. package/es/library/dot/components/part-wrapper/frameworks/ui/PartWrapperFactory.js +0 -27
  28. package/es/library/dot/components/part-wrapper/frameworks/ui/PartWrapperView.js +0 -45
  29. package/es/library/dot/components/part-wrapper/frameworks/ui/css/PartWrapper.module.css +0 -16
  30. package/es/library/dot/components/part-wrapper/poc/index.js +0 -9
  31. package/es/library/dot/components/part-wrapper/poc/layouts/POCFormLayout.js +0 -130
  32. package/es/library/dot/components/part-wrapper/poc/layouts/POCMixedLayout.js +0 -128
  33. package/es/library/dot/components/part-wrapper/poc/layouts/POCTableCellLayout.js +0 -105
  34. package/es/library/dot/components/part-wrapper/poc/layouts/css/poc-form-layout.module.css +0 -46
  35. package/es/library/dot/components/part-wrapper/poc/layouts/css/poc-mixed-layout.module.css +0 -89
  36. package/es/library/dot/components/part-wrapper/poc/layouts/css/poc-table-cell-layout.module.css +0 -70
  37. package/es/library/dot/components/table-list/adapters/controllers/SortByController.js +0 -25
  38. package/es/library/dot/components/table-list/adapters/controllers/SortedController.js +0 -18
  39. package/es/library/dot/components/table-list/frameworks/ui/handlers/HandleSortClick.js +0 -12
  40. package/es/library/dot/legacy-to-new-arch/flex-container/frameworks/ui/FlexContainer.js +0 -11
  41. package/es/library/dot/legacy-to-new-arch/flex-container/frameworks/ui/FlexContainerView.js +0 -37
  42. package/es/library/poc-slot-usage/employee-card/adapters/presenters/EmployeeCardStateMapper.js +0 -14
  43. package/es/library/poc-slot-usage/employee-card/applications/usecases/BuildEmployeeCardUseCase.js +0 -25
  44. package/es/library/poc-slot-usage/employee-card/domain/entities/EmployeeCardSlots.js +0 -68
  45. package/es/library/poc-slot-usage/employee-card/frameworks/ui/EmployeeCard.js +0 -13
  46. package/es/library/poc-slot-usage/employee-card/frameworks/ui/EmployeeCardView.js +0 -42
  47. package/es/library/poc-slot-usage/flex-container/adapters/presenters/FlexContainerStateMapper.js +0 -8
  48. package/es/library/poc-slot-usage/flex-container/applications/usecases/BuildFlexContainerUseCase.js +0 -44
  49. package/es/library/poc-slot-usage/flex-container/domain/entities/FlexContainerSlots.js +0 -18
  50. package/es/library/poc-slot-usage/flex-container/frameworks/ui/FlexContainer.js +0 -5
  51. package/es/library/poc-slot-usage/flex-container/frameworks/ui/FlexContainerView.js +0 -40
  52. package/es/library/poc-slot-usage/index.js +0 -7
  53. package/es/library/poc-slot-usage/mismatched-card/applications/usecases/BuildMismatchedCardUseCase.js +0 -23
  54. package/es/library/poc-slot-usage/mismatched-card/domain/entities/MismatchedCardSlots.js +0 -39
  55. package/es/library/poc-slot-usage/mismatched-card/frameworks/ui/MismatchedCard.js +0 -9
  56. package/es/library/poc-slot-usage/mismatched-card/frameworks/ui/MismatchedCardView.js +0 -39
  57. package/es/library/poc-slot-usage/product-action/applications/usecases/BuildProductActionUseCase.js +0 -21
  58. package/es/library/poc-slot-usage/product-action/frameworks/ui/ProductAction.js +0 -5
  59. package/es/library/poc-slot-usage/product-action/frameworks/ui/ProductActionView.js +0 -24
  60. package/es/library/poc-slot-usage/product-body/applications/usecases/BuildProductBodyUseCase.js +0 -21
  61. package/es/library/poc-slot-usage/product-body/frameworks/ui/ProductBody.js +0 -5
  62. package/es/library/poc-slot-usage/product-body/frameworks/ui/ProductBodyView.js +0 -20
  63. package/es/library/poc-slot-usage/product-card/adapters/presenters/ProductCardStateMapper.js +0 -8
  64. package/es/library/poc-slot-usage/product-card/applications/usecases/BuildProductCardUseCase.js +0 -34
  65. package/es/library/poc-slot-usage/product-card/domain/entities/ProductCardSlots.js +0 -70
  66. package/es/library/poc-slot-usage/product-card/frameworks/ui/ProductCard.js +0 -5
  67. package/es/library/poc-slot-usage/product-card/frameworks/ui/ProductCardView.js +0 -40
  68. package/es/library/poc-slot-usage/product-title/applications/usecases/BuildProductTitleUseCase.js +0 -21
  69. package/es/library/poc-slot-usage/product-title/frameworks/ui/ProductTitle.js +0 -5
  70. package/es/library/poc-slot-usage/product-title/frameworks/ui/ProductTitleView.js +0 -20
  71. package/es/platform/components/table-connected/adapters/controllers/ColumnChooserOpenedController.js +0 -28
  72. package/es/platform/components/table-connected/adapters/controllers/ColumnChooserUpdateController.js +0 -31
  73. package/es/platform/zlist/adapters/gateways/SortBy.js +0 -38
  74. package/es/platform/zlist/applications/interfaces/gateways/ISortBy.js +0 -1
  75. package/es/platform/zlist/domain/entities/SortBy.js +0 -58
  76. package/es/platform/zlist/domain/entities/interfaces/ISortBy.js +0 -1
  77. package/es/to-do-app/ToDo.js +0 -10
  78. package/es/to-do-app/cc/button/Constants.js +0 -0
  79. package/es/to-do-app/cc/button/Events.js +0 -0
  80. package/es/to-do-app/cc/button/Properties.js +0 -4
  81. package/es/to-do-app/cc/button/index.js +0 -0
  82. package/es/to-do-app/cc/textbox/Constants.js +0 -7
  83. package/es/to-do-app/cc/textbox/Events.js +0 -20
  84. package/es/to-do-app/cc/textbox/Properties.js +0 -51
  85. package/es/to-do-app/cc/textbox/index.js +0 -3
  86. package/es/to-do-app/component/textbox/framework/TextBox.js +0 -30
  87. package/es/to-do-app/component/textbox/framework/TextBoxView.js +0 -42
  88. /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
-