@wipco/sdui 0.4.1 → 0.5.0
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/README.md +1 -1
- package/dist/index.d.ts +122 -2
- package/dist/index.js +272 -3
- package/dist/meta.d.ts +19 -4
- package/dist/meta.js +104 -1
- package/dist/styles.css +11 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ export function Page({ spec }: { spec: unknown }) {
|
|
|
18
18
|
|
|
19
19
|
| 진입점 | 내용 | 추가 peer |
|
|
20
20
|
| --- | --- | --- |
|
|
21
|
-
| `@wipco/sdui` | 렌더러 · 기본 어휘
|
|
21
|
+
| `@wipco/sdui` | 렌더러 · 기본 어휘 41종 · 검증 · 권한 게이트 | 없음 |
|
|
22
22
|
| `@wipco/sdui/rich` | `Chart` · `MapPreview` — `createRegistry(richComponents)`로 얹는다 | `recharts` · `ol` (선택) |
|
|
23
23
|
| `@wipco/sdui/meta` | 컴포넌트 메타데이터 — 빌더 UI의 팔레트·속성 패널용 | 없음 |
|
|
24
24
|
| `@wipco/sdui/styles.css` | 공용 시트 (`.wp-*`) | — |
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import React__default, { ComponentType } from 'react';
|
|
2
|
+
import React__default, { CSSProperties, ReactNode, ComponentType } from 'react';
|
|
3
3
|
import { D as DatePicker, a as DateRangePicker } from './DatePicker-CTDcFR7V.js';
|
|
4
4
|
export { b as DatePickerProps, c as DateRangePickerProps, d as DateRangeValue } from './DatePicker-CTDcFR7V.js';
|
|
5
5
|
import { S as SduiAction, a as SduiPropValue, b as SduiRule, c as SduiPage } from './types-D5Tkrynd.js';
|
|
@@ -1235,6 +1235,124 @@ interface SduiFormProps {
|
|
|
1235
1235
|
}
|
|
1236
1236
|
declare function SduiForm({ id, initial, initialFromUrl, rules, labels, guard, onSubmit, className, style, children, }: SduiFormProps): React__default.JSX.Element;
|
|
1237
1237
|
|
|
1238
|
+
/**
|
|
1239
|
+
* `SortableList` / `SortHandle` — 손잡이를 끌어 **배열 상태의 순서를 바꾸는** 목록.
|
|
1240
|
+
*
|
|
1241
|
+
* 어휘에 이미 `arrayMove` 액션이 있었다. 없던 것은 그 액션을 **드래그로 일으키는
|
|
1242
|
+
* 표현**뿐이라, 여기서 새로 만드는 것도 그 표현 하나다 — 상태 변경은 전부 기존
|
|
1243
|
+
* `arrayMove`로 나간다(새 액션 0개, `types.ts` 무변경).
|
|
1244
|
+
*
|
|
1245
|
+
* ## 왜 컨테이너와 손잡이 둘로 나눴나
|
|
1246
|
+
*
|
|
1247
|
+
* `$item`/`$index` 스코프는 **렌더러 소유**다(`SduiRenderer.tsx`의 ForEach 분기만
|
|
1248
|
+
* `rowScope`를 만든다). 레지스트리 컴포넌트는 배열을 스스로 돌 수 없으므로, 반복은
|
|
1249
|
+
* 그대로 `ForEach`가 하고 이쪽은 **인덱스만** 안다.
|
|
1250
|
+
*
|
|
1251
|
+
* SortableList(stateKey) > ForEach(source) > Card( SortHandle(index:{$index:true}) … )
|
|
1252
|
+
*
|
|
1253
|
+
* 손잡이가 `{"$index": true}`로 **자기 번호를 스펙에서 선언**하는 것이 핵심이다.
|
|
1254
|
+
* 컨테이너가 DOM 자식을 세어 인덱스를 매기는 안(손잡이 없는 단일 컴포넌트)은 버렸다 —
|
|
1255
|
+
* ForEach는 fragment라 보통은 "DOM 자식 하나 = 행 하나"가 맞지만, 행 템플릿이 노드
|
|
1256
|
+
* 둘이 되거나 `Show`가 아무것도 안 그리는 순간 매핑이 **조용히** 어긋난다. 어휘가
|
|
1257
|
+
* "행은 노드 하나여야 함"이라는 암묵 규칙을 갖게 되는 값이 손잡이 하나보다 비싸다.
|
|
1258
|
+
*
|
|
1259
|
+
* 손잡이가 필요한 독립적인 이유도 있다. 이 목록의 행은 `Input`/`Textarea`를 품는다.
|
|
1260
|
+
* 행 전체를 드래그로 잡으면 그 입력칸의 텍스트 선택이 죽는다. 캔버스 자신도 같은
|
|
1261
|
+
* 결론이라 노드 이동을 행이 아니라 그립 뱃지에 붙였다.
|
|
1262
|
+
*
|
|
1263
|
+
* ## 왜 HTML5 DnD가 아니라 포인터 이벤트인가
|
|
1264
|
+
*
|
|
1265
|
+
* 실측(Chromium): 캔버스가 캡처 단계에서 `mousedown`을 삼키면 `dragstart`는 안 뜨지만
|
|
1266
|
+
* `pointerdown`/`pointermove`는 그대로 온다. 그리고 터치에서는 `dragstart`가 아예 안 뜬다.
|
|
1267
|
+
* 포인터 이벤트를 고르면 캔버스의 노드 이동(HTML5 DnD)과 **이벤트 종류부터 달라져**,
|
|
1268
|
+
* `dragover`마다 "이건 행 정렬인가 노드 이동인가"를 되묻는 분기가 아예 생기지 않는다.
|
|
1269
|
+
* 대신 캔버스에서는 이쪽이 완전히 살아 있으므로 **명시적으로 끊어야 한다** — 아래 참고.
|
|
1270
|
+
* 라이브러리 선례도 포인터 쪽이다(`OrgChart`의 `setPointerCapture` + `touchAction:'none'`).
|
|
1271
|
+
*
|
|
1272
|
+
* ## 캔버스에서는 정렬이 노드 이동에 진다
|
|
1273
|
+
*
|
|
1274
|
+
* `useSduiSandbox()`가 true면 포인터·키보드 핸들러를 **아예 붙이지 않는다.** 근거는
|
|
1275
|
+
* 두 가지다. ① 캔버스는 스펙을 고치는 자리이고, 거기서 바꾼 행 순서는 런타임 상태라
|
|
1276
|
+
* **저장되지 않는다** — 사용자가 한 일이 사라지는 것이 가장 나쁜 결과다. ② 캔버스에서
|
|
1277
|
+
* 행을 끄는 동작에는 이미 뜻이 있다(노드 이동). 두 뜻이 겹치면 어느 쪽인지 정해야
|
|
1278
|
+
* 하는데, 저장되지 않는 쪽이 이길 이유가 없다.
|
|
1279
|
+
* 표식은 그린다 — 손잡이는 그립 모양 그대로 나오되 `aria-disabled`와 사유 툴팁이 붙는다.
|
|
1280
|
+
* "막았다"를 화면에 남기는 방식과 문구를 캔버스의 다른 차단과 맞춘 것이다.
|
|
1281
|
+
*
|
|
1282
|
+
* ## 잠기는 자리를 전부 툴팁으로 말한다
|
|
1283
|
+
*
|
|
1284
|
+
* `arrayMove`는 키가 배열이 아니면 **경고 한 줄 없이** 끝난다. 이 화면은 정확히 그
|
|
1285
|
+
* 함정 위에 지어지므로(`{"$state":"rows","default":[…]}`처럼 `default`만 있는 상태는
|
|
1286
|
+
* 화면에 행이 보여도 `arrayMove`가 무동작이다), 손잡이가 스스로 그 사실을 말한다.
|
|
1287
|
+
* 사유는 전부 `title` + `aria-disabled`로 나가고, 스펙 작성 실수(키 없음·손잡이가
|
|
1288
|
+
* 컨테이너 밖)만 콘솔에도 한 번 남긴다.
|
|
1289
|
+
*
|
|
1290
|
+
* ## 접근성 — 세 경로가 같은 액션 하나로 모인다
|
|
1291
|
+
*
|
|
1292
|
+
* 드래그(포인터) ─┐
|
|
1293
|
+
* 키보드(손잡이) ─┼→ dispatch({action:'arrayMove', key, from, to})
|
|
1294
|
+
* 위/아래 버튼 ─┘ (버튼은 스펙이 직접 선언한다 — 라이브러리 개입 0)
|
|
1295
|
+
*
|
|
1296
|
+
* WCAG SC 2.5.7(Dragging Movements)은 드래그에 단일 포인터 대안을 요구하고 SC 2.1.1은
|
|
1297
|
+
* 키보드 대안을 요구한다. 손잡이는 진짜 `<button>`이고 Space로 집어 ↑↓로 옮긴다.
|
|
1298
|
+
* 위/아래 버튼은 오늘 어휘만으로 완성되므로(`arrayMove.to`에 `$calc` sum/sub를 넣으면
|
|
1299
|
+
* 된다 — 실측 확인) 여기서 대신 만들지 않는다. 스펙이 그대로 쓰면 된다.
|
|
1300
|
+
* 그립은 SC 2.5.8(Target Size)을 지켜 24×24 CSS px 이상이다.
|
|
1301
|
+
*/
|
|
1302
|
+
|
|
1303
|
+
/**
|
|
1304
|
+
* 손잡이들의 세로 중심 좌표에서 드롭 지점을 인덱스로 바꾼다. 가장 가까운 중심이 답이다.
|
|
1305
|
+
*
|
|
1306
|
+
* 행 높이가 같으면 이 규칙에 히스테리시스가 저절로 생긴다 — 한 칸 옮겨진 직후에는
|
|
1307
|
+
* 포인터 밑에 **옮겨진 행 자신**이 있으므로 가장 가까운 중심이 자기 자리가 되어
|
|
1308
|
+
* 되돌아가지 않는다. 그래서 끄는 동안 곧바로 `arrayMove`를 쏘는(=행이 실제로 움직이는)
|
|
1309
|
+
* 방식이 흔들리지 않고, 자리표시자·유령 행 같은 장치가 필요 없다.
|
|
1310
|
+
*
|
|
1311
|
+
* 빈 자리(`null`/`undefined`)는 건너뛴다. `Show`가 어떤 행의 손잡이를 안 그리면
|
|
1312
|
+
* 그 인덱스는 비는데, 그때 0으로 뭉개면 엉뚱한 행이 목적지가 된다.
|
|
1313
|
+
*
|
|
1314
|
+
* @param centers 인덱스별 손잡이 중심의 clientY. 성기게(sparse) 와도 된다.
|
|
1315
|
+
* @returns 가장 가까운 인덱스. 잴 손잡이가 하나도 없으면 null.
|
|
1316
|
+
*/
|
|
1317
|
+
declare function sortableDropIndex(centers: ReadonlyArray<number | null | undefined>, pointerY: number): number | null;
|
|
1318
|
+
/**
|
|
1319
|
+
* 이동을 확정한다 — **`arrayMove`가 실제로 하는 일과 같은 규칙**으로.
|
|
1320
|
+
*
|
|
1321
|
+
* `arrayMove`는 `from`이 범위를 벗어나면 무시하고, `to`는 양끝으로 clamp한 뒤
|
|
1322
|
+
* `from === to`면 아무 것도 하지 않는다(`SduiRenderer.tsx`). 그 판정을 여기서 미리
|
|
1323
|
+
* 해서 **무동작 액션을 아예 쏘지 않는다.** 렌더러에 맡겨도 결과는 같지만, 그러면
|
|
1324
|
+
* "첫 행을 위로"가 상태 갱신 한 번을 헛돌게 하고 낭독도 한 번 헛나간다.
|
|
1325
|
+
*
|
|
1326
|
+
* @returns 옮길 자리. 할 일이 없으면(범위 밖·같은 자리) null.
|
|
1327
|
+
*/
|
|
1328
|
+
declare function sortableMove(from: number, to: number | null, length: number): number | null;
|
|
1329
|
+
interface SortableListProps {
|
|
1330
|
+
/**
|
|
1331
|
+
* 순서를 바꿀 배열의 상태 키. `arrayMove.key`로 그대로 나간다.
|
|
1332
|
+
*
|
|
1333
|
+
* **최상위 키여야 한다** — `arrayMove`는 `state[key]`를 직접 조회하므로 `a.0.b` 같은
|
|
1334
|
+
* 중첩 경로는 조용히 무동작이다. 행마다 다른 배열을 다뤄야 하면(분류 안의 항목 등)
|
|
1335
|
+
* 상태를 평면으로 펴고 `{"$calc":{"op":"concat","args":["items.",{"$item":"id"}]}}`로
|
|
1336
|
+
* 키를 만들어 넘긴다 — 렌더러가 프롭을 해석해 주므로 여기에는 문자열로 도착한다.
|
|
1337
|
+
*/
|
|
1338
|
+
stateKey?: string;
|
|
1339
|
+
/** 순서 변경 잠금. 손잡이는 그대로 보이고 사유가 툴팁으로 붙는다. */
|
|
1340
|
+
disabled?: boolean;
|
|
1341
|
+
className?: string;
|
|
1342
|
+
style?: CSSProperties;
|
|
1343
|
+
children?: ReactNode;
|
|
1344
|
+
}
|
|
1345
|
+
declare function SortableList({ stateKey, disabled, className, style, children, }: SortableListProps): React__default.JSX.Element;
|
|
1346
|
+
interface SortHandleProps {
|
|
1347
|
+
/** 이 행의 번호. 스펙에서는 거의 항상 `{"$index": true}`이고, 그 값이 곧 `arrayMove.from`이다. */
|
|
1348
|
+
index?: number;
|
|
1349
|
+
/** 보조기술이 읽을 행 이름. 보통 `{"$item":"title"}`. 없으면 "N번째 항목". */
|
|
1350
|
+
label?: string;
|
|
1351
|
+
className?: string;
|
|
1352
|
+
style?: CSSProperties;
|
|
1353
|
+
}
|
|
1354
|
+
declare function SortHandle({ index, label, className, style }: SortHandleProps): React__default.JSX.Element;
|
|
1355
|
+
|
|
1238
1356
|
/**
|
|
1239
1357
|
* JSON 스펙의 `component` 이름 → 실제 컴포넌트.
|
|
1240
1358
|
* 이 키 목록이 곧 스펙에서 사용할 수 있는 컴포넌트 어휘 전체다.
|
|
@@ -1292,6 +1410,8 @@ declare const defaultRegistry: {
|
|
|
1292
1410
|
Text: typeof Text;
|
|
1293
1411
|
Form: typeof SduiForm;
|
|
1294
1412
|
Can: typeof Can;
|
|
1413
|
+
SortableList: typeof SortableList;
|
|
1414
|
+
SortHandle: typeof SortHandle;
|
|
1295
1415
|
SearchResults: typeof SduiSearchResults;
|
|
1296
1416
|
RemoteSearchResults: typeof RemoteSearchResults;
|
|
1297
1417
|
};
|
|
@@ -1431,4 +1551,4 @@ declare class NodeErrorBoundary extends React__default.Component<Props, State> {
|
|
|
1431
1551
|
render(): string | number | bigint | boolean | Iterable<React__default.ReactNode> | Promise<string | number | bigint | boolean | React__default.ReactPortal | React__default.ReactElement<unknown, string | React__default.JSXElementConstructor<any>> | Iterable<React__default.ReactNode> | null | undefined> | React__default.JSX.Element | null | undefined;
|
|
1432
1552
|
}
|
|
1433
1553
|
|
|
1434
|
-
export { Actions, type ActionsProps, Alert, type AlertProps, Avatar, type AvatarProps, Badge, type BadgeProps, type BadgeTone, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, Button, type ButtonProps, Can, type CanProps, Card, type CardProps, type CascadingSearchOption, Checkbox, type CheckboxProps, Col, type CustomActionHandler, type CustomValidator, DatePicker, DateRangePicker, Dialog, type DialogProps, Divider, EmptyState, type EmptyStateProps, Field, type FieldProps, Grid, type GridCardConfig, type GridCardRow, type GridProps, Icon, IconButton, type IconButtonProps, type IconProps, Input, type InputProps, type Justify, type KeyValueItem, KeyValueList, type KeyValueListProps, type MeterTone, NodeErrorBoundary, PageHead, type PageHeadProps, Pagination, type PaginationProps, ProgressBar, type ProgressBarProps, Radio, type RadioProps, RemoteSearchResults, type RemoteSearchResultsProps, ResultTable, type ResultTableColumn, type ResultTableProps, Row, type RowProps, SduiAction, type SduiAdapter, type SduiAssetContext, type SduiAssetResolver, type SduiBadgeCell, type SduiCanFn, type SduiCanQuery, type SduiCanSetting, type SduiCell, type SduiDateCell, SduiDialog, type SduiDispatch, SduiForm, type SduiFormConfig, type SduiFormProps, type SduiGridCard, type SduiGridCardAction, type SduiGridCardRow, type SduiIconCell, type SduiLinkCell, type SduiNumberCell, SduiPage, SduiPermissionProvider, type SduiProgressCell, SduiPropValue, type SduiRegistry, SduiRenderer, type SduiRendererProps, type SduiRowCondition, SduiRule, type SduiRuntime, type SduiSearchAction, type SduiSearchColumn, SduiSearchDataProvider, SduiSearchResults, type SduiSearchResultsProps, type SduiTagCell, type SduiTextCell, type SearchAction, type SearchColumn, type SearchFetcher, type SearchField, type SearchFilter, type SearchOption, type SearchPage, SearchResults, type SearchResultsProps, type SearchRow, SearchToolbar, type SearchValues, type SearchViewItem, Section, type SectionProps, SegmentedControl, type SegmentedControlProps, type SegmentedItem, Select, type SelectOption, type SelectProps, Show, type ShowProps, SidebarNav, type SidebarNavItem, type SidebarNavProps, type SidebarNavSection, type SortDir, Spacer, type SpacerProps, type SpecIssue, Spinner, type SpinnerProps, StatCard, type StatCardProps, Switch, type SwitchProps, type TabItem, Table, type TableColumn, type TableProps, Tabs, type TabsProps, Tag, type TagProps, Text, type TextPreset, type TextProps, Textarea, type TextareaProps, Toast, type ToastProps, Tooltip, type TooltipProps, cascadingValuePaths, cellRenderer, cleanSearchValues, createRegistry, defaultAdapter, defaultRegistry, includesLooseSearch, joinMultiValue, normalizePageSizes, normalizeSearchText, searchFieldKeys, searchValuesFromParams, splitMultiValue, useRemoteSearchPage, useSduiAssetResolver, useSduiCan, useSduiDispatch, useSduiRuntime, useSduiSandbox, useSearchFetcher, validateSpec };
|
|
1554
|
+
export { Actions, type ActionsProps, Alert, type AlertProps, Avatar, type AvatarProps, Badge, type BadgeProps, type BadgeTone, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, Button, type ButtonProps, Can, type CanProps, Card, type CardProps, type CascadingSearchOption, Checkbox, type CheckboxProps, Col, type CustomActionHandler, type CustomValidator, DatePicker, DateRangePicker, Dialog, type DialogProps, Divider, EmptyState, type EmptyStateProps, Field, type FieldProps, Grid, type GridCardConfig, type GridCardRow, type GridProps, Icon, IconButton, type IconButtonProps, type IconProps, Input, type InputProps, type Justify, type KeyValueItem, KeyValueList, type KeyValueListProps, type MeterTone, NodeErrorBoundary, PageHead, type PageHeadProps, Pagination, type PaginationProps, ProgressBar, type ProgressBarProps, Radio, type RadioProps, RemoteSearchResults, type RemoteSearchResultsProps, ResultTable, type ResultTableColumn, type ResultTableProps, Row, type RowProps, SduiAction, type SduiAdapter, type SduiAssetContext, type SduiAssetResolver, type SduiBadgeCell, type SduiCanFn, type SduiCanQuery, type SduiCanSetting, type SduiCell, type SduiDateCell, SduiDialog, type SduiDispatch, SduiForm, type SduiFormConfig, type SduiFormProps, type SduiGridCard, type SduiGridCardAction, type SduiGridCardRow, type SduiIconCell, type SduiLinkCell, type SduiNumberCell, SduiPage, SduiPermissionProvider, type SduiProgressCell, SduiPropValue, type SduiRegistry, SduiRenderer, type SduiRendererProps, type SduiRowCondition, SduiRule, type SduiRuntime, type SduiSearchAction, type SduiSearchColumn, SduiSearchDataProvider, SduiSearchResults, type SduiSearchResultsProps, type SduiTagCell, type SduiTextCell, type SearchAction, type SearchColumn, type SearchFetcher, type SearchField, type SearchFilter, type SearchOption, type SearchPage, SearchResults, type SearchResultsProps, type SearchRow, SearchToolbar, type SearchValues, type SearchViewItem, Section, type SectionProps, SegmentedControl, type SegmentedControlProps, type SegmentedItem, Select, type SelectOption, type SelectProps, Show, type ShowProps, SidebarNav, type SidebarNavItem, type SidebarNavProps, type SidebarNavSection, type SortDir, SortHandle, type SortHandleProps, SortableList, type SortableListProps, Spacer, type SpacerProps, type SpecIssue, Spinner, type SpinnerProps, StatCard, type StatCardProps, Switch, type SwitchProps, type TabItem, Table, type TableColumn, type TableProps, Tabs, type TabsProps, Tag, type TagProps, Text, type TextPreset, type TextProps, Textarea, type TextareaProps, Toast, type ToastProps, Tooltip, type TooltipProps, cascadingValuePaths, cellRenderer, cleanSearchValues, createRegistry, defaultAdapter, defaultRegistry, includesLooseSearch, joinMultiValue, normalizePageSizes, normalizeSearchText, searchFieldKeys, searchValuesFromParams, sortableDropIndex, sortableMove, splitMultiValue, useRemoteSearchPage, useSduiAssetResolver, useSduiCan, useSduiDispatch, useSduiRuntime, useSduiSandbox, useSearchFetcher, validateSpec };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEV, DateRangePicker, DatePicker, isSafeNavigationTarget, useSduiAdapter, hostFetchOf, HoistedStyle, resolveAdapter, assetResolverOf, SduiAdapterContext } from './chunk-FGRR3ROJ.js';
|
|
1
|
+
import { DEV, DateRangePicker, DatePicker, isSafeNavigationTarget, useSduiAdapter, hostFetchOf, HoistedStyle, useSduiSandbox, resolveAdapter, assetResolverOf, SduiAdapterContext } from './chunk-FGRR3ROJ.js';
|
|
2
2
|
export { DatePicker, DateRangePicker, defaultAdapter, useSduiAssetResolver, useSduiSandbox } from './chunk-FGRR3ROJ.js';
|
|
3
3
|
import { searchValuesFromParams, splitMultiValue, includesLooseSearch, cascadingValuePaths, SearchToolbar, searchFieldKeys, Popover } from './chunk-KADFN76X.js';
|
|
4
4
|
export { SearchToolbar, cascadingValuePaths, cleanSearchValues, includesLooseSearch, joinMultiValue, normalizeSearchText, searchFieldKeys, searchValuesFromParams, splitMultiValue } from './chunk-KADFN76X.js';
|
|
@@ -1314,6 +1314,264 @@ function SduiForm({
|
|
|
1314
1314
|
}
|
|
1315
1315
|
);
|
|
1316
1316
|
}
|
|
1317
|
+
function sortableDropIndex(centers, pointerY) {
|
|
1318
|
+
let best = null;
|
|
1319
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
1320
|
+
for (let index = 0; index < centers.length; index += 1) {
|
|
1321
|
+
const center = centers[index];
|
|
1322
|
+
if (typeof center !== "number" || !Number.isFinite(center)) continue;
|
|
1323
|
+
const distance = Math.abs(center - pointerY);
|
|
1324
|
+
if (distance < bestDistance) {
|
|
1325
|
+
bestDistance = distance;
|
|
1326
|
+
best = index;
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
return best;
|
|
1330
|
+
}
|
|
1331
|
+
function sortableMove(from, to, length) {
|
|
1332
|
+
if (!Number.isInteger(from) || from < 0 || from >= length) return null;
|
|
1333
|
+
if (to === null || !Number.isInteger(to)) return null;
|
|
1334
|
+
const clamped = Math.max(0, Math.min(length - 1, to));
|
|
1335
|
+
return clamped === from ? null : clamped;
|
|
1336
|
+
}
|
|
1337
|
+
var SortableContext = createContext(null);
|
|
1338
|
+
var HANDLE_SELECTOR = "[data-wp-sort-index]";
|
|
1339
|
+
var HOST_SELECTOR = "[data-wp-sortable]";
|
|
1340
|
+
function SortableList({
|
|
1341
|
+
stateKey,
|
|
1342
|
+
disabled = false,
|
|
1343
|
+
className,
|
|
1344
|
+
style,
|
|
1345
|
+
children
|
|
1346
|
+
}) {
|
|
1347
|
+
const sandbox = useSduiSandbox();
|
|
1348
|
+
const dispatch = useContext(SduiDispatchContext);
|
|
1349
|
+
const runtime = useContext(SduiRuntimeContext);
|
|
1350
|
+
const hostRef = useRef(null);
|
|
1351
|
+
const dragRef = useRef(null);
|
|
1352
|
+
const refocusRef = useRef(null);
|
|
1353
|
+
const [activeIndex, setActiveIndex] = useState(null);
|
|
1354
|
+
const [grabbed, setGrabbed] = useState(null);
|
|
1355
|
+
const [notice, setNotice] = useState("");
|
|
1356
|
+
const key = typeof stateKey === "string" && stateKey !== "" ? stateKey : null;
|
|
1357
|
+
const rows = key !== null && runtime ? runtime.state[key] : void 0;
|
|
1358
|
+
const length = Array.isArray(rows) ? rows.length : 0;
|
|
1359
|
+
let locked = null;
|
|
1360
|
+
if (sandbox) {
|
|
1361
|
+
locked = "\uCE94\uBC84\uC2A4\uC5D0\uC11C\uB294 \uC21C\uC11C\uB97C \uBC14\uAFC0 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uC5EC\uAE30\uC11C \uBC14\uAFBC \uC21C\uC11C\uB294 \uC800\uC7A5\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4";
|
|
1362
|
+
} else if (!dispatch || !runtime) {
|
|
1363
|
+
locked = "SduiRenderer \uC548\uC5D0\uC11C\uB9CC \uC21C\uC11C\uB97C \uBC14\uAFC0 \uC218 \uC788\uC2B5\uB2C8\uB2E4";
|
|
1364
|
+
} else if (key === null) {
|
|
1365
|
+
locked = "\uC21C\uC11C\uB97C \uBC14\uAFC0 \uBC30\uC5F4\uC758 \uC0C1\uD0DC \uD0A4(stateKey)\uAC00 \uC9C0\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4";
|
|
1366
|
+
warnOnce("SortableList:stateKey", "[sdui] <SortableList>\uC5D0 stateKey\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uC21C\uC11C\uB97C \uBC14\uAFC0 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
1367
|
+
} else if (disabled) {
|
|
1368
|
+
locked = "\uC21C\uC11C \uBCC0\uACBD\uC774 \uC7A0\uACA8 \uC788\uC2B5\uB2C8\uB2E4";
|
|
1369
|
+
} else if (!Array.isArray(rows)) {
|
|
1370
|
+
locked = `\uC21C\uC11C\uB97C \uBC14\uAFC0 \uBC30\uC5F4 ${key}\uAC00 \uC544\uC9C1 \uC0C1\uD0DC\uC5D0 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 setState\uB098 submitForm\uC758 onSuccess\uB85C \uC2E4\uCCB4\uD654\uD574\uC57C \uC21C\uC11C\uAC00 \uBC14\uB01D\uB2C8\uB2E4`;
|
|
1371
|
+
} else if (length < 2) {
|
|
1372
|
+
locked = "\uC62E\uAE38 \uD589\uC774 \uB458 \uC774\uC0C1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4";
|
|
1373
|
+
}
|
|
1374
|
+
const ownHandles = useCallback(() => {
|
|
1375
|
+
const host = hostRef.current;
|
|
1376
|
+
if (!host) return [];
|
|
1377
|
+
return Array.from(host.querySelectorAll(HANDLE_SELECTOR)).filter(
|
|
1378
|
+
(element) => element.closest(HOST_SELECTOR) === host
|
|
1379
|
+
);
|
|
1380
|
+
}, []);
|
|
1381
|
+
const centersOf = useCallback(() => {
|
|
1382
|
+
const centers = [];
|
|
1383
|
+
for (const element of ownHandles()) {
|
|
1384
|
+
const index = Number(element.dataset.wpSortIndex);
|
|
1385
|
+
if (!Number.isInteger(index) || index < 0) continue;
|
|
1386
|
+
const rect = element.getBoundingClientRect();
|
|
1387
|
+
centers[index] = rect.top + rect.height / 2;
|
|
1388
|
+
}
|
|
1389
|
+
return centers;
|
|
1390
|
+
}, [ownHandles]);
|
|
1391
|
+
const move = useCallback(
|
|
1392
|
+
(from, to) => {
|
|
1393
|
+
const target = sortableMove(from, to, length);
|
|
1394
|
+
if (target === null || !dispatch || key === null) return null;
|
|
1395
|
+
dispatch({ action: "arrayMove", key, from, to: target });
|
|
1396
|
+
return target;
|
|
1397
|
+
},
|
|
1398
|
+
[dispatch, key, length]
|
|
1399
|
+
);
|
|
1400
|
+
useEffect(() => {
|
|
1401
|
+
const index = refocusRef.current;
|
|
1402
|
+
if (index === null) return;
|
|
1403
|
+
refocusRef.current = null;
|
|
1404
|
+
ownHandles().find((element) => Number(element.dataset.wpSortIndex) === index)?.focus();
|
|
1405
|
+
});
|
|
1406
|
+
const announceAt = useCallback(
|
|
1407
|
+
(name, to) => setNotice(`${name} \xB7 ${length}\uAC1C \uC911 ${to + 1}\uBC88\uC9F8\uB85C \uC62E\uACBC\uC2B5\uB2C8\uB2E4`),
|
|
1408
|
+
[length]
|
|
1409
|
+
);
|
|
1410
|
+
const onHandlePointerDown = useCallback(
|
|
1411
|
+
(index, name, event) => {
|
|
1412
|
+
if (event.button !== 0) return;
|
|
1413
|
+
dragRef.current = { pointerId: event.pointerId, name, origin: index, at: index };
|
|
1414
|
+
setActiveIndex(index);
|
|
1415
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
1416
|
+
},
|
|
1417
|
+
[]
|
|
1418
|
+
);
|
|
1419
|
+
useEffect(() => {
|
|
1420
|
+
if (activeIndex === null) return;
|
|
1421
|
+
const onMove = (event) => {
|
|
1422
|
+
const drag = dragRef.current;
|
|
1423
|
+
if (!drag || drag.pointerId !== event.pointerId) return;
|
|
1424
|
+
const target = move(drag.at, sortableDropIndex(centersOf(), event.clientY));
|
|
1425
|
+
if (target !== null) drag.at = target;
|
|
1426
|
+
};
|
|
1427
|
+
const onEnd = (event) => {
|
|
1428
|
+
const drag = dragRef.current;
|
|
1429
|
+
if (!drag || drag.pointerId !== event.pointerId) return;
|
|
1430
|
+
dragRef.current = null;
|
|
1431
|
+
setActiveIndex(null);
|
|
1432
|
+
if (drag.at !== drag.origin) announceAt(drag.name, drag.at);
|
|
1433
|
+
};
|
|
1434
|
+
window.addEventListener("pointermove", onMove);
|
|
1435
|
+
window.addEventListener("pointerup", onEnd);
|
|
1436
|
+
window.addEventListener("pointercancel", onEnd);
|
|
1437
|
+
return () => {
|
|
1438
|
+
window.removeEventListener("pointermove", onMove);
|
|
1439
|
+
window.removeEventListener("pointerup", onEnd);
|
|
1440
|
+
window.removeEventListener("pointercancel", onEnd);
|
|
1441
|
+
};
|
|
1442
|
+
}, [activeIndex, move, centersOf, announceAt]);
|
|
1443
|
+
const onHandleKeyDown = useCallback(
|
|
1444
|
+
(index, name, event) => {
|
|
1445
|
+
const pressed = event.key;
|
|
1446
|
+
if (pressed === " " || pressed === "Spacebar" || pressed === "Enter") {
|
|
1447
|
+
event.preventDefault();
|
|
1448
|
+
if (grabbed) {
|
|
1449
|
+
setGrabbed(null);
|
|
1450
|
+
setNotice(`${name} \xB7 ${length}\uAC1C \uC911 ${grabbed.at + 1}\uBC88\uC9F8\uC5D0 \uB193\uC558\uC2B5\uB2C8\uB2E4`);
|
|
1451
|
+
} else {
|
|
1452
|
+
setGrabbed({ origin: index, at: index });
|
|
1453
|
+
setNotice(`${name} \xB7 \uC9D1\uC5C8\uC2B5\uB2C8\uB2E4. \uC704\xB7\uC544\uB798 \uD654\uC0B4\uD45C\uB85C \uC62E\uAE30\uACE0 Space\uB85C \uB193\uC2B5\uB2C8\uB2E4`);
|
|
1454
|
+
}
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
if (pressed === "Escape") {
|
|
1458
|
+
if (!grabbed) return;
|
|
1459
|
+
event.preventDefault();
|
|
1460
|
+
const back = move(grabbed.at, grabbed.origin);
|
|
1461
|
+
refocusRef.current = back ?? grabbed.at;
|
|
1462
|
+
setGrabbed(null);
|
|
1463
|
+
setNotice(`${name} \xB7 \uCDE8\uC18C\uD588\uC2B5\uB2C8\uB2E4. \uC6D0\uB798 \uC790\uB9AC\uB85C \uB418\uB3CC\uB838\uC2B5\uB2C8\uB2E4`);
|
|
1464
|
+
return;
|
|
1465
|
+
}
|
|
1466
|
+
if (pressed === "ArrowUp" || pressed === "ArrowDown") {
|
|
1467
|
+
if (!grabbed) return;
|
|
1468
|
+
event.preventDefault();
|
|
1469
|
+
const target = move(grabbed.at, grabbed.at + (pressed === "ArrowUp" ? -1 : 1));
|
|
1470
|
+
if (target === null) return;
|
|
1471
|
+
refocusRef.current = target;
|
|
1472
|
+
setGrabbed({ origin: grabbed.origin, at: target });
|
|
1473
|
+
announceAt(name, target);
|
|
1474
|
+
}
|
|
1475
|
+
},
|
|
1476
|
+
[grabbed, move, announceAt, length]
|
|
1477
|
+
);
|
|
1478
|
+
const onHandleBlur = useCallback(
|
|
1479
|
+
(index) => {
|
|
1480
|
+
setGrabbed((current) => current && current.at === index ? null : current);
|
|
1481
|
+
},
|
|
1482
|
+
[]
|
|
1483
|
+
);
|
|
1484
|
+
const classes = ["wp-sortable"];
|
|
1485
|
+
if (activeIndex !== null) classes.push("wp-sortable--sorting");
|
|
1486
|
+
if (className) classes.push(className);
|
|
1487
|
+
const context = {
|
|
1488
|
+
locked,
|
|
1489
|
+
sandbox,
|
|
1490
|
+
activeIndex,
|
|
1491
|
+
grabbedIndex: grabbed?.at ?? null,
|
|
1492
|
+
onHandlePointerDown,
|
|
1493
|
+
onHandleKeyDown,
|
|
1494
|
+
onHandleBlur
|
|
1495
|
+
};
|
|
1496
|
+
return /* @__PURE__ */ jsxs(
|
|
1497
|
+
"div",
|
|
1498
|
+
{
|
|
1499
|
+
ref: hostRef,
|
|
1500
|
+
className: classes.join(" "),
|
|
1501
|
+
style,
|
|
1502
|
+
"data-wp-sortable": key ?? "",
|
|
1503
|
+
children: [
|
|
1504
|
+
/* @__PURE__ */ jsx(HoistedStyle, { id: "wp-sortable", css: SORTABLE_CSS }),
|
|
1505
|
+
/* @__PURE__ */ jsx(SortableContext.Provider, { value: context, children }),
|
|
1506
|
+
/* @__PURE__ */ jsx("div", { className: "wp-sortable__status", role: "status", "aria-live": "polite", children: notice })
|
|
1507
|
+
]
|
|
1508
|
+
}
|
|
1509
|
+
);
|
|
1510
|
+
}
|
|
1511
|
+
function SortHandle({ index, label, className, style }) {
|
|
1512
|
+
const context = useContext(SortableContext);
|
|
1513
|
+
const position = typeof index === "number" && Number.isInteger(index) && index >= 0 ? index : null;
|
|
1514
|
+
let locked;
|
|
1515
|
+
if (!context) {
|
|
1516
|
+
locked = "\uC21C\uC11C \uBCC0\uACBD \uBAA9\uB85D(SortableList) \uC548\uC5D0 \uC788\uC5B4\uC57C \uB3D9\uC791\uD569\uB2C8\uB2E4";
|
|
1517
|
+
warnOnce(
|
|
1518
|
+
"SortHandle:orphan",
|
|
1519
|
+
"[sdui] <SortHandle>\uC774 <SortableList> \uBC16\uC5D0 \uC788\uC2B5\uB2C8\uB2E4 \u2014 \uC21C\uC11C\uB97C \uBC14\uAFC0 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4"
|
|
1520
|
+
);
|
|
1521
|
+
} else if (context.sandbox) {
|
|
1522
|
+
locked = context.locked;
|
|
1523
|
+
} else if (position === null) {
|
|
1524
|
+
locked = '\uD589 \uBC88\uD638(index)\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 {"$index": true}\uB97C \uB123\uC5B4\uC57C \uD569\uB2C8\uB2E4';
|
|
1525
|
+
warnOnce(
|
|
1526
|
+
"SortHandle:index",
|
|
1527
|
+
'[sdui] <SortHandle>.index\uAC00 \uC815\uC218\uAC00 \uC544\uB2D9\uB2C8\uB2E4 \u2014 {"$index": true}\uB97C \uB123\uC73C\uC138\uC694'
|
|
1528
|
+
);
|
|
1529
|
+
} else {
|
|
1530
|
+
locked = context.locked;
|
|
1531
|
+
}
|
|
1532
|
+
const name = typeof label === "string" && label.trim() !== "" ? label.trim() : `${(position ?? 0) + 1}\uBC88\uC9F8 \uD56D\uBAA9`;
|
|
1533
|
+
const classes = ["wp-sortable__handle"];
|
|
1534
|
+
if (locked) classes.push("wp-sortable__handle--locked");
|
|
1535
|
+
else if (context?.activeIndex === position) classes.push("wp-sortable__handle--active");
|
|
1536
|
+
else if (context?.grabbedIndex === position) classes.push("wp-sortable__handle--grabbed");
|
|
1537
|
+
if (className) classes.push(className);
|
|
1538
|
+
const live = locked === null && context !== null && position !== null ? { list: context, at: position } : null;
|
|
1539
|
+
return /* @__PURE__ */ jsx(
|
|
1540
|
+
"button",
|
|
1541
|
+
{
|
|
1542
|
+
type: "button",
|
|
1543
|
+
className: classes.join(" "),
|
|
1544
|
+
style,
|
|
1545
|
+
"data-wp-sort-index": position ?? void 0,
|
|
1546
|
+
"aria-roledescription": "\uC21C\uC11C \uC190\uC7A1\uC774",
|
|
1547
|
+
"aria-label": name,
|
|
1548
|
+
"aria-disabled": locked ? true : void 0,
|
|
1549
|
+
title: locked ?? "\uB04C\uC5B4\uC11C \uC62E\uAE30\uAC70\uB098, Space\uB85C \uC9D1\uC5B4 \uC704\xB7\uC544\uB798 \uD654\uC0B4\uD45C\uB85C \uC62E\uAE41\uB2C8\uB2E4",
|
|
1550
|
+
onPointerDown: live ? (event) => live.list.onHandlePointerDown(live.at, name, event) : void 0,
|
|
1551
|
+
onKeyDown: live ? (event) => live.list.onHandleKeyDown(live.at, name, event) : void 0,
|
|
1552
|
+
onBlur: live ? () => live.list.onHandleBlur(live.at) : void 0,
|
|
1553
|
+
children: /* @__PURE__ */ jsx(Icon, { name: "move", size: 14 })
|
|
1554
|
+
}
|
|
1555
|
+
);
|
|
1556
|
+
}
|
|
1557
|
+
var SORTABLE_CSS = `
|
|
1558
|
+
.wp-sortable{display:flex;flex-direction:column;gap:var(--wp-space-2);position:relative}
|
|
1559
|
+
.wp-sortable--sorting{user-select:none;-webkit-user-select:none}
|
|
1560
|
+
.wp-sortable__handle{display:inline-flex;align-items:center;justify-content:center;flex:none;
|
|
1561
|
+
min-width:24px;min-height:24px;padding:0;margin:0;font:inherit;
|
|
1562
|
+
background:transparent;color:var(--wp-text-subtle);
|
|
1563
|
+
border:var(--wp-border-w) solid transparent;border-radius:var(--wp-radius-sm);
|
|
1564
|
+
cursor:grab;touch-action:none}
|
|
1565
|
+
.wp-sortable__handle:hover{background:var(--wp-surface-hover);color:var(--wp-text-default)}
|
|
1566
|
+
.wp-sortable__handle:focus-visible{outline:2px solid var(--wp-border-focus);outline-offset:1px}
|
|
1567
|
+
.wp-sortable__handle--active{cursor:grabbing;background:var(--wp-surface-selected);color:var(--wp-text-strong)}
|
|
1568
|
+
.wp-sortable__handle--grabbed{background:var(--wp-surface-selected);color:var(--wp-text-strong);
|
|
1569
|
+
border-color:var(--wp-border-focus)}
|
|
1570
|
+
.wp-sortable__handle--locked{cursor:not-allowed;color:var(--wp-text-disabled);touch-action:auto}
|
|
1571
|
+
.wp-sortable__handle--locked:hover{background:transparent;color:var(--wp-text-disabled)}
|
|
1572
|
+
.wp-sortable__status{position:absolute;width:1px;height:1px;margin:-1px;padding:0;
|
|
1573
|
+
overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}
|
|
1574
|
+
`;
|
|
1317
1575
|
|
|
1318
1576
|
// src/registry.tsx
|
|
1319
1577
|
var defaultRegistry = {
|
|
@@ -1367,6 +1625,11 @@ var defaultRegistry = {
|
|
|
1367
1625
|
// (SduiComponentName이 그 Record의 키라 빠지면 컴파일이 막힌다).
|
|
1368
1626
|
// - 표시 위젯 20종은 `widgetComponents` 묶음으로 나가 있다. 정적 import로 core에
|
|
1369
1627
|
// 붙지 않게 하려는 것이고 이유는 위 Chart·MapPreview와 같다(widgets/index.ts 참고).
|
|
1628
|
+
// 순서 변경 목록 — 반복은 ForEach가 하고 이 둘은 인덱스만 안다. 상태 변경은 기존
|
|
1629
|
+
// `arrayMove` 액션으로 나가므로 계약이 넓어지지 않는다(SortableList.tsx 주석 참고).
|
|
1630
|
+
// 외부 의존성이 없어 여기 있을 자격이 된다.
|
|
1631
|
+
SortableList,
|
|
1632
|
+
SortHandle,
|
|
1370
1633
|
// 검색+목록 화면. 스펙 이름은 ERP 어휘를 그대로 쓰고, 구현은 JSON 전용 어댑터를 가리킨다
|
|
1371
1634
|
// (SearchResults 자체는 함수 프롭을 받는 타입드 컴포넌트라 스펙에서 직접 못 쓴다).
|
|
1372
1635
|
SearchResults: SduiSearchResults,
|
|
@@ -2042,7 +2305,13 @@ function SduiRenderer({
|
|
|
2042
2305
|
}
|
|
2043
2306
|
case "arrayMove": {
|
|
2044
2307
|
const current = stateRef.current[action.key];
|
|
2045
|
-
if (!Array.isArray(current))
|
|
2308
|
+
if (!Array.isArray(current)) {
|
|
2309
|
+
warnOnce2(
|
|
2310
|
+
`arrayMove:key:${action.key}`,
|
|
2311
|
+
`[sdui] arrayMove(${action.key}): \uC0C1\uD0DC\uAC00 \uBC30\uC5F4\uC774 \uC544\uB2D9\uB2C8\uB2E4 \u2014 \uC911\uCCA9 \uACBD\uB85C\uC774\uAC70\uB098 default\uB85C\uB9CC \uADF8\uB824\uC9C4 \uBC30\uC5F4\uC785\uB2C8\uB2E4`
|
|
2312
|
+
);
|
|
2313
|
+
break;
|
|
2314
|
+
}
|
|
2046
2315
|
const from = toIndex(action.from);
|
|
2047
2316
|
const rawTo = toIndex(action.to);
|
|
2048
2317
|
if (!inRange(from, current.length) || rawTo === null) {
|
|
@@ -2189,4 +2458,4 @@ function SduiRenderer({
|
|
|
2189
2458
|
);
|
|
2190
2459
|
}
|
|
2191
2460
|
|
|
2192
|
-
export { Actions, Can, Col, Divider, Grid, KeyValueList, NodeErrorBoundary, PageHead, RemoteSearchResults, ResultTable, Row, SduiDialog, SduiForm, SduiPermissionProvider, SduiRenderer, SduiSearchDataProvider, SduiSearchResults, SearchResults, Section, Show, Spacer, Text, cellRenderer, createRegistry, defaultRegistry, normalizePageSizes, useRemoteSearchPage, useSduiCan, useSduiDispatch, useSduiRuntime, useSearchFetcher, validateSpec };
|
|
2461
|
+
export { Actions, Can, Col, Divider, Grid, KeyValueList, NodeErrorBoundary, PageHead, RemoteSearchResults, ResultTable, Row, SduiDialog, SduiForm, SduiPermissionProvider, SduiRenderer, SduiSearchDataProvider, SduiSearchResults, SearchResults, Section, Show, SortHandle, SortableList, Spacer, Text, cellRenderer, createRegistry, defaultRegistry, normalizePageSizes, sortableDropIndex, sortableMove, useRemoteSearchPage, useSduiCan, useSduiDispatch, useSduiRuntime, useSearchFetcher, validateSpec };
|
package/dist/meta.d.ts
CHANGED
|
@@ -215,7 +215,7 @@ interface SduiComponentMeta {
|
|
|
215
215
|
}
|
|
216
216
|
|
|
217
217
|
/**
|
|
218
|
-
* 기본 어휘(`defaultRegistry`)
|
|
218
|
+
* 기본 어휘(`defaultRegistry`) 41종의 빌더 메타데이터.
|
|
219
219
|
*
|
|
220
220
|
* 프롭 목록은 `components.tsx`·`primitives.tsx`·`SduiForm.tsx`·`Can.tsx`·
|
|
221
221
|
* `DatePicker.tsx`·`search/SduiSearchResults.tsx`의 **실제 프롭 인터페이스**와,
|
|
@@ -261,6 +261,19 @@ declare const PaginationMeta: SduiComponentMeta;
|
|
|
261
261
|
declare const TableMeta: SduiComponentMeta;
|
|
262
262
|
declare const SearchResultsMeta: SduiComponentMeta;
|
|
263
263
|
declare const RemoteSearchResultsMeta: SduiComponentMeta;
|
|
264
|
+
/**
|
|
265
|
+
* 순서 변경 목록.
|
|
266
|
+
*
|
|
267
|
+
* **`sortable`이라는 낱말을 라벨에 쓰지 않는다.** 어휘에 이미 `Table.columns[].sortable`과
|
|
268
|
+
* `SearchResults.columns[].sortable`이 있고 그 한국어 라벨이 '정렬 가능'(값 기준 정렬)이다.
|
|
269
|
+
* 순서 변경에 같은 말을 쓰면 속성 패널에서 전혀 다른 두 개념이 같은 이름으로 나란히 선다.
|
|
270
|
+
* 그래서 이쪽은 '순서 변경' 계열로 통일했다.
|
|
271
|
+
*
|
|
272
|
+
* 프롭 이름이 `key`가 아니라 `stateKey`인 것도 같은 종류의 제약이다 — `key`는
|
|
273
|
+
* `DENIED_PROPS`라 렌더러가 통째로 버린다(저장은 되는데 화면에서는 무시되는 최악의 조합).
|
|
274
|
+
*/
|
|
275
|
+
declare const SortableListMeta: SduiComponentMeta;
|
|
276
|
+
declare const SortHandleMeta: SduiComponentMeta;
|
|
264
277
|
declare const AlertMeta: SduiComponentMeta;
|
|
265
278
|
declare const DialogMeta: SduiComponentMeta;
|
|
266
279
|
declare const EmptyStateMeta: SduiComponentMeta;
|
|
@@ -277,7 +290,7 @@ declare const ShowMeta: SduiComponentMeta;
|
|
|
277
290
|
* 컴파일 시점에 난다 — 둘이 어긋난 채로는 빌드가 통과하지 않는다.
|
|
278
291
|
*
|
|
279
292
|
* 팔레트처럼 전체가 필요한 곳이 아니면 개별 export(`ButtonMeta` 등)를 쓰는 편이
|
|
280
|
-
* 번들에 유리하다 — 이 객체를 참조하는 순간
|
|
293
|
+
* 번들에 유리하다 — 이 객체를 참조하는 순간 41종이 전부 따라온다.
|
|
281
294
|
*/
|
|
282
295
|
declare const coreComponentMeta: {
|
|
283
296
|
Actions: SduiComponentMeta;
|
|
@@ -309,6 +322,8 @@ declare const coreComponentMeta: {
|
|
|
309
322
|
Section: SduiComponentMeta;
|
|
310
323
|
Select: SduiComponentMeta;
|
|
311
324
|
Show: SduiComponentMeta;
|
|
325
|
+
SortableList: SduiComponentMeta;
|
|
326
|
+
SortHandle: SduiComponentMeta;
|
|
312
327
|
Spacer: SduiComponentMeta;
|
|
313
328
|
Spinner: SduiComponentMeta;
|
|
314
329
|
StatCard: SduiComponentMeta;
|
|
@@ -497,7 +512,7 @@ declare const SDUI_CATEGORIES: ReadonlyArray<{
|
|
|
497
512
|
label: string;
|
|
498
513
|
}>;
|
|
499
514
|
/**
|
|
500
|
-
* 라이브러리가 아는 어휘 전체 — 기본
|
|
515
|
+
* 라이브러리가 아는 어휘 전체 — 기본 41 + rich 2 + 위젯 21 + DS 6 + 렌더러 내장 1 = 71종.
|
|
501
516
|
* 편집기처럼 어휘 전부를 다뤄야 하는 쪽에서 쓴다.
|
|
502
517
|
*
|
|
503
518
|
* **레지스트리와 일대일이 아니다.** `defaultRegistry`에 없는 것도 들어 있다 —
|
|
@@ -526,4 +541,4 @@ declare function paletteGroups(catalog?: Record<string, SduiComponentMeta>): Arr
|
|
|
526
541
|
items: SduiComponentMeta[];
|
|
527
542
|
}>;
|
|
528
543
|
|
|
529
|
-
export { ActionsMeta, AlertMeta, AutocompleteFieldMeta, AvatarMeta, BadgeMeta, BreadcrumbMeta, ButtonMeta, CanMeta, CardMeta, ChartMeta, CheckboxMeta, ColMeta, ColumnBoardMeta, DatePickerMeta, DateRangePickerMeta, DialogMeta, DiffViewMeta, DisclosureMeta, DividerMeta, EmptyStateMeta, EventCalendarMeta, FieldMeta, ForEachMeta, FormMeta, GridMeta, IconButtonMeta, IconMeta, ImageLightboxMeta, InlineEditMeta, InputMeta, JsonFieldMeta, KeyValueListMeta, LiveClockMeta, MapPreviewMeta, OrgChartMeta, PageHeadMeta, PaginationMeta, PeriodFilterMeta, PrintSheetMeta, ProgressBarMeta, RadioMeta, RemoteSearchResultsMeta, RowMeta, SDUI_CATEGORIES, type SduiCategory, type SduiChildrenMeta, type SduiComponentMeta, type SduiDomMeta, type SduiEnumOption, type SduiPropGroup, type SduiPropKind, type SduiPropMeta, type SduiResizeMeta, type SduiSlotMeta, SearchResultsMeta, SearchableMultiPickerMeta, SectionMeta, SecureScreenMeta, SegmentedControlMeta, SelectMeta, ShowMeta, SidebarNavMeta, SpacerMeta, SpinnerMeta, StackedMeterMeta, StatCardMeta, StripMeta, SummaryTableMeta, SwitchMeta, TableMeta, TabsMeta, TagMeta, TagMultiSelectMeta, TextMeta, TextareaMeta, TimelineMeta, ToastMeta, TooltipMeta, TreePickerMeta, TreeViewMeta, allComponentMeta, builtinComponentMeta, coreComponentMeta, dsComponentMeta, getComponentMeta, paletteGroups, richComponentMeta, slotNamesOf, widgetComponentMeta };
|
|
544
|
+
export { ActionsMeta, AlertMeta, AutocompleteFieldMeta, AvatarMeta, BadgeMeta, BreadcrumbMeta, ButtonMeta, CanMeta, CardMeta, ChartMeta, CheckboxMeta, ColMeta, ColumnBoardMeta, DatePickerMeta, DateRangePickerMeta, DialogMeta, DiffViewMeta, DisclosureMeta, DividerMeta, EmptyStateMeta, EventCalendarMeta, FieldMeta, ForEachMeta, FormMeta, GridMeta, IconButtonMeta, IconMeta, ImageLightboxMeta, InlineEditMeta, InputMeta, JsonFieldMeta, KeyValueListMeta, LiveClockMeta, MapPreviewMeta, OrgChartMeta, PageHeadMeta, PaginationMeta, PeriodFilterMeta, PrintSheetMeta, ProgressBarMeta, RadioMeta, RemoteSearchResultsMeta, RowMeta, SDUI_CATEGORIES, type SduiCategory, type SduiChildrenMeta, type SduiComponentMeta, type SduiDomMeta, type SduiEnumOption, type SduiPropGroup, type SduiPropKind, type SduiPropMeta, type SduiResizeMeta, type SduiSlotMeta, SearchResultsMeta, SearchableMultiPickerMeta, SectionMeta, SecureScreenMeta, SegmentedControlMeta, SelectMeta, ShowMeta, SidebarNavMeta, SortHandleMeta, SortableListMeta, SpacerMeta, SpinnerMeta, StackedMeterMeta, StatCardMeta, StripMeta, SummaryTableMeta, SwitchMeta, TableMeta, TabsMeta, TagMeta, TagMultiSelectMeta, TextMeta, TextareaMeta, TimelineMeta, ToastMeta, TooltipMeta, TreePickerMeta, TreeViewMeta, allComponentMeta, builtinComponentMeta, coreComponentMeta, dsComponentMeta, getComponentMeta, paletteGroups, richComponentMeta, slotNamesOf, widgetComponentMeta };
|
package/dist/meta.js
CHANGED
|
@@ -1731,6 +1731,107 @@ var RemoteSearchResultsMeta = {
|
|
|
1731
1731
|
},
|
|
1732
1732
|
resize: { kind: "none" }
|
|
1733
1733
|
};
|
|
1734
|
+
var SortableListMeta = {
|
|
1735
|
+
name: "SortableList",
|
|
1736
|
+
label: "\uC21C\uC11C \uBCC0\uACBD \uBAA9\uB85D",
|
|
1737
|
+
category: "data",
|
|
1738
|
+
icon: "list",
|
|
1739
|
+
description: '\uC790\uC2DD ForEach\uAC00 \uADF8\uB9B0 \uD589\uC744 \uC190\uC7A1\uC774\uB85C \uB04C\uC5B4 \uC21C\uC11C\uB97C \uBC14\uAFBC\uB2E4. \uD45C\uC758 "\uC815\uB82C \uAC00\uB2A5"(\uAC12 \uAE30\uC900 \uC815\uB82C)\uACFC \uB2E4\uB974\uB2E4. \uD3B8\uC9D1\uAE30 \uCE94\uBC84\uC2A4\uC5D0\uC11C\uB294 \uD45C\uC2DD\uB9CC \uBCF4\uC774\uACE0 \uB3D9\uC791\uD558\uC9C0 \uC54A\uB294\uB2E4.',
|
|
1740
|
+
props: [
|
|
1741
|
+
{
|
|
1742
|
+
name: "stateKey",
|
|
1743
|
+
kind: "string",
|
|
1744
|
+
label: "\uC21C\uC11C\uB97C \uBC14\uAFC0 \uBC30\uC5F4\uC758 \uC0C1\uD0DC \uD0A4",
|
|
1745
|
+
required: true,
|
|
1746
|
+
bindable: true,
|
|
1747
|
+
description: "arrayMove\uAC00 \uC4F8 \uD0A4. \uCD5C\uC0C1\uC704 \uD0A4\uC5EC\uC57C \uD55C\uB2E4 \u2014 \uC911\uCCA9 \uACBD\uB85C(a.0.b)\uB294 \uBB34\uB3D9\uC791\uC774\uB2E4. \uD589\uB9C8\uB2E4 \uB2E4\uB978 \uBC30\uC5F4\uC774 \uD544\uC694\uD558\uBA74 $calc concat\uC73C\uB85C \uD0A4\uB97C \uB9CC\uB4E0\uB2E4.",
|
|
1748
|
+
group: "data"
|
|
1749
|
+
},
|
|
1750
|
+
{
|
|
1751
|
+
name: "disabled",
|
|
1752
|
+
kind: "boolean",
|
|
1753
|
+
label: "\uC21C\uC11C \uBCC0\uACBD \uC7A0\uAE08",
|
|
1754
|
+
default: false,
|
|
1755
|
+
bindable: true,
|
|
1756
|
+
description: "\uC190\uC7A1\uC774\uB294 \uADF8\uB300\uB85C \uBCF4\uC774\uACE0 \uC7A0\uAE34 \uC0AC\uC720\uAC00 \uD234\uD301\uC73C\uB85C \uBD99\uB294\uB2E4.",
|
|
1757
|
+
group: "behavior"
|
|
1758
|
+
},
|
|
1759
|
+
classNameProp,
|
|
1760
|
+
styleProp
|
|
1761
|
+
],
|
|
1762
|
+
// 자식은 보통 ForEach 하나다. `allow`는 금지가 아니라 "흔히 넣는 게 아니다"라는 약한
|
|
1763
|
+
// 경고로 걸린다(canDrop) — 정적 목록에 손잡이를 다는 쓰임까지 막지는 않으려는 것이다.
|
|
1764
|
+
children: { accepts: true, text: false, label: "\uD589 \uBAA9\uB85D(\uBCF4\uD1B5 ForEach \uD558\uB098)", allow: ["ForEach"] },
|
|
1765
|
+
dom: { element: "div" },
|
|
1766
|
+
/*
|
|
1767
|
+
* 프리셋의 배열이 `default`로만 있는 것은 **알고 고른 것**이다. `$state`만 두면 드롭한
|
|
1768
|
+
* 직후 캔버스에도 화면에도 아무것도 안 나온다("드롭했는데 사라졌다"). default를 두면
|
|
1769
|
+
* 행이 보이고, 실제로 끌면 손잡이가 "아직 배열 상태가 아니다"를 툴팁으로 말해 준다 —
|
|
1770
|
+
* 무엇을 더 해야 하는지가 화면에 남는 쪽을 골랐다.
|
|
1771
|
+
*/
|
|
1772
|
+
preset: {
|
|
1773
|
+
component: "SortableList",
|
|
1774
|
+
props: { stateKey: "sortRows" },
|
|
1775
|
+
children: [
|
|
1776
|
+
{
|
|
1777
|
+
component: "ForEach",
|
|
1778
|
+
props: {
|
|
1779
|
+
source: {
|
|
1780
|
+
$state: "sortRows",
|
|
1781
|
+
default: [
|
|
1782
|
+
{ id: "r1", name: "\uCCAB \uBC88\uC9F8 \uD56D\uBAA9" },
|
|
1783
|
+
{ id: "r2", name: "\uB450 \uBC88\uC9F8 \uD56D\uBAA9" },
|
|
1784
|
+
{ id: "r3", name: "\uC138 \uBC88\uC9F8 \uD56D\uBAA9" }
|
|
1785
|
+
]
|
|
1786
|
+
}
|
|
1787
|
+
},
|
|
1788
|
+
children: [
|
|
1789
|
+
{
|
|
1790
|
+
component: "Row",
|
|
1791
|
+
children: [
|
|
1792
|
+
{ component: "SortHandle", props: { index: { $index: true }, label: { $item: "name" } } },
|
|
1793
|
+
{ component: "Text", props: { preset: "body", value: { $item: "name" } } }
|
|
1794
|
+
]
|
|
1795
|
+
}
|
|
1796
|
+
]
|
|
1797
|
+
}
|
|
1798
|
+
]
|
|
1799
|
+
},
|
|
1800
|
+
resize: { kind: "none" }
|
|
1801
|
+
};
|
|
1802
|
+
var SortHandleMeta = {
|
|
1803
|
+
name: "SortHandle",
|
|
1804
|
+
label: "\uC21C\uC11C \uC190\uC7A1\uC774",
|
|
1805
|
+
category: "input",
|
|
1806
|
+
icon: "move",
|
|
1807
|
+
description: 'ForEach \uD589 \uC548\uC5D0 \uB450\uB294 \uADF8\uB9BD. \uB04C\uBA74 \uC21C\uC11C\uAC00 \uBC14\uB00C\uACE0, \uD0A4\uBCF4\uB4DC\uB294 Space\uB85C \uC9D1\uC5B4 \uC704\xB7\uC544\uB798 \uD654\uC0B4\uD45C\uB85C \uC62E\uAE34\uB2E4. index\uC5D0 {"$index": true}\uB97C \uB123\uC5B4\uC57C \uD55C\uB2E4.',
|
|
1808
|
+
props: [
|
|
1809
|
+
{
|
|
1810
|
+
name: "index",
|
|
1811
|
+
kind: "binding",
|
|
1812
|
+
label: "\uD589 \uBC88\uD638",
|
|
1813
|
+
required: true,
|
|
1814
|
+
bindable: true,
|
|
1815
|
+
description: '\uAC70\uC758 \uD56D\uC0C1 {"$index": true}. \uC774 \uAC12\uC774 \uACE7 arrayMove\uC758 from\uC774\uB2E4.',
|
|
1816
|
+
group: "data"
|
|
1817
|
+
},
|
|
1818
|
+
{
|
|
1819
|
+
name: "label",
|
|
1820
|
+
kind: "string",
|
|
1821
|
+
label: "\uBCF4\uC870\uAE30\uC220\uC6A9 \uC774\uB984",
|
|
1822
|
+
bindable: true,
|
|
1823
|
+
description: '"\u25CB\u25CB \uC21C\uC11C \uC190\uC7A1\uC774"\uB85C \uC77D\uD78C\uB2E4. \uBCF4\uD1B5 {"$item":"title"}. \uC5C6\uC73C\uBA74 "N\uBC88\uC9F8 \uD56D\uBAA9".',
|
|
1824
|
+
group: "content"
|
|
1825
|
+
},
|
|
1826
|
+
classNameProp,
|
|
1827
|
+
styleProp
|
|
1828
|
+
],
|
|
1829
|
+
children: { accepts: false },
|
|
1830
|
+
dom: { element: "button" },
|
|
1831
|
+
preset: { component: "SortHandle", props: { index: { $index: true } } },
|
|
1832
|
+
// 그립은 24×24가 최소다(WCAG SC 2.5.8). 크기 손잡이를 달면 그 최소치를 깰 수 있다.
|
|
1833
|
+
resize: { kind: "none" }
|
|
1834
|
+
};
|
|
1734
1835
|
var AlertMeta = {
|
|
1735
1836
|
name: "Alert",
|
|
1736
1837
|
label: "\uC54C\uB9BC \uBC15\uC2A4",
|
|
@@ -2082,6 +2183,8 @@ var coreComponentMeta = {
|
|
|
2082
2183
|
Section: SectionMeta,
|
|
2083
2184
|
Select: SelectMeta,
|
|
2084
2185
|
Show: ShowMeta,
|
|
2186
|
+
SortableList: SortableListMeta,
|
|
2187
|
+
SortHandle: SortHandleMeta,
|
|
2085
2188
|
Spacer: SpacerMeta,
|
|
2086
2189
|
Spinner: SpinnerMeta,
|
|
2087
2190
|
StatCard: StatCardMeta,
|
|
@@ -3883,4 +3986,4 @@ function paletteGroups(catalog = allComponentMeta) {
|
|
|
3883
3986
|
})).filter((group) => group.items.length > 0);
|
|
3884
3987
|
}
|
|
3885
3988
|
|
|
3886
|
-
export { ActionsMeta, AlertMeta, AutocompleteFieldMeta, AvatarMeta, BadgeMeta, BreadcrumbMeta, ButtonMeta, CanMeta, CardMeta, ChartMeta, CheckboxMeta, ColMeta, ColumnBoardMeta, DatePickerMeta, DateRangePickerMeta, DialogMeta, DiffViewMeta, DisclosureMeta, DividerMeta, EmptyStateMeta, EventCalendarMeta, FieldMeta, ForEachMeta, FormMeta, GridMeta, IconButtonMeta, IconMeta, ImageLightboxMeta, InlineEditMeta, InputMeta, JsonFieldMeta, KeyValueListMeta, LiveClockMeta, MapPreviewMeta, OrgChartMeta, PageHeadMeta, PaginationMeta, PeriodFilterMeta, PrintSheetMeta, ProgressBarMeta, RadioMeta, RemoteSearchResultsMeta, RowMeta, SDUI_CATEGORIES, SearchResultsMeta, SearchableMultiPickerMeta, SectionMeta, SecureScreenMeta, SegmentedControlMeta, SelectMeta, ShowMeta, SidebarNavMeta, SpacerMeta, SpinnerMeta, StackedMeterMeta, StatCardMeta, StripMeta, SummaryTableMeta, SwitchMeta, TableMeta, TabsMeta, TagMeta, TagMultiSelectMeta, TextMeta, TextareaMeta, TimelineMeta, ToastMeta, TooltipMeta, TreePickerMeta, TreeViewMeta, allComponentMeta, builtinComponentMeta, coreComponentMeta, dsComponentMeta, getComponentMeta, paletteGroups, richComponentMeta, slotNamesOf, widgetComponentMeta };
|
|
3989
|
+
export { ActionsMeta, AlertMeta, AutocompleteFieldMeta, AvatarMeta, BadgeMeta, BreadcrumbMeta, ButtonMeta, CanMeta, CardMeta, ChartMeta, CheckboxMeta, ColMeta, ColumnBoardMeta, DatePickerMeta, DateRangePickerMeta, DialogMeta, DiffViewMeta, DisclosureMeta, DividerMeta, EmptyStateMeta, EventCalendarMeta, FieldMeta, ForEachMeta, FormMeta, GridMeta, IconButtonMeta, IconMeta, ImageLightboxMeta, InlineEditMeta, InputMeta, JsonFieldMeta, KeyValueListMeta, LiveClockMeta, MapPreviewMeta, OrgChartMeta, PageHeadMeta, PaginationMeta, PeriodFilterMeta, PrintSheetMeta, ProgressBarMeta, RadioMeta, RemoteSearchResultsMeta, RowMeta, SDUI_CATEGORIES, SearchResultsMeta, SearchableMultiPickerMeta, SectionMeta, SecureScreenMeta, SegmentedControlMeta, SelectMeta, ShowMeta, SidebarNavMeta, SortHandleMeta, SortableListMeta, SpacerMeta, SpinnerMeta, StackedMeterMeta, StatCardMeta, StripMeta, SummaryTableMeta, SwitchMeta, TableMeta, TabsMeta, TagMeta, TagMultiSelectMeta, TextMeta, TextareaMeta, TimelineMeta, ToastMeta, TooltipMeta, TreePickerMeta, TreeViewMeta, allComponentMeta, builtinComponentMeta, coreComponentMeta, dsComponentMeta, getComponentMeta, paletteGroups, richComponentMeta, slotNamesOf, widgetComponentMeta };
|
package/dist/styles.css
CHANGED
|
@@ -262,16 +262,24 @@
|
|
|
262
262
|
}
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
-
/* ---------- 베이스 ----------
|
|
265
|
+
/* ---------- 베이스 ----------
|
|
266
|
+
*
|
|
267
|
+
* `.wp-app` 을 여기 걸지 않는다. 그 이름은 **소비 앱의 셸 루트**로 이미 쓰이고 있다 —
|
|
268
|
+
* ERP 는 `display:grid; height:100%; overflow:hidden` 인 앱 껍데기다. 편집기가 글꼴을
|
|
269
|
+
* 얻으려고 같은 클래스를 자기 화면에 붙였더니 그 격자·잘라내기가 함께 따라와,
|
|
270
|
+
* 페이지 목록이 **높이 780px에 갇힌 채 내용 3773px**이 잘렸다(84개 중 17개만 닿았다).
|
|
271
|
+
*
|
|
272
|
+
* 디자인 토큰을 담은 클래스 이름은 소비 앱과 같은 공간을 쓰므로, 라이브러리가 새로
|
|
273
|
+
* 거는 것은 `sdui-` 접두사로 둔다. `wp-*` 는 **읽기만** 한다(토큰·타입 프리셋). */
|
|
266
274
|
.sdui-root,
|
|
267
|
-
.
|
|
275
|
+
.sdui-base {
|
|
268
276
|
font-family: var(--wp-font-sans);
|
|
269
277
|
color: var(--wp-text-default);
|
|
270
278
|
font-size: 13px;
|
|
271
279
|
line-height: 1.5;
|
|
272
280
|
}
|
|
273
281
|
.sdui-root *,
|
|
274
|
-
.
|
|
282
|
+
.sdui-base * {
|
|
275
283
|
box-sizing: border-box;
|
|
276
284
|
}
|
|
277
285
|
|