@bug-on/md3-react 2.0.2 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import * as React31 from 'react';
3
- import { createContext, forwardRef, useMemo, useState, useEffect, useRef, useCallback, useContext, useId, cloneElement, useLayoutEffect, createElement } from 'react';
3
+ import { createContext, forwardRef, useMemo, useRef, useEffect, useState, useCallback, useContext, useId, cloneElement, useLayoutEffect, createElement } from 'react';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
  import { argbFromHex, themeFromSourceColor, hexFromArgb } from '@material/material-color-utilities';
6
6
  import { clsx } from 'clsx';
@@ -212,7 +212,7 @@ function generateM3Theme(sourceColorHex, mode = "light") {
212
212
  function applyTheme(sourceColorHex, mode = "light", root = document.documentElement) {
213
213
  const colors = generateM3Theme(sourceColorHex, mode);
214
214
  for (const [key, value] of Object.entries(colors)) {
215
- const kebabKey = key.replace(/[A-Z]/g, (m38) => `-${m38.toLowerCase()}`);
215
+ const kebabKey = key.replace(/[A-Z]/g, (m43) => `-${m43.toLowerCase()}`);
216
216
  root.style.setProperty(`--md-sys-color-${kebabKey}`, value);
217
217
  root.style.setProperty(`--color-m3-${kebabKey}`, value);
218
218
  }
@@ -6696,6 +6696,825 @@ var RadioGroupComponent = React31.forwardRef(
6696
6696
  );
6697
6697
  RadioGroupComponent.displayName = "RadioGroup";
6698
6698
  var RadioGroup2 = React31.memo(RadioGroupComponent);
6699
+ function useSearchKeyboard({
6700
+ active,
6701
+ onActiveChange,
6702
+ onSearch,
6703
+ query,
6704
+ itemCount,
6705
+ onSelectSuggestion
6706
+ }) {
6707
+ const [activeIndex, setActiveIndex] = React31.useState(-1);
6708
+ const resetKeyRef = React31.useRef(`${active}:${query}`);
6709
+ const currentKey = `${active}:${query}`;
6710
+ if (resetKeyRef.current !== currentKey) {
6711
+ resetKeyRef.current = currentKey;
6712
+ setActiveIndex(-1);
6713
+ }
6714
+ const handleKeyDown = React31.useCallback(
6715
+ (e) => {
6716
+ if (!active) return;
6717
+ switch (e.key) {
6718
+ case "ArrowDown": {
6719
+ e.preventDefault();
6720
+ setActiveIndex((i) => i < itemCount - 1 ? i + 1 : i);
6721
+ break;
6722
+ }
6723
+ case "ArrowUp": {
6724
+ e.preventDefault();
6725
+ setActiveIndex((i) => i > -1 ? i - 1 : -1);
6726
+ break;
6727
+ }
6728
+ case "Enter": {
6729
+ e.preventDefault();
6730
+ if (activeIndex >= 0 && onSelectSuggestion) {
6731
+ onSelectSuggestion(activeIndex);
6732
+ } else {
6733
+ onSearch(query);
6734
+ }
6735
+ break;
6736
+ }
6737
+ case "Escape": {
6738
+ e.preventDefault();
6739
+ onActiveChange(false);
6740
+ break;
6741
+ }
6742
+ }
6743
+ },
6744
+ [
6745
+ active,
6746
+ activeIndex,
6747
+ itemCount,
6748
+ onActiveChange,
6749
+ onSearch,
6750
+ onSelectSuggestion,
6751
+ query
6752
+ ]
6753
+ );
6754
+ const resetActiveIndex = React31.useCallback(() => {
6755
+ setActiveIndex(-1);
6756
+ }, []);
6757
+ return { activeIndex, handleKeyDown, resetActiveIndex };
6758
+ }
6759
+
6760
+ // src/ui/search/search.tokens.ts
6761
+ var SearchTokens = {
6762
+ // ── Heights ─────────────────────────────────────────────────────────────
6763
+ heights: {
6764
+ /** SearchBarTokens.ContainerHeight = 56dp */
6765
+ bar: 56,
6766
+ /** SearchViewTokens.DockedHeaderContainerHeight = 56dp */
6767
+ dockedHeader: 56,
6768
+ /** SearchViewTokens.FullScreenHeaderContainerHeight = 72dp */
6769
+ fullScreenHeader: 72
6770
+ },
6771
+ // ── Avatar ────────────────────────────────────────────────────────────
6772
+ /** SearchBarTokens.AvatarSize = 30dp */
6773
+ avatarSize: 30,
6774
+ // ── Icon ───────────────────────────────────────────────────────────────
6775
+ /** Standard icon size for leading/trailing icons. */
6776
+ iconSize: 20,
6777
+ /** Touch target for interactive icons per MD3 a11y spec. */
6778
+ iconTouchTarget: 48,
6779
+ // ── Gap ────────────────────────────────────────────────────────────────
6780
+ /** Gap between SearchBar and results list when hasGap=true. */
6781
+ dropdownGap: 2
6782
+ };
6783
+ var SEARCH_COLORS = {
6784
+ /** SearchBarTokens.ContainerColor → surface-container-high */
6785
+ container: "var(--md-sys-color-surface-container-high)",
6786
+ /** SearchBarTokens.LeadingIconColor → on-surface */
6787
+ leadingIcon: "var(--md-sys-color-on-surface)",
6788
+ /** SearchBarTokens.TrailingIconColor → on-surface-variant */
6789
+ trailingIcon: "var(--md-sys-color-on-surface-variant)",
6790
+ /** SearchBarTokens.InputTextColor → on-surface */
6791
+ inputText: "var(--md-sys-color-on-surface)",
6792
+ /** SearchBarTokens.SupportingTextColor → on-surface-variant (placeholder) */
6793
+ supportingText: "var(--md-sys-color-on-surface-variant)",
6794
+ /** SearchViewTokens.DividerColor → outline */
6795
+ divider: "var(--md-sys-color-outline)",
6796
+ /** Focus indicator → secondary */
6797
+ focusIndicator: "var(--md-sys-color-secondary)"
6798
+ };
6799
+ var SEARCH_TYPOGRAPHY = {
6800
+ /** BodyLarge — used for input text and placeholder. */
6801
+ bodyLarge: "text-[16px] leading-6 font-normal tracking-[0.5px]"
6802
+ };
6803
+ var SEARCH_BAR_EXPAND_SPRING = {
6804
+ type: "spring",
6805
+ stiffness: 380,
6806
+ damping: 38,
6807
+ mass: 1
6808
+ };
6809
+ var SEARCH_DOCKED_REVEAL_SPRING = {
6810
+ type: "spring",
6811
+ stiffness: 400,
6812
+ damping: 35,
6813
+ mass: 0.8
6814
+ };
6815
+ var SEARCH_FULLSCREEN_SPRING = {
6816
+ type: "spring",
6817
+ stiffness: 300,
6818
+ damping: 30,
6819
+ mass: 0.9
6820
+ };
6821
+ var SEARCH_BAR_EXIT_SPRING = {
6822
+ type: "spring",
6823
+ stiffness: 500,
6824
+ damping: 40,
6825
+ mass: 0.6
6826
+ };
6827
+ var PLACEHOLDER_SPRING = {
6828
+ type: "spring",
6829
+ stiffness: 350,
6830
+ damping: 30,
6831
+ mass: 0.8
6832
+ };
6833
+ function AnimatedPlaceholder({
6834
+ text,
6835
+ textAlign,
6836
+ visible,
6837
+ focused,
6838
+ children,
6839
+ className
6840
+ }) {
6841
+ const shouldReduceMotion = useReducedMotion();
6842
+ const containerRef = React31.useRef(null);
6843
+ const spanRef = React31.useRef(null);
6844
+ const [xOffset, setXOffset] = React31.useState(0);
6845
+ const recalculate = React31.useCallback(() => {
6846
+ const container = containerRef.current;
6847
+ const span = spanRef.current;
6848
+ if (!container || !span || textAlign === "left") {
6849
+ setXOffset(0);
6850
+ return;
6851
+ }
6852
+ const containerWidth = container.offsetWidth;
6853
+ const textWidth = span.offsetWidth;
6854
+ if (textAlign === "center") {
6855
+ setXOffset(Math.max(0, (containerWidth - textWidth) / 2));
6856
+ } else {
6857
+ setXOffset(Math.max(0, containerWidth - textWidth));
6858
+ }
6859
+ }, [textAlign]);
6860
+ React31.useLayoutEffect(() => {
6861
+ recalculate();
6862
+ }, [recalculate]);
6863
+ React31.useEffect(() => {
6864
+ const container = containerRef.current;
6865
+ if (!container) return;
6866
+ const observer = new ResizeObserver(recalculate);
6867
+ observer.observe(container);
6868
+ return () => observer.disconnect();
6869
+ }, [recalculate]);
6870
+ const targetX = focused || !visible ? 0 : xOffset;
6871
+ return /* @__PURE__ */ jsxs(
6872
+ "div",
6873
+ {
6874
+ ref: containerRef,
6875
+ className: cn("relative flex-1 min-w-0", className),
6876
+ children: [
6877
+ children,
6878
+ /* @__PURE__ */ jsx(
6879
+ m.span,
6880
+ {
6881
+ ref: (el) => {
6882
+ spanRef.current = el;
6883
+ },
6884
+ "aria-hidden": "true",
6885
+ className: cn(
6886
+ "pointer-events-none absolute inset-y-0 left-0",
6887
+ "flex items-center whitespace-nowrap select-none",
6888
+ SEARCH_TYPOGRAPHY.bodyLarge
6889
+ ),
6890
+ style: { color: SEARCH_COLORS.supportingText },
6891
+ animate: {
6892
+ x: targetX,
6893
+ opacity: visible ? 1 : 0
6894
+ },
6895
+ transition: shouldReduceMotion ? { duration: 0 } : PLACEHOLDER_SPRING,
6896
+ children: text
6897
+ }
6898
+ )
6899
+ ]
6900
+ }
6901
+ );
6902
+ }
6903
+ function DefaultSearchIcon() {
6904
+ return /* @__PURE__ */ jsx(
6905
+ "span",
6906
+ {
6907
+ className: "material-symbols-rounded select-none leading-none",
6908
+ style: { fontSize: SearchTokens.iconSize },
6909
+ "aria-hidden": "true",
6910
+ children: "search"
6911
+ }
6912
+ );
6913
+ }
6914
+ function SearchBar({
6915
+ query,
6916
+ onQueryChange,
6917
+ onSearch,
6918
+ active,
6919
+ onActiveChange,
6920
+ leadingIcon,
6921
+ trailingIcon,
6922
+ placeholder = "Search",
6923
+ textAlign = "left",
6924
+ className,
6925
+ "aria-label": ariaLabel = "Search",
6926
+ searchId,
6927
+ listboxId,
6928
+ onKeyDown,
6929
+ activeIndex
6930
+ }) {
6931
+ const shouldReduceMotion = useReducedMotion();
6932
+ const inputRef = React31.useRef(null);
6933
+ const prevActiveRef = React31.useRef(active);
6934
+ const isRestoringFocusRef = React31.useRef(false);
6935
+ React31.useEffect(() => {
6936
+ var _a;
6937
+ let rafId;
6938
+ if (prevActiveRef.current === true && active === false) {
6939
+ isRestoringFocusRef.current = true;
6940
+ (_a = inputRef.current) == null ? void 0 : _a.focus();
6941
+ rafId = requestAnimationFrame(() => {
6942
+ isRestoringFocusRef.current = false;
6943
+ });
6944
+ }
6945
+ prevActiveRef.current = active;
6946
+ return () => {
6947
+ if (rafId) cancelAnimationFrame(rafId);
6948
+ };
6949
+ }, [active]);
6950
+ const handleFocus = () => {
6951
+ if (!active && !isRestoringFocusRef.current) {
6952
+ onActiveChange(true);
6953
+ }
6954
+ };
6955
+ const handleFormSubmit = (e) => {
6956
+ e.preventDefault();
6957
+ onSearch(query);
6958
+ };
6959
+ const activeDescendant = activeIndex >= 0 ? `${listboxId}-option-${activeIndex}` : void 0;
6960
+ return (
6961
+ /*
6962
+ * AnimatePresence mode="popLayout":
6963
+ * When SearchView opens (active=true), SearchBar plays its exit animation
6964
+ * first, then unmounts — releasing the shared layoutId for SearchView to
6965
+ * claim and morph from the pill shape.
6966
+ */
6967
+ /* @__PURE__ */ jsx(AnimatePresence, { mode: "popLayout", children: !active && /* @__PURE__ */ jsxs(
6968
+ m.div,
6969
+ {
6970
+ layout: !shouldReduceMotion,
6971
+ layoutId: shouldReduceMotion ? void 0 : searchId,
6972
+ transition: shouldReduceMotion ? void 0 : SEARCH_BAR_EXPAND_SPRING,
6973
+ className: cn("relative", className),
6974
+ style: { height: SearchTokens.heights.bar },
6975
+ initial: shouldReduceMotion ? false : { opacity: 0, scale: 0.95 },
6976
+ animate: { opacity: 1, scale: 1 },
6977
+ exit: shouldReduceMotion ? {} : { opacity: 0, scale: 0.95, transition: SEARCH_BAR_EXIT_SPRING },
6978
+ children: [
6979
+ /* @__PURE__ */ jsx(
6980
+ "div",
6981
+ {
6982
+ className: "absolute inset-0 rounded-full",
6983
+ style: { backgroundColor: SEARCH_COLORS.container },
6984
+ "aria-hidden": "true"
6985
+ }
6986
+ ),
6987
+ /* @__PURE__ */ jsx(
6988
+ "search",
6989
+ {
6990
+ "aria-label": ariaLabel,
6991
+ className: "relative flex h-full items-center gap-2 rounded-full px-4",
6992
+ children: /* @__PURE__ */ jsxs("form", { className: "contents", onSubmit: handleFormSubmit, children: [
6993
+ /* @__PURE__ */ jsx(
6994
+ "span",
6995
+ {
6996
+ className: "flex shrink-0 items-center justify-center",
6997
+ style: { color: SEARCH_COLORS.leadingIcon },
6998
+ "aria-hidden": "true",
6999
+ children: leadingIcon != null ? leadingIcon : /* @__PURE__ */ jsx(DefaultSearchIcon, {})
7000
+ }
7001
+ ),
7002
+ /* @__PURE__ */ jsx(
7003
+ AnimatedPlaceholder,
7004
+ {
7005
+ text: placeholder,
7006
+ textAlign,
7007
+ visible: !query,
7008
+ focused: active,
7009
+ children: /* @__PURE__ */ jsx(
7010
+ "input",
7011
+ {
7012
+ ref: inputRef,
7013
+ id: searchId,
7014
+ type: "search",
7015
+ role: "combobox",
7016
+ "aria-expanded": active,
7017
+ "aria-controls": listboxId,
7018
+ "aria-autocomplete": "list",
7019
+ "aria-activedescendant": activeDescendant,
7020
+ "aria-label": placeholder,
7021
+ value: query,
7022
+ placeholder,
7023
+ className: cn(
7024
+ "w-full bg-transparent border-none outline-none",
7025
+ "text-[16px] leading-6 font-normal tracking-[0.5px]",
7026
+ "placeholder:text-transparent"
7027
+ ),
7028
+ style: { color: SEARCH_COLORS.inputText },
7029
+ onFocus: handleFocus,
7030
+ onChange: (e) => onQueryChange(e.target.value),
7031
+ onKeyDown
7032
+ }
7033
+ )
7034
+ }
7035
+ ),
7036
+ trailingIcon && /* @__PURE__ */ jsx(
7037
+ "span",
7038
+ {
7039
+ className: "flex shrink-0 items-center justify-center",
7040
+ style: { color: SEARCH_COLORS.trailingIcon },
7041
+ "aria-hidden": "true",
7042
+ children: trailingIcon
7043
+ }
7044
+ )
7045
+ ] })
7046
+ }
7047
+ )
7048
+ ]
7049
+ },
7050
+ searchId
7051
+ ) })
7052
+ );
7053
+ }
7054
+ var SearchContext = React31.createContext(null);
7055
+ function SearchProvider({
7056
+ children,
7057
+ value
7058
+ }) {
7059
+ return /* @__PURE__ */ jsx(SearchContext.Provider, { value, children });
7060
+ }
7061
+ function useSearch() {
7062
+ const context = React31.useContext(SearchContext);
7063
+ if (!context) {
7064
+ return { listboxId: "", activeIndex: -1 };
7065
+ }
7066
+ return context;
7067
+ }
7068
+ function useClickOutside(handler, enabled = true) {
7069
+ const ref = useRef(null);
7070
+ useEffect(() => {
7071
+ if (!enabled) return;
7072
+ const listener = (event) => {
7073
+ const el = ref.current;
7074
+ if (!el || el.contains(event.target)) {
7075
+ return;
7076
+ }
7077
+ handler();
7078
+ };
7079
+ document.addEventListener("mousedown", listener);
7080
+ document.addEventListener("touchstart", listener);
7081
+ return () => {
7082
+ document.removeEventListener("mousedown", listener);
7083
+ document.removeEventListener("touchstart", listener);
7084
+ };
7085
+ }, [handler, enabled]);
7086
+ return ref;
7087
+ }
7088
+ function useSearchViewFocus(inputRef, active) {
7089
+ React31.useEffect(() => {
7090
+ if (!active) return;
7091
+ let raf2;
7092
+ const raf1 = requestAnimationFrame(() => {
7093
+ raf2 = requestAnimationFrame(() => {
7094
+ var _a;
7095
+ (_a = inputRef.current) == null ? void 0 : _a.focus();
7096
+ });
7097
+ });
7098
+ return () => {
7099
+ cancelAnimationFrame(raf1);
7100
+ if (raf2) cancelAnimationFrame(raf2);
7101
+ };
7102
+ }, [active, inputRef]);
7103
+ }
7104
+ function TrailingAction({
7105
+ query,
7106
+ trailingIcon,
7107
+ onClear
7108
+ }) {
7109
+ if (query) {
7110
+ return /* @__PURE__ */ jsx(
7111
+ IconButton,
7112
+ {
7113
+ size: "sm",
7114
+ style: { color: SEARCH_COLORS.trailingIcon },
7115
+ "aria-label": "Clear search",
7116
+ onClick: onClear,
7117
+ children: /* @__PURE__ */ jsx(
7118
+ "span",
7119
+ {
7120
+ className: "material-symbols-rounded select-none leading-none",
7121
+ style: { fontSize: 20 },
7122
+ "aria-hidden": "true",
7123
+ children: "close"
7124
+ }
7125
+ )
7126
+ }
7127
+ );
7128
+ }
7129
+ if (trailingIcon) {
7130
+ return /* @__PURE__ */ jsx(
7131
+ "span",
7132
+ {
7133
+ className: "flex shrink-0 items-center justify-center",
7134
+ style: { color: SEARCH_COLORS.trailingIcon },
7135
+ "aria-hidden": "true",
7136
+ children: trailingIcon
7137
+ }
7138
+ );
7139
+ }
7140
+ return null;
7141
+ }
7142
+ function SearchViewDocked({
7143
+ query,
7144
+ onQueryChange,
7145
+ onSearch,
7146
+ active,
7147
+ onActiveChange,
7148
+ leadingIcon,
7149
+ trailingIcon,
7150
+ placeholder = "Search",
7151
+ textAlign = "left",
7152
+ styleType = "contained",
7153
+ hasGap = false,
7154
+ children,
7155
+ viewClassName,
7156
+ "aria-label": ariaLabel = "Search",
7157
+ searchId,
7158
+ listboxId,
7159
+ onKeyDown,
7160
+ activeIndex
7161
+ }) {
7162
+ const shouldReduceMotion = useReducedMotion();
7163
+ const inputRef = React31.useRef(null);
7164
+ useSearchViewFocus(inputRef, active);
7165
+ const dropdownRef = useClickOutside(() => {
7166
+ onActiveChange(false);
7167
+ }, active);
7168
+ const handleFormSubmit = (e) => {
7169
+ e.preventDefault();
7170
+ onSearch(query);
7171
+ };
7172
+ const activeDescendant = activeIndex >= 0 ? `${listboxId}-option-${activeIndex}` : void 0;
7173
+ return (
7174
+ /*
7175
+ * mode="popLayout": when SearchView exits, it plays exit animation first,
7176
+ * then unmounts — freeing the layoutId for SearchBar to re-enter and morph back.
7177
+ */
7178
+ /* @__PURE__ */ jsx(AnimatePresence, { mode: "popLayout", children: active && /* @__PURE__ */ jsxs(
7179
+ m.div,
7180
+ {
7181
+ ref: dropdownRef,
7182
+ layoutId: shouldReduceMotion ? void 0 : searchId,
7183
+ className: cn(
7184
+ // DockedContainerShape = CornerExtraLarge = 28dp radius
7185
+ "rounded-[28px] overflow-hidden",
7186
+ viewClassName
7187
+ ),
7188
+ style: { backgroundColor: SEARCH_COLORS.container },
7189
+ initial: shouldReduceMotion ? {} : { opacity: 0, y: -8 },
7190
+ animate: { opacity: 1, y: 0 },
7191
+ exit: shouldReduceMotion ? {} : { opacity: 0, y: -8 },
7192
+ transition: shouldReduceMotion ? { duration: 0 } : SEARCH_DOCKED_REVEAL_SPRING,
7193
+ children: [
7194
+ /* @__PURE__ */ jsx(
7195
+ "search",
7196
+ {
7197
+ "aria-label": ariaLabel,
7198
+ className: "flex items-center gap-2 px-4",
7199
+ style: { height: SearchTokens.heights.dockedHeader },
7200
+ children: /* @__PURE__ */ jsxs("form", { className: "contents", onSubmit: handleFormSubmit, children: [
7201
+ /* @__PURE__ */ jsx(
7202
+ "span",
7203
+ {
7204
+ className: "flex shrink-0 items-center justify-center",
7205
+ style: { color: SEARCH_COLORS.leadingIcon },
7206
+ "aria-hidden": "true",
7207
+ children: leadingIcon
7208
+ }
7209
+ ),
7210
+ /* @__PURE__ */ jsx(
7211
+ AnimatedPlaceholder,
7212
+ {
7213
+ text: placeholder,
7214
+ textAlign,
7215
+ visible: !query,
7216
+ focused: active,
7217
+ children: /* @__PURE__ */ jsx(
7218
+ "input",
7219
+ {
7220
+ ref: inputRef,
7221
+ id: `${searchId}-view`,
7222
+ type: "search",
7223
+ role: "combobox",
7224
+ "aria-expanded": true,
7225
+ "aria-controls": listboxId,
7226
+ "aria-autocomplete": "list",
7227
+ "aria-activedescendant": activeDescendant,
7228
+ "aria-label": placeholder,
7229
+ value: query,
7230
+ placeholder,
7231
+ className: cn(
7232
+ "w-full bg-transparent border-none outline-none",
7233
+ "text-[16px] leading-6 font-normal tracking-[0.5px]",
7234
+ "placeholder:text-transparent"
7235
+ ),
7236
+ style: { color: SEARCH_COLORS.inputText },
7237
+ onChange: (e) => onQueryChange(e.target.value),
7238
+ onKeyDown
7239
+ }
7240
+ )
7241
+ }
7242
+ ),
7243
+ /* @__PURE__ */ jsx(
7244
+ TrailingAction,
7245
+ {
7246
+ query,
7247
+ trailingIcon,
7248
+ onClear: () => onQueryChange("")
7249
+ }
7250
+ )
7251
+ ] })
7252
+ }
7253
+ ),
7254
+ hasGap && children && /* @__PURE__ */ jsx("div", { className: "h-0.5", "aria-hidden": "true" }),
7255
+ styleType === "divided" && children && /* @__PURE__ */ jsx(
7256
+ "hr",
7257
+ {
7258
+ className: "border-0 border-t",
7259
+ style: { borderColor: SEARCH_COLORS.divider }
7260
+ }
7261
+ ),
7262
+ children && /* @__PURE__ */ jsx(
7263
+ "div",
7264
+ {
7265
+ id: listboxId,
7266
+ role: "listbox",
7267
+ "aria-label": `${ariaLabel} results`,
7268
+ className: "min-h-30",
7269
+ children
7270
+ }
7271
+ )
7272
+ ]
7273
+ },
7274
+ `${searchId}-view`
7275
+ ) })
7276
+ );
7277
+ }
7278
+ function ArrowBackIcon2() {
7279
+ return /* @__PURE__ */ jsx(
7280
+ "span",
7281
+ {
7282
+ className: "material-symbols-rounded select-none leading-none",
7283
+ style: { fontSize: 24 },
7284
+ "aria-hidden": "true",
7285
+ children: "arrow_back"
7286
+ }
7287
+ );
7288
+ }
7289
+ function SearchViewFullScreen({
7290
+ query,
7291
+ onQueryChange,
7292
+ onSearch,
7293
+ active,
7294
+ onActiveChange,
7295
+ leadingIcon,
7296
+ trailingIcon,
7297
+ placeholder = "Search",
7298
+ textAlign = "left",
7299
+ styleType = "contained",
7300
+ children,
7301
+ viewClassName,
7302
+ "aria-label": ariaLabel = "Search",
7303
+ searchId,
7304
+ listboxId,
7305
+ onKeyDown,
7306
+ activeIndex
7307
+ }) {
7308
+ const shouldReduceMotion = useReducedMotion();
7309
+ const inputRef = React31.useRef(null);
7310
+ useSearchViewFocus(inputRef, active);
7311
+ const [mounted, setMounted] = React31.useState(false);
7312
+ React31.useEffect(() => {
7313
+ setMounted(true);
7314
+ }, []);
7315
+ const handleFormSubmit = (e) => {
7316
+ e.preventDefault();
7317
+ onSearch(query);
7318
+ };
7319
+ const handleEscape = (e) => {
7320
+ if (e.key === "Escape") {
7321
+ e.stopPropagation();
7322
+ onActiveChange(false);
7323
+ }
7324
+ };
7325
+ const activeDescendant = activeIndex >= 0 ? `${listboxId}-option-${activeIndex}` : void 0;
7326
+ if (!mounted) return null;
7327
+ const content = (
7328
+ /*
7329
+ * mode="popLayout": when FullScreen exits, it plays exit animation first,
7330
+ * then unmounts — freeing the layoutId for SearchBar to re-enter and morph back
7331
+ * to the pill shape.
7332
+ */
7333
+ /* @__PURE__ */ jsx(AnimatePresence, { mode: "popLayout", children: active && /* @__PURE__ */ jsxs(
7334
+ m.div,
7335
+ {
7336
+ layoutId: shouldReduceMotion ? void 0 : searchId,
7337
+ role: "dialog",
7338
+ "aria-modal": "true",
7339
+ "aria-label": ariaLabel,
7340
+ className: cn(
7341
+ "fixed inset-0 z-50 flex flex-col overflow-hidden",
7342
+ // CornerNone — shape is resolved by Framer Motion layout animation
7343
+ "rounded-none",
7344
+ viewClassName
7345
+ ),
7346
+ style: { backgroundColor: SEARCH_COLORS.container },
7347
+ initial: shouldReduceMotion ? {} : { opacity: 0 },
7348
+ animate: { opacity: 1 },
7349
+ exit: shouldReduceMotion ? {} : { opacity: 0 },
7350
+ transition: shouldReduceMotion ? { duration: 0 } : SEARCH_FULLSCREEN_SPRING,
7351
+ onKeyDown: handleEscape,
7352
+ children: [
7353
+ /* @__PURE__ */ jsx(
7354
+ "search",
7355
+ {
7356
+ "aria-label": ariaLabel,
7357
+ className: "flex shrink-0 items-center gap-2 px-4",
7358
+ style: { height: SearchTokens.heights.fullScreenHeader },
7359
+ children: /* @__PURE__ */ jsxs("form", { className: "contents", onSubmit: handleFormSubmit, children: [
7360
+ /* @__PURE__ */ jsx(
7361
+ IconButton,
7362
+ {
7363
+ size: "sm",
7364
+ style: { color: SEARCH_COLORS.leadingIcon },
7365
+ "aria-label": "Close search",
7366
+ onClick: (e) => {
7367
+ e.stopPropagation();
7368
+ onActiveChange(false);
7369
+ },
7370
+ children: leadingIcon != null ? leadingIcon : /* @__PURE__ */ jsx(ArrowBackIcon2, {})
7371
+ }
7372
+ ),
7373
+ /* @__PURE__ */ jsx(
7374
+ AnimatedPlaceholder,
7375
+ {
7376
+ text: placeholder,
7377
+ textAlign,
7378
+ visible: !query,
7379
+ focused: active,
7380
+ children: /* @__PURE__ */ jsx(
7381
+ "input",
7382
+ {
7383
+ ref: inputRef,
7384
+ id: `${searchId}-fs`,
7385
+ type: "search",
7386
+ role: "combobox",
7387
+ "aria-expanded": true,
7388
+ "aria-controls": listboxId,
7389
+ "aria-autocomplete": "list",
7390
+ "aria-activedescendant": activeDescendant,
7391
+ "aria-label": placeholder,
7392
+ value: query,
7393
+ placeholder,
7394
+ className: cn(
7395
+ "w-full bg-transparent border-none outline-none",
7396
+ "text-[16px] leading-6 font-normal tracking-[0.5px]",
7397
+ "placeholder:text-transparent"
7398
+ ),
7399
+ style: { color: SEARCH_COLORS.inputText },
7400
+ onChange: (e) => onQueryChange(e.target.value),
7401
+ onKeyDown
7402
+ }
7403
+ )
7404
+ }
7405
+ ),
7406
+ /* @__PURE__ */ jsx(
7407
+ TrailingAction,
7408
+ {
7409
+ query,
7410
+ trailingIcon,
7411
+ onClear: () => onQueryChange("")
7412
+ }
7413
+ )
7414
+ ] })
7415
+ }
7416
+ ),
7417
+ styleType === "divided" && /* @__PURE__ */ jsx(
7418
+ "hr",
7419
+ {
7420
+ className: "border-0 border-t shrink-0",
7421
+ style: { borderColor: SEARCH_COLORS.divider }
7422
+ }
7423
+ ),
7424
+ /* @__PURE__ */ jsx(
7425
+ "div",
7426
+ {
7427
+ id: listboxId,
7428
+ role: "listbox",
7429
+ tabIndex: 0,
7430
+ "aria-label": `${ariaLabel} results`,
7431
+ className: "flex-1 overflow-y-auto outline-none",
7432
+ onClick: (e) => {
7433
+ if (e.target === e.currentTarget) onActiveChange(false);
7434
+ },
7435
+ onKeyDown: (e) => {
7436
+ if (e.target === e.currentTarget && (e.key === "Enter" || e.key === " ")) {
7437
+ e.preventDefault();
7438
+ onActiveChange(false);
7439
+ }
7440
+ },
7441
+ children
7442
+ }
7443
+ )
7444
+ ]
7445
+ },
7446
+ `${searchId}-fs`
7447
+ ) })
7448
+ );
7449
+ return createPortal(content, document.body);
7450
+ }
7451
+ function SearchComponent({
7452
+ query,
7453
+ onQueryChange,
7454
+ onSearch,
7455
+ active,
7456
+ onActiveChange,
7457
+ variant = "docked",
7458
+ styleType = "contained",
7459
+ hasGap = false,
7460
+ leadingIcon,
7461
+ trailingIcon,
7462
+ placeholder = "Search",
7463
+ textAlign = "left",
7464
+ children,
7465
+ id,
7466
+ "aria-label": ariaLabel = "Search",
7467
+ className,
7468
+ viewClassName
7469
+ }) {
7470
+ const generatedId = React31.useId();
7471
+ const searchId = id != null ? id : generatedId;
7472
+ const listboxId = `${searchId}-listbox`;
7473
+ const itemCount = React31.Children.count(children);
7474
+ const { activeIndex, handleKeyDown } = useSearchKeyboard({
7475
+ active,
7476
+ onActiveChange,
7477
+ onSearch,
7478
+ query,
7479
+ itemCount
7480
+ });
7481
+ const sharedProps = {
7482
+ query,
7483
+ onQueryChange,
7484
+ onSearch,
7485
+ active,
7486
+ onActiveChange,
7487
+ leadingIcon,
7488
+ trailingIcon,
7489
+ placeholder,
7490
+ textAlign,
7491
+ "aria-label": ariaLabel,
7492
+ searchId,
7493
+ listboxId,
7494
+ onKeyDown: handleKeyDown,
7495
+ activeIndex
7496
+ };
7497
+ return /* @__PURE__ */ jsx(SearchProvider, { value: { activeIndex, listboxId }, children: /* @__PURE__ */ jsxs(m.div, { className, style: { minHeight: 56 }, children: [
7498
+ /* @__PURE__ */ jsx(SearchBar, __spreadValues({}, sharedProps)),
7499
+ variant === "fullscreen" ? /* @__PURE__ */ jsx(
7500
+ SearchViewFullScreen,
7501
+ __spreadProps(__spreadValues({}, sharedProps), {
7502
+ styleType,
7503
+ viewClassName,
7504
+ children
7505
+ })
7506
+ ) : /* @__PURE__ */ jsx(
7507
+ SearchViewDocked,
7508
+ __spreadProps(__spreadValues({}, sharedProps), {
7509
+ styleType,
7510
+ hasGap,
7511
+ viewClassName,
7512
+ children
7513
+ })
7514
+ )
7515
+ ] }) });
7516
+ }
7517
+ var Search = Object.assign(SearchComponent, { useSearch });
6699
7518
  function coerceValue(value, min, max) {
6700
7519
  return Math.max(min, Math.min(max, value));
6701
7520
  }
@@ -8629,7 +9448,9 @@ function SnackbarHost({ state, className }) {
8629
9448
  ) });
8630
9449
  }
8631
9450
  SnackbarHost.displayName = "SnackbarHost";
8632
- var SnackbarContext = React31.createContext(null);
9451
+ var SnackbarContext = React31.createContext(
9452
+ null
9453
+ );
8633
9454
  function SnackbarProvider({ children }) {
8634
9455
  const state = useSnackbarState();
8635
9456
  const contextValue = React31.useMemo(
@@ -11920,6 +12741,6 @@ function TooltipBox({
11920
12741
  ] });
11921
12742
  }
11922
12743
 
11923
- export { APP_BAR_BOTTOM_SPRING, APP_BAR_COLORS, APP_BAR_COLOR_TRANSITION, APP_BAR_ENTER_ALWAYS_SPRING, APP_BAR_TITLE_FADE, AppBarColumn, AppBarOverflowIndicator, AppBarRow, AppBarTokens, Badge, BadgedBox, BottomAppBar, Button, ButtonGroup, Card, Checkbox, Chip, CodeBlock, Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogFullScreenContent, DialogHeader, DialogIcon, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Divider, DockedToolbar, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, FAB, FABMenu, FABMenuItem, FABPosition, Icon, IconButton, LargeFlexibleAppBar, LoadingIndicator, MD3ThemeProvider, MD3_EXPRESSIVE_FONT_VARIATION, MaterialSymbolsPreconnect, MediumFlexibleAppBar, NavigationRail, NavigationRailItem, PlainTooltip, ProgressIndicator, RadioButton, RadioGroup2 as RadioGroup, RangeSlider, RichTooltip, Ripple, SEARCH_VIEW_SPRING, ScrollArea, ScrollAreaScrollbar, SearchAppBar, SearchView, SearchViewContainer, Slider, SliderColors, SliderTokens, SmallAppBar, Snackbar, SnackbarHost, SnackbarProvider, Switch, SwitchColors, SwitchTokens, Tab, TableOfContents, Tabs, TabsColors, TabsContent, TabsList, TabsTokens, TextField, ToggleFAB, TooltipBox, TooltipCaretShape, TooltipTokens, TriStateCheckbox, TypeScaleTokens, Typography, TypographyContext, TypographyKeyTokens, TypographyProvider, TypographyTokens, appBarTypography, applyTheme, buildWavePath, cn, generateM3Theme, useAppBarScroll, useRipple as useDOMRipple, useMediaQuery, useRipple2 as useRipple, useRippleState, useSnackbar, useSnackbarState, useTheme, useThemeMode, useTooltipPosition, useTooltipState, useTypography };
12744
+ export { APP_BAR_BOTTOM_SPRING, APP_BAR_COLORS, APP_BAR_COLOR_TRANSITION, APP_BAR_ENTER_ALWAYS_SPRING, APP_BAR_TITLE_FADE, AppBarColumn, AppBarOverflowIndicator, AppBarRow, AppBarTokens, Badge, BadgedBox, BottomAppBar, Button, ButtonGroup, Card, Checkbox, Chip, CodeBlock, Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogFullScreenContent, DialogHeader, DialogIcon, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Divider, DockedToolbar, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, FAB, FABMenu, FABMenuItem, FABPosition, Icon, IconButton, LargeFlexibleAppBar, LoadingIndicator, MD3ThemeProvider, MD3_EXPRESSIVE_FONT_VARIATION, MaterialSymbolsPreconnect, MediumFlexibleAppBar, NavigationRail, NavigationRailItem, PlainTooltip, ProgressIndicator, RadioButton, RadioGroup2 as RadioGroup, RangeSlider, RichTooltip, Ripple, SEARCH_BAR_EXPAND_SPRING, SEARCH_COLORS, SEARCH_DOCKED_REVEAL_SPRING, SEARCH_FULLSCREEN_SPRING, SEARCH_TYPOGRAPHY, SEARCH_VIEW_SPRING, ScrollArea, ScrollAreaScrollbar, Search, SearchAppBar, SearchBar, SearchTokens, SearchView, SearchViewContainer, SearchViewDocked, SearchViewFullScreen, Slider, SliderColors, SliderTokens, SmallAppBar, Snackbar, SnackbarHost, SnackbarProvider, Switch, SwitchColors, SwitchTokens, Tab, TableOfContents, Tabs, TabsColors, TabsContent, TabsList, TabsTokens, TextField, ToggleFAB, TooltipBox, TooltipCaretShape, TooltipTokens, TriStateCheckbox, TypeScaleTokens, Typography, TypographyContext, TypographyKeyTokens, TypographyProvider, TypographyTokens, appBarTypography, applyTheme, buildWavePath, cn, generateM3Theme, useAppBarScroll, useRipple as useDOMRipple, useMediaQuery, useRipple2 as useRipple, useRippleState, useSearchKeyboard, useSnackbar, useSnackbarState, useTheme, useThemeMode, useTooltipPosition, useTooltipState, useTypography };
11924
12745
  //# sourceMappingURL=index.mjs.map
11925
12746
  //# sourceMappingURL=index.mjs.map