@ncds/ui-admin-mcp 1.0.0-alpha.13 → 1.0.0-alpha.15
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/bin/components.bundle.js +1 -1
- package/bin/definitions/external/editor.d.ts +50 -0
- package/bin/definitions/external/editor.js +53 -0
- package/bin/definitions/js-api.json +139 -21
- package/bin/server.d.ts +5 -0
- package/bin/server.js +21 -15
- package/bin/tools/external/editor.d.ts +17 -0
- package/bin/tools/external/editor.js +88 -0
- package/bin/tools/renderToHtml.d.ts +22 -7
- package/bin/tools/renderToHtml.js +232 -11
- package/bin/types.d.ts +10 -0
- package/data/badge-group.json +4 -4
- package/data/badge.json +3 -3
- package/data/bread-crumb.json +1 -1
- package/data/button-group.json +10 -0
- package/data/button.json +2 -2
- package/data/carousel-arrow.json +1 -1
- package/data/carousel-number-group.json +1 -1
- package/data/checkbox.json +1 -1
- package/data/combo-box.json +4 -4
- package/data/date-picker.json +2 -2
- package/data/divider.json +1 -1
- package/data/dot.json +1 -1
- package/data/dropdown.json +38 -18
- package/data/editor.json +85 -0
- package/data/empty-state.json +4 -4
- package/data/featured-icon.json +2 -2
- package/data/file-input.json +6 -1
- package/data/horizontal-tab.json +4 -4
- package/data/image-file-input.json +6 -1
- package/data/input-base.json +1 -1
- package/data/modal.json +51 -7
- package/data/notification.json +7 -23
- package/data/number-input.json +1 -1
- package/data/password-input.json +1 -1
- package/data/progress-bar.json +2 -2
- package/data/progress-circle.json +1 -1
- package/data/radio.json +1 -1
- package/data/range-date-picker-with-buttons.json +5 -5
- package/data/range-date-picker.json +5 -5
- package/data/select-box.json +4 -4
- package/data/select.json +8 -3
- package/data/slider.json +1 -1
- package/data/spinner.json +1 -1
- package/data/switch.json +1 -1
- package/data/table.json +3 -1
- package/data/tag.json +2 -2
- package/data/textarea.json +1 -1
- package/data/toggle.json +1 -1
- package/data/tooltip.json +1 -1
- package/data/vertical-tab.json +4 -4
- package/package.json +3 -2
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.renderToHtml = void 0;
|
|
3
|
+
exports.renderToHtml = exports.mergeCdnDefaults = exports.stripFunctionProps = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* render_to_html tool — 컴포넌트 속성을 전달하면 정확한 HTML + React 매핑을 반환 (순수 함수)
|
|
6
|
+
*
|
|
7
|
+
* DOM 환경과 React/ReactDOM은 server.ts에서 초기화하고 파라미터로 전달한다.
|
|
8
|
+
* 이 파일에는 fs, path, require, let이 없다.
|
|
9
|
+
*/
|
|
10
|
+
// biome-ignore-all lint/complexity/noExcessiveCognitiveComplexity: 기존 spec-walk 함수 복잡도 (Story 5.8 scope 외, 별도 refactor 스토리)
|
|
11
|
+
// biome-ignore-all lint/style/useExportsLast: RenderToHtmlParams export interface 상단 위치 (파일 관행)
|
|
12
|
+
// biome-ignore-all lint/style/noNonNullAssertion: 기존 fallback 패턴 (Story 5.8 scope 외)
|
|
13
|
+
// biome-ignore-all lint/style/noNestedTernary: 기존 reactElementToJsx의 type 판정 로직 (Story 5.8 scope 외)
|
|
14
|
+
// biome-ignore-all lint/style/useTemplate: 기존 attrsPrefix 문자열 결합 (Story 5.8 scope 외)
|
|
15
|
+
const lodash_1 = require("lodash");
|
|
4
16
|
const response_js_1 = require("../utils/response.js");
|
|
17
|
+
const editor_js_1 = require("./external/editor.js");
|
|
5
18
|
// ── 상수 ──────────────────────────────────────────────────────────
|
|
6
19
|
/** React 특수 props 차단 — injection 방지 */
|
|
7
20
|
const BLOCKED_PROPS = new Set(['dangerouslySetInnerHTML', 'ref', '__self', '__source']);
|
|
@@ -19,7 +32,7 @@ const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);
|
|
|
19
32
|
const resolveSimpleComponent = (name, ctx) => {
|
|
20
33
|
const normalized = (0, response_js_1.normalizeName)(name);
|
|
21
34
|
const childData = ctx.componentMap.get(normalized);
|
|
22
|
-
const Component = childData?.exportName ? ctx.bundle[childData.exportName] ?? null : null;
|
|
35
|
+
const Component = childData?.exportName ? (ctx.bundle[childData.exportName] ?? null) : null;
|
|
23
36
|
return { Component, propsSpec: childData?.props };
|
|
24
37
|
};
|
|
25
38
|
/** compound component resolve: "modal.header" → { Component: bundle.Modal.Header, propsSpec: subComponents["Modal.Header"].props } */
|
|
@@ -331,6 +344,180 @@ const correctMissingRequired = (userProps, propsSpec) => {
|
|
|
331
344
|
}
|
|
332
345
|
return corrected;
|
|
333
346
|
};
|
|
347
|
+
// ── CDN init 패턴 (Story 5.8) ─────────────────────────────────────
|
|
348
|
+
/**
|
|
349
|
+
* CDN 호출 패턴은 `js-api.json`의 `cdnPattern` 필드(A/B/C)로 단일 관리한다.
|
|
350
|
+
* 컴포넌트 추가 시 js-api.json 한 곳에만 패턴을 명시하면 자동 인식된다.
|
|
351
|
+
*/
|
|
352
|
+
/** options를 안전하게 직렬화 — script 블록 탈출 방지(`</script>` 이스케이프) */
|
|
353
|
+
const serializeCdnOptions = (options) => JSON.stringify(options).replace(/<\/script/gi, '<\\/script');
|
|
354
|
+
/** 패턴 A: `new ncua.X(wrapper, options)` */
|
|
355
|
+
const buildPatternA = (id, className, options) => [
|
|
356
|
+
`<div id="${id}"></div>`,
|
|
357
|
+
'<script>',
|
|
358
|
+
" document.addEventListener('DOMContentLoaded', function () {",
|
|
359
|
+
` const wrapper = document.querySelector('#${id}');`,
|
|
360
|
+
` new ncua.${className}(wrapper, ${serializeCdnOptions(options)});`,
|
|
361
|
+
' });',
|
|
362
|
+
'</script>',
|
|
363
|
+
].join('\n');
|
|
364
|
+
/** 패턴 B: `new ncua.X({...options, container: id})` — container 자동 주입 */
|
|
365
|
+
const buildPatternB = (id, className, options) => [
|
|
366
|
+
`<div id="${id}"></div>`,
|
|
367
|
+
'<script>',
|
|
368
|
+
" document.addEventListener('DOMContentLoaded', function () {",
|
|
369
|
+
` new ncua.${className}(${serializeCdnOptions({ ...options, container: id })});`,
|
|
370
|
+
' });',
|
|
371
|
+
'</script>',
|
|
372
|
+
].join('\n');
|
|
373
|
+
/** 패턴 C: `new ncua.X(options)` + `getElement()` + 수동 append */
|
|
374
|
+
const buildPatternC = (id, className, options) => [
|
|
375
|
+
`<div id="${id}"></div>`,
|
|
376
|
+
'<script>',
|
|
377
|
+
" document.addEventListener('DOMContentLoaded', function () {",
|
|
378
|
+
` const wrapper = document.querySelector('#${id}');`,
|
|
379
|
+
` const inst = new ncua.${className}(${serializeCdnOptions(options)});`,
|
|
380
|
+
' wrapper.appendChild(inst.getElement());',
|
|
381
|
+
' });',
|
|
382
|
+
'</script>',
|
|
383
|
+
].join('\n');
|
|
384
|
+
/**
|
|
385
|
+
* CDN init 스크립트 생성. 패턴은 `jsApi.cdnPattern` (A/B/C)으로 결정한다.
|
|
386
|
+
* 호출자는 `cdnPattern` 부재 시 isCdnInput=false로 분기하여 React renderToStaticMarkup 경로를 사용한다.
|
|
387
|
+
*/
|
|
388
|
+
const buildCdnInitScript = (pattern, componentName, instanceId, className, options) => {
|
|
389
|
+
const id = `ncua-${componentName}-${instanceId}`;
|
|
390
|
+
if (pattern === 'A')
|
|
391
|
+
return buildPatternA(id, className, options);
|
|
392
|
+
if (pattern === 'B')
|
|
393
|
+
return buildPatternB(id, className, options);
|
|
394
|
+
return buildPatternC(id, className, options);
|
|
395
|
+
};
|
|
396
|
+
/** options 키 중 `jsApi.constructorParams`에 없는 키를 warning 메시지로 변환.
|
|
397
|
+
* constructorParams의 키는 `config.options` 같은 dot 표기 — 마지막 segment만 추출해 비교. */
|
|
398
|
+
const checkUnknownCdnKeys = (options, jsApi) => {
|
|
399
|
+
if (Object.keys(jsApi.constructorParams).length === 0)
|
|
400
|
+
return [];
|
|
401
|
+
const allowedKeys = new Set();
|
|
402
|
+
for (const param of Object.keys(jsApi.constructorParams)) {
|
|
403
|
+
const lastDot = param.lastIndexOf('.');
|
|
404
|
+
allowedKeys.add(lastDot >= 0 ? param.slice(lastDot + 1) : param);
|
|
405
|
+
}
|
|
406
|
+
return Object.keys(options)
|
|
407
|
+
.filter((key) => !allowedKeys.has(key))
|
|
408
|
+
.map((key) => `Unknown CDN option key '${key}' for component '${jsApi.className}'. Passed through as-is. Verify against constructorParams.`);
|
|
409
|
+
};
|
|
410
|
+
/** SelectBox/ComboBox `options` 배열 요소 키 정규화.
|
|
411
|
+
* 공식 docs는 `{id, label}` 또는 `{id, text}` 허용. React 매칭은 `label`만 받으므로
|
|
412
|
+
* 통일된 `{id, label}`로 정규화. `{value, text}`는 비표준 alias(HTML `<option>` 컨벤션
|
|
413
|
+
* 오용)이며 함께 변환. 정규화 발생 시 warning 기록(에이전트 학습용). */
|
|
414
|
+
const normalizeCdnInputProps = (componentName, cdnConfig) => {
|
|
415
|
+
if (componentName !== 'select-box' && componentName !== 'combo-box') {
|
|
416
|
+
return { normalized: cdnConfig, warnings: [] };
|
|
417
|
+
}
|
|
418
|
+
const { options } = cdnConfig;
|
|
419
|
+
if (!Array.isArray(options))
|
|
420
|
+
return { normalized: cdnConfig, warnings: [] };
|
|
421
|
+
const aliasesUsed = new Set();
|
|
422
|
+
const normalizedOptions = options.map((opt) => {
|
|
423
|
+
if (opt === null || typeof opt !== 'object')
|
|
424
|
+
return opt;
|
|
425
|
+
const item = { ...opt };
|
|
426
|
+
if (item.id === undefined && item.value !== undefined) {
|
|
427
|
+
item.id = item.value;
|
|
428
|
+
delete item.value;
|
|
429
|
+
aliasesUsed.add('value');
|
|
430
|
+
}
|
|
431
|
+
if (item.label === undefined && item.text !== undefined) {
|
|
432
|
+
item.label = item.text;
|
|
433
|
+
delete item.text;
|
|
434
|
+
aliasesUsed.add('text');
|
|
435
|
+
}
|
|
436
|
+
return item;
|
|
437
|
+
});
|
|
438
|
+
const warnings = [];
|
|
439
|
+
if (aliasesUsed.has('value')) {
|
|
440
|
+
warnings.push(`Option key 'value' normalized to 'id' for component '${componentName}'. Canonical form is {id, label}.`);
|
|
441
|
+
}
|
|
442
|
+
if (aliasesUsed.has('text')) {
|
|
443
|
+
warnings.push(`Option key 'text' normalized to 'label' for component '${componentName}'. Canonical form is {id, label}.`);
|
|
444
|
+
}
|
|
445
|
+
return { normalized: { ...cdnConfig, options: normalizedOptions }, warnings };
|
|
446
|
+
};
|
|
447
|
+
/** Story 5.9: 사용자 props 에서 함수 prop 재귀 제거 (JSON 직렬화·deep merge 안전).
|
|
448
|
+
* cdnDefaults 는 JSON 이라 함수 없음 → 사용자 props 만 정리.
|
|
449
|
+
* Story 5.10: 외부 분기 모듈(`tools/external/editor.ts`)에서도 재사용 → export. */
|
|
450
|
+
const stripFunctionProps = (obj) => {
|
|
451
|
+
if (typeof obj === 'function')
|
|
452
|
+
return undefined;
|
|
453
|
+
if (Array.isArray(obj)) {
|
|
454
|
+
return obj.map((item) => (0, exports.stripFunctionProps)(item)).filter((item) => item !== undefined);
|
|
455
|
+
}
|
|
456
|
+
if (typeof obj === 'object' && obj !== null) {
|
|
457
|
+
return Object.fromEntries(Object.entries(obj)
|
|
458
|
+
.filter(([, value]) => typeof value !== 'function')
|
|
459
|
+
.map(([key, value]) => [key, (0, exports.stripFunctionProps)(value)]));
|
|
460
|
+
}
|
|
461
|
+
return obj;
|
|
462
|
+
};
|
|
463
|
+
exports.stripFunctionProps = stripFunctionProps;
|
|
464
|
+
/** Story 5.9: cdnDefaults 와 사용자 props 를 deep merge.
|
|
465
|
+
* - 사용자 값 우선 (lodash merge 의 두 번째 인자가 우선)
|
|
466
|
+
* - 함수 prop 은 사용자 props 에서 stripFunctionProps 로 사전 제거
|
|
467
|
+
* - DatePicker `datePickerOptions` 배열 한정: 각 항목의 `options` 에 cdnDefaults 의 첫 항목 options 를 per-element 부분 merge
|
|
468
|
+
* - 다른 배열(`buttons` 등) 은 사용자 값 그대로
|
|
469
|
+
* 반환: { merged, defaultsApplied } — defaultsApplied 는 cdnDefaults 에 있고 사용자 props 에 없는 top-level 키 (defaultsUsed 응답용)
|
|
470
|
+
* Story 5.10: 외부 분기 모듈(`tools/external/editor.ts`)에서도 재사용 → export. */
|
|
471
|
+
const mergeCdnDefaults = (componentName, cdnDefaults, userProps) => {
|
|
472
|
+
if (!cdnDefaults)
|
|
473
|
+
return { merged: userProps, defaultsApplied: [] };
|
|
474
|
+
const userPropsClean = (0, exports.stripFunctionProps)(userProps);
|
|
475
|
+
const baseMerged = (0, lodash_1.merge)((0, lodash_1.cloneDeep)(cdnDefaults), userPropsClean);
|
|
476
|
+
const merged = componentName === 'date-picker' && Array.isArray(userPropsClean.datePickerOptions)
|
|
477
|
+
? {
|
|
478
|
+
...baseMerged,
|
|
479
|
+
datePickerOptions: userPropsClean.datePickerOptions.map((item) => {
|
|
480
|
+
const baseOptions = cdnDefaults.datePickerOptions?.[0]?.options ?? {};
|
|
481
|
+
return {
|
|
482
|
+
...item,
|
|
483
|
+
options: (0, lodash_1.merge)((0, lodash_1.cloneDeep)(baseOptions), item.options ?? {}),
|
|
484
|
+
};
|
|
485
|
+
}),
|
|
486
|
+
}
|
|
487
|
+
: baseMerged;
|
|
488
|
+
const defaultsApplied = Object.keys(cdnDefaults).filter((key) => !(key in userPropsClean));
|
|
489
|
+
return { merged, defaultsApplied };
|
|
490
|
+
};
|
|
491
|
+
exports.mergeCdnDefaults = mergeCdnDefaults;
|
|
492
|
+
/** CDN config(에이전트 입력) → React props (buildReactOutput 입력) 번역.
|
|
493
|
+
* 단순 컴포넌트는 identity, 차이가 있는 컴포넌트는 별도 룰. */
|
|
494
|
+
const translateCdnToReactProps = (componentName, cdnConfig) => {
|
|
495
|
+
// SelectBox/ComboBox: options → optionItems, value(원시) → value(객체) (배열에서 매칭해 wrap)
|
|
496
|
+
if (componentName === 'select-box' || componentName === 'combo-box') {
|
|
497
|
+
const { options, value, ...rest } = cdnConfig;
|
|
498
|
+
const result = { ...rest };
|
|
499
|
+
if (options !== undefined)
|
|
500
|
+
result.optionItems = options;
|
|
501
|
+
if (value !== undefined) {
|
|
502
|
+
if ((typeof value === 'string' || typeof value === 'number') && Array.isArray(options)) {
|
|
503
|
+
const matched = options.find((opt) => typeof opt === 'object' && opt !== null && opt.id === value);
|
|
504
|
+
result.value = matched !== undefined ? matched : value;
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
result.value = value;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return result;
|
|
511
|
+
}
|
|
512
|
+
// FileInput/ImageFileInput: container 키는 React props에 없음 → 제거
|
|
513
|
+
if (componentName === 'file-input' || componentName === 'image-file-input') {
|
|
514
|
+
const result = { ...cdnConfig };
|
|
515
|
+
delete result.container;
|
|
516
|
+
return result;
|
|
517
|
+
}
|
|
518
|
+
// 그 외: identity (Slider, DatePicker, Tooltip, Notification, ProgressBar, Tag — key 동일 가정)
|
|
519
|
+
return cdnConfig;
|
|
520
|
+
};
|
|
334
521
|
// ── 응답 조립 ──────────────────────────────────────────────────────
|
|
335
522
|
/** componentMap의 props 스펙에서 defaultsUsed를 자동 계산 */
|
|
336
523
|
const calcDefaultsUsed = (propsSpec, userProps) => Object.fromEntries(Object.entries(propsSpec)
|
|
@@ -391,9 +578,9 @@ const reactElementToJsx = (node, ctx) => {
|
|
|
391
578
|
const name = typeof el.type === 'string'
|
|
392
579
|
? el.type
|
|
393
580
|
: typeof el.type === 'function'
|
|
394
|
-
? el.type.displayName
|
|
395
|
-
|
|
396
|
-
|
|
581
|
+
? el.type.displayName ||
|
|
582
|
+
el.type.name ||
|
|
583
|
+
'Component'
|
|
397
584
|
: 'Component';
|
|
398
585
|
const { children: childChildren, ...restProps } = el.props || {};
|
|
399
586
|
const attrs = Object.entries(restProps)
|
|
@@ -464,8 +651,12 @@ const buildJsField = (componentData, jsApiMap) => {
|
|
|
464
651
|
// ── 진입점 ────────────────────────────────────────────────────────
|
|
465
652
|
/** render_to_html tool — props로 React 컴포넌트를 렌더링하여 HTML + React 매핑 반환 */
|
|
466
653
|
const renderToHtml = (params) => {
|
|
467
|
-
const { componentMap, bundle, iconBundle, cdnMeta, iconMeta, reactRuntime, jsApiMap, name, props } = params;
|
|
654
|
+
const { componentMap, bundle, iconBundle, cdnMeta, iconMeta, reactRuntime, jsApiMap, name, props, instanceId } = params;
|
|
468
655
|
const normalized = (0, response_js_1.normalizeName)(name);
|
|
656
|
+
// Story 5.10: editor alias 검출 시 ui-admin 흐름 우회 — 외부 분기에서 응답 직접 조립
|
|
657
|
+
if ((0, editor_js_1.isEditorAlias)(normalized)) {
|
|
658
|
+
return (0, editor_js_1.buildEditorResponse)(props ?? {}, instanceId);
|
|
659
|
+
}
|
|
469
660
|
const componentData = componentMap.get(normalized);
|
|
470
661
|
if (!componentData)
|
|
471
662
|
return (0, response_js_1.componentNotFoundResponse)(normalized);
|
|
@@ -478,29 +669,59 @@ const renderToHtml = (params) => {
|
|
|
478
669
|
return (0, response_js_1.errorResponse)('COMPONENT_NOT_IN_BUNDLE', `'${normalized}' (${exportName}) 컴포넌트가 번들에 없습니다.`, '번들을 재빌드해주세요 (pnpm build:bundle).');
|
|
479
670
|
}
|
|
480
671
|
try {
|
|
481
|
-
|
|
672
|
+
// Story 5.8: jsRequired + js-api 정의 + jsApi.cdnPattern 등록 → CDN-input 모드
|
|
673
|
+
const jsApi = jsApiMap.get(exportName);
|
|
674
|
+
const isCdnInput = componentData.jsRequired && jsApi !== undefined && jsApi.cdnPattern !== undefined;
|
|
675
|
+
// CDN-input은 입력이 CDN config 스키마 → 옵션 배열 키 정규화 → React 파이프라인을 위해 React props로 번역
|
|
676
|
+
const cdnNormalization = isCdnInput
|
|
677
|
+
? normalizeCdnInputProps(normalized, props ?? {})
|
|
678
|
+
: { normalized: props ?? {}, warnings: [] };
|
|
679
|
+
const normalizedCdnProps = cdnNormalization.normalized;
|
|
680
|
+
// Story 5.9: cdnDefaults 와 deep merge (사용자 props 우선, 함수 prop skip, datePickerOptions per-element 부분 merge)
|
|
681
|
+
const cdnMergeResult = isCdnInput && jsApi
|
|
682
|
+
? (0, exports.mergeCdnDefaults)(normalized, jsApi.cdnDefaults, normalizedCdnProps)
|
|
683
|
+
: { merged: normalizedCdnProps, defaultsApplied: [] };
|
|
684
|
+
const mergedCdnProps = cdnMergeResult.merged;
|
|
685
|
+
const reactInput = isCdnInput ? translateCdnToReactProps(normalized, mergedCdnProps) : mergedCdnProps;
|
|
686
|
+
const safeProps = removeBlockedProps(reactInput);
|
|
482
687
|
const sanitized = componentData.props ? sanitizeProps(safeProps, componentData.props) : safeProps;
|
|
483
688
|
const enumWarnings = componentData.props ? validateProps(sanitized, componentData.props) : [];
|
|
484
689
|
const enumCorrected = componentData.props ? correctInvalidEnums(sanitized, componentData.props) : sanitized;
|
|
485
690
|
const corrected = componentData.props ? correctMissingRequired(enumCorrected, componentData.props) : enumCorrected;
|
|
486
691
|
const postWarnings = componentData.props ? validateProps(corrected, componentData.props) : [];
|
|
487
|
-
const
|
|
692
|
+
const cdnKeyWarnings = isCdnInput && jsApi ? checkUnknownCdnKeys(normalizedCdnProps, jsApi) : [];
|
|
693
|
+
const warnings = [
|
|
694
|
+
...enumWarnings,
|
|
695
|
+
...postWarnings.filter((w) => !enumWarnings.includes(w)),
|
|
696
|
+
...cdnKeyWarnings,
|
|
697
|
+
...cdnNormalization.warnings,
|
|
698
|
+
];
|
|
488
699
|
const resolvedProps = resolveIconProps(corrected, iconBundle, componentData.props);
|
|
489
700
|
// children이 컴포넌트 descriptor면 React element로 변환
|
|
490
701
|
if (resolvedProps.children !== undefined) {
|
|
491
702
|
const ctx = { bundle, iconBundle, componentMap, reactRuntime };
|
|
492
703
|
resolvedProps.children = resolveChildren(resolvedProps.children, ctx, 0);
|
|
493
704
|
}
|
|
494
|
-
|
|
495
|
-
const
|
|
705
|
+
// HTML 출력: CDN-input은 init script, 그 외는 React 정적 HTML (renderToStaticMarkup은 필요할 때만 호출)
|
|
706
|
+
const cdnInitScript = isCdnInput && jsApi?.cdnPattern
|
|
707
|
+
? buildCdnInitScript(jsApi.cdnPattern, normalized, instanceId, jsApi.className, removeBlockedProps(mergedCdnProps))
|
|
708
|
+
: null;
|
|
709
|
+
const html = cdnInitScript !== null
|
|
710
|
+
? cdnInitScript
|
|
711
|
+
: `<!-- ncua:${normalized} start -->\n${reactRuntime.renderToStaticMarkup(reactRuntime.createElement(Component, resolvedProps))}\n<!-- ncua:${normalized} end -->`;
|
|
496
712
|
const hasDefaults = componentData.props ? calcDefaultsUsed(componentData.props, corrected) : {};
|
|
713
|
+
// Story 5.9: cdnDefaults 적용된 키도 defaultsUsed 에 노출 (에이전트 학습용)
|
|
714
|
+
const cdnDefaultsApplied = isCdnInput && jsApi?.cdnDefaults
|
|
715
|
+
? Object.fromEntries(cdnMergeResult.defaultsApplied.map((key) => [key, jsApi.cdnDefaults?.[key]]))
|
|
716
|
+
: {};
|
|
717
|
+
const finalDefaultsUsed = { ...hasDefaults, ...cdnDefaultsApplied };
|
|
497
718
|
return (0, response_js_1.successResponse)({
|
|
498
719
|
html,
|
|
499
720
|
component: normalized,
|
|
500
721
|
exportName,
|
|
501
722
|
importPath: componentData.importPath,
|
|
502
723
|
appliedProps: corrected,
|
|
503
|
-
...(Object.keys(
|
|
724
|
+
...(Object.keys(finalDefaultsUsed).length > 0 && { defaultsUsed: finalDefaultsUsed }),
|
|
504
725
|
...(warnings.length > 0 && { warnings }),
|
|
505
726
|
js: buildJsField(componentData, jsApiMap),
|
|
506
727
|
cdn: cdnMeta ?? undefined,
|
package/bin/types.d.ts
CHANGED
|
@@ -85,6 +85,16 @@ export interface JsApiInfo {
|
|
|
85
85
|
methods: string[];
|
|
86
86
|
staticMethods?: string[];
|
|
87
87
|
example: string;
|
|
88
|
+
/** Story 5.9: CDN init script 생성 시 사용자 props 와 deep merge 되는 default 옵션. JSON-serializable 만 (함수 prop 없음). */
|
|
89
|
+
cdnDefaults?: Record<string, unknown>;
|
|
90
|
+
/**
|
|
91
|
+
* CDN 호출 패턴.
|
|
92
|
+
* - A: `new ncua.X(element, options)` — element가 곧 컨테이너
|
|
93
|
+
* - B: `new ncua.X(options)` — options.container로 자동 append
|
|
94
|
+
* - C: `new ncua.X(options)` — getElement() + 수동 append
|
|
95
|
+
* 부재 시 React 정적 HTML 경로로 fallback.
|
|
96
|
+
*/
|
|
97
|
+
cdnPattern?: 'A' | 'B' | 'C';
|
|
88
98
|
}
|
|
89
99
|
export interface ComplianceRuleHint {
|
|
90
100
|
attr: string;
|
package/data/badge-group.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"exportName": "BadgeGroup",
|
|
4
4
|
"importPath": "@ncds/ui-admin",
|
|
5
5
|
"jsRequired": false,
|
|
6
|
-
"category": "feedback",
|
|
6
|
+
"category": "feedback-and-status",
|
|
7
7
|
"description": "BadgeGroup은 Badge 컴포넌트를 그룹화하여 표시하는 컴포넌트입니다. 주로 Badge와 함께 추가적인 라벨과 아이콘을 표시하여 액션유도할 수 있는 아이템으로 활용할 수 있습니다.",
|
|
8
8
|
"aliases": [
|
|
9
9
|
"BadgeGroup",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"groupIcon": {
|
|
60
60
|
"type": "object",
|
|
61
61
|
"required": false,
|
|
62
|
-
"rawType": "import(\"/Users/
|
|
62
|
+
"rawType": "import(\"/Users/nhncommerce/Desktop/project/ncds/packages/ui-admin/src/types/side-slot\").SideSlotType | undefined",
|
|
63
63
|
"properties": {
|
|
64
64
|
"type": {
|
|
65
65
|
"type": "string",
|
|
@@ -134,7 +134,7 @@
|
|
|
134
134
|
"leadingIcon": {
|
|
135
135
|
"type": "object",
|
|
136
136
|
"required": false,
|
|
137
|
-
"rawType": "import(\"/Users/
|
|
137
|
+
"rawType": "import(\"/Users/nhncommerce/Desktop/project/ncds/packages/ui-admin/src/types/side-slot\").SideSlotType | undefined",
|
|
138
138
|
"properties": {
|
|
139
139
|
"type": {
|
|
140
140
|
"type": "string",
|
|
@@ -211,7 +211,7 @@
|
|
|
211
211
|
"trailingIcon": {
|
|
212
212
|
"type": "object",
|
|
213
213
|
"required": false,
|
|
214
|
-
"rawType": "import(\"/Users/
|
|
214
|
+
"rawType": "import(\"/Users/nhncommerce/Desktop/project/ncds/packages/ui-admin/src/types/side-slot\").SideSlotType | undefined",
|
|
215
215
|
"properties": {
|
|
216
216
|
"type": {
|
|
217
217
|
"type": "string",
|
package/data/badge.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"exportName": "Badge",
|
|
4
4
|
"importPath": "@ncds/ui-admin",
|
|
5
5
|
"jsRequired": false,
|
|
6
|
-
"category": "
|
|
6
|
+
"category": "feedback-and-status",
|
|
7
7
|
"description": "Badge는 상태나 중요성 및 이벤트 강조를 나타내는 레이블이며 빠른 인식을 제공해야 합니다.",
|
|
8
8
|
"aliases": [
|
|
9
9
|
"Badge",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"leadingIcon": {
|
|
82
82
|
"type": "object",
|
|
83
83
|
"required": false,
|
|
84
|
-
"rawType": "import(\"/Users/
|
|
84
|
+
"rawType": "import(\"/Users/nhncommerce/Desktop/project/ncds/packages/ui-admin/src/types/side-slot\").SideSlotType | undefined",
|
|
85
85
|
"properties": {
|
|
86
86
|
"type": {
|
|
87
87
|
"type": "string",
|
|
@@ -154,7 +154,7 @@
|
|
|
154
154
|
"trailingIcon": {
|
|
155
155
|
"type": "object",
|
|
156
156
|
"required": false,
|
|
157
|
-
"rawType": "import(\"/Users/
|
|
157
|
+
"rawType": "import(\"/Users/nhncommerce/Desktop/project/ncds/packages/ui-admin/src/types/side-slot\").SideSlotType | undefined",
|
|
158
158
|
"properties": {
|
|
159
159
|
"type": {
|
|
160
160
|
"type": "string",
|
package/data/bread-crumb.json
CHANGED
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"items": {
|
|
58
58
|
"type": "object",
|
|
59
59
|
"required": true,
|
|
60
|
-
"rawType": "import(\"/Users/
|
|
60
|
+
"rawType": "import(\"/Users/nhncommerce/Desktop/project/ncds/packages/ui-admin/src/components/navigation/bread-crumb/BreadCrumb\").BreadcrumbItemProps[]",
|
|
61
61
|
"properties": {
|
|
62
62
|
"href": {
|
|
63
63
|
"type": "string",
|
package/data/button-group.json
CHANGED
|
@@ -89,6 +89,16 @@
|
|
|
89
89
|
"required": false,
|
|
90
90
|
"default": false
|
|
91
91
|
},
|
|
92
|
+
"fullWidth": {
|
|
93
|
+
"type": "boolean",
|
|
94
|
+
"required": false,
|
|
95
|
+
"default": false
|
|
96
|
+
},
|
|
97
|
+
"hasBorder": {
|
|
98
|
+
"type": "boolean",
|
|
99
|
+
"required": false,
|
|
100
|
+
"default": true
|
|
101
|
+
},
|
|
92
102
|
"icon": {
|
|
93
103
|
"type": "string",
|
|
94
104
|
"required": false,
|
package/data/button.json
CHANGED
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"leadingIcon": {
|
|
78
78
|
"type": "object",
|
|
79
79
|
"required": false,
|
|
80
|
-
"rawType": "import(\"/Users/
|
|
80
|
+
"rawType": "import(\"/Users/nhncommerce/Desktop/project/ncds/packages/ui-admin/src/types/side-slot\").SideSlotType | undefined",
|
|
81
81
|
"properties": {
|
|
82
82
|
"type": {
|
|
83
83
|
"type": "string",
|
|
@@ -158,7 +158,7 @@
|
|
|
158
158
|
"trailingIcon": {
|
|
159
159
|
"type": "object",
|
|
160
160
|
"required": false,
|
|
161
|
-
"rawType": "import(\"/Users/
|
|
161
|
+
"rawType": "import(\"/Users/nhncommerce/Desktop/project/ncds/packages/ui-admin/src/types/side-slot\").SideSlotType | undefined",
|
|
162
162
|
"properties": {
|
|
163
163
|
"type": {
|
|
164
164
|
"type": "string",
|
package/data/carousel-arrow.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"exportName": "CarouselNumberGroup",
|
|
4
4
|
"importPath": "@ncds/ui-admin",
|
|
5
5
|
"jsRequired": false,
|
|
6
|
-
"category": "feedback",
|
|
6
|
+
"category": "feedback-and-status",
|
|
7
7
|
"description": "CarouselNumberGroup은 캐러셀의 현재 페이지와 전체 페이지 수를 표시하는 컴포넌트입니다.",
|
|
8
8
|
"aliases": [
|
|
9
9
|
"CarouselNumberGroup",
|
package/data/checkbox.json
CHANGED
package/data/combo-box.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"exportName": "ComboBox",
|
|
4
4
|
"importPath": "@ncds/ui-admin",
|
|
5
5
|
"jsRequired": true,
|
|
6
|
-
"category": "input",
|
|
6
|
+
"category": "forms-and-input",
|
|
7
7
|
"description": "ComboBox는 여러 개의 데이터에서 검색(입력)하거나 리스트에서 옵션을 골라 빠르게 선택하는 복합 선택 컴포넌트입니다.",
|
|
8
8
|
"aliases": [
|
|
9
9
|
"콤보박스",
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"optionItems": {
|
|
93
93
|
"type": "object",
|
|
94
94
|
"required": false,
|
|
95
|
-
"rawType": "import(\"/Users/
|
|
95
|
+
"rawType": "import(\"/Users/nhncommerce/Desktop/project/ncds/packages/ui-admin/src/types/dropdown/option\").OptionType[] | undefined",
|
|
96
96
|
"properties": {
|
|
97
97
|
"id": {
|
|
98
98
|
"type": "string",
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
"register": {
|
|
120
120
|
"type": "object",
|
|
121
121
|
"required": false,
|
|
122
|
-
"rawType": "import(\"/Users/
|
|
122
|
+
"rawType": "import(\"/Users/nhncommerce/Desktop/project/ncds/node_modules/.pnpm/react-hook-form@7.72.0_react@18.2.0/node_modules/react-hook-form/dist/types/form\").UseFormRegisterReturn | undefined"
|
|
123
123
|
},
|
|
124
124
|
"required": {
|
|
125
125
|
"type": "boolean",
|
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
"value": {
|
|
149
149
|
"type": "object",
|
|
150
150
|
"required": false,
|
|
151
|
-
"rawType": "import(\"/Users/
|
|
151
|
+
"rawType": "import(\"/Users/nhncommerce/Desktop/project/ncds/packages/ui-admin/src/types/dropdown/option\").OptionValue | undefined"
|
|
152
152
|
}
|
|
153
153
|
},
|
|
154
154
|
"html": {},
|
package/data/date-picker.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"exportName": "DatePicker",
|
|
4
4
|
"importPath": "@ncds/ui-admin",
|
|
5
5
|
"jsRequired": true,
|
|
6
|
-
"category": "input",
|
|
6
|
+
"category": "forms-and-input",
|
|
7
7
|
"description": "DatePicker는 등록일, 노출일, 검색 기간 등 날짜 또는 시간 설정이 필요한 경우 사용하는 선택 위젯입니다.",
|
|
8
8
|
"aliases": [
|
|
9
9
|
"날짜선택위젯",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"datePickerOptions": {
|
|
63
63
|
"type": "object",
|
|
64
64
|
"required": false,
|
|
65
|
-
"rawType": "Partial<import(\"/Users/
|
|
65
|
+
"rawType": "Partial<import(\"/Users/nhncommerce/Desktop/project/ncds/node_modules/.pnpm/flatpickr@4.6.13/node_modules/flatpickr/dist/types/options\").BaseOptions> | undefined"
|
|
66
66
|
},
|
|
67
67
|
"destructive": {
|
|
68
68
|
"type": "string",
|
package/data/divider.json
CHANGED