@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.web.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 {
@@ -757,6 +787,7 @@ function KjunButton(input) {
757
787
  size: declaredSize = "md",
758
788
  loading = false,
759
789
  block = false,
790
+ wrap = false,
760
791
  disabled = false,
761
792
  prefixIcon,
762
793
  suffixIcon,
@@ -777,7 +808,7 @@ function KjunButton(input) {
777
808
  const formActions = useContext5(FormActionsContext);
778
809
  const cardActions = useContext5(CardActionsContext);
779
810
  const stateContent = useContext5(DataStateContentContext);
780
- const multiline = !!formActions || cardActions || !!stateContent || (navigation == null ? void 0 : navigation.slot) === "actions";
811
+ const multiline = wrap || !!formActions || cardActions || !!stateContent || (navigation == null ? void 0 : navigation.slot) === "actions";
781
812
  const height = Math.max(tokens3.button.heights[size], (_a = navigation == null ? void 0 : navigation.controlSize) != null ? _a : 0);
782
813
  const iconSize = (_b = navigation == null ? void 0 : navigation.iconSize) != null ? _b : tokens3.button.iconSizes[size];
783
814
  const hasContent2 = children !== void 0 && children !== null && children !== false && children !== "";
@@ -938,6 +969,16 @@ function FieldClear({ size, disabled, label, onPress }) {
938
969
  );
939
970
  }
940
971
 
972
+ // shared/package-runtime/aria-ids.ts
973
+ function mergeIds(...lists) {
974
+ const ids = [...new Set(lists.flatMap((list) => (list != null ? list : "").split(/\s+/)).filter(Boolean))];
975
+ return ids.length ? ids.join(" ") : void 0;
976
+ }
977
+ function joinHints(...parts) {
978
+ const text = parts.filter(Boolean);
979
+ return text.length ? text.join(". ") : void 0;
980
+ }
981
+
941
982
  // packages/native/src/field-surface.ts
942
983
  import { useState as useState6 } from "react";
943
984
  import { Animated as Animated3, Platform as Platform5, TextInput } from "react-native";
@@ -1125,6 +1166,7 @@ function KjunFormGroup({
1125
1166
  required = false,
1126
1167
  error = "",
1127
1168
  hint = "",
1169
+ labelHidden = false,
1128
1170
  children,
1129
1171
  style
1130
1172
  }) {
@@ -1134,9 +1176,17 @@ function KjunFormGroup({
1134
1176
  return /* @__PURE__ */ jsx8(
1135
1177
  FieldContext.Provider,
1136
1178
  {
1137
- value: { id: fieldId, label, error, hint, required, describedBy: error ? fieldId + "-error" : hint ? fieldId + "-hint" : void 0 },
1179
+ value: {
1180
+ id: fieldId,
1181
+ label,
1182
+ error,
1183
+ hint,
1184
+ required,
1185
+ labelHidden,
1186
+ describedBy: mergeIds(hint ? fieldId + "-hint" : void 0, error ? fieldId + "-error" : void 0)
1187
+ },
1138
1188
  children: /* @__PURE__ */ jsxs2(View4, { style: [{ minWidth: 0, marginBottom: tokens7.extensions.form.itemGap }, style], children: [
1139
- label && /* @__PURE__ */ jsxs2(
1189
+ label && !labelHidden && /* @__PURE__ */ jsxs2(
1140
1190
  Text3,
1141
1191
  {
1142
1192
  nativeID: fieldId + "-label",
@@ -1153,7 +1203,15 @@ function KjunFormGroup({
1153
1203
  }
1154
1204
  ),
1155
1205
  children,
1156
- (error || hint) && /* @__PURE__ */ jsxs2(
1206
+ !!hint && /* @__PURE__ */ jsx8(
1207
+ Text3,
1208
+ {
1209
+ nativeID: fieldId + "-hint",
1210
+ style: { fontFamily, ...typeStyle("caption"), marginTop: tokens7.extensions.form.messageGap, color: c.textSecondary },
1211
+ children: hint
1212
+ }
1213
+ ),
1214
+ !!error && /* @__PURE__ */ jsxs2(
1157
1215
  View4,
1158
1216
  {
1159
1217
  style: {
@@ -1163,19 +1221,14 @@ function KjunFormGroup({
1163
1221
  marginTop: tokens7.extensions.form.messageGap
1164
1222
  },
1165
1223
  children: [
1166
- !!error && /* @__PURE__ */ jsx8(KjunIcon, { name: "alert-circle", size: tokens7.extensions.form.messageIconSize, color: c.danger }),
1224
+ /* @__PURE__ */ jsx8(KjunIcon, { name: "alert-circle", size: tokens7.extensions.form.messageIconSize, color: c.danger }),
1167
1225
  /* @__PURE__ */ jsx8(
1168
1226
  Text3,
1169
1227
  {
1170
- nativeID: fieldId + (error ? "-error" : "-hint"),
1171
- accessibilityRole: error ? "alert" : void 0,
1172
- style: {
1173
- fontFamily,
1174
- ...typeStyle("caption"),
1175
- flexShrink: 1,
1176
- color: error ? c.danger : c.textSecondary
1177
- },
1178
- children: error || hint
1228
+ nativeID: fieldId + "-error",
1229
+ accessibilityRole: "alert",
1230
+ style: { fontFamily, ...typeStyle("caption"), flexShrink: 1, color: c.danger },
1231
+ children: error
1179
1232
  }
1180
1233
  )
1181
1234
  ]
@@ -1249,8 +1302,8 @@ var KjunInput = forwardRef2(function KjunInput2({
1249
1302
  editable: canEdit,
1250
1303
  readOnly,
1251
1304
  accessibilityLabel: ariaLabel || props.accessibilityLabel || (field == null ? void 0 : field.label),
1252
- accessibilityLabelledBy: (field == null ? void 0 : field.label) ? field.id + "-label" : void 0,
1253
- accessibilityHint: errorMessage || (field == null ? void 0 : field.error) || (field == null ? void 0 : field.hint) || props.accessibilityHint,
1305
+ accessibilityLabelledBy: (field == null ? void 0 : field.label) && !field.labelHidden ? field.id + "-label" : void 0,
1306
+ accessibilityHint: joinHints(field == null ? void 0 : field.hint, errorMessage || (field == null ? void 0 : field.error)) || props.accessibilityHint,
1254
1307
  accessibilityState: { disabled },
1255
1308
  ...Platform6.OS === "web" ? {
1256
1309
  // Native Web TextInput does not translate accessibilityState.
@@ -1863,6 +1916,13 @@ function KjunCard({
1863
1916
  ] });
1864
1917
  }
1865
1918
 
1919
+ // shared/package-runtime/alert-live.ts
1920
+ function alertRole(tone, live) {
1921
+ if (live === "off") return void 0;
1922
+ if (live) return live === "assertive" ? "alert" : "status";
1923
+ return tone === "danger" ? "alert" : "status";
1924
+ }
1925
+
1866
1926
  // packages/native/src/display-primitives.tsx
1867
1927
  import { tokens as tokens10 } from "@kjun-ui/tokens";
1868
1928
  import { useContext as useContext8, useEffect as useEffect5, useRef as useRef6, useState as useState10 } from "react";
@@ -2060,6 +2120,7 @@ function KjunAlert({
2060
2120
  size = "md",
2061
2121
  title,
2062
2122
  closable = false,
2123
+ live,
2063
2124
  children,
2064
2125
  actions,
2065
2126
  onClose
@@ -2076,7 +2137,7 @@ function KjunAlert({
2076
2137
  return /* @__PURE__ */ jsxs7(
2077
2138
  View10,
2078
2139
  {
2079
- role: tone === "danger" ? "alert" : "status",
2140
+ role: alertRole(tone, live),
2080
2141
  style: {
2081
2142
  flexDirection: "row",
2082
2143
  alignItems: "flex-start",
@@ -3193,7 +3254,7 @@ function KjunRadioGroup({
3193
3254
  accessibilityLabel: ariaLabel,
3194
3255
  style: {
3195
3256
  flexDirection: direction === "vertical" ? "column" : "row",
3196
- flexWrap: "wrap",
3257
+ flexWrap: direction === "vertical" ? "nowrap" : "wrap",
3197
3258
  gap: direction === "vertical" ? tokens17.dimension.value8 : tokens17.dimension.value16
3198
3259
  },
3199
3260
  children: children || options.map((option) => /* @__PURE__ */ jsx19(
@@ -3593,13 +3654,13 @@ var KjunTextarea = forwardRef3(
3593
3654
  editable: !disabled && !readonly && !props.readOnly && props.editable !== false,
3594
3655
  nativeID: props.nativeID || (field == null ? void 0 : field.id),
3595
3656
  accessibilityLabel: ariaLabel || props.accessibilityLabel || (field == null ? void 0 : field.label),
3596
- accessibilityHint: (field == null ? void 0 : field.error) || (field == null ? void 0 : field.hint) || props.accessibilityHint,
3657
+ accessibilityHint: joinHints(field == null ? void 0 : field.hint, field == null ? void 0 : field.error) || props.accessibilityHint,
3597
3658
  accessibilityState: { disabled },
3598
3659
  ...Platform13.OS === "web" ? {
3599
3660
  "aria-disabled": disabled,
3600
3661
  "aria-invalid": invalid || void 0,
3601
3662
  "aria-describedby": field == null ? void 0 : field.describedBy,
3602
- "aria-labelledby": (field == null ? void 0 : field.label) ? field.id + "-label" : void 0,
3663
+ "aria-labelledby": (field == null ? void 0 : field.label) && !field.labelHidden ? field.id + "-label" : void 0,
3603
3664
  "aria-required": (field == null ? void 0 : field.required) || void 0
3604
3665
  } : {},
3605
3666
  placeholderTextColor: disabled ? colors.textDisabled : colors.inputPlaceholder,
@@ -3869,8 +3930,11 @@ function FloatingPanel({
3869
3930
  flip = true,
3870
3931
  minimumWidth = 0,
3871
3932
  focusOnOpen = false,
3872
- reveal
3933
+ reveal,
3934
+ header,
3935
+ nonModal = false
3873
3936
  }) {
3937
+ const [headerHeight, setHeaderHeight] = useState17(0);
3874
3938
  const panelRef = useRef17(null);
3875
3939
  const scroll = useRef17(null), viewport = useRef17(0);
3876
3940
  const { colors } = useKjunStyles(), window2 = useWindowDimensions4(), [anchored, setAnchored] = useState17(false), [sized, setSized] = useState17(false), [anchor, setAnchor] = useState17({ x: 0, y: 0, width: 0, height: 0 }), [panel, setPanel] = useState17({ width: 0, height: 0 });
@@ -3925,7 +3989,7 @@ function FloatingPanel({
3925
3989
  maximumWidth,
3926
3990
  Math.max(minimumPanelWidth, contentSizedMenu ? panel.width : matchTriggerWidth ? anchor.width : tokens22.extensions.popover.width)
3927
3991
  );
3928
- const available = Math.min(maxHeight != null ? maxHeight : field ? tokens22.extensions.menu.listMaxHeight : tokens22.extensions.floating.maxHeight, window2.height - 2 * tokens22.extensions.floating.viewportInset), height = Math.min(panel.height, available), roomAbove = anchor.y - gap - tokens22.extensions.floating.viewportInset, roomBelow = window2.height - anchor.y - anchor.height - gap - tokens22.extensions.floating.viewportInset, above = placement.startsWith("top") ? !(flip && height > roomAbove && roomBelow > roomAbove) : flip && height > roomBelow && roomAbove > roomBelow;
3992
+ const available = Math.min(maxHeight != null ? maxHeight : field ? tokens22.extensions.menu.listMaxHeight : tokens22.extensions.floating.maxHeight, window2.height - 2 * tokens22.extensions.floating.viewportInset), height = Math.min(panel.height + (header != null ? headerHeight : 0), available), roomAbove = anchor.y - gap - tokens22.extensions.floating.viewportInset, roomBelow = window2.height - anchor.y - anchor.height - gap - tokens22.extensions.floating.viewportInset, above = placement.startsWith("top") ? !(flip && height > roomAbove && roomBelow > roomAbove) : flip && height > roomBelow && roomAbove > roomBelow;
3929
3993
  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;
3930
3994
  if (placement === "left" || placement === "right") {
3931
3995
  left = placement === "left" ? anchor.x - width - gap : anchor.x + anchor.width + gap;
@@ -4009,7 +4073,7 @@ function FloatingPanel({
4009
4073
  }
4010
4074
  }
4011
4075
  ),
4012
- /* @__PURE__ */ jsx24(
4076
+ /* @__PURE__ */ jsxs15(
4013
4077
  Animated13.View,
4014
4078
  {
4015
4079
  ref: panelRef,
@@ -4020,7 +4084,7 @@ function FloatingPanel({
4020
4084
  pointerEvents: open && anchored && sized ? "auto" : "none",
4021
4085
  accessibilityElementsHidden: !open || !anchored || !sized,
4022
4086
  ...!open || !anchored || !sized ? { inert: true } : {},
4023
- accessibilityViewIsModal: true,
4087
+ accessibilityViewIsModal: !nonModal,
4024
4088
  accessibilityLabel: ariaLabel,
4025
4089
  style: {
4026
4090
  opacity: anchored && sized ? motion.opacity : 0,
@@ -4042,26 +4106,42 @@ function FloatingPanel({
4042
4106
  boxShadow: (tooltip ? tokens22.extensions.tooltip.elevation : popover ? tokens22.extensions.popover.elevation : tokens22.extensions.menu.elevation).map(({ colorRole, ...layer2 }) => ({ ...layer2, color: colors[colorRole] })),
4043
4107
  overflow: "hidden"
4044
4108
  },
4045
- children: /* @__PURE__ */ jsx24(
4046
- ScrollView4,
4047
- {
4048
- ref: scroll,
4049
- onLayout: (event) => {
4050
- viewport.current = event.nativeEvent.layout.height;
4051
- },
4052
- keyboardShouldPersistTaps: "handled",
4053
- style: { maxHeight: available },
4054
- contentContainerStyle: { padding: menu || field ? tokens22.extensions.menu.padding : 0 },
4055
- onContentSizeChange: (width2, height2) => {
4056
- if (contentSizedMenu) return;
4057
- setSized(true);
4058
- setPanel(
4059
- (previous) => previous.width === width2 && previous.height === height2 ? previous : { width: width2, height: height2 }
4060
- );
4061
- },
4062
- children
4063
- }
4064
- )
4109
+ children: [
4110
+ header != null && /* @__PURE__ */ jsx24(
4111
+ View19,
4112
+ {
4113
+ onLayout: ({ nativeEvent: { layout } }) => setHeaderHeight(layout.height),
4114
+ style: {
4115
+ paddingTop: tokens22.extensions.popover.padding,
4116
+ paddingHorizontal: tokens22.extensions.popover.padding,
4117
+ paddingBottom: tokens22.extensions.popover.headerPaddingBottom,
4118
+ borderBottomWidth: tokens22.border.defaultWidth,
4119
+ borderBottomColor: colors.divider
4120
+ },
4121
+ children: header
4122
+ }
4123
+ ),
4124
+ /* @__PURE__ */ jsx24(
4125
+ ScrollView4,
4126
+ {
4127
+ ref: scroll,
4128
+ onLayout: (event) => {
4129
+ viewport.current = event.nativeEvent.layout.height;
4130
+ },
4131
+ keyboardShouldPersistTaps: "handled",
4132
+ style: { maxHeight: available - (header != null ? headerHeight : 0), flexShrink: 1 },
4133
+ contentContainerStyle: { padding: menu || field ? tokens22.extensions.menu.padding : 0 },
4134
+ onContentSizeChange: (width2, height2) => {
4135
+ if (contentSizedMenu) return;
4136
+ setSized(true);
4137
+ setPanel(
4138
+ (previous) => previous.width === width2 && previous.height === height2 ? previous : { width: width2, height: height2 }
4139
+ );
4140
+ },
4141
+ children
4142
+ }
4143
+ )
4144
+ ]
4065
4145
  }
4066
4146
  )
4067
4147
  ]
@@ -4096,9 +4176,12 @@ function KjunPopover({
4096
4176
  focusOnOpen = false,
4097
4177
  ariaLabel,
4098
4178
  placement = "bottom",
4179
+ header,
4180
+ modal = true,
4099
4181
  trigger,
4100
4182
  children
4101
4183
  }) {
4184
+ const { colors } = useKjunStyles();
4102
4185
  const [internal, setInternal] = useState18(false), open = controlled != null ? controlled : internal, ref = useRef18(null);
4103
4186
  const change = (v) => {
4104
4187
  setInternal(v);
@@ -4127,6 +4210,8 @@ function KjunPopover({
4127
4210
  popover: true,
4128
4211
  focusOnOpen,
4129
4212
  ariaLabel,
4213
+ header: header != null ? content(header, { ...typeStyle("cardTitle"), color: colors.text }) : void 0,
4214
+ nonModal: !modal,
4130
4215
  children: /* @__PURE__ */ jsx25(View20, { style: { padding: noPadding ? 0 : tokens23.extensions.popover.padding, gap: noPadding ? 0 : tokens23.extensions.popover.gap }, children: content(
4131
4216
  typeof children === "function" ? children(() => change(false)) : children,
4132
4217
  Platform16.OS === "web" ? { wordBreak: "keep-all", overflowWrap: "anywhere" } : void 0
@@ -4141,9 +4226,18 @@ function KjunTooltip({
4141
4226
  delay = 0,
4142
4227
  children,
4143
4228
  onPress,
4144
- disabled
4229
+ disabled,
4230
+ open: controlled,
4231
+ onOpenChange
4145
4232
  }) {
4146
- const { colors } = useKjunStyles(), [open, setOpen] = useState18(false), ref = useRef18(null), timer = useRef18(void 0);
4233
+ const { colors } = useKjunStyles(), [internal, setInternal] = useState18(false), open = controlled != null ? controlled : internal, latest = useRef18({ open, onOpenChange }), setOpen = (value) => {
4234
+ var _a, _b;
4235
+ if (latest.current.open === value) return;
4236
+ latest.current.open = value;
4237
+ setInternal(value);
4238
+ (_b = (_a = latest.current).onOpenChange) == null ? void 0 : _b.call(_a, value);
4239
+ }, ref = useRef18(null), timer = useRef18(void 0);
4240
+ latest.current = { open, onOpenChange };
4147
4241
  const scopeActive = useActiveLayerScope();
4148
4242
  const hide = () => {
4149
4243
  clearTimeout(timer.current);
@@ -4431,6 +4525,17 @@ function nextOptionValue(value, option, key, multiple) {
4431
4525
  if (!multiple) return id;
4432
4526
  return Array.isArray(value) && value.includes(id) ? value.filter((item) => item !== id) : [...Array.isArray(value) ? value : [], id];
4433
4527
  }
4528
+ function optionMatchRank(option, query, labelKey, searchKeys = []) {
4529
+ const needle = query.trim().toLocaleLowerCase();
4530
+ if (!needle) return 0;
4531
+ const label = optionLabel(option, labelKey).toLocaleLowerCase();
4532
+ if (label.startsWith(needle)) return 0;
4533
+ if (label.includes(needle)) return 1;
4534
+ return typeof option === "object" && searchKeys.some((key) => {
4535
+ var _a;
4536
+ return String((_a = option[key]) != null ? _a : "").toLocaleLowerCase().includes(needle);
4537
+ }) ? 2 : -1;
4538
+ }
4434
4539
  function selectOptions({
4435
4540
  options,
4436
4541
  value,
@@ -4440,10 +4545,13 @@ function selectOptions({
4440
4545
  pageSize,
4441
4546
  limit,
4442
4547
  loading,
4443
- multiple
4548
+ multiple,
4549
+ filterFn,
4550
+ searchKeys = []
4444
4551
  }) {
4445
4552
  const selected = (option) => optionSelected(value, option, valueKey);
4446
- const matching = options.map((option, index) => ({ option, key: optionKey(option, valueKey, index) })).filter(({ option }) => optionLabel(option, labelKey).toLocaleLowerCase().includes(query.trim().toLocaleLowerCase()));
4553
+ const indexed = options.map((option, index) => ({ option, key: optionKey(option, valueKey, index) }));
4554
+ 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);
4447
4555
  const visible = loading ? [] : pageSize > 0 ? matching.slice(0, limit) : matching;
4448
4556
  const selectedOption = options.find(selected);
4449
4557
  const label = multiple && Array.isArray(value) && value.length > 1 ? `${value.length}\uAC1C \uC120\uD0DD\uB428` : selectedOption !== void 0 ? optionLabel(selectedOption, labelKey) : "";
@@ -5178,7 +5286,9 @@ function KjunSelect({
5178
5286
  onChange,
5179
5287
  onOpenChange,
5180
5288
  onSearch,
5181
- onClear
5289
+ onClear,
5290
+ filterFn,
5291
+ searchKeys
5182
5292
  }) {
5183
5293
  const { open, query, limit, changeOpen, search, showMore } = useSelectState(controlled, disabled, optionPageSize, onOpenChange);
5184
5294
  const { colors } = useKjunStyles(), field = useKjunField(), ref = useRef24(null), spec = tokens29.input[size];
@@ -5203,7 +5313,9 @@ function KjunSelect({
5203
5313
  pageSize: optionPageSize,
5204
5314
  limit,
5205
5315
  loading,
5206
- multiple
5316
+ multiple,
5317
+ filterFn,
5318
+ searchKeys
5207
5319
  });
5208
5320
  const firstSelected = visible.findIndex(({ option }) => selected(option));
5209
5321
  if (firstSelected < 0) selectedRow.current = null;
@@ -6265,7 +6377,7 @@ function KjunRefreshButton({
6265
6377
  ...props
6266
6378
  }) {
6267
6379
  const reduced = useReducedMotion();
6268
- const label = ariaLabel || [targetName, "\uC0C8\uB85C\uACE0\uCE68"].filter(Boolean).join(" ");
6380
+ const label = ariaLabel || [targetName, mode === "text" ? text : "\uC0C8\uB85C\uACE0\uCE68"].filter(Boolean).join(" ");
6269
6381
  const hint = tooltip != null ? tooltip : mode === "icon" ? label : "";
6270
6382
  const button = /* @__PURE__ */ jsx36(KjunTooltip, { content: props.disabled || props.loading ? "" : hint, placement: tooltipPlacement, delay: tooltipDelay, children: /* @__PURE__ */ jsx36(
6271
6383
  KjunButton,
@@ -7446,9 +7558,18 @@ function tablePresentation(props, mobile) {
7446
7558
  }
7447
7559
  };
7448
7560
  }
7561
+ var warnedFormats = /* @__PURE__ */ new Set();
7562
+ function columnFormat(column) {
7563
+ if (typeof column.format === "function") return column.format;
7564
+ if (column.format != null && !warnedFormats.has(column.key)) {
7565
+ warnedFormats.add(column.key);
7566
+ console.warn(`KJUN Table: ${column.key} \uC5F4\uC758 format\uC740 \uD568\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4.`);
7567
+ }
7568
+ return void 0;
7569
+ }
7449
7570
  function tableCellValue(props, column, row, index) {
7450
7571
  const value = getValue(row, column.key);
7451
- 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);
7572
+ 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);
7452
7573
  return { value, body };
7453
7574
  }
7454
7575
 
@@ -7484,7 +7605,7 @@ function TableToolbar({
7484
7605
  selectable && selected.length > 0 && /* @__PURE__ */ jsxs30(
7485
7606
  View36,
7486
7607
  {
7487
- accessibilityRole: "toolbar",
7608
+ role: "group",
7488
7609
  accessibilityLabel: "\uC120\uD0DD \uC791\uC5C5",
7489
7610
  style: [toolbarStyle, { backgroundColor: colors.selectedBg }],
7490
7611
  children: [
@@ -9836,7 +9957,7 @@ function KjunQuantityStepper({ value, min = 0, max, step = 1, precision = 0, dis
9836
9957
  const { colors, fontFamily } = useKjunStyles();
9837
9958
  const field = useKjunField();
9838
9959
  const invalid = error || !!(field == null ? void 0 : field.error);
9839
- const labelledBy = !ariaLabel && (field == null ? void 0 : field.label) ? field.id + "-label" : void 0;
9960
+ const labelledBy = !ariaLabel && (field == null ? void 0 : field.label) && !field.labelHidden ? field.id + "-label" : void 0;
9840
9961
  const geometry2 = tokens58.extensions.compound;
9841
9962
  const buttonStyle = { width: tokens58.input[size].height, paddingHorizontal: 0 };
9842
9963
  const outer = tokens58.input[size].radius;
@@ -9944,7 +10065,7 @@ function KjunQuantityStepper({ value, min = 0, max, step = 1, precision = 0, dis
9944
10065
  nativeID: id || (field == null ? void 0 : field.id),
9945
10066
  accessibilityLabel: ariaLabel || (field == null ? void 0 : field.label) || "\uC218\uB7C9",
9946
10067
  accessibilityLabelledBy: labelledBy,
9947
- accessibilityHint: (field == null ? void 0 : field.error) || (field == null ? void 0 : field.hint),
10068
+ accessibilityHint: joinHints(field == null ? void 0 : field.hint, field == null ? void 0 : field.error),
9948
10069
  role: "spinbutton",
9949
10070
  accessibilityValue: { min, max, now: value },
9950
10071
  accessibilityState: { disabled: blocked },