@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
package/dist/index.js CHANGED
@@ -26,9 +26,23 @@ function mergeIcons(parent, own) {
26
26
  function withFallbackIcons(registry, fallbacks) {
27
27
  return mergeIcons(fallbacks, registry);
28
28
  }
29
+ var warnedIcons = /* @__PURE__ */ new Set();
30
+ function developmentMode() {
31
+ try {
32
+ return true;
33
+ } catch (e) {
34
+ return false;
35
+ }
36
+ }
37
+ function warnMissingIcon(name) {
38
+ if (!developmentMode() || warnedIcons.has(name)) return;
39
+ warnedIcons.add(name);
40
+ 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.`);
41
+ }
29
42
  function resolveIcon(registry, name, filled = false) {
30
43
  var _a;
31
44
  const definition = Object.prototype.hasOwnProperty.call(registry, name) ? registry[name] : void 0;
45
+ if (!definition && name) warnMissingIcon(name);
32
46
  const filledNodes = filled && (definition == null ? void 0 : definition.filled);
33
47
  return { nodes: filledNodes || (definition == null ? void 0 : definition.outline) || ((_a = registry["help-circle"]) == null ? void 0 : _a.outline) || defaultIcons["help-circle"].outline, filled: !!filledNodes };
34
48
  }
@@ -119,6 +133,10 @@ function installLayerKeyboard(document2, state, keyupFallback = false) {
119
133
  import { createContext as createContext2, useContext as useContext2, useId, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
120
134
 
121
135
  // shared/package-runtime/layer-state.ts
136
+ function chooseReturnTarget(chosen, fallback2) {
137
+ if (chosen === false) return void 0;
138
+ return chosen && chosen.isConnected !== false ? chosen : fallback2;
139
+ }
122
140
  function createLayerState() {
123
141
  const entries = /* @__PURE__ */ new Map(), listeners = /* @__PURE__ */ new Set();
124
142
  const restoredFocus = /* @__PURE__ */ new WeakSet();
@@ -139,6 +157,7 @@ function createLayerState() {
139
157
  return !layer.blocked && (layer.kind === "window" ? ((_a = topWindow()) == null ? void 0 : _a.id) === layer.id : ((_b = topWindow()) == null ? void 0 : _b.id) === layer.owner);
140
158
  };
141
159
  const top = () => [...entries.values()].filter(active).sort((a, b) => a.order - b.order).at(-1);
160
+ const topPopup = () => [...entries.values()].filter((layer) => layer.kind !== "tooltip" && active(layer)).sort((a, b) => a.order - b.order).at(-1);
142
161
  function dismiss(layer) {
143
162
  if (layer.closeRequested) return;
144
163
  layer.closeRequested = true;
@@ -158,6 +177,7 @@ function createLayerState() {
158
177
  entries,
159
178
  topWindow,
160
179
  top,
180
+ topPopup,
161
181
  windowOf,
162
182
  isRestoredFocus(target) {
163
183
  return !!target && restoredFocus.has(target);
@@ -306,21 +326,31 @@ function focusTrigger(target) {
306
326
  }
307
327
  return target;
308
328
  }
309
- function restoreFocus(target, state, id) {
329
+ function restoreFocus(target, state, id, fallback2) {
330
+ const usable = (item) => (item == null ? void 0 : item.isConnected) !== false && !(typeof HTMLElement !== "undefined" && item instanceof HTMLElement && item.closest("[inert]"));
310
331
  const restore = () => {
311
- var _a, _b, _c, _d;
332
+ var _a, _b, _c, _d, _e;
312
333
  if (state.entries.has(id)) return;
313
- 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 });
314
- else (_d = (_c = (_b = state.topWindow()) == null ? void 0 : _b.root) == null ? void 0 : _c.focus) == null ? void 0 : _d.call(_c, { preventScroll: true });
334
+ if (usable(target)) (_a = target == null ? void 0 : target.focus) == null ? void 0 : _a.call(target, { preventScroll: true });
335
+ else if (fallback2 && fallback2 !== target && usable(fallback2)) (_b = fallback2.focus) == null ? void 0 : _b.call(fallback2, { preventScroll: true });
336
+ else (_e = (_d = (_c = state.topWindow()) == null ? void 0 : _c.root) == null ? void 0 : _d.focus) == null ? void 0 : _e.call(_d, { preventScroll: true });
315
337
  };
316
338
  if (typeof requestAnimationFrame !== "undefined") requestAnimationFrame(restore);
317
339
  else queueMicrotask(restore);
318
340
  }
319
- function useLayer(open, present, kind, close, trigger) {
341
+ function useLayer(open, present, kind, close, trigger, returnFocus) {
320
342
  var _a;
321
343
  const state = useLayerState(), parent = useContext2(LayerScope), id = useId();
322
344
  const callback = useRef(close);
323
345
  callback.current = close;
346
+ const returnRef = useRef(returnFocus);
347
+ returnRef.current = returnFocus;
348
+ const fallbackFocus = useRef(null);
349
+ const removeLayer = () => {
350
+ const removed = state.remove(id, currentFocus());
351
+ if (removed) fallbackFocus.current = removed;
352
+ return removed && returnRef.current ? chooseReturnTarget(returnRef.current(), removed) : removed;
353
+ };
324
354
  const openingFocus = useRef(null);
325
355
  const wasOpen = useRef(false);
326
356
  const pendingFocus = useRef(null);
@@ -332,17 +362,17 @@ function useLayer(open, present, kind, close, trigger) {
332
362
  state.open(id, kind, parent, () => callback.current(), focusTrigger(trigger == null ? void 0 : trigger.current) || openingFocus.current);
333
363
  } else if (kind === "window" && present) state.markClosing(id);
334
364
  else if (kind !== "window" || !present) {
335
- const target = state.remove(id, currentFocus());
365
+ const target = removeLayer();
336
366
  if (target) pendingFocus.current = target;
337
367
  if (!present && pendingFocus.current) {
338
- restoreFocus(pendingFocus.current, state, id);
368
+ restoreFocus(pendingFocus.current, state, id, fallbackFocus.current);
339
369
  pendingFocus.current = null;
340
370
  }
341
371
  }
342
372
  }, [open, present, state, id, kind, parent, trigger]);
343
373
  useLayoutEffect(() => () => {
344
- const target = state.remove(id, currentFocus());
345
- if (target || pendingFocus.current) restoreFocus(target || pendingFocus.current, state, id);
374
+ const target = removeLayer();
375
+ if (target || pendingFocus.current) restoreFocus(target || pendingFocus.current, state, id, fallbackFocus.current);
346
376
  }, [state, id]);
347
377
  const entry = state.entries.get(id);
348
378
  return {
@@ -677,6 +707,7 @@ function KjunButton(input) {
677
707
  size: declaredSize = "md",
678
708
  loading = false,
679
709
  block = false,
710
+ wrap = false,
680
711
  disabled = false,
681
712
  prefixIcon,
682
713
  suffixIcon,
@@ -697,7 +728,7 @@ function KjunButton(input) {
697
728
  const formActions = useContext5(FormActionsContext);
698
729
  const cardActions = useContext5(CardActionsContext);
699
730
  const stateContent = useContext5(DataStateContentContext);
700
- const multiline = !!formActions || cardActions || !!stateContent || (navigation == null ? void 0 : navigation.slot) === "actions";
731
+ const multiline = wrap || !!formActions || cardActions || !!stateContent || (navigation == null ? void 0 : navigation.slot) === "actions";
701
732
  const height = Math.max(tokens3.button.heights[size], (_a = navigation == null ? void 0 : navigation.controlSize) != null ? _a : 0);
702
733
  const iconSize = (_b = navigation == null ? void 0 : navigation.iconSize) != null ? _b : tokens3.button.iconSizes[size];
703
734
  const hasContent2 = children !== void 0 && children !== null && children !== false && children !== "";
@@ -858,6 +889,16 @@ function FieldClear({ size, disabled, label, onPress }) {
858
889
  );
859
890
  }
860
891
 
892
+ // shared/package-runtime/aria-ids.ts
893
+ function mergeIds(...lists) {
894
+ const ids = [...new Set(lists.flatMap((list) => (list != null ? list : "").split(/\s+/)).filter(Boolean))];
895
+ return ids.length ? ids.join(" ") : void 0;
896
+ }
897
+ function joinHints(...parts) {
898
+ const text = parts.filter(Boolean);
899
+ return text.length ? text.join(". ") : void 0;
900
+ }
901
+
861
902
  // packages/native/src/field-surface.ts
862
903
  import { useState as useState6 } from "react";
863
904
  import { Animated as Animated3, Platform as Platform5, TextInput } from "react-native";
@@ -1045,6 +1086,7 @@ function KjunFormGroup({
1045
1086
  required = false,
1046
1087
  error = "",
1047
1088
  hint = "",
1089
+ labelHidden = false,
1048
1090
  children,
1049
1091
  style
1050
1092
  }) {
@@ -1054,9 +1096,17 @@ function KjunFormGroup({
1054
1096
  return /* @__PURE__ */ jsx8(
1055
1097
  FieldContext.Provider,
1056
1098
  {
1057
- value: { id: fieldId, label, error, hint, required, describedBy: error ? fieldId + "-error" : hint ? fieldId + "-hint" : void 0 },
1099
+ value: {
1100
+ id: fieldId,
1101
+ label,
1102
+ error,
1103
+ hint,
1104
+ required,
1105
+ labelHidden,
1106
+ describedBy: mergeIds(hint ? fieldId + "-hint" : void 0, error ? fieldId + "-error" : void 0)
1107
+ },
1058
1108
  children: /* @__PURE__ */ jsxs2(View4, { style: [{ minWidth: 0, marginBottom: tokens7.extensions.form.itemGap }, style], children: [
1059
- label && /* @__PURE__ */ jsxs2(
1109
+ label && !labelHidden && /* @__PURE__ */ jsxs2(
1060
1110
  Text3,
1061
1111
  {
1062
1112
  nativeID: fieldId + "-label",
@@ -1073,7 +1123,15 @@ function KjunFormGroup({
1073
1123
  }
1074
1124
  ),
1075
1125
  children,
1076
- (error || hint) && /* @__PURE__ */ jsxs2(
1126
+ !!hint && /* @__PURE__ */ jsx8(
1127
+ Text3,
1128
+ {
1129
+ nativeID: fieldId + "-hint",
1130
+ style: { fontFamily, ...typeStyle("caption"), marginTop: tokens7.extensions.form.messageGap, color: c.textSecondary },
1131
+ children: hint
1132
+ }
1133
+ ),
1134
+ !!error && /* @__PURE__ */ jsxs2(
1077
1135
  View4,
1078
1136
  {
1079
1137
  style: {
@@ -1083,19 +1141,14 @@ function KjunFormGroup({
1083
1141
  marginTop: tokens7.extensions.form.messageGap
1084
1142
  },
1085
1143
  children: [
1086
- !!error && /* @__PURE__ */ jsx8(KjunIcon, { name: "alert-circle", size: tokens7.extensions.form.messageIconSize, color: c.danger }),
1144
+ /* @__PURE__ */ jsx8(KjunIcon, { name: "alert-circle", size: tokens7.extensions.form.messageIconSize, color: c.danger }),
1087
1145
  /* @__PURE__ */ jsx8(
1088
1146
  Text3,
1089
1147
  {
1090
- nativeID: fieldId + (error ? "-error" : "-hint"),
1091
- accessibilityRole: error ? "alert" : void 0,
1092
- style: {
1093
- fontFamily,
1094
- ...typeStyle("caption"),
1095
- flexShrink: 1,
1096
- color: error ? c.danger : c.textSecondary
1097
- },
1098
- children: error || hint
1148
+ nativeID: fieldId + "-error",
1149
+ accessibilityRole: "alert",
1150
+ style: { fontFamily, ...typeStyle("caption"), flexShrink: 1, color: c.danger },
1151
+ children: error
1099
1152
  }
1100
1153
  )
1101
1154
  ]
@@ -1169,8 +1222,8 @@ var KjunInput = forwardRef2(function KjunInput2({
1169
1222
  editable: canEdit,
1170
1223
  readOnly,
1171
1224
  accessibilityLabel: ariaLabel || props.accessibilityLabel || (field == null ? void 0 : field.label),
1172
- accessibilityLabelledBy: (field == null ? void 0 : field.label) ? field.id + "-label" : void 0,
1173
- accessibilityHint: errorMessage || (field == null ? void 0 : field.error) || (field == null ? void 0 : field.hint) || props.accessibilityHint,
1225
+ accessibilityLabelledBy: (field == null ? void 0 : field.label) && !field.labelHidden ? field.id + "-label" : void 0,
1226
+ accessibilityHint: joinHints(field == null ? void 0 : field.hint, errorMessage || (field == null ? void 0 : field.error)) || props.accessibilityHint,
1174
1227
  accessibilityState: { disabled },
1175
1228
  ...Platform6.OS === "web" ? {
1176
1229
  // Native Web TextInput does not translate accessibilityState.
@@ -1768,6 +1821,13 @@ function KjunCard({
1768
1821
  ] });
1769
1822
  }
1770
1823
 
1824
+ // shared/package-runtime/alert-live.ts
1825
+ function alertRole(tone, live) {
1826
+ if (live === "off") return void 0;
1827
+ if (live) return live === "assertive" ? "alert" : "status";
1828
+ return tone === "danger" ? "alert" : "status";
1829
+ }
1830
+
1771
1831
  // packages/native/src/display-primitives.tsx
1772
1832
  import { tokens as tokens10 } from "@kjun-ui/tokens";
1773
1833
  import { useContext as useContext8, useEffect as useEffect5, useRef as useRef6, useState as useState9 } from "react";
@@ -1965,6 +2025,7 @@ function KjunAlert({
1965
2025
  size = "md",
1966
2026
  title,
1967
2027
  closable = false,
2028
+ live,
1968
2029
  children,
1969
2030
  actions,
1970
2031
  onClose
@@ -1981,7 +2042,7 @@ function KjunAlert({
1981
2042
  return /* @__PURE__ */ jsxs6(
1982
2043
  View9,
1983
2044
  {
1984
- role: tone === "danger" ? "alert" : "status",
2045
+ role: alertRole(tone, live),
1985
2046
  style: {
1986
2047
  flexDirection: "row",
1987
2048
  alignItems: "flex-start",
@@ -3031,7 +3092,7 @@ function KjunRadioGroup({
3031
3092
  accessibilityLabel: ariaLabel,
3032
3093
  style: {
3033
3094
  flexDirection: direction === "vertical" ? "column" : "row",
3034
- flexWrap: "wrap",
3095
+ flexWrap: direction === "vertical" ? "nowrap" : "wrap",
3035
3096
  gap: direction === "vertical" ? tokens16.dimension.value8 : tokens16.dimension.value16
3036
3097
  },
3037
3098
  children: children || options.map((option) => /* @__PURE__ */ jsx19(
@@ -3431,13 +3492,13 @@ var KjunTextarea = forwardRef3(
3431
3492
  editable: !disabled && !readonly && !props.readOnly && props.editable !== false,
3432
3493
  nativeID: props.nativeID || (field == null ? void 0 : field.id),
3433
3494
  accessibilityLabel: ariaLabel || props.accessibilityLabel || (field == null ? void 0 : field.label),
3434
- accessibilityHint: (field == null ? void 0 : field.error) || (field == null ? void 0 : field.hint) || props.accessibilityHint,
3495
+ accessibilityHint: joinHints(field == null ? void 0 : field.hint, field == null ? void 0 : field.error) || props.accessibilityHint,
3435
3496
  accessibilityState: { disabled },
3436
3497
  ...Platform13.OS === "web" ? {
3437
3498
  "aria-disabled": disabled,
3438
3499
  "aria-invalid": invalid || void 0,
3439
3500
  "aria-describedby": field == null ? void 0 : field.describedBy,
3440
- "aria-labelledby": (field == null ? void 0 : field.label) ? field.id + "-label" : void 0,
3501
+ "aria-labelledby": (field == null ? void 0 : field.label) && !field.labelHidden ? field.id + "-label" : void 0,
3441
3502
  "aria-required": (field == null ? void 0 : field.required) || void 0
3442
3503
  } : {},
3443
3504
  placeholderTextColor: disabled ? colors.textDisabled : colors.inputPlaceholder,
@@ -3707,8 +3768,11 @@ function FloatingPanel({
3707
3768
  flip = true,
3708
3769
  minimumWidth = 0,
3709
3770
  focusOnOpen = false,
3710
- reveal
3771
+ reveal,
3772
+ header,
3773
+ nonModal = false
3711
3774
  }) {
3775
+ const [headerHeight, setHeaderHeight] = useState16(0);
3712
3776
  const panelRef = useRef16(null);
3713
3777
  const scroll = useRef16(null), viewport = useRef16(0);
3714
3778
  const { colors } = useKjunStyles(), window2 = useWindowDimensions4(), [anchored, setAnchored] = useState16(false), [sized, setSized] = useState16(false), [anchor, setAnchor] = useState16({ x: 0, y: 0, width: 0, height: 0 }), [panel, setPanel] = useState16({ width: 0, height: 0 });
@@ -3763,7 +3827,7 @@ function FloatingPanel({
3763
3827
  maximumWidth,
3764
3828
  Math.max(minimumPanelWidth, contentSizedMenu ? panel.width : matchTriggerWidth ? anchor.width : tokens21.extensions.popover.width)
3765
3829
  );
3766
- const available = Math.min(maxHeight != null ? maxHeight : field ? tokens21.extensions.menu.listMaxHeight : tokens21.extensions.floating.maxHeight, window2.height - 2 * tokens21.extensions.floating.viewportInset), height = Math.min(panel.height, available), roomAbove = anchor.y - gap - tokens21.extensions.floating.viewportInset, roomBelow = window2.height - anchor.y - anchor.height - gap - tokens21.extensions.floating.viewportInset, above = placement.startsWith("top") ? !(flip && height > roomAbove && roomBelow > roomAbove) : flip && height > roomBelow && roomAbove > roomBelow;
3830
+ const available = Math.min(maxHeight != null ? maxHeight : field ? tokens21.extensions.menu.listMaxHeight : tokens21.extensions.floating.maxHeight, window2.height - 2 * tokens21.extensions.floating.viewportInset), height = Math.min(panel.height + (header != null ? headerHeight : 0), available), roomAbove = anchor.y - gap - tokens21.extensions.floating.viewportInset, roomBelow = window2.height - anchor.y - anchor.height - gap - tokens21.extensions.floating.viewportInset, above = placement.startsWith("top") ? !(flip && height > roomAbove && roomBelow > roomAbove) : flip && height > roomBelow && roomAbove > roomBelow;
3767
3831
  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;
3768
3832
  if (placement === "left" || placement === "right") {
3769
3833
  left = placement === "left" ? anchor.x - width - gap : anchor.x + anchor.width + gap;
@@ -3847,7 +3911,7 @@ function FloatingPanel({
3847
3911
  }
3848
3912
  }
3849
3913
  ),
3850
- /* @__PURE__ */ jsx24(
3914
+ /* @__PURE__ */ jsxs14(
3851
3915
  Animated13.View,
3852
3916
  {
3853
3917
  ref: panelRef,
@@ -3858,7 +3922,7 @@ function FloatingPanel({
3858
3922
  pointerEvents: open && anchored && sized ? "auto" : "none",
3859
3923
  accessibilityElementsHidden: !open || !anchored || !sized,
3860
3924
  ...!open || !anchored || !sized ? { inert: true } : {},
3861
- accessibilityViewIsModal: true,
3925
+ accessibilityViewIsModal: !nonModal,
3862
3926
  accessibilityLabel: ariaLabel,
3863
3927
  style: {
3864
3928
  opacity: anchored && sized ? motion.opacity : 0,
@@ -3880,26 +3944,42 @@ function FloatingPanel({
3880
3944
  boxShadow: (tooltip ? tokens21.extensions.tooltip.elevation : popover ? tokens21.extensions.popover.elevation : tokens21.extensions.menu.elevation).map(({ colorRole, ...layer2 }) => ({ ...layer2, color: colors[colorRole] })),
3881
3945
  overflow: "hidden"
3882
3946
  },
3883
- children: /* @__PURE__ */ jsx24(
3884
- ScrollView4,
3885
- {
3886
- ref: scroll,
3887
- onLayout: (event) => {
3888
- viewport.current = event.nativeEvent.layout.height;
3889
- },
3890
- keyboardShouldPersistTaps: "handled",
3891
- style: { maxHeight: available },
3892
- contentContainerStyle: { padding: menu || field ? tokens21.extensions.menu.padding : 0 },
3893
- onContentSizeChange: (width2, height2) => {
3894
- if (contentSizedMenu) return;
3895
- setSized(true);
3896
- setPanel(
3897
- (previous) => previous.width === width2 && previous.height === height2 ? previous : { width: width2, height: height2 }
3898
- );
3899
- },
3900
- children
3901
- }
3902
- )
3947
+ children: [
3948
+ header != null && /* @__PURE__ */ jsx24(
3949
+ View18,
3950
+ {
3951
+ onLayout: ({ nativeEvent: { layout } }) => setHeaderHeight(layout.height),
3952
+ style: {
3953
+ paddingTop: tokens21.extensions.popover.padding,
3954
+ paddingHorizontal: tokens21.extensions.popover.padding,
3955
+ paddingBottom: tokens21.extensions.popover.headerPaddingBottom,
3956
+ borderBottomWidth: tokens21.border.defaultWidth,
3957
+ borderBottomColor: colors.divider
3958
+ },
3959
+ children: header
3960
+ }
3961
+ ),
3962
+ /* @__PURE__ */ jsx24(
3963
+ ScrollView4,
3964
+ {
3965
+ ref: scroll,
3966
+ onLayout: (event) => {
3967
+ viewport.current = event.nativeEvent.layout.height;
3968
+ },
3969
+ keyboardShouldPersistTaps: "handled",
3970
+ style: { maxHeight: available - (header != null ? headerHeight : 0), flexShrink: 1 },
3971
+ contentContainerStyle: { padding: menu || field ? tokens21.extensions.menu.padding : 0 },
3972
+ onContentSizeChange: (width2, height2) => {
3973
+ if (contentSizedMenu) return;
3974
+ setSized(true);
3975
+ setPanel(
3976
+ (previous) => previous.width === width2 && previous.height === height2 ? previous : { width: width2, height: height2 }
3977
+ );
3978
+ },
3979
+ children
3980
+ }
3981
+ )
3982
+ ]
3903
3983
  }
3904
3984
  )
3905
3985
  ]
@@ -3934,9 +4014,12 @@ function KjunPopover({
3934
4014
  focusOnOpen = false,
3935
4015
  ariaLabel,
3936
4016
  placement = "bottom",
4017
+ header,
4018
+ modal = true,
3937
4019
  trigger,
3938
4020
  children
3939
4021
  }) {
4022
+ const { colors } = useKjunStyles();
3940
4023
  const [internal, setInternal] = useState17(false), open = controlled != null ? controlled : internal, ref = useRef17(null);
3941
4024
  const change = (v) => {
3942
4025
  setInternal(v);
@@ -3965,6 +4048,8 @@ function KjunPopover({
3965
4048
  popover: true,
3966
4049
  focusOnOpen,
3967
4050
  ariaLabel,
4051
+ header: header != null ? content(header, { ...typeStyle("cardTitle"), color: colors.text }) : void 0,
4052
+ nonModal: !modal,
3968
4053
  children: /* @__PURE__ */ jsx25(View19, { style: { padding: noPadding ? 0 : tokens22.extensions.popover.padding, gap: noPadding ? 0 : tokens22.extensions.popover.gap }, children: content(
3969
4054
  typeof children === "function" ? children(() => change(false)) : children,
3970
4055
  Platform16.OS === "web" ? { wordBreak: "keep-all", overflowWrap: "anywhere" } : void 0
@@ -3979,9 +4064,18 @@ function KjunTooltip({
3979
4064
  delay = 0,
3980
4065
  children,
3981
4066
  onPress,
3982
- disabled
4067
+ disabled,
4068
+ open: controlled,
4069
+ onOpenChange
3983
4070
  }) {
3984
- const { colors } = useKjunStyles(), [open, setOpen] = useState17(false), ref = useRef17(null), timer = useRef17(void 0);
4071
+ const { colors } = useKjunStyles(), [internal, setInternal] = useState17(false), open = controlled != null ? controlled : internal, latest = useRef17({ open, onOpenChange }), setOpen = (value) => {
4072
+ var _a, _b;
4073
+ if (latest.current.open === value) return;
4074
+ latest.current.open = value;
4075
+ setInternal(value);
4076
+ (_b = (_a = latest.current).onOpenChange) == null ? void 0 : _b.call(_a, value);
4077
+ }, ref = useRef17(null), timer = useRef17(void 0);
4078
+ latest.current = { open, onOpenChange };
3985
4079
  const scopeActive = useActiveLayerScope();
3986
4080
  const hide = () => {
3987
4081
  clearTimeout(timer.current);
@@ -4269,6 +4363,17 @@ function nextOptionValue(value, option, key, multiple) {
4269
4363
  if (!multiple) return id;
4270
4364
  return Array.isArray(value) && value.includes(id) ? value.filter((item) => item !== id) : [...Array.isArray(value) ? value : [], id];
4271
4365
  }
4366
+ function optionMatchRank(option, query, labelKey, searchKeys = []) {
4367
+ const needle = query.trim().toLocaleLowerCase();
4368
+ if (!needle) return 0;
4369
+ const label = optionLabel(option, labelKey).toLocaleLowerCase();
4370
+ if (label.startsWith(needle)) return 0;
4371
+ if (label.includes(needle)) return 1;
4372
+ return typeof option === "object" && searchKeys.some((key) => {
4373
+ var _a;
4374
+ return String((_a = option[key]) != null ? _a : "").toLocaleLowerCase().includes(needle);
4375
+ }) ? 2 : -1;
4376
+ }
4272
4377
  function selectOptions({
4273
4378
  options,
4274
4379
  value,
@@ -4278,10 +4383,13 @@ function selectOptions({
4278
4383
  pageSize,
4279
4384
  limit,
4280
4385
  loading,
4281
- multiple
4386
+ multiple,
4387
+ filterFn,
4388
+ searchKeys = []
4282
4389
  }) {
4283
4390
  const selected = (option) => optionSelected(value, option, valueKey);
4284
- const matching = options.map((option, index) => ({ option, key: optionKey(option, valueKey, index) })).filter(({ option }) => optionLabel(option, labelKey).toLocaleLowerCase().includes(query.trim().toLocaleLowerCase()));
4391
+ const indexed = options.map((option, index) => ({ option, key: optionKey(option, valueKey, index) }));
4392
+ 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);
4285
4393
  const visible = loading ? [] : pageSize > 0 ? matching.slice(0, limit) : matching;
4286
4394
  const selectedOption = options.find(selected);
4287
4395
  const label = multiple && Array.isArray(value) && value.length > 1 ? `${value.length}\uAC1C \uC120\uD0DD\uB428` : selectedOption !== void 0 ? optionLabel(selectedOption, labelKey) : "";
@@ -5016,7 +5124,9 @@ function KjunSelect({
5016
5124
  onChange,
5017
5125
  onOpenChange,
5018
5126
  onSearch,
5019
- onClear
5127
+ onClear,
5128
+ filterFn,
5129
+ searchKeys
5020
5130
  }) {
5021
5131
  const { open, query, limit, changeOpen, search, showMore } = useSelectState(controlled, disabled, optionPageSize, onOpenChange);
5022
5132
  const { colors } = useKjunStyles(), field = useKjunField(), ref = useRef23(null), spec = tokens28.input[size];
@@ -5041,7 +5151,9 @@ function KjunSelect({
5041
5151
  pageSize: optionPageSize,
5042
5152
  limit,
5043
5153
  loading,
5044
- multiple
5154
+ multiple,
5155
+ filterFn,
5156
+ searchKeys
5045
5157
  });
5046
5158
  const firstSelected = visible.findIndex(({ option }) => selected(option));
5047
5159
  if (firstSelected < 0) selectedRow.current = null;
@@ -6010,7 +6122,7 @@ function KjunRefreshButton({
6010
6122
  ...props
6011
6123
  }) {
6012
6124
  const reduced = useReducedMotion();
6013
- const label = ariaLabel || [targetName, "\uC0C8\uB85C\uACE0\uCE68"].filter(Boolean).join(" ");
6125
+ const label = ariaLabel || [targetName, mode === "text" ? text : "\uC0C8\uB85C\uACE0\uCE68"].filter(Boolean).join(" ");
6014
6126
  const hint = tooltip != null ? tooltip : mode === "icon" ? label : "";
6015
6127
  const button = /* @__PURE__ */ jsx36(KjunTooltip, { content: props.disabled || props.loading ? "" : hint, placement: tooltipPlacement, delay: tooltipDelay, children: /* @__PURE__ */ jsx36(
6016
6128
  KjunButton,
@@ -7191,9 +7303,18 @@ function tablePresentation(props, mobile) {
7191
7303
  }
7192
7304
  };
7193
7305
  }
7306
+ var warnedFormats = /* @__PURE__ */ new Set();
7307
+ function columnFormat(column) {
7308
+ if (typeof column.format === "function") return column.format;
7309
+ if (column.format != null && !warnedFormats.has(column.key)) {
7310
+ warnedFormats.add(column.key);
7311
+ console.warn(`KJUN Table: ${column.key} \uC5F4\uC758 format\uC740 \uD568\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4.`);
7312
+ }
7313
+ return void 0;
7314
+ }
7194
7315
  function tableCellValue(props, column, row, index) {
7195
7316
  const value = getValue(row, column.key);
7196
- 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);
7317
+ 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);
7197
7318
  return { value, body };
7198
7319
  }
7199
7320
 
@@ -7229,7 +7350,7 @@ function TableToolbar({
7229
7350
  selectable && selected.length > 0 && /* @__PURE__ */ jsxs29(
7230
7351
  View35,
7231
7352
  {
7232
- accessibilityRole: "toolbar",
7353
+ role: "group",
7233
7354
  accessibilityLabel: "\uC120\uD0DD \uC791\uC5C5",
7234
7355
  style: [toolbarStyle, { backgroundColor: colors.selectedBg }],
7235
7356
  children: [
@@ -9581,7 +9702,7 @@ function KjunQuantityStepper({ value, min = 0, max, step = 1, precision = 0, dis
9581
9702
  const { colors, fontFamily } = useKjunStyles();
9582
9703
  const field = useKjunField();
9583
9704
  const invalid = error || !!(field == null ? void 0 : field.error);
9584
- const labelledBy = !ariaLabel && (field == null ? void 0 : field.label) ? field.id + "-label" : void 0;
9705
+ const labelledBy = !ariaLabel && (field == null ? void 0 : field.label) && !field.labelHidden ? field.id + "-label" : void 0;
9585
9706
  const geometry2 = tokens57.extensions.compound;
9586
9707
  const buttonStyle = { width: tokens57.input[size].height, paddingHorizontal: 0 };
9587
9708
  const outer = tokens57.input[size].radius;
@@ -9689,7 +9810,7 @@ function KjunQuantityStepper({ value, min = 0, max, step = 1, precision = 0, dis
9689
9810
  nativeID: id || (field == null ? void 0 : field.id),
9690
9811
  accessibilityLabel: ariaLabel || (field == null ? void 0 : field.label) || "\uC218\uB7C9",
9691
9812
  accessibilityLabelledBy: labelledBy,
9692
- accessibilityHint: (field == null ? void 0 : field.error) || (field == null ? void 0 : field.hint),
9813
+ accessibilityHint: joinHints(field == null ? void 0 : field.hint, field == null ? void 0 : field.error),
9693
9814
  role: "spinbutton",
9694
9815
  accessibilityValue: { min, max, now: value },
9695
9816
  accessibilityState: { disabled: blocked },