@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.cjs CHANGED
@@ -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 {
@@ -767,6 +797,7 @@ function KjunButton(input) {
767
797
  size: declaredSize = "md",
768
798
  loading = false,
769
799
  block = false,
800
+ wrap = false,
770
801
  disabled = false,
771
802
  prefixIcon,
772
803
  suffixIcon,
@@ -787,7 +818,7 @@ function KjunButton(input) {
787
818
  const formActions = (0, import_react12.useContext)(FormActionsContext);
788
819
  const cardActions = (0, import_react12.useContext)(CardActionsContext);
789
820
  const stateContent = (0, import_react12.useContext)(DataStateContentContext);
790
- const multiline = !!formActions || cardActions || !!stateContent || (navigation == null ? void 0 : navigation.slot) === "actions";
821
+ const multiline = wrap || !!formActions || cardActions || !!stateContent || (navigation == null ? void 0 : navigation.slot) === "actions";
791
822
  const height = Math.max(import_tokens5.tokens.button.heights[size], (_a = navigation == null ? void 0 : navigation.controlSize) != null ? _a : 0);
792
823
  const iconSize = (_b = navigation == null ? void 0 : navigation.iconSize) != null ? _b : import_tokens5.tokens.button.iconSizes[size];
793
824
  const hasContent2 = children !== void 0 && children !== null && children !== false && children !== "";
@@ -948,6 +979,16 @@ function FieldClear({ size, disabled, label, onPress }) {
948
979
  );
949
980
  }
950
981
 
982
+ // shared/package-runtime/aria-ids.ts
983
+ function mergeIds(...lists) {
984
+ const ids = [...new Set(lists.flatMap((list) => (list != null ? list : "").split(/\s+/)).filter(Boolean))];
985
+ return ids.length ? ids.join(" ") : void 0;
986
+ }
987
+ function joinHints(...parts) {
988
+ const text = parts.filter(Boolean);
989
+ return text.length ? text.join(". ") : void 0;
990
+ }
991
+
951
992
  // packages/native/src/field-surface.ts
952
993
  var import_react15 = require("react");
953
994
  var import_react_native7 = require("react-native");
@@ -1116,6 +1157,7 @@ function KjunFormGroup({
1116
1157
  required = false,
1117
1158
  error = "",
1118
1159
  hint = "",
1160
+ labelHidden = false,
1119
1161
  children,
1120
1162
  style
1121
1163
  }) {
@@ -1125,9 +1167,17 @@ function KjunFormGroup({
1125
1167
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1126
1168
  FieldContext.Provider,
1127
1169
  {
1128
- value: { id: fieldId, label, error, hint, required, describedBy: error ? fieldId + "-error" : hint ? fieldId + "-hint" : void 0 },
1170
+ value: {
1171
+ id: fieldId,
1172
+ label,
1173
+ error,
1174
+ hint,
1175
+ required,
1176
+ labelHidden,
1177
+ describedBy: mergeIds(hint ? fieldId + "-hint" : void 0, error ? fieldId + "-error" : void 0)
1178
+ },
1129
1179
  children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native8.View, { style: [{ minWidth: 0, marginBottom: import_tokens9.tokens.extensions.form.itemGap }, style], children: [
1130
- label && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1180
+ label && !labelHidden && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1131
1181
  import_react_native8.Text,
1132
1182
  {
1133
1183
  nativeID: fieldId + "-label",
@@ -1144,7 +1194,15 @@ function KjunFormGroup({
1144
1194
  }
1145
1195
  ),
1146
1196
  children,
1147
- (error || hint) && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1197
+ !!hint && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1198
+ import_react_native8.Text,
1199
+ {
1200
+ nativeID: fieldId + "-hint",
1201
+ style: { fontFamily, ...typeStyle("caption"), marginTop: import_tokens9.tokens.extensions.form.messageGap, color: c.textSecondary },
1202
+ children: hint
1203
+ }
1204
+ ),
1205
+ !!error && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1148
1206
  import_react_native8.View,
1149
1207
  {
1150
1208
  style: {
@@ -1154,19 +1212,14 @@ function KjunFormGroup({
1154
1212
  marginTop: import_tokens9.tokens.extensions.form.messageGap
1155
1213
  },
1156
1214
  children: [
1157
- !!error && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(KjunIcon, { name: "alert-circle", size: import_tokens9.tokens.extensions.form.messageIconSize, color: c.danger }),
1215
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(KjunIcon, { name: "alert-circle", size: import_tokens9.tokens.extensions.form.messageIconSize, color: c.danger }),
1158
1216
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1159
1217
  import_react_native8.Text,
1160
1218
  {
1161
- nativeID: fieldId + (error ? "-error" : "-hint"),
1162
- accessibilityRole: error ? "alert" : void 0,
1163
- style: {
1164
- fontFamily,
1165
- ...typeStyle("caption"),
1166
- flexShrink: 1,
1167
- color: error ? c.danger : c.textSecondary
1168
- },
1169
- children: error || hint
1219
+ nativeID: fieldId + "-error",
1220
+ accessibilityRole: "alert",
1221
+ style: { fontFamily, ...typeStyle("caption"), flexShrink: 1, color: c.danger },
1222
+ children: error
1170
1223
  }
1171
1224
  )
1172
1225
  ]
@@ -1240,8 +1293,8 @@ var KjunInput = (0, import_react16.forwardRef)(function KjunInput2({
1240
1293
  editable: canEdit,
1241
1294
  readOnly,
1242
1295
  accessibilityLabel: ariaLabel || props.accessibilityLabel || (field == null ? void 0 : field.label),
1243
- accessibilityLabelledBy: (field == null ? void 0 : field.label) ? field.id + "-label" : void 0,
1244
- accessibilityHint: errorMessage || (field == null ? void 0 : field.error) || (field == null ? void 0 : field.hint) || props.accessibilityHint,
1296
+ accessibilityLabelledBy: (field == null ? void 0 : field.label) && !field.labelHidden ? field.id + "-label" : void 0,
1297
+ accessibilityHint: joinHints(field == null ? void 0 : field.hint, errorMessage || (field == null ? void 0 : field.error)) || props.accessibilityHint,
1245
1298
  accessibilityState: { disabled },
1246
1299
  ...import_react_native8.Platform.OS === "web" ? {
1247
1300
  // Native Web TextInput does not translate accessibilityState.
@@ -1827,6 +1880,13 @@ function KjunCard({
1827
1880
  ] });
1828
1881
  }
1829
1882
 
1883
+ // shared/package-runtime/alert-live.ts
1884
+ function alertRole(tone, live) {
1885
+ if (live === "off") return void 0;
1886
+ if (live) return live === "assertive" ? "alert" : "status";
1887
+ return tone === "danger" ? "alert" : "status";
1888
+ }
1889
+
1830
1890
  // packages/native/src/display-primitives.tsx
1831
1891
  var import_tokens12 = require("@kjun-ui/tokens");
1832
1892
  var import_react21 = require("react");
@@ -2019,6 +2079,7 @@ function KjunAlert({
2019
2079
  size = "md",
2020
2080
  title,
2021
2081
  closable = false,
2082
+ live,
2022
2083
  children,
2023
2084
  actions,
2024
2085
  onClose
@@ -2035,7 +2096,7 @@ function KjunAlert({
2035
2096
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2036
2097
  import_react_native15.View,
2037
2098
  {
2038
- role: tone === "danger" ? "alert" : "status",
2099
+ role: alertRole(tone, live),
2039
2100
  style: {
2040
2101
  flexDirection: "row",
2041
2102
  alignItems: "flex-start",
@@ -3065,7 +3126,7 @@ function KjunRadioGroup({
3065
3126
  accessibilityLabel: ariaLabel,
3066
3127
  style: {
3067
3128
  flexDirection: direction === "vertical" ? "column" : "row",
3068
- flexWrap: "wrap",
3129
+ flexWrap: direction === "vertical" ? "nowrap" : "wrap",
3069
3130
  gap: direction === "vertical" ? import_tokens18.tokens.dimension.value8 : import_tokens18.tokens.dimension.value16
3070
3131
  },
3071
3132
  children: children || options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
@@ -3456,13 +3517,13 @@ var KjunTextarea = (0, import_react32.forwardRef)(
3456
3517
  editable: !disabled && !readonly && !props.readOnly && props.editable !== false,
3457
3518
  nativeID: props.nativeID || (field == null ? void 0 : field.id),
3458
3519
  accessibilityLabel: ariaLabel || props.accessibilityLabel || (field == null ? void 0 : field.label),
3459
- accessibilityHint: (field == null ? void 0 : field.error) || (field == null ? void 0 : field.hint) || props.accessibilityHint,
3520
+ accessibilityHint: joinHints(field == null ? void 0 : field.hint, field == null ? void 0 : field.error) || props.accessibilityHint,
3460
3521
  accessibilityState: { disabled },
3461
3522
  ...import_react_native24.Platform.OS === "web" ? {
3462
3523
  "aria-disabled": disabled,
3463
3524
  "aria-invalid": invalid || void 0,
3464
3525
  "aria-describedby": field == null ? void 0 : field.describedBy,
3465
- "aria-labelledby": (field == null ? void 0 : field.label) ? field.id + "-label" : void 0,
3526
+ "aria-labelledby": (field == null ? void 0 : field.label) && !field.labelHidden ? field.id + "-label" : void 0,
3466
3527
  "aria-required": (field == null ? void 0 : field.required) || void 0
3467
3528
  } : {},
3468
3529
  placeholderTextColor: disabled ? colors.textDisabled : colors.inputPlaceholder,
@@ -3701,8 +3762,11 @@ function FloatingPanel({
3701
3762
  flip = true,
3702
3763
  minimumWidth = 0,
3703
3764
  focusOnOpen = false,
3704
- reveal
3765
+ reveal,
3766
+ header,
3767
+ nonModal = false
3705
3768
  }) {
3769
+ const [headerHeight, setHeaderHeight] = (0, import_react34.useState)(0);
3706
3770
  const panelRef = (0, import_react34.useRef)(null);
3707
3771
  const scroll = (0, import_react34.useRef)(null), viewport = (0, import_react34.useRef)(0);
3708
3772
  const { colors } = useKjunStyles(), window2 = (0, import_react_native28.useWindowDimensions)(), [anchored, setAnchored] = (0, import_react34.useState)(false), [sized, setSized] = (0, import_react34.useState)(false), [anchor, setAnchor] = (0, import_react34.useState)({ x: 0, y: 0, width: 0, height: 0 }), [panel, setPanel] = (0, import_react34.useState)({ width: 0, height: 0 });
@@ -3757,7 +3821,7 @@ function FloatingPanel({
3757
3821
  maximumWidth,
3758
3822
  Math.max(minimumPanelWidth, contentSizedMenu ? panel.width : matchTriggerWidth ? anchor.width : import_tokens23.tokens.extensions.popover.width)
3759
3823
  );
3760
- const available = Math.min(maxHeight != null ? maxHeight : field ? import_tokens23.tokens.extensions.menu.listMaxHeight : import_tokens23.tokens.extensions.floating.maxHeight, window2.height - 2 * import_tokens23.tokens.extensions.floating.viewportInset), height = Math.min(panel.height, available), roomAbove = anchor.y - gap - import_tokens23.tokens.extensions.floating.viewportInset, roomBelow = window2.height - anchor.y - anchor.height - gap - import_tokens23.tokens.extensions.floating.viewportInset, above = placement.startsWith("top") ? !(flip && height > roomAbove && roomBelow > roomAbove) : flip && height > roomBelow && roomAbove > roomBelow;
3824
+ const available = Math.min(maxHeight != null ? maxHeight : field ? import_tokens23.tokens.extensions.menu.listMaxHeight : import_tokens23.tokens.extensions.floating.maxHeight, window2.height - 2 * import_tokens23.tokens.extensions.floating.viewportInset), height = Math.min(panel.height + (header != null ? headerHeight : 0), available), roomAbove = anchor.y - gap - import_tokens23.tokens.extensions.floating.viewportInset, roomBelow = window2.height - anchor.y - anchor.height - gap - import_tokens23.tokens.extensions.floating.viewportInset, above = placement.startsWith("top") ? !(flip && height > roomAbove && roomBelow > roomAbove) : flip && height > roomBelow && roomAbove > roomBelow;
3761
3825
  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;
3762
3826
  if (placement === "left" || placement === "right") {
3763
3827
  left = placement === "left" ? anchor.x - width - gap : anchor.x + anchor.width + gap;
@@ -3841,7 +3905,7 @@ function FloatingPanel({
3841
3905
  }
3842
3906
  }
3843
3907
  ),
3844
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
3908
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
3845
3909
  import_react_native27.Animated.View,
3846
3910
  {
3847
3911
  ref: panelRef,
@@ -3852,7 +3916,7 @@ function FloatingPanel({
3852
3916
  pointerEvents: open && anchored && sized ? "auto" : "none",
3853
3917
  accessibilityElementsHidden: !open || !anchored || !sized,
3854
3918
  ...!open || !anchored || !sized ? { inert: true } : {},
3855
- accessibilityViewIsModal: true,
3919
+ accessibilityViewIsModal: !nonModal,
3856
3920
  accessibilityLabel: ariaLabel,
3857
3921
  style: {
3858
3922
  opacity: anchored && sized ? motion.opacity : 0,
@@ -3874,26 +3938,42 @@ function FloatingPanel({
3874
3938
  boxShadow: (tooltip ? import_tokens23.tokens.extensions.tooltip.elevation : popover ? import_tokens23.tokens.extensions.popover.elevation : import_tokens23.tokens.extensions.menu.elevation).map(({ colorRole, ...layer2 }) => ({ ...layer2, color: colors[colorRole] })),
3875
3939
  overflow: "hidden"
3876
3940
  },
3877
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
3878
- import_react_native28.ScrollView,
3879
- {
3880
- ref: scroll,
3881
- onLayout: (event) => {
3882
- viewport.current = event.nativeEvent.layout.height;
3883
- },
3884
- keyboardShouldPersistTaps: "handled",
3885
- style: { maxHeight: available },
3886
- contentContainerStyle: { padding: menu || field ? import_tokens23.tokens.extensions.menu.padding : 0 },
3887
- onContentSizeChange: (width2, height2) => {
3888
- if (contentSizedMenu) return;
3889
- setSized(true);
3890
- setPanel(
3891
- (previous) => previous.width === width2 && previous.height === height2 ? previous : { width: width2, height: height2 }
3892
- );
3893
- },
3894
- children
3895
- }
3896
- )
3941
+ children: [
3942
+ header != null && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
3943
+ import_react_native28.View,
3944
+ {
3945
+ onLayout: ({ nativeEvent: { layout } }) => setHeaderHeight(layout.height),
3946
+ style: {
3947
+ paddingTop: import_tokens23.tokens.extensions.popover.padding,
3948
+ paddingHorizontal: import_tokens23.tokens.extensions.popover.padding,
3949
+ paddingBottom: import_tokens23.tokens.extensions.popover.headerPaddingBottom,
3950
+ borderBottomWidth: import_tokens23.tokens.border.defaultWidth,
3951
+ borderBottomColor: colors.divider
3952
+ },
3953
+ children: header
3954
+ }
3955
+ ),
3956
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
3957
+ import_react_native28.ScrollView,
3958
+ {
3959
+ ref: scroll,
3960
+ onLayout: (event) => {
3961
+ viewport.current = event.nativeEvent.layout.height;
3962
+ },
3963
+ keyboardShouldPersistTaps: "handled",
3964
+ style: { maxHeight: available - (header != null ? headerHeight : 0), flexShrink: 1 },
3965
+ contentContainerStyle: { padding: menu || field ? import_tokens23.tokens.extensions.menu.padding : 0 },
3966
+ onContentSizeChange: (width2, height2) => {
3967
+ if (contentSizedMenu) return;
3968
+ setSized(true);
3969
+ setPanel(
3970
+ (previous) => previous.width === width2 && previous.height === height2 ? previous : { width: width2, height: height2 }
3971
+ );
3972
+ },
3973
+ children
3974
+ }
3975
+ )
3976
+ ]
3897
3977
  }
3898
3978
  )
3899
3979
  ]
@@ -3919,9 +3999,12 @@ function KjunPopover({
3919
3999
  focusOnOpen = false,
3920
4000
  ariaLabel,
3921
4001
  placement = "bottom",
4002
+ header,
4003
+ modal = true,
3922
4004
  trigger,
3923
4005
  children
3924
4006
  }) {
4007
+ const { colors } = useKjunStyles();
3925
4008
  const [internal, setInternal] = (0, import_react35.useState)(false), open = controlled != null ? controlled : internal, ref = (0, import_react35.useRef)(null);
3926
4009
  const change = (v) => {
3927
4010
  setInternal(v);
@@ -3950,6 +4033,8 @@ function KjunPopover({
3950
4033
  popover: true,
3951
4034
  focusOnOpen,
3952
4035
  ariaLabel,
4036
+ header: header != null ? content(header, { ...typeStyle("cardTitle"), color: colors.text }) : void 0,
4037
+ nonModal: !modal,
3953
4038
  children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_react_native29.View, { style: { padding: noPadding ? 0 : import_tokens24.tokens.extensions.popover.padding, gap: noPadding ? 0 : import_tokens24.tokens.extensions.popover.gap }, children: content(
3954
4039
  typeof children === "function" ? children(() => change(false)) : children,
3955
4040
  import_react_native29.Platform.OS === "web" ? { wordBreak: "keep-all", overflowWrap: "anywhere" } : void 0
@@ -3964,9 +4049,18 @@ function KjunTooltip({
3964
4049
  delay = 0,
3965
4050
  children,
3966
4051
  onPress,
3967
- disabled
4052
+ disabled,
4053
+ open: controlled,
4054
+ onOpenChange
3968
4055
  }) {
3969
- const { colors } = useKjunStyles(), [open, setOpen] = (0, import_react35.useState)(false), ref = (0, import_react35.useRef)(null), timer = (0, import_react35.useRef)(void 0);
4056
+ const { colors } = useKjunStyles(), [internal, setInternal] = (0, import_react35.useState)(false), open = controlled != null ? controlled : internal, latest = (0, import_react35.useRef)({ open, onOpenChange }), setOpen = (value) => {
4057
+ var _a, _b;
4058
+ if (latest.current.open === value) return;
4059
+ latest.current.open = value;
4060
+ setInternal(value);
4061
+ (_b = (_a = latest.current).onOpenChange) == null ? void 0 : _b.call(_a, value);
4062
+ }, ref = (0, import_react35.useRef)(null), timer = (0, import_react35.useRef)(void 0);
4063
+ latest.current = { open, onOpenChange };
3970
4064
  const scopeActive = useActiveLayerScope();
3971
4065
  const hide = () => {
3972
4066
  clearTimeout(timer.current);
@@ -4254,6 +4348,17 @@ function nextOptionValue(value, option, key, multiple) {
4254
4348
  if (!multiple) return id;
4255
4349
  return Array.isArray(value) && value.includes(id) ? value.filter((item) => item !== id) : [...Array.isArray(value) ? value : [], id];
4256
4350
  }
4351
+ function optionMatchRank(option, query, labelKey, searchKeys = []) {
4352
+ const needle = query.trim().toLocaleLowerCase();
4353
+ if (!needle) return 0;
4354
+ const label = optionLabel(option, labelKey).toLocaleLowerCase();
4355
+ if (label.startsWith(needle)) return 0;
4356
+ if (label.includes(needle)) return 1;
4357
+ return typeof option === "object" && searchKeys.some((key) => {
4358
+ var _a;
4359
+ return String((_a = option[key]) != null ? _a : "").toLocaleLowerCase().includes(needle);
4360
+ }) ? 2 : -1;
4361
+ }
4257
4362
  function selectOptions({
4258
4363
  options,
4259
4364
  value,
@@ -4263,10 +4368,13 @@ function selectOptions({
4263
4368
  pageSize,
4264
4369
  limit,
4265
4370
  loading,
4266
- multiple
4371
+ multiple,
4372
+ filterFn,
4373
+ searchKeys = []
4267
4374
  }) {
4268
4375
  const selected = (option) => optionSelected(value, option, valueKey);
4269
- const matching = options.map((option, index) => ({ option, key: optionKey(option, valueKey, index) })).filter(({ option }) => optionLabel(option, labelKey).toLocaleLowerCase().includes(query.trim().toLocaleLowerCase()));
4376
+ const indexed = options.map((option, index) => ({ option, key: optionKey(option, valueKey, index) }));
4377
+ 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);
4270
4378
  const visible = loading ? [] : pageSize > 0 ? matching.slice(0, limit) : matching;
4271
4379
  const selectedOption = options.find(selected);
4272
4380
  const label = multiple && Array.isArray(value) && value.length > 1 ? `${value.length}\uAC1C \uC120\uD0DD\uB428` : selectedOption !== void 0 ? optionLabel(selectedOption, labelKey) : "";
@@ -5001,7 +5109,9 @@ function KjunSelect({
5001
5109
  onChange,
5002
5110
  onOpenChange,
5003
5111
  onSearch,
5004
- onClear
5112
+ onClear,
5113
+ filterFn,
5114
+ searchKeys
5005
5115
  }) {
5006
5116
  const { open, query, limit, changeOpen, search, showMore } = useSelectState(controlled, disabled, optionPageSize, onOpenChange);
5007
5117
  const { colors } = useKjunStyles(), field = useKjunField(), ref = (0, import_react44.useRef)(null), spec = import_tokens30.tokens.input[size];
@@ -5026,7 +5136,9 @@ function KjunSelect({
5026
5136
  pageSize: optionPageSize,
5027
5137
  limit,
5028
5138
  loading,
5029
- multiple
5139
+ multiple,
5140
+ filterFn,
5141
+ searchKeys
5030
5142
  });
5031
5143
  const firstSelected = visible.findIndex(({ option }) => selected(option));
5032
5144
  if (firstSelected < 0) selectedRow.current = null;
@@ -5988,7 +6100,7 @@ function KjunRefreshButton({
5988
6100
  ...props
5989
6101
  }) {
5990
6102
  const reduced = useReducedMotion();
5991
- const label = ariaLabel || [targetName, "\uC0C8\uB85C\uACE0\uCE68"].filter(Boolean).join(" ");
6103
+ const label = ariaLabel || [targetName, mode === "text" ? text : "\uC0C8\uB85C\uACE0\uCE68"].filter(Boolean).join(" ");
5992
6104
  const hint = tooltip != null ? tooltip : mode === "icon" ? label : "";
5993
6105
  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)(
5994
6106
  KjunButton,
@@ -7152,9 +7264,18 @@ function tablePresentation(props, mobile) {
7152
7264
  }
7153
7265
  };
7154
7266
  }
7267
+ var warnedFormats = /* @__PURE__ */ new Set();
7268
+ function columnFormat(column) {
7269
+ if (typeof column.format === "function") return column.format;
7270
+ if (column.format != null && !warnedFormats.has(column.key)) {
7271
+ warnedFormats.add(column.key);
7272
+ console.warn(`KJUN Table: ${column.key} \uC5F4\uC758 format\uC740 \uD568\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4.`);
7273
+ }
7274
+ return void 0;
7275
+ }
7155
7276
  function tableCellValue(props, column, row, index) {
7156
7277
  const value = getValue(row, column.key);
7157
- 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);
7278
+ 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);
7158
7279
  return { value, body };
7159
7280
  }
7160
7281
 
@@ -7190,7 +7311,7 @@ function TableToolbar({
7190
7311
  selectable && selected.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
7191
7312
  import_react_native45.View,
7192
7313
  {
7193
- accessibilityRole: "toolbar",
7314
+ role: "group",
7194
7315
  accessibilityLabel: "\uC120\uD0DD \uC791\uC5C5",
7195
7316
  style: [toolbarStyle, { backgroundColor: colors.selectedBg }],
7196
7317
  children: [
@@ -9530,7 +9651,7 @@ function KjunQuantityStepper({ value, min = 0, max, step = 1, precision = 0, dis
9530
9651
  const { colors, fontFamily } = useKjunStyles();
9531
9652
  const field = useKjunField();
9532
9653
  const invalid = error || !!(field == null ? void 0 : field.error);
9533
- const labelledBy = !ariaLabel && (field == null ? void 0 : field.label) ? field.id + "-label" : void 0;
9654
+ const labelledBy = !ariaLabel && (field == null ? void 0 : field.label) && !field.labelHidden ? field.id + "-label" : void 0;
9534
9655
  const geometry2 = import_tokens60.tokens.extensions.compound;
9535
9656
  const buttonStyle = { width: import_tokens60.tokens.input[size].height, paddingHorizontal: 0 };
9536
9657
  const outer = import_tokens60.tokens.input[size].radius;
@@ -9638,7 +9759,7 @@ function KjunQuantityStepper({ value, min = 0, max, step = 1, precision = 0, dis
9638
9759
  nativeID: id || (field == null ? void 0 : field.id),
9639
9760
  accessibilityLabel: ariaLabel || (field == null ? void 0 : field.label) || "\uC218\uB7C9",
9640
9761
  accessibilityLabelledBy: labelledBy,
9641
- accessibilityHint: (field == null ? void 0 : field.error) || (field == null ? void 0 : field.hint),
9762
+ accessibilityHint: joinHints(field == null ? void 0 : field.hint, field == null ? void 0 : field.error),
9642
9763
  role: "spinbutton",
9643
9764
  accessibilityValue: { min, max, now: value },
9644
9765
  accessibilityState: { disabled: blocked },