@marianmeres/stuic 2.58.0 → 2.60.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.
@@ -45,6 +45,26 @@ export type TooltipConfig = () => {
45
45
  onShow?: CallableFunction;
46
46
  onHide?: CallableFunction;
47
47
  };
48
+ /**
49
+ * Globally enable or disable all tooltips.
50
+ * Useful for disabling tooltips on touch devices where they interfere with interactions.
51
+ *
52
+ * @param value - `true` to enable tooltips, `false` to disable
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * // Disable tooltips on touch devices
57
+ * if ('ontouchstart' in window) {
58
+ * setTooltipsEnabled(false);
59
+ * }
60
+ * ```
61
+ */
62
+ export declare function setTooltipsEnabled(value: boolean): void;
63
+ /**
64
+ * Get the current global tooltip enabled state.
65
+ * @returns `true` if tooltips are globally enabled, `false` otherwise
66
+ */
67
+ export declare function getTooltipsEnabled(): boolean;
48
68
  /**
49
69
  * A Svelte action that displays a tooltip anchored to an element using CSS Anchor Positioning.
50
70
  *
@@ -48,6 +48,44 @@ const POSITION_MAP = {
48
48
  left: "left",
49
49
  right: "right",
50
50
  };
51
+ // Global tooltip configuration - allows disabling all tooltips at runtime
52
+ const globalTooltipConfig = $state({ enabled: true });
53
+ // Touch device auto-detection (runs once on first tooltip init)
54
+ let touchDetectionDone = false;
55
+ function detectTouchDevice() {
56
+ if (touchDetectionDone)
57
+ return;
58
+ touchDetectionDone = true;
59
+ // Detect touch device and disable tooltips by default
60
+ const isTouchDevice = "ontouchstart" in window || navigator.maxTouchPoints > 0;
61
+ if (isTouchDevice) {
62
+ globalTooltipConfig.enabled = false;
63
+ }
64
+ }
65
+ /**
66
+ * Globally enable or disable all tooltips.
67
+ * Useful for disabling tooltips on touch devices where they interfere with interactions.
68
+ *
69
+ * @param value - `true` to enable tooltips, `false` to disable
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * // Disable tooltips on touch devices
74
+ * if ('ontouchstart' in window) {
75
+ * setTooltipsEnabled(false);
76
+ * }
77
+ * ```
78
+ */
79
+ export function setTooltipsEnabled(value) {
80
+ globalTooltipConfig.enabled = value;
81
+ }
82
+ /**
83
+ * Get the current global tooltip enabled state.
84
+ * @returns `true` if tooltips are globally enabled, `false` otherwise
85
+ */
86
+ export function getTooltipsEnabled() {
87
+ return globalTooltipConfig.enabled;
88
+ }
51
89
  /**
52
90
  * A Svelte action that displays a tooltip anchored to an element using CSS Anchor Positioning.
53
91
  *
@@ -88,6 +126,8 @@ export function tooltip(anchorEl, fn) {
88
126
  // the node has been mounted in the DOM
89
127
  if (!isTooltipSupported())
90
128
  return;
129
+ // Auto-detect touch device on first tooltip init
130
+ detectTouchDevice();
91
131
  //
92
132
  let tooltipEl;
93
133
  let hide_timer = null;
@@ -215,7 +255,7 @@ export function tooltip(anchorEl, fn) {
215
255
  on_hide = onHide;
216
256
  content = _content || anchorEl.getAttribute("aria-label");
217
257
  classTooltip = _classTooltip;
218
- enabled = _enabled ?? true;
258
+ enabled = globalTooltipConfig.enabled && (_enabled ?? true);
219
259
  position = _position || "top";
220
260
  // this will be effective here only if currently in open state, otherwise noop
221
261
  set_content();
@@ -38,7 +38,7 @@
38
38
  elFooter?: HTMLElement;
39
39
  }
40
40
 
41
- export const MAIN_WIDTH = Symbol("MAIN_WIDTH");
41
+ export const APP_SHELL_MAIN_WIDTH = Symbol("APP_SHELL_MAIN_WIDTH");
42
42
 
43
43
  /**
44
44
  * Helper utility function which sets document.body height to 100vh, and overflow: hidden.
@@ -103,7 +103,7 @@
103
103
 
104
104
  // pragmatic use case...
105
105
  let mainWidth: number = $state(0);
106
- setContext(MAIN_WIDTH, {
106
+ setContext(APP_SHELL_MAIN_WIDTH, {
107
107
  get current() {
108
108
  return mainWidth;
109
109
  },
@@ -33,7 +33,7 @@ export interface Props {
33
33
  elSidebarRight?: HTMLElement;
34
34
  elFooter?: HTMLElement;
35
35
  }
36
- export declare const MAIN_WIDTH: unique symbol;
36
+ export declare const APP_SHELL_MAIN_WIDTH: unique symbol;
37
37
  /**
38
38
  * Helper utility function which sets document.body height to 100vh, and overflow: hidden.
39
39
  * It also returns a function which unsets the full height. So we can write:
@@ -9,6 +9,8 @@
9
9
  railClass?: string;
10
10
  asideClass?: string;
11
11
  mainClass?: string;
12
+ // header support style as well (for iOS theming, seem to be better supported)
13
+ headerStyle?: string;
12
14
  //
13
15
  rail?: Snippet;
14
16
  header?: Snippet;
@@ -21,7 +23,7 @@
21
23
  elMain?: HTMLElement;
22
24
  }
23
25
 
24
- export const MAIN_WIDTH = Symbol("MAIN_WIDTH");
26
+ export const APP_SHELL_SIMPLE_MAIN_WIDTH = Symbol("APP_SHELL_SIMPLE_MAIN_WIDTH");
25
27
  </script>
26
28
 
27
29
  <script lang="ts">
@@ -33,6 +35,8 @@
33
35
  asideClass,
34
36
  mainClass,
35
37
  //
38
+ headerStyle = "",
39
+ //
36
40
  rail,
37
41
  header,
38
42
  aside,
@@ -48,7 +52,7 @@
48
52
 
49
53
  // pragmatic use case...
50
54
  let mainWidth: number = $state(0);
51
- setContext(MAIN_WIDTH, {
55
+ setContext(APP_SHELL_SIMPLE_MAIN_WIDTH, {
52
56
  get current() {
53
57
  return mainWidth;
54
58
  },
@@ -61,6 +65,7 @@
61
65
  bind:clientHeight={headerHeight}
62
66
  data-shell="header"
63
67
  class={twMerge("sticky top-0 z-10", headerClass)}
68
+ style={headerStyle}
64
69
  >
65
70
  {@render header()}
66
71
  </header>
@@ -106,7 +111,7 @@
106
111
  <main
107
112
  bind:this={elMain}
108
113
  data-shell="main"
109
- class={twMerge("flex-1", mainClass)}
114
+ class={twMerge("flex-1 max-w-full", mainClass)}
110
115
  bind:offsetWidth={mainWidth}
111
116
  >
112
117
  {@render children?.()}
@@ -5,6 +5,7 @@ export interface Props {
5
5
  railClass?: string;
6
6
  asideClass?: string;
7
7
  mainClass?: string;
8
+ headerStyle?: string;
8
9
  rail?: Snippet;
9
10
  header?: Snippet;
10
11
  aside?: Snippet;
@@ -14,7 +15,7 @@ export interface Props {
14
15
  elAside?: HTMLElement;
15
16
  elMain?: HTMLElement;
16
17
  }
17
- export declare const MAIN_WIDTH: unique symbol;
18
+ export declare const APP_SHELL_SIMPLE_MAIN_WIDTH: unique symbol;
18
19
  declare const AppShellSimple: import("svelte").Component<Props, {}, "elRail" | "elHeader" | "elAside" | "elMain">;
19
20
  type AppShellSimple = ReturnType<typeof AppShellSimple>;
20
21
  export default AppShellSimple;
@@ -1,2 +1,2 @@
1
- export { default as AppShell, type Props as AppShellProps, appShellSetHtmlBodyHeight, MAIN_WIDTH, } from "./AppShell.svelte";
2
- export { default as AppShellSimple, type Props as AppShellSimpleProps, MAIN_WIDTH as APP_SHELL_SIMPLE_MAIN_WIDTH, } from "./AppShellSimple.svelte";
1
+ export { default as AppShell, type Props as AppShellProps, appShellSetHtmlBodyHeight, APP_SHELL_MAIN_WIDTH, } from "./AppShell.svelte";
2
+ export { default as AppShellSimple, type Props as AppShellSimpleProps, APP_SHELL_SIMPLE_MAIN_WIDTH, } from "./AppShellSimple.svelte";
@@ -1,2 +1,2 @@
1
- export { default as AppShell, appShellSetHtmlBodyHeight, MAIN_WIDTH, } from "./AppShell.svelte";
2
- export { default as AppShellSimple, MAIN_WIDTH as APP_SHELL_SIMPLE_MAIN_WIDTH, } from "./AppShellSimple.svelte";
1
+ export { default as AppShell, appShellSetHtmlBodyHeight, APP_SHELL_MAIN_WIDTH, } from "./AppShell.svelte";
2
+ export { default as AppShellSimple, APP_SHELL_SIMPLE_MAIN_WIDTH, } from "./AppShellSimple.svelte";
@@ -1,6 +1,11 @@
1
1
  <script lang="ts" module>
2
2
  import type { Snippet } from "svelte";
3
3
  import type { FocusTrapOptions } from "../../actions/focus-trap.js";
4
+ import { BodyScroll, focusTrap as focusTrapAction, twMerge } from "../../index.js";
5
+ import { createClog } from "@marianmeres/clog";
6
+ import { watch } from "runed";
7
+ import { onDestroy } from "svelte";
8
+ import { fade } from "svelte/transition";
4
9
 
5
10
  export interface Props extends Record<string, any> {
6
11
  class?: string;
@@ -11,6 +16,7 @@
11
16
  el?: HTMLDivElement;
12
17
  children?: Snippet;
13
18
  onEscape?: () => void;
19
+ onBackdropClick?: () => void;
14
20
  visible?: boolean;
15
21
  noScrollLock?: boolean;
16
22
  }
@@ -20,13 +26,7 @@
20
26
  </script>
21
27
 
22
28
  <script lang="ts">
23
- import { BodyScroll, focusTrap as focusTrapAction, twMerge } from "../../index.js";
24
- import { createClog } from "@marianmeres/clog";
25
- import { watch } from "runed";
26
- import { onDestroy } from "svelte";
27
- import { fade } from "svelte/transition";
28
-
29
- const clog = createClog("Backdrop").debug;
29
+ const clog = createClog("Backdrop", { color: "auto" });
30
30
 
31
31
  let {
32
32
  class: classProp,
@@ -37,6 +37,7 @@
37
37
  el = $bindable(),
38
38
  children,
39
39
  onEscape,
40
+ onBackdropClick,
40
41
  visible = $bindable(false),
41
42
  noScrollLock,
42
43
  ...rest
@@ -152,6 +153,11 @@
152
153
  in:fade={{ duration: fadeInDuration }}
153
154
  out:fade={{ duration: fadeOutDuration }}
154
155
  use:focusTrapAction={focusTrapOptions}
156
+ onmousedown={(e) => {
157
+ if (e.target === el && onBackdropClick) {
158
+ onBackdropClick();
159
+ }
160
+ }}
155
161
  {...rest}
156
162
  >
157
163
  {@render children?.()}
@@ -9,6 +9,7 @@ export interface Props extends Record<string, any> {
9
9
  el?: HTMLDivElement;
10
10
  children?: Snippet;
11
11
  onEscape?: () => void;
12
+ onBackdropClick?: () => void;
12
13
  visible?: boolean;
13
14
  noScrollLock?: boolean;
14
15
  }
@@ -105,6 +105,7 @@
105
105
  inputBox: {
106
106
  size: {
107
107
  sm: "text-sm",
108
+ lg: "p-1",
108
109
  } as any,
109
110
  },
110
111
  };
@@ -29,6 +29,8 @@
29
29
  onEscape?: undefined | (() => void);
30
30
  /** Disable body scroll lock when modal is open */
31
31
  noScrollLock?: boolean;
32
+ /** Fires when the backdrop is clicked "directly" */
33
+ onBackdropClick?: undefined | (() => void);
32
34
  }
33
35
  </script>
34
36
 
@@ -59,6 +61,7 @@
59
61
  focusTrap = true,
60
62
  onEscape,
61
63
  noScrollLock = false,
64
+ onBackdropClick,
62
65
  }: Props = $props();
63
66
 
64
67
  let backdrop: Backdrop = $state()!;
@@ -94,6 +97,7 @@
94
97
  fadeOutDuration={transitionDuration}
95
98
  {onEscape}
96
99
  {noScrollLock}
100
+ {onBackdropClick}
97
101
  >
98
102
  <div
99
103
  bind:this={el}
@@ -27,6 +27,8 @@ export interface Props {
27
27
  onEscape?: undefined | (() => void);
28
28
  /** Disable body scroll lock when modal is open */
29
29
  noScrollLock?: boolean;
30
+ /** Fires when the backdrop is clicked "directly" */
31
+ onBackdropClick?: undefined | (() => void);
30
32
  }
31
33
  declare const Modal: import("svelte").Component<Props, {
32
34
  close: () => void;
package/dist/index.css CHANGED
@@ -1,4 +1,11 @@
1
- @import "tailwindcss";
1
+ /*
2
+ Tailwind import removed here because it causes CSS cascade/layer issues in WebKit browsers
3
+ (Safari, iOS Chrome) when consumers also import Tailwind. The double import can result in
4
+ Tailwind's preflight styles (like border-style: solid) not being applied correctly.
5
+ Consumers of STUIC should import Tailwind at their own app level.
6
+ */
7
+ /* @import "tailwindcss"; */
8
+
2
9
  @plugin '@tailwindcss/forms';
3
10
 
4
11
  @custom-variant dark (&:where(.dark, .dark *));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/stuic",
3
- "version": "2.58.0",
3
+ "version": "2.60.0",
4
4
  "files": [
5
5
  "dist",
6
6
  "!dist/**/*.test.*",