@sigx/lynx-daisyui 0.4.2 → 0.4.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/README.md CHANGED
@@ -83,10 +83,88 @@ layout role. For multi-class compositions (color + modifier),
83
83
  `theme.set('daisy-light daisy-rounded')` works — the class string is
84
84
  applied verbatim to the host view.
85
85
 
86
+ ### Two layers: content vs. OS chrome
87
+
88
+ A theme drives two different things, and they scope differently:
89
+
90
+ 1. **In-app content** — the `--color-*` / radius variables and icon
91
+ tints. These live on a host view and inherit down a subtree, so they
92
+ are genuinely *scopable*.
93
+ 2. **OS chrome** — the status- and navigation-bar tint (pushed by
94
+ `<StatusBarSync>`). This is a global OS singleton; it can only reflect
95
+ one theme at a time.
96
+
97
+ The rule:
98
+
99
+ > `useTheme()` is the theme for the **content you render** — the nearest
100
+ > `<ThemeProvider>`, or the app-global theme at the root / in headless
101
+ > code. **System chrome always follows the global theme.** `StatusBarSync`
102
+ > binds to the global controller, so a nested provider can't hijack the
103
+ > bars.
104
+ >
105
+ > *Scopes recolor pixels you draw; only the global theme touches the OS.*
106
+
107
+ This mirrors Flutter, where `Theme` nests freely for content while system
108
+ chrome goes through a separate channel (`AnnotatedRegion`/`SystemChrome`).
109
+
110
+ ### Headless control (no provider required)
111
+
112
+ The active theme lives in a module-level singleton, so you can read and
113
+ set it from anywhere — a store, a service, app-boot logic, an effect —
114
+ without a mounted `<ThemeProvider>` ancestor. `useTheme()` resolves to
115
+ this same controller when no provider is in scope (it never throws).
116
+
117
+ ```tsx
118
+ import { themeController } from '@sigx/lynx-daisyui';
119
+
120
+ // From any non-component module:
121
+ themeController.set('daisy-dark');
122
+ themeController.toggle();
123
+ themeController.followSystem();
124
+ themeController.name; // current selection
125
+ ```
126
+
127
+ A mounted root `<ThemeProvider>` binds this singleton, so headless
128
+ mutations render and the OS bars follow.
129
+
130
+ ### Per-screen themes
131
+
132
+ Different screens can use different themes — and the status-bar icons
133
+ follow the active screen so they stay legible. Because this drives the
134
+ **global** theme, the bars update automatically:
135
+
136
+ ```tsx
137
+ import { useScreenTheme } from '@sigx/lynx-daisyui';
138
+
139
+ const Gallery = component(() => {
140
+ useScreenTheme('daisy-dark'); // dark (incl. status bar) while focused; restored on blur
141
+ return () => <view>…</view>;
142
+ });
143
+ ```
144
+
145
+ `useScreenTheme` is built on `@sigx/lynx-navigation`'s `useFocusEffect`
146
+ (an optional peer) and must be called from a routed screen.
147
+
148
+ ### Scoped sub-overrides
149
+
150
+ To recolor just a **region** without touching the OS bars, nest a
151
+ `<ThemeProvider>`. Its subtree (content + icons) re-themes; the status
152
+ bar stays on the global theme.
153
+
154
+ ```tsx
155
+ <ThemeProvider initial="daisy-light">
156
+ <App />
157
+ {/* this card renders synthwave; the status bar stays light */}
158
+ <ThemeProvider initial="daisy-synthwave">
159
+ <PreviewCard />
160
+ </ThemeProvider>
161
+ </ThemeProvider>
162
+ ```
163
+
86
164
  ## Navigation chrome
87
165
 
88
166
  Two daisy-themed components that pair with
89
- [`@sigx/lynx-navigation`](../lynx-navigation). Both read state via the
167
+ [`@sigx/lynx-navigation`](https://github.com/signalxjs/lynx/tree/main/packages/lynx-navigation). Both read state via the
90
168
  navigation package's hooks (no internal-module imports), so swapping
91
169
  in custom designs later is a one-component change.
92
170
 
@@ -146,7 +224,7 @@ one consumer of that hook.
146
224
  ### `<SwiperIndicator>`
147
225
 
148
226
  Themed wrapper around the headless `useSwiperDot*` hooks from
149
- [`@sigx/lynx-gestures`](../lynx-gestures#swiper-and-headless-dot-hooks).
227
+ [`@sigx/lynx-gestures`](https://github.com/signalxjs/lynx/tree/main/packages/lynx-gestures#swiper-and-headless-dot-hooks).
150
228
  Reads colours from the active daisy theme so the indicator follows light
151
229
  / dark mode automatically.
152
230
 
package/dist/index.d.ts CHANGED
@@ -58,6 +58,8 @@ export { ThemeProvider, useTheme, listThemes, registerTheme, extendTheme, pickTh
58
58
  export type { DaisyTheme, ThemeController, ThemeProviderProps, Theme, ThemePalette, ThemeRadius, ThemeVariant, } from './theme/ThemeProvider.js';
59
59
  export { StatusBarSync } from './theme/StatusBarSync.js';
60
60
  export type { StatusBarSyncProps } from './theme/StatusBarSync.js';
61
+ export { themeController } from './theme/theme-state.js';
62
+ export { useScreenTheme } from './theme/use-screen-theme.js';
61
63
  export { Avatar } from './data/Avatar.js';
62
64
  export type { AvatarProps, AvatarSize } from './data/Avatar.js';
63
65
  export { Text } from './typography/Text.js';
package/dist/index.js CHANGED
@@ -34,6 +34,13 @@ export { SwiperIndicator } from './navigation/SwiperIndicator.js';
34
34
  // Theme
35
35
  export { ThemeProvider, useTheme, listThemes, registerTheme, extendTheme, pickThemeFor, pairOf, variantOf, colorsOf, radiusOf, } from './theme/ThemeProvider.js';
36
36
  export { StatusBarSync } from './theme/StatusBarSync.js';
37
+ // Headless theme handle (issue #113): import and call from anywhere — stores,
38
+ // services, effects, app-boot — with no `<ThemeProvider>` ancestor required.
39
+ // `useTheme()` resolves to this when no provider is in scope.
40
+ export { themeController } from './theme/theme-state.js';
41
+ // Per-screen theming: pin the global theme while a navigation screen is focused
42
+ // (requires the optional `@sigx/lynx-navigation` peer).
43
+ export { useScreenTheme } from './theme/use-screen-theme.js';
37
44
  // Data
38
45
  export { Avatar } from './data/Avatar.js';
39
46
  // Typography
@@ -28,10 +28,13 @@ import { jsx as _jsx } from "@sigx/lynx/jsx-runtime";
28
28
  */
29
29
  import { component, effect, onMounted, onUnmounted } from '@sigx/lynx';
30
30
  import { isAvailable, setSystemBarsStyle } from '@sigx/lynx-appearance';
31
- import { useTheme } from './ThemeProvider.js';
31
+ import { themeController } from './theme-state.js';
32
32
  import { variantOf } from './registry.js';
33
33
  export const StatusBarSync = component(({ props }) => {
34
- const theme = useTheme();
34
+ // Bind to the *global* theme not `useTheme()` — so the OS bars always
35
+ // track the app/screen theme and can't be hijacked by a content sub-scope
36
+ // (a nested `<ThemeProvider>` recolors its subtree but leaves the bars put).
37
+ const theme = themeController;
35
38
  let lastApplied = null;
36
39
  let runner;
37
40
  function apply(name) {
@@ -90,8 +90,12 @@ export interface ThemeController {
90
90
  followSystem(): void;
91
91
  }
92
92
  /**
93
- * Access the enclosing daisyui theme controller. Throws when used
94
- * outside `<ThemeProvider>` install a provider at your app root.
93
+ * Access the active daisyui theme controller. Resolves to the nearest
94
+ * `<ThemeProvider>`'s controller (a content sub-scope), or — at the app root
95
+ * and in *headless* code with no provider mounted — the global controller
96
+ * (`themeController`). Never throws: theme control is reachable from anywhere
97
+ * (issue #113). For control that must always target the app/OS theme
98
+ * regardless of scope (e.g. a status-bar sync), import `themeController`.
95
99
  */
96
100
  export declare const useTheme: import("@sigx/runtime-core").InjectableFunction<ThemeController>;
97
101
  export type ThemeProviderProps =
@@ -35,17 +35,28 @@ import { jsx as _jsx } from "@sigx/lynx/jsx-runtime";
35
35
  * <ThemeProvider light="daisy-cupcake" dark="daisy-synthwave">…</ThemeProvider>
36
36
  * ```
37
37
  */
38
- import { component, defineInjectable, defineProvide, onMounted, onUnmounted, signal, } from '@sigx/lynx';
38
+ import { component, defineInjectable, defineProvide, effect, onMounted, onUnmounted, signal, untrack, } from '@sigx/lynx';
39
39
  import { useIconColorResolver } from '@sigx/lynx-icons';
40
40
  import { useSystemColorScheme } from '@sigx/lynx-appearance';
41
- import { colorsOf, pairOf, pickThemeFor, radiusOf } from './registry.js';
41
+ import { colorsOf, pickThemeFor, radiusOf } from './registry.js';
42
+ import { globalThemeState, makeThemeController, themeController, } from './theme-state.js';
42
43
  /**
43
- * Access the enclosing daisyui theme controller. Throws when used
44
- * outside `<ThemeProvider>` install a provider at your app root.
44
+ * Access the active daisyui theme controller. Resolves to the nearest
45
+ * `<ThemeProvider>`'s controller (a content sub-scope), or — at the app root
46
+ * and in *headless* code with no provider mounted — the global controller
47
+ * (`themeController`). Never throws: theme control is reachable from anywhere
48
+ * (issue #113). For control that must always target the app/OS theme
49
+ * regardless of scope (e.g. a status-bar sync), import `themeController`.
45
50
  */
46
- export const useTheme = defineInjectable(() => {
47
- throw new Error('[lynx-daisyui] useTheme() called outside <ThemeProvider>. Wrap your app root with `<ThemeProvider>…</ThemeProvider>`.');
48
- });
51
+ export const useTheme = defineInjectable(() => themeController);
52
+ /**
53
+ * Nesting-depth marker. The outermost `<ThemeProvider>` sees depth 0 and binds
54
+ * the global singleton (so headless `themeController` mutations render and the
55
+ * OS bars track it); a nested provider sees >= 1 and creates its own local
56
+ * state — a content sub-scope that recolors its subtree without touching the
57
+ * global theme or the system bars.
58
+ */
59
+ const useThemeDepth = defineInjectable(() => 0);
49
60
  /**
50
61
  * Wraps children in a `<view class={theme}>` so the daisyui CSS variables
51
62
  * defined inside the theme class inherit down to every descendant.
@@ -64,44 +75,42 @@ export const ThemeProvider = component(({ props, slots }) => {
64
75
  // The underlying signal widens to PrimitiveSignal<string> via Widen<T>;
65
76
  // cast at read sites to keep the narrow union throughout the component.
66
77
  const readScheme = () => systemScheme.value;
67
- // Seed: pin to `initial` if set, otherwise follow system.
68
- const initialState = props.initial
69
- ? { name: props.initial, following: false }
70
- : {
71
- name: readScheme() === 'dark'
78
+ // Root vs. nested. The outermost provider (depth 0) binds the global
79
+ // singleton so headless `themeController` mutations render here and the OS
80
+ // bars (via StatusBarSync) follow this theme. A nested provider gets its own
81
+ // local state: a content sub-scope that overrides its subtree only.
82
+ const depth = useThemeDepth();
83
+ const isRoot = depth === 0;
84
+ defineProvide(useThemeDepth, () => depth + 1);
85
+ const state = isRoot
86
+ ? globalThemeState
87
+ : signal(props.initial
88
+ ? { name: props.initial, following: false }
89
+ : {
90
+ name: readScheme() === 'dark'
91
+ ? (props.dark ?? pickThemeFor('dark'))
92
+ : (props.light ?? pickThemeFor('light')),
93
+ following: true,
94
+ });
95
+ // Seed the root from props/system. An explicit `initial` pin is author
96
+ // intent and wins. With no `initial`, reflect the current system scheme into
97
+ // the first render — but only while `following`, so a theme a headless
98
+ // caller set before this mounted is respected, not clobbered. The follow
99
+ // effect below keeps it in sync afterwards.
100
+ if (isRoot) {
101
+ if (props.initial) {
102
+ state.name = props.initial;
103
+ state.following = false;
104
+ }
105
+ else if (state.following) {
106
+ state.name = readScheme() === 'dark'
72
107
  ? (props.dark ?? pickThemeFor('dark'))
73
- : (props.light ?? pickThemeFor('light')),
74
- following: true,
75
- };
76
- const state = signal(initialState);
77
- // Guard against re-applying the same theme on stray re-fires.
78
- let lastApplied = state.following ? readScheme() : null;
79
- function applySystem(scheme, force = false) {
80
- if (!state.following)
81
- return;
82
- if (!force && lastApplied === scheme)
83
- return;
84
- lastApplied = scheme;
85
- state.name = scheme === 'dark'
86
- ? (props.dark ?? pickThemeFor('dark'))
87
- : (props.light ?? pickThemeFor('light'));
108
+ : (props.light ?? pickThemeFor('light'));
109
+ }
88
110
  }
89
- const controller = {
90
- get name() { return state.name; },
91
- get followingSystem() { return state.following; },
92
- set(next) {
93
- state.name = next;
94
- state.following = false;
95
- },
96
- toggle() {
97
- state.name = pairOf(state.name);
98
- state.following = false;
99
- },
100
- followSystem() {
101
- state.following = true;
102
- applySystem(readScheme(), /* force */ true);
103
- },
104
- };
111
+ const controller = isRoot
112
+ ? themeController
113
+ : makeThemeController(state);
105
114
  defineProvide(useTheme, () => controller);
106
115
  // Wire the daisy color resolver into `@sigx/lynx-icons`'s injectable
107
116
  // so any `<Icon variant="primary">` rendered inside this subtree gets
@@ -118,23 +127,32 @@ export const ThemeProvider = component(({ props, slots }) => {
118
127
  return palette?.[variant];
119
128
  };
120
129
  defineProvide(useIconColorResolver, () => resolver);
121
- // Subscribe to system color-scheme changes. Both PrimitiveSignal and
122
- // Computed expose `.subscribe(fn)` returning an unsubscribe handle —
123
- // we lean on the structural shape so this file doesn't pull
124
- // @sigx/reactivity into its imports.
125
- let unsubscribe;
130
+ // Follow the system color scheme while `following`. Reactive: re-runs when
131
+ // `following` flips true (e.g. `controller.followSystem()`, including the
132
+ // headless `themeController`) or when the OS scheme changes, and writes the
133
+ // matching theme. Reading `state.following` and `systemScheme.value` tracks
134
+ // them; the `name` write is `untrack`ed so it can't re-trigger the effect.
135
+ // Created on mount (the native publisher may populate the scheme between
136
+ // setup and mount) and torn down on unmount.
137
+ let follow;
126
138
  onMounted(() => {
127
- // Re-seed once mounted — covers the case where the native publisher
128
- // populated `__globalProps` between setup and mount.
129
- applySystem(readScheme());
130
- const sig = systemScheme;
131
- if (typeof sig.subscribe === 'function') {
132
- unsubscribe = sig.subscribe(() => applySystem(readScheme()));
133
- }
139
+ follow = effect(() => {
140
+ const following = state.following;
141
+ const scheme = readScheme();
142
+ if (!following)
143
+ return;
144
+ const next = scheme === 'dark'
145
+ ? (props.dark ?? pickThemeFor('dark'))
146
+ : (props.light ?? pickThemeFor('light'));
147
+ untrack(() => {
148
+ if (state.name !== next)
149
+ state.name = next;
150
+ });
151
+ });
134
152
  });
135
153
  onUnmounted(() => {
136
- unsubscribe?.();
137
- unsubscribe = undefined;
154
+ follow?.stop();
155
+ follow = undefined;
138
156
  });
139
157
  return () => {
140
158
  // Every theme is data. Apply its color tokens as inline CSS custom
@@ -0,0 +1,28 @@
1
+ import type { DaisyTheme, ThemeController } from './ThemeProvider.js';
2
+ /** The mutable selection a `ThemeController` reads from and writes to. */
3
+ export interface ThemeState {
4
+ name: DaisyTheme;
5
+ following: boolean;
6
+ }
7
+ /**
8
+ * Build a `ThemeController` over a given state object. Used for both the global
9
+ * singleton (below) and each nested `<ThemeProvider>`'s local state — same
10
+ * behaviour, different backing store. `followSystem()` only flips the flag; the
11
+ * owning provider's follow effect performs the re-apply.
12
+ */
13
+ export declare function makeThemeController(state: ThemeState): ThemeController;
14
+ /**
15
+ * The backing signal for the global theme. Read/written by the root
16
+ * `<ThemeProvider>` and shared with `themeController`; not part of the public
17
+ * API.
18
+ * @internal
19
+ */
20
+ export declare const globalThemeState: import("@sigx/reactivity").Signal<ThemeState>;
21
+ /**
22
+ * The global theme controller — the headless handle for issue #113. Import and
23
+ * call from anywhere (no `<ThemeProvider>` ancestor required); `useTheme()`'s
24
+ * default factory returns this same instance, and the root `<ThemeProvider>`
25
+ * provides it to its subtree. `StatusBarSync` binds to it so the OS bars always
26
+ * follow the global/screen theme, never a content sub-scope.
27
+ */
28
+ export declare const themeController: ThemeController;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Global theme state — the headless DI singleton behind `useTheme()`.
3
+ *
4
+ * The active selection (current theme name + follow-system flag) lives here as
5
+ * a module-level signal, mirroring how `./registry.ts` is already a global
6
+ * module singleton. This is what makes theme control reachable from *headless*
7
+ * code — a store, a service, app-boot logic, an effect — not just from a
8
+ * component mounted under `<ThemeProvider>`.
9
+ *
10
+ * The root `<ThemeProvider>` (depth 0) binds to this state: it renders its host
11
+ * view from it, owns the system-color-scheme follow effect that writes to it
12
+ * while `following`, and seeds an `initial` prop into it. Nested providers
13
+ * (depth >= 1) build their own local state via `makeThemeController` so a
14
+ * subtree can be overridden without touching the global — see
15
+ * `./ThemeProvider.tsx`.
16
+ *
17
+ * `followSystem()` here only flips the flag; the actual re-apply on an OS color
18
+ * scheme change is driven by the root provider's follow effect (which has the
19
+ * appearance signal in scope).
20
+ */
21
+ import { signal } from '@sigx/lynx';
22
+ import { pairOf, pickThemeFor } from './registry.js';
23
+ /**
24
+ * Build a `ThemeController` over a given state object. Used for both the global
25
+ * singleton (below) and each nested `<ThemeProvider>`'s local state — same
26
+ * behaviour, different backing store. `followSystem()` only flips the flag; the
27
+ * owning provider's follow effect performs the re-apply.
28
+ */
29
+ export function makeThemeController(state) {
30
+ return {
31
+ get name() {
32
+ return state.name;
33
+ },
34
+ get followingSystem() {
35
+ return state.following;
36
+ },
37
+ set(next) {
38
+ state.name = next;
39
+ state.following = false;
40
+ },
41
+ toggle() {
42
+ state.name = pairOf(state.name);
43
+ state.following = false;
44
+ },
45
+ followSystem() {
46
+ state.following = true;
47
+ },
48
+ };
49
+ }
50
+ // Object signal (not primitive) so the `DaisyTheme` literal union survives —
51
+ // `signal<T>` widens primitive literals to plain `string` via `Widen<T>`.
52
+ // Seeded to a sane default; the root <ThemeProvider> re-seeds from the system
53
+ // color scheme + its props on mount.
54
+ const state = signal({
55
+ name: pickThemeFor('light'),
56
+ following: true,
57
+ });
58
+ /**
59
+ * The backing signal for the global theme. Read/written by the root
60
+ * `<ThemeProvider>` and shared with `themeController`; not part of the public
61
+ * API.
62
+ * @internal
63
+ */
64
+ export const globalThemeState = state;
65
+ /**
66
+ * The global theme controller — the headless handle for issue #113. Import and
67
+ * call from anywhere (no `<ThemeProvider>` ancestor required); `useTheme()`'s
68
+ * default factory returns this same instance, and the root `<ThemeProvider>`
69
+ * provides it to its subtree. `StatusBarSync` binds to it so the OS bars always
70
+ * follow the global/screen theme, never a content sub-scope.
71
+ */
72
+ export const themeController = makeThemeController(state);
@@ -0,0 +1,3 @@
1
+ import type { DaisyTheme } from './ThemeProvider.js';
2
+ /** Pin the global theme to `name` while this screen is focused; restore on blur. */
3
+ export declare function useScreenTheme(name: DaisyTheme): void;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * `useScreenTheme(name)` — pin the **global** daisy theme while a navigation
3
+ * screen is focused, restoring the previous selection when it blurs.
4
+ *
5
+ * This is the right tool for *per-screen* theming — "this screen is dark, that
6
+ * one is light." Because it drives the global theme (not a content sub-scope),
7
+ * the OS status/navigation bars follow automatically via `<StatusBarSync>`, so
8
+ * the bar icons stay legible against each screen's background. For recoloring a
9
+ * *region within* a screen without touching the bars, nest a `<ThemeProvider>`
10
+ * instead.
11
+ *
12
+ * Built on `useFocusEffect` from `@sigx/lynx-navigation` (an optional peer
13
+ * dependency): it must be called from inside a component rendered as a route by
14
+ * `<Stack>` / `<Tabs>` — the same constraint as `useFocusEffect`/`useIsFocused`.
15
+ *
16
+ * Save/restore composes with the stack (LIFO focus/blur): pushing a themed
17
+ * screen saves whatever was live, applies its own theme, and restores on pop —
18
+ * including resuming follow-system if that's what was active.
19
+ *
20
+ * ```tsx
21
+ * const Gallery = component(() => {
22
+ * useScreenTheme('daisy-dark'); // dark while this screen is on top
23
+ * return () => <view>…</view>;
24
+ * });
25
+ * ```
26
+ */
27
+ import { useFocusEffect } from '@sigx/lynx-navigation';
28
+ import { themeController } from './theme-state.js';
29
+ /** Pin the global theme to `name` while this screen is focused; restore on blur. */
30
+ export function useScreenTheme(name) {
31
+ useFocusEffect(() => {
32
+ const prevName = themeController.name;
33
+ const prevFollowing = themeController.followingSystem;
34
+ themeController.set(name);
35
+ return () => {
36
+ if (prevFollowing)
37
+ themeController.followSystem();
38
+ else
39
+ themeController.set(prevName);
40
+ };
41
+ });
42
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sigx/lynx-daisyui",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "DaisyUI integration for sigx-lynx",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -37,15 +37,15 @@
37
37
  "LICENSE"
38
38
  ],
39
39
  "dependencies": {
40
- "@sigx/lynx": "^0.4.2",
41
- "@sigx/lynx-appearance": "^0.4.2",
42
- "@sigx/lynx-icons": "^0.4.2",
43
- "@sigx/lynx-motion": "^0.4.2",
44
- "@sigx/lynx-gestures": "^0.4.2"
40
+ "@sigx/lynx": "^0.4.3",
41
+ "@sigx/lynx-gestures": "^0.4.3",
42
+ "@sigx/lynx-icons": "^0.4.3",
43
+ "@sigx/lynx-appearance": "^0.4.3",
44
+ "@sigx/lynx-motion": "^0.4.3"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "tailwindcss": "^3.0.0 || ^4.0.0",
48
- "@sigx/lynx-navigation": "^0.4.2"
48
+ "@sigx/lynx-navigation": "^0.4.3"
49
49
  },
50
50
  "peerDependenciesMeta": {
51
51
  "@sigx/lynx-navigation": {
@@ -56,7 +56,7 @@
56
56
  "@typescript/native-preview": "7.0.0-dev.20260521.1",
57
57
  "tailwindcss": "^4.0.0",
58
58
  "typescript": "^6.0.3",
59
- "@sigx/lynx-navigation": "^0.4.2"
59
+ "@sigx/lynx-navigation": "^0.4.3"
60
60
  },
61
61
  "publishConfig": {
62
62
  "access": "public"