@kjun-ui/native 0.8.1 → 0.10.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.
Files changed (30) hide show
  1. package/dist/index.cjs +185 -64
  2. package/dist/index.js +185 -64
  3. package/dist/index.web.cjs +185 -64
  4. package/dist/index.web.js +185 -64
  5. package/dist/styles.css +30 -19
  6. package/dist/types/packages/native/src/button.d.cts +2 -0
  7. package/dist/types/packages/native/src/button.d.ts +2 -0
  8. package/dist/types/packages/native/src/display-primitives.d.cts +4 -1
  9. package/dist/types/packages/native/src/display-primitives.d.ts +4 -1
  10. package/dist/types/packages/native/src/floating-panel.d.cts +5 -1
  11. package/dist/types/packages/native/src/floating-panel.d.ts +5 -1
  12. package/dist/types/packages/native/src/input.d.cts +4 -1
  13. package/dist/types/packages/native/src/input.d.ts +4 -1
  14. package/dist/types/packages/native/src/popover.d.cts +9 -2
  15. package/dist/types/packages/native/src/popover.d.ts +9 -2
  16. package/dist/types/packages/native/src/select-control.d.cts +5 -1
  17. package/dist/types/packages/native/src/select-control.d.ts +5 -1
  18. package/dist/types/shared/package-runtime/alert-live.d.cts +3 -0
  19. package/dist/types/shared/package-runtime/alert-live.d.ts +3 -0
  20. package/dist/types/shared/package-runtime/aria-ids.d.cts +4 -0
  21. package/dist/types/shared/package-runtime/aria-ids.d.ts +4 -0
  22. package/dist/types/shared/package-runtime/layer-state.d.cts +3 -0
  23. package/dist/types/shared/package-runtime/layer-state.d.ts +3 -0
  24. package/dist/types/shared/package-runtime/options.d.cts +5 -1
  25. package/dist/types/shared/package-runtime/options.d.ts +5 -1
  26. package/dist/types/shared/package-runtime/table-presentation.d.cts +4 -0
  27. package/dist/types/shared/package-runtime/table-presentation.d.ts +4 -0
  28. package/dist/types/shared/package-runtime/use-layer.d.cts +3 -1
  29. package/dist/types/shared/package-runtime/use-layer.d.ts +3 -1
  30. package/package.json +3 -3
@@ -141,9 +141,23 @@ function mergeIcons(parent, own) {
141
141
  function withFallbackIcons(registry, fallbacks) {
142
142
  return mergeIcons(fallbacks, registry);
143
143
  }
144
+ var warnedIcons = /* @__PURE__ */ new Set();
145
+ function developmentMode() {
146
+ try {
147
+ return true;
148
+ } catch (e) {
149
+ return false;
150
+ }
151
+ }
152
+ function warnMissingIcon(name) {
153
+ if (!developmentMode() || warnedIcons.has(name)) return;
154
+ warnedIcons.add(name);
155
+ console.warn(`KJUN Icon: "${name}" \uC544\uC774\uCF58\uC774 \uB4F1\uB85D\uB418\uC9C0 \uC54A\uC544 help-circle\uB85C \uB300\uC2E0\uD569\uB2C8\uB2E4. @kjun-ui/icons/icons/${name}\uB97C import\uD574 KjunProvider\uC758 icons\uC5D0 \uB4F1\uB85D\uD558\uC138\uC694.`);
156
+ }
144
157
  function resolveIcon(registry, name, filled = false) {
145
158
  var _a;
146
159
  const definition = Object.prototype.hasOwnProperty.call(registry, name) ? registry[name] : void 0;
160
+ if (!definition && name) warnMissingIcon(name);
147
161
  const filledNodes = filled && (definition == null ? void 0 : definition.filled);
148
162
  return { nodes: filledNodes || (definition == null ? void 0 : definition.outline) || ((_a = registry["help-circle"]) == null ? void 0 : _a.outline) || import_defaults.defaultIcons["help-circle"].outline, filled: !!filledNodes };
149
163
  }
@@ -234,6 +248,10 @@ function installLayerKeyboard(document2, state, keyupFallback = false) {
234
248
  var import_react2 = require("react");
235
249
 
236
250
  // shared/package-runtime/layer-state.ts
251
+ function chooseReturnTarget(chosen, fallback2) {
252
+ if (chosen === false) return void 0;
253
+ return chosen && chosen.isConnected !== false ? chosen : fallback2;
254
+ }
237
255
  function createLayerState() {
238
256
  const entries = /* @__PURE__ */ new Map(), listeners = /* @__PURE__ */ new Set();
239
257
  const restoredFocus = /* @__PURE__ */ new WeakSet();
@@ -254,6 +272,7 @@ function createLayerState() {
254
272
  return !layer.blocked && (layer.kind === "window" ? ((_a = topWindow()) == null ? void 0 : _a.id) === layer.id : ((_b = topWindow()) == null ? void 0 : _b.id) === layer.owner);
255
273
  };
256
274
  const top = () => [...entries.values()].filter(active).sort((a, b) => a.order - b.order).at(-1);
275
+ const topPopup = () => [...entries.values()].filter((layer) => layer.kind !== "tooltip" && active(layer)).sort((a, b) => a.order - b.order).at(-1);
257
276
  function dismiss(layer) {
258
277
  if (layer.closeRequested) return;
259
278
  layer.closeRequested = true;
@@ -273,6 +292,7 @@ function createLayerState() {
273
292
  entries,
274
293
  topWindow,
275
294
  top,
295
+ topPopup,
276
296
  windowOf,
277
297
  isRestoredFocus(target) {
278
298
  return !!target && restoredFocus.has(target);
@@ -421,21 +441,31 @@ function focusTrigger(target) {
421
441
  }
422
442
  return target;
423
443
  }
424
- function restoreFocus(target, state, id) {
444
+ function restoreFocus(target, state, id, fallback2) {
445
+ const usable = (item) => (item == null ? void 0 : item.isConnected) !== false && !(typeof HTMLElement !== "undefined" && item instanceof HTMLElement && item.closest("[inert]"));
425
446
  const restore = () => {
426
- var _a, _b, _c, _d;
447
+ var _a, _b, _c, _d, _e;
427
448
  if (state.entries.has(id)) return;
428
- if ((target == null ? void 0 : target.isConnected) !== false && !(typeof HTMLElement !== "undefined" && target instanceof HTMLElement && target.closest("[inert]"))) (_a = target == null ? void 0 : target.focus) == null ? void 0 : _a.call(target, { preventScroll: true });
429
- else (_d = (_c = (_b = state.topWindow()) == null ? void 0 : _b.root) == null ? void 0 : _c.focus) == null ? void 0 : _d.call(_c, { preventScroll: true });
449
+ if (usable(target)) (_a = target == null ? void 0 : target.focus) == null ? void 0 : _a.call(target, { preventScroll: true });
450
+ else if (fallback2 && fallback2 !== target && usable(fallback2)) (_b = fallback2.focus) == null ? void 0 : _b.call(fallback2, { preventScroll: true });
451
+ else (_e = (_d = (_c = state.topWindow()) == null ? void 0 : _c.root) == null ? void 0 : _d.focus) == null ? void 0 : _e.call(_d, { preventScroll: true });
430
452
  };
431
453
  if (typeof requestAnimationFrame !== "undefined") requestAnimationFrame(restore);
432
454
  else queueMicrotask(restore);
433
455
  }
434
- function useLayer(open, present, kind, close, trigger) {
456
+ function useLayer(open, present, kind, close, trigger, returnFocus) {
435
457
  var _a;
436
458
  const state = useLayerState(), parent = (0, import_react2.useContext)(LayerScope), id = (0, import_react2.useId)();
437
459
  const callback = (0, import_react2.useRef)(close);
438
460
  callback.current = close;
461
+ const returnRef = (0, import_react2.useRef)(returnFocus);
462
+ returnRef.current = returnFocus;
463
+ const fallbackFocus = (0, import_react2.useRef)(null);
464
+ const removeLayer = () => {
465
+ const removed = state.remove(id, currentFocus());
466
+ if (removed) fallbackFocus.current = removed;
467
+ return removed && returnRef.current ? chooseReturnTarget(returnRef.current(), removed) : removed;
468
+ };
439
469
  const openingFocus = (0, import_react2.useRef)(null);
440
470
  const wasOpen = (0, import_react2.useRef)(false);
441
471
  const pendingFocus = (0, import_react2.useRef)(null);
@@ -447,17 +477,17 @@ function useLayer(open, present, kind, close, trigger) {
447
477
  state.open(id, kind, parent, () => callback.current(), focusTrigger(trigger == null ? void 0 : trigger.current) || openingFocus.current);
448
478
  } else if (kind === "window" && present) state.markClosing(id);
449
479
  else if (kind !== "window" || !present) {
450
- const target = state.remove(id, currentFocus());
480
+ const target = removeLayer();
451
481
  if (target) pendingFocus.current = target;
452
482
  if (!present && pendingFocus.current) {
453
- restoreFocus(pendingFocus.current, state, id);
483
+ restoreFocus(pendingFocus.current, state, id, fallbackFocus.current);
454
484
  pendingFocus.current = null;
455
485
  }
456
486
  }
457
487
  }, [open, present, state, id, kind, parent, trigger]);
458
488
  (0, import_react2.useLayoutEffect)(() => () => {
459
- const target = state.remove(id, currentFocus());
460
- if (target || pendingFocus.current) restoreFocus(target || pendingFocus.current, state, id);
489
+ const target = removeLayer();
490
+ if (target || pendingFocus.current) restoreFocus(target || pendingFocus.current, state, id, fallbackFocus.current);
461
491
  }, [state, id]);
462
492
  const entry = state.entries.get(id);
463
493
  return {
@@ -847,6 +877,7 @@ function KjunButton(input) {
847
877
  size: declaredSize = "md",
848
878
  loading = false,
849
879
  block = false,
880
+ wrap = false,
850
881
  disabled = false,
851
882
  prefixIcon,
852
883
  suffixIcon,
@@ -867,7 +898,7 @@ function KjunButton(input) {
867
898
  const formActions = (0, import_react13.useContext)(FormActionsContext);
868
899
  const cardActions = (0, import_react13.useContext)(CardActionsContext);
869
900
  const stateContent = (0, import_react13.useContext)(DataStateContentContext);
870
- const multiline = !!formActions || cardActions || !!stateContent || (navigation == null ? void 0 : navigation.slot) === "actions";
901
+ const multiline = wrap || !!formActions || cardActions || !!stateContent || (navigation == null ? void 0 : navigation.slot) === "actions";
871
902
  const height = Math.max(import_tokens5.tokens.button.heights[size], (_a = navigation == null ? void 0 : navigation.controlSize) != null ? _a : 0);
872
903
  const iconSize = (_b = navigation == null ? void 0 : navigation.iconSize) != null ? _b : import_tokens5.tokens.button.iconSizes[size];
873
904
  const hasContent2 = children !== void 0 && children !== null && children !== false && children !== "";
@@ -1028,6 +1059,16 @@ function FieldClear({ size, disabled, label, onPress }) {
1028
1059
  );
1029
1060
  }
1030
1061
 
1062
+ // shared/package-runtime/aria-ids.ts
1063
+ function mergeIds(...lists) {
1064
+ const ids = [...new Set(lists.flatMap((list) => (list != null ? list : "").split(/\s+/)).filter(Boolean))];
1065
+ return ids.length ? ids.join(" ") : void 0;
1066
+ }
1067
+ function joinHints(...parts) {
1068
+ const text = parts.filter(Boolean);
1069
+ return text.length ? text.join(". ") : void 0;
1070
+ }
1071
+
1031
1072
  // packages/native/src/field-surface.ts
1032
1073
  var import_react16 = require("react");
1033
1074
  var import_react_native7 = require("react-native");
@@ -1196,6 +1237,7 @@ function KjunFormGroup({
1196
1237
  required = false,
1197
1238
  error = "",
1198
1239
  hint = "",
1240
+ labelHidden = false,
1199
1241
  children,
1200
1242
  style
1201
1243
  }) {
@@ -1205,9 +1247,17 @@ function KjunFormGroup({
1205
1247
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1206
1248
  FieldContext.Provider,
1207
1249
  {
1208
- value: { id: fieldId, label, error, hint, required, describedBy: error ? fieldId + "-error" : hint ? fieldId + "-hint" : void 0 },
1250
+ value: {
1251
+ id: fieldId,
1252
+ label,
1253
+ error,
1254
+ hint,
1255
+ required,
1256
+ labelHidden,
1257
+ describedBy: mergeIds(hint ? fieldId + "-hint" : void 0, error ? fieldId + "-error" : void 0)
1258
+ },
1209
1259
  children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native8.View, { style: [{ minWidth: 0, marginBottom: import_tokens9.tokens.extensions.form.itemGap }, style], children: [
1210
- label && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1260
+ label && !labelHidden && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1211
1261
  import_react_native8.Text,
1212
1262
  {
1213
1263
  nativeID: fieldId + "-label",
@@ -1224,7 +1274,15 @@ function KjunFormGroup({
1224
1274
  }
1225
1275
  ),
1226
1276
  children,
1227
- (error || hint) && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1277
+ !!hint && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1278
+ import_react_native8.Text,
1279
+ {
1280
+ nativeID: fieldId + "-hint",
1281
+ style: { fontFamily, ...typeStyle("caption"), marginTop: import_tokens9.tokens.extensions.form.messageGap, color: c.textSecondary },
1282
+ children: hint
1283
+ }
1284
+ ),
1285
+ !!error && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1228
1286
  import_react_native8.View,
1229
1287
  {
1230
1288
  style: {
@@ -1234,19 +1292,14 @@ function KjunFormGroup({
1234
1292
  marginTop: import_tokens9.tokens.extensions.form.messageGap
1235
1293
  },
1236
1294
  children: [
1237
- !!error && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(KjunIcon, { name: "alert-circle", size: import_tokens9.tokens.extensions.form.messageIconSize, color: c.danger }),
1295
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(KjunIcon, { name: "alert-circle", size: import_tokens9.tokens.extensions.form.messageIconSize, color: c.danger }),
1238
1296
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1239
1297
  import_react_native8.Text,
1240
1298
  {
1241
- nativeID: fieldId + (error ? "-error" : "-hint"),
1242
- accessibilityRole: error ? "alert" : void 0,
1243
- style: {
1244
- fontFamily,
1245
- ...typeStyle("caption"),
1246
- flexShrink: 1,
1247
- color: error ? c.danger : c.textSecondary
1248
- },
1249
- children: error || hint
1299
+ nativeID: fieldId + "-error",
1300
+ accessibilityRole: "alert",
1301
+ style: { fontFamily, ...typeStyle("caption"), flexShrink: 1, color: c.danger },
1302
+ children: error
1250
1303
  }
1251
1304
  )
1252
1305
  ]
@@ -1320,8 +1373,8 @@ var KjunInput = (0, import_react17.forwardRef)(function KjunInput2({
1320
1373
  editable: canEdit,
1321
1374
  readOnly,
1322
1375
  accessibilityLabel: ariaLabel || props.accessibilityLabel || (field == null ? void 0 : field.label),
1323
- accessibilityLabelledBy: (field == null ? void 0 : field.label) ? field.id + "-label" : void 0,
1324
- accessibilityHint: errorMessage || (field == null ? void 0 : field.error) || (field == null ? void 0 : field.hint) || props.accessibilityHint,
1376
+ accessibilityLabelledBy: (field == null ? void 0 : field.label) && !field.labelHidden ? field.id + "-label" : void 0,
1377
+ accessibilityHint: joinHints(field == null ? void 0 : field.hint, errorMessage || (field == null ? void 0 : field.error)) || props.accessibilityHint,
1325
1378
  accessibilityState: { disabled },
1326
1379
  ...import_react_native8.Platform.OS === "web" ? {
1327
1380
  // Native Web TextInput does not translate accessibilityState.
@@ -1922,6 +1975,13 @@ function KjunCard({
1922
1975
  ] });
1923
1976
  }
1924
1977
 
1978
+ // shared/package-runtime/alert-live.ts
1979
+ function alertRole(tone, live) {
1980
+ if (live === "off") return void 0;
1981
+ if (live) return live === "assertive" ? "alert" : "status";
1982
+ return tone === "danger" ? "alert" : "status";
1983
+ }
1984
+
1925
1985
  // packages/native/src/display-primitives.tsx
1926
1986
  var import_tokens12 = require("@kjun-ui/tokens");
1927
1987
  var import_react23 = require("react");
@@ -2114,6 +2174,7 @@ function KjunAlert({
2114
2174
  size = "md",
2115
2175
  title,
2116
2176
  closable = false,
2177
+ live,
2117
2178
  children,
2118
2179
  actions,
2119
2180
  onClose
@@ -2130,7 +2191,7 @@ function KjunAlert({
2130
2191
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2131
2192
  import_react_native15.View,
2132
2193
  {
2133
- role: tone === "danger" ? "alert" : "status",
2194
+ role: alertRole(tone, live),
2134
2195
  style: {
2135
2196
  flexDirection: "row",
2136
2197
  alignItems: "flex-start",
@@ -3227,7 +3288,7 @@ function KjunRadioGroup({
3227
3288
  accessibilityLabel: ariaLabel,
3228
3289
  style: {
3229
3290
  flexDirection: direction === "vertical" ? "column" : "row",
3230
- flexWrap: "wrap",
3291
+ flexWrap: direction === "vertical" ? "nowrap" : "wrap",
3231
3292
  gap: direction === "vertical" ? import_tokens19.tokens.dimension.value8 : import_tokens19.tokens.dimension.value16
3232
3293
  },
3233
3294
  children: children || options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
@@ -3618,13 +3679,13 @@ var KjunTextarea = (0, import_react35.forwardRef)(
3618
3679
  editable: !disabled && !readonly && !props.readOnly && props.editable !== false,
3619
3680
  nativeID: props.nativeID || (field == null ? void 0 : field.id),
3620
3681
  accessibilityLabel: ariaLabel || props.accessibilityLabel || (field == null ? void 0 : field.label),
3621
- accessibilityHint: (field == null ? void 0 : field.error) || (field == null ? void 0 : field.hint) || props.accessibilityHint,
3682
+ accessibilityHint: joinHints(field == null ? void 0 : field.hint, field == null ? void 0 : field.error) || props.accessibilityHint,
3622
3683
  accessibilityState: { disabled },
3623
3684
  ...import_react_native24.Platform.OS === "web" ? {
3624
3685
  "aria-disabled": disabled,
3625
3686
  "aria-invalid": invalid || void 0,
3626
3687
  "aria-describedby": field == null ? void 0 : field.describedBy,
3627
- "aria-labelledby": (field == null ? void 0 : field.label) ? field.id + "-label" : void 0,
3688
+ "aria-labelledby": (field == null ? void 0 : field.label) && !field.labelHidden ? field.id + "-label" : void 0,
3628
3689
  "aria-required": (field == null ? void 0 : field.required) || void 0
3629
3690
  } : {},
3630
3691
  placeholderTextColor: disabled ? colors.textDisabled : colors.inputPlaceholder,
@@ -3863,8 +3924,11 @@ function FloatingPanel({
3863
3924
  flip = true,
3864
3925
  minimumWidth = 0,
3865
3926
  focusOnOpen = false,
3866
- reveal
3927
+ reveal,
3928
+ header,
3929
+ nonModal = false
3867
3930
  }) {
3931
+ const [headerHeight, setHeaderHeight] = (0, import_react37.useState)(0);
3868
3932
  const panelRef = (0, import_react37.useRef)(null);
3869
3933
  const scroll = (0, import_react37.useRef)(null), viewport = (0, import_react37.useRef)(0);
3870
3934
  const { colors } = useKjunStyles(), window2 = (0, import_react_native28.useWindowDimensions)(), [anchored, setAnchored] = (0, import_react37.useState)(false), [sized, setSized] = (0, import_react37.useState)(false), [anchor, setAnchor] = (0, import_react37.useState)({ x: 0, y: 0, width: 0, height: 0 }), [panel, setPanel] = (0, import_react37.useState)({ width: 0, height: 0 });
@@ -3919,7 +3983,7 @@ function FloatingPanel({
3919
3983
  maximumWidth,
3920
3984
  Math.max(minimumPanelWidth, contentSizedMenu ? panel.width : matchTriggerWidth ? anchor.width : import_tokens24.tokens.extensions.popover.width)
3921
3985
  );
3922
- const available = Math.min(maxHeight != null ? maxHeight : field ? import_tokens24.tokens.extensions.menu.listMaxHeight : import_tokens24.tokens.extensions.floating.maxHeight, window2.height - 2 * import_tokens24.tokens.extensions.floating.viewportInset), height = Math.min(panel.height, available), roomAbove = anchor.y - gap - import_tokens24.tokens.extensions.floating.viewportInset, roomBelow = window2.height - anchor.y - anchor.height - gap - import_tokens24.tokens.extensions.floating.viewportInset, above = placement.startsWith("top") ? !(flip && height > roomAbove && roomBelow > roomAbove) : flip && height > roomBelow && roomAbove > roomBelow;
3986
+ const available = Math.min(maxHeight != null ? maxHeight : field ? import_tokens24.tokens.extensions.menu.listMaxHeight : import_tokens24.tokens.extensions.floating.maxHeight, window2.height - 2 * import_tokens24.tokens.extensions.floating.viewportInset), height = Math.min(panel.height + (header != null ? headerHeight : 0), available), roomAbove = anchor.y - gap - import_tokens24.tokens.extensions.floating.viewportInset, roomBelow = window2.height - anchor.y - anchor.height - gap - import_tokens24.tokens.extensions.floating.viewportInset, above = placement.startsWith("top") ? !(flip && height > roomAbove && roomBelow > roomAbove) : flip && height > roomBelow && roomAbove > roomBelow;
3923
3987
  let left = placement.endsWith("end") ? anchor.x + anchor.width - width : placement === "top" || placement === "bottom" ? anchor.x + (anchor.width - width) / 2 : anchor.x, top = above ? anchor.y - height - gap : anchor.y + anchor.height + gap;
3924
3988
  if (placement === "left" || placement === "right") {
3925
3989
  left = placement === "left" ? anchor.x - width - gap : anchor.x + anchor.width + gap;
@@ -4003,7 +4067,7 @@ function FloatingPanel({
4003
4067
  }
4004
4068
  }
4005
4069
  ),
4006
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
4070
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
4007
4071
  import_react_native27.Animated.View,
4008
4072
  {
4009
4073
  ref: panelRef,
@@ -4014,7 +4078,7 @@ function FloatingPanel({
4014
4078
  pointerEvents: open && anchored && sized ? "auto" : "none",
4015
4079
  accessibilityElementsHidden: !open || !anchored || !sized,
4016
4080
  ...!open || !anchored || !sized ? { inert: true } : {},
4017
- accessibilityViewIsModal: true,
4081
+ accessibilityViewIsModal: !nonModal,
4018
4082
  accessibilityLabel: ariaLabel,
4019
4083
  style: {
4020
4084
  opacity: anchored && sized ? motion.opacity : 0,
@@ -4036,26 +4100,42 @@ function FloatingPanel({
4036
4100
  boxShadow: (tooltip ? import_tokens24.tokens.extensions.tooltip.elevation : popover ? import_tokens24.tokens.extensions.popover.elevation : import_tokens24.tokens.extensions.menu.elevation).map(({ colorRole, ...layer2 }) => ({ ...layer2, color: colors[colorRole] })),
4037
4101
  overflow: "hidden"
4038
4102
  },
4039
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
4040
- import_react_native28.ScrollView,
4041
- {
4042
- ref: scroll,
4043
- onLayout: (event) => {
4044
- viewport.current = event.nativeEvent.layout.height;
4045
- },
4046
- keyboardShouldPersistTaps: "handled",
4047
- style: { maxHeight: available },
4048
- contentContainerStyle: { padding: menu || field ? import_tokens24.tokens.extensions.menu.padding : 0 },
4049
- onContentSizeChange: (width2, height2) => {
4050
- if (contentSizedMenu) return;
4051
- setSized(true);
4052
- setPanel(
4053
- (previous) => previous.width === width2 && previous.height === height2 ? previous : { width: width2, height: height2 }
4054
- );
4055
- },
4056
- children
4057
- }
4058
- )
4103
+ children: [
4104
+ header != null && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
4105
+ import_react_native28.View,
4106
+ {
4107
+ onLayout: ({ nativeEvent: { layout } }) => setHeaderHeight(layout.height),
4108
+ style: {
4109
+ paddingTop: import_tokens24.tokens.extensions.popover.padding,
4110
+ paddingHorizontal: import_tokens24.tokens.extensions.popover.padding,
4111
+ paddingBottom: import_tokens24.tokens.extensions.popover.headerPaddingBottom,
4112
+ borderBottomWidth: import_tokens24.tokens.border.defaultWidth,
4113
+ borderBottomColor: colors.divider
4114
+ },
4115
+ children: header
4116
+ }
4117
+ ),
4118
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
4119
+ import_react_native28.ScrollView,
4120
+ {
4121
+ ref: scroll,
4122
+ onLayout: (event) => {
4123
+ viewport.current = event.nativeEvent.layout.height;
4124
+ },
4125
+ keyboardShouldPersistTaps: "handled",
4126
+ style: { maxHeight: available - (header != null ? headerHeight : 0), flexShrink: 1 },
4127
+ contentContainerStyle: { padding: menu || field ? import_tokens24.tokens.extensions.menu.padding : 0 },
4128
+ onContentSizeChange: (width2, height2) => {
4129
+ if (contentSizedMenu) return;
4130
+ setSized(true);
4131
+ setPanel(
4132
+ (previous) => previous.width === width2 && previous.height === height2 ? previous : { width: width2, height: height2 }
4133
+ );
4134
+ },
4135
+ children
4136
+ }
4137
+ )
4138
+ ]
4059
4139
  }
4060
4140
  )
4061
4141
  ]
@@ -4081,9 +4161,12 @@ function KjunPopover({
4081
4161
  focusOnOpen = false,
4082
4162
  ariaLabel,
4083
4163
  placement = "bottom",
4164
+ header,
4165
+ modal = true,
4084
4166
  trigger,
4085
4167
  children
4086
4168
  }) {
4169
+ const { colors } = useKjunStyles();
4087
4170
  const [internal, setInternal] = (0, import_react38.useState)(false), open = controlled != null ? controlled : internal, ref = (0, import_react38.useRef)(null);
4088
4171
  const change = (v) => {
4089
4172
  setInternal(v);
@@ -4112,6 +4195,8 @@ function KjunPopover({
4112
4195
  popover: true,
4113
4196
  focusOnOpen,
4114
4197
  ariaLabel,
4198
+ header: header != null ? content(header, { ...typeStyle("cardTitle"), color: colors.text }) : void 0,
4199
+ nonModal: !modal,
4115
4200
  children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_react_native29.View, { style: { padding: noPadding ? 0 : import_tokens25.tokens.extensions.popover.padding, gap: noPadding ? 0 : import_tokens25.tokens.extensions.popover.gap }, children: content(
4116
4201
  typeof children === "function" ? children(() => change(false)) : children,
4117
4202
  import_react_native29.Platform.OS === "web" ? { wordBreak: "keep-all", overflowWrap: "anywhere" } : void 0
@@ -4126,9 +4211,18 @@ function KjunTooltip({
4126
4211
  delay = 0,
4127
4212
  children,
4128
4213
  onPress,
4129
- disabled
4214
+ disabled,
4215
+ open: controlled,
4216
+ onOpenChange
4130
4217
  }) {
4131
- const { colors } = useKjunStyles(), [open, setOpen] = (0, import_react38.useState)(false), ref = (0, import_react38.useRef)(null), timer = (0, import_react38.useRef)(void 0);
4218
+ const { colors } = useKjunStyles(), [internal, setInternal] = (0, import_react38.useState)(false), open = controlled != null ? controlled : internal, latest = (0, import_react38.useRef)({ open, onOpenChange }), setOpen = (value) => {
4219
+ var _a, _b;
4220
+ if (latest.current.open === value) return;
4221
+ latest.current.open = value;
4222
+ setInternal(value);
4223
+ (_b = (_a = latest.current).onOpenChange) == null ? void 0 : _b.call(_a, value);
4224
+ }, ref = (0, import_react38.useRef)(null), timer = (0, import_react38.useRef)(void 0);
4225
+ latest.current = { open, onOpenChange };
4132
4226
  const scopeActive = useActiveLayerScope();
4133
4227
  const hide = () => {
4134
4228
  clearTimeout(timer.current);
@@ -4416,6 +4510,17 @@ function nextOptionValue(value, option, key, multiple) {
4416
4510
  if (!multiple) return id;
4417
4511
  return Array.isArray(value) && value.includes(id) ? value.filter((item) => item !== id) : [...Array.isArray(value) ? value : [], id];
4418
4512
  }
4513
+ function optionMatchRank(option, query, labelKey, searchKeys = []) {
4514
+ const needle = query.trim().toLocaleLowerCase();
4515
+ if (!needle) return 0;
4516
+ const label = optionLabel(option, labelKey).toLocaleLowerCase();
4517
+ if (label.startsWith(needle)) return 0;
4518
+ if (label.includes(needle)) return 1;
4519
+ return typeof option === "object" && searchKeys.some((key) => {
4520
+ var _a;
4521
+ return String((_a = option[key]) != null ? _a : "").toLocaleLowerCase().includes(needle);
4522
+ }) ? 2 : -1;
4523
+ }
4419
4524
  function selectOptions({
4420
4525
  options,
4421
4526
  value,
@@ -4425,10 +4530,13 @@ function selectOptions({
4425
4530
  pageSize,
4426
4531
  limit,
4427
4532
  loading,
4428
- multiple
4533
+ multiple,
4534
+ filterFn,
4535
+ searchKeys = []
4429
4536
  }) {
4430
4537
  const selected = (option) => optionSelected(value, option, valueKey);
4431
- const matching = options.map((option, index) => ({ option, key: optionKey(option, valueKey, index) })).filter(({ option }) => optionLabel(option, labelKey).toLocaleLowerCase().includes(query.trim().toLocaleLowerCase()));
4538
+ const indexed = options.map((option, index) => ({ option, key: optionKey(option, valueKey, index) }));
4539
+ const matching = filterFn ? indexed.filter(({ option }) => filterFn(option, query)) : indexed.map((item, index) => ({ item, index, rank: optionMatchRank(item.option, query, labelKey, searchKeys) })).filter(({ rank }) => rank >= 0).sort((a, b) => a.rank - b.rank || a.index - b.index).map(({ item }) => item);
4432
4540
  const visible = loading ? [] : pageSize > 0 ? matching.slice(0, limit) : matching;
4433
4541
  const selectedOption = options.find(selected);
4434
4542
  const label = multiple && Array.isArray(value) && value.length > 1 ? `${value.length}\uAC1C \uC120\uD0DD\uB428` : selectedOption !== void 0 ? optionLabel(selectedOption, labelKey) : "";
@@ -5163,7 +5271,9 @@ function KjunSelect({
5163
5271
  onChange,
5164
5272
  onOpenChange,
5165
5273
  onSearch,
5166
- onClear
5274
+ onClear,
5275
+ filterFn,
5276
+ searchKeys
5167
5277
  }) {
5168
5278
  const { open, query, limit, changeOpen, search, showMore } = useSelectState(controlled, disabled, optionPageSize, onOpenChange);
5169
5279
  const { colors } = useKjunStyles(), field = useKjunField(), ref = (0, import_react47.useRef)(null), spec = import_tokens31.tokens.input[size];
@@ -5188,7 +5298,9 @@ function KjunSelect({
5188
5298
  pageSize: optionPageSize,
5189
5299
  limit,
5190
5300
  loading,
5191
- multiple
5301
+ multiple,
5302
+ filterFn,
5303
+ searchKeys
5192
5304
  });
5193
5305
  const firstSelected = visible.findIndex(({ option }) => selected(option));
5194
5306
  if (firstSelected < 0) selectedRow.current = null;
@@ -6243,7 +6355,7 @@ function KjunRefreshButton({
6243
6355
  ...props
6244
6356
  }) {
6245
6357
  const reduced = useReducedMotion();
6246
- const label = ariaLabel || [targetName, "\uC0C8\uB85C\uACE0\uCE68"].filter(Boolean).join(" ");
6358
+ const label = ariaLabel || [targetName, mode === "text" ? text : "\uC0C8\uB85C\uACE0\uCE68"].filter(Boolean).join(" ");
6247
6359
  const hint = tooltip != null ? tooltip : mode === "icon" ? label : "";
6248
6360
  const button = /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(KjunTooltip, { content: props.disabled || props.loading ? "" : hint, placement: tooltipPlacement, delay: tooltipDelay, children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
6249
6361
  KjunButton,
@@ -7407,9 +7519,18 @@ function tablePresentation(props, mobile) {
7407
7519
  }
7408
7520
  };
7409
7521
  }
7522
+ var warnedFormats = /* @__PURE__ */ new Set();
7523
+ function columnFormat(column) {
7524
+ if (typeof column.format === "function") return column.format;
7525
+ if (column.format != null && !warnedFormats.has(column.key)) {
7526
+ warnedFormats.add(column.key);
7527
+ console.warn(`KJUN Table: ${column.key} \uC5F4\uC758 format\uC740 \uD568\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4.`);
7528
+ }
7529
+ return void 0;
7530
+ }
7410
7531
  function tableCellValue(props, column, row, index) {
7411
7532
  const value = getValue(row, column.key);
7412
- const body = props.renderCell ? props.renderCell(value, column, row, index) : column.render ? column.render(value, row, index) : column.format ? column.format(value, row) : props.formatValue ? props.formatValue(value, column, row) : value == null ? "-" : typeof value === "number" && column.type === "number" ? new Intl.NumberFormat().format(value) : String(value);
7533
+ const body = props.renderCell ? props.renderCell(value, column, row, index) : column.render ? column.render(value, row, index) : columnFormat(column) ? columnFormat(column)(value, row) : props.formatValue ? props.formatValue(value, column, row) : value == null ? "-" : typeof value === "number" && column.type === "number" ? new Intl.NumberFormat().format(value) : String(value);
7413
7534
  return { value, body };
7414
7535
  }
7415
7536
 
@@ -7445,7 +7566,7 @@ function TableToolbar({
7445
7566
  selectable && selected.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
7446
7567
  import_react_native45.View,
7447
7568
  {
7448
- accessibilityRole: "toolbar",
7569
+ role: "group",
7449
7570
  accessibilityLabel: "\uC120\uD0DD \uC791\uC5C5",
7450
7571
  style: [toolbarStyle, { backgroundColor: colors.selectedBg }],
7451
7572
  children: [
@@ -9785,7 +9906,7 @@ function KjunQuantityStepper({ value, min = 0, max, step = 1, precision = 0, dis
9785
9906
  const { colors, fontFamily } = useKjunStyles();
9786
9907
  const field = useKjunField();
9787
9908
  const invalid = error || !!(field == null ? void 0 : field.error);
9788
- const labelledBy = !ariaLabel && (field == null ? void 0 : field.label) ? field.id + "-label" : void 0;
9909
+ const labelledBy = !ariaLabel && (field == null ? void 0 : field.label) && !field.labelHidden ? field.id + "-label" : void 0;
9789
9910
  const geometry2 = import_tokens61.tokens.extensions.compound;
9790
9911
  const buttonStyle = { width: import_tokens61.tokens.input[size].height, paddingHorizontal: 0 };
9791
9912
  const outer = import_tokens61.tokens.input[size].radius;
@@ -9893,7 +10014,7 @@ function KjunQuantityStepper({ value, min = 0, max, step = 1, precision = 0, dis
9893
10014
  nativeID: id || (field == null ? void 0 : field.id),
9894
10015
  accessibilityLabel: ariaLabel || (field == null ? void 0 : field.label) || "\uC218\uB7C9",
9895
10016
  accessibilityLabelledBy: labelledBy,
9896
- accessibilityHint: (field == null ? void 0 : field.error) || (field == null ? void 0 : field.hint),
10017
+ accessibilityHint: joinHints(field == null ? void 0 : field.hint, field == null ? void 0 : field.error),
9897
10018
  role: "spinbutton",
9898
10019
  accessibilityValue: { min, max, now: value },
9899
10020
  accessibilityState: { disabled: blocked },