ts-obsidian-ui-settings 1.0.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.
- package/CHANGELOG.md +38 -0
- package/LICENSE +21 -0
- package/README.md +233 -0
- package/ROADMAP.md +44 -0
- package/index.d.ts +242 -0
- package/index.js +52 -0
- package/package.json +62 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[BOTTOM](#100---2026-01-11) [LICENSE](LICENSE) [ROADMAP](ROADMAP.md) [README](README.md)
|
|
2
|
+
|
|
3
|
+
# Changelog
|
|
4
|
+
|
|
5
|
+
All notable changes to this project will be documented in this file.
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- No additions yet
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- No changes yet
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- No fixes yet
|
|
20
|
+
|
|
21
|
+
## [1.0.0] - 2026-01-11
|
|
22
|
+
|
|
23
|
+
- Initial version
|
|
24
|
+
|
|
25
|
+
### Features
|
|
26
|
+
|
|
27
|
+
- Type-safe, extensible framework for Obsidian plugin settings
|
|
28
|
+
- Support for multiple sub-tabs with compile-time enforced unique IDs
|
|
29
|
+
- Declarative sub-tab registry for centralized management
|
|
30
|
+
- Decoupled `PluginSettingsRenderContext` for isolated rendering
|
|
31
|
+
- Automatic navigation and active-tab state handling
|
|
32
|
+
- Seamless integration with `ts-obsidian-plugin` settings management
|
|
33
|
+
- Designed for TypeScript and Rollup bundling in Obsidian plugins
|
|
34
|
+
- SubTabs do not manage DOM ownership beyond their render call
|
|
35
|
+
- Settings persistence handled via `plugin.saveSettings()`
|
|
36
|
+
- No global state or side effects introduced by the framework
|
|
37
|
+
|
|
38
|
+
[TOP](#changelog) [LICENSE](LICENSE) [ROADMAP](ROADMAP.md) [README](README.md)
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Dirk Brenckmann, db-developer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
[](https://www.npmjs.com/package/ts-obsidian-ui-settings)
|
|
2
|
+
[](https://opensource.org/licenses/MIT)
|
|
3
|
+
[](https://jsdoc.app/)
|
|
4
|
+

|
|
5
|
+
[](https://codecov.io/gh/db-developer/ts-obsidian-ui-settings)
|
|
6
|
+
|
|
7
|
+
[BOTTOM](#pluginsettingssubtabidmap--pluginsettingssubtabidttabids) [CHANGELOG](CHANGELOG.md) [LICENSE](LICENSE) [ROADMAP](ROADMAP.md)
|
|
8
|
+
|
|
9
|
+
# ts-obsidian-ui-settings
|
|
10
|
+
|
|
11
|
+
This module provides a **type-safe, extensible, and declarative framework** for building plugin settings in Obsidian with multiple sub-tabs. It allows separating rendering, state, and navigation responsibilities while keeping each sub-tab isolated and reusable.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Motivation
|
|
16
|
+
|
|
17
|
+
Obsidian plugins run in a constrained environment:
|
|
18
|
+
|
|
19
|
+
- External runtime dependencies are discouraged
|
|
20
|
+
- `node_modules` are typically not shipped with plugins
|
|
21
|
+
- All code should be bundled into the final plugin file
|
|
22
|
+
|
|
23
|
+
This module is therefore designed to:
|
|
24
|
+
|
|
25
|
+
- Provide centralized navigation and active tab management
|
|
26
|
+
- SubTabs are **isolated** and **reusable** across plugins
|
|
27
|
+
- Full type safety with compile-time enforcement of SubTabIDs
|
|
28
|
+
- Easy to extend by adding new SubTab classes and updating the registry
|
|
29
|
+
- Integrate cleanly with Obsidian’s plugin environment
|
|
30
|
+
- Be **bundled (e.g. via Rollup)** directly into the plugin output
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Features
|
|
35
|
+
|
|
36
|
+
- Strongly typed sub-tab identifiers
|
|
37
|
+
- Compile-time enforcement of unique IDs
|
|
38
|
+
- Declarative sub-tab registry
|
|
39
|
+
- Decoupled render context for each sub-tab
|
|
40
|
+
- Automatic navigation and active-state management
|
|
41
|
+
- Seamlessly works with [ts-obsidian-ui-settings](https://github.com/db-developer/ts-obsidian-ui-settings) settings management
|
|
42
|
+
- Designed for **TypeScript** and **Rollup bundling** in Obsidian plugins
|
|
43
|
+
- SubTabs **do not manage DOM ownership** beyond the render call
|
|
44
|
+
- Settings persistence is always handled via `plugin.saveSettings()`
|
|
45
|
+
- No global state or side effects are introduced by this framework
|
|
46
|
+
|
|
47
|
+
[Details on AI assistance during development](AI.md)
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Usage
|
|
52
|
+
|
|
53
|
+
### 1. Define SubTab IDs
|
|
54
|
+
|
|
55
|
+
Use module augmentation to declare all valid sub-tab IDs:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
// In a plugin-specific module (e.g., "my-plugin/subtabs")
|
|
59
|
+
declare module 'my-plugin/subtabs' {
|
|
60
|
+
interface PluginSettingsSubTabIdMap {
|
|
61
|
+
'general': true;
|
|
62
|
+
'structure': true;
|
|
63
|
+
'ribbons': true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 2. Implement SubTabs
|
|
69
|
+
|
|
70
|
+
Each sub-tab must implement the `PluginSettingsSubTab<TSettings>` interface:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import type { PluginSettingsSubTab, PluginSettingsRenderContext } from "ts-obsidian-ui-settings";
|
|
74
|
+
|
|
75
|
+
export class GeneralSubTab implements PluginSettingsSubTab<MySettings> {
|
|
76
|
+
header = 'General';
|
|
77
|
+
|
|
78
|
+
render(context: PluginSettingsRenderContext<MySettings>) {
|
|
79
|
+
const { containerEl, settings, saveSettings } = context;
|
|
80
|
+
containerEl.empty();
|
|
81
|
+
const el = containerEl.createEl('div', { text: `Current value: ${settings.someOption}` });
|
|
82
|
+
|
|
83
|
+
// Example input
|
|
84
|
+
const input = el.createEl('input', { type: 'text', value: settings.someOption });
|
|
85
|
+
input.addEventListener('change', (e) => {
|
|
86
|
+
settings.someOption = (e.target as HTMLInputElement).value;
|
|
87
|
+
saveSettings();
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 3. Create the SubTab Registry
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import type { PluginSettingsSubTabRegistry } from "ts-obsidian-ui-settings";
|
|
97
|
+
|
|
98
|
+
const subTabRegistry: PluginSettingsSubTabRegistry<MySettings, MyPluginSubTabIdMap> = {
|
|
99
|
+
general: new GeneralSubTab(),
|
|
100
|
+
structure: new StructureSubTab(),
|
|
101
|
+
ribbons: new RibbonsSubTab(),
|
|
102
|
+
};
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 4. Implement the Settings Tab Controller
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { PluginSettingsTabWithSubTabs } from "ts-obsidian-ui-settings";
|
|
109
|
+
|
|
110
|
+
export class MyPluginSettingsTab extends PluginSettingsTabWithSubTabs<
|
|
111
|
+
MySettings,
|
|
112
|
+
MyPluginSubTabIdMap
|
|
113
|
+
> {
|
|
114
|
+
constructor(app: App, plugin: PluginWithSettings<MySettings>) {
|
|
115
|
+
super(app, plugin, subTabRegistry, 'general');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 5. Register the Settings Tab in Obsidian
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
this.addSettingTab(new MyPluginSettingsTab(this.app, this));
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Build & Test
|
|
129
|
+
|
|
130
|
+
The package uses **Rollup** for bundling and Vitest for testing.
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
# Build
|
|
134
|
+
npm run build
|
|
135
|
+
|
|
136
|
+
# Run tests
|
|
137
|
+
npm run test
|
|
138
|
+
|
|
139
|
+
# Run tests in watch mode
|
|
140
|
+
npm run test:watch
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Bundling (Required)
|
|
146
|
+
|
|
147
|
+
Obsidian does not support loading external npm modules at runtime.
|
|
148
|
+
All dependencies must be bundled.
|
|
149
|
+
|
|
150
|
+
### Rollup Example
|
|
151
|
+
|
|
152
|
+
```js
|
|
153
|
+
import resolve from "@rollup/plugin-node-resolve";
|
|
154
|
+
import typescript from "@rollup/plugin-typescript";
|
|
155
|
+
|
|
156
|
+
export default {
|
|
157
|
+
input: "src/main.ts",
|
|
158
|
+
output: {
|
|
159
|
+
file: "dist/main.js",
|
|
160
|
+
format: "cjs",
|
|
161
|
+
},
|
|
162
|
+
plugins: [
|
|
163
|
+
resolve(),
|
|
164
|
+
typescript(),
|
|
165
|
+
],
|
|
166
|
+
external: ["obsidian"],
|
|
167
|
+
};
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## API Reference
|
|
173
|
+
|
|
174
|
+
### `PluginSettingsTabWithSubTabs<TSettings, TTabIds>`
|
|
175
|
+
|
|
176
|
+
Abstract controller class that manages multiple sub-tabs in a plugin settings tab.
|
|
177
|
+
|
|
178
|
+
**Constructor:**
|
|
179
|
+
```ts
|
|
180
|
+
// using import { App } from 'obsidian'
|
|
181
|
+
// using import { PluginWithSettings, PluginSettingsSubTabRegistry, PluginSettingsSubTabId} from 'ts-obsidian-ui-settings'
|
|
182
|
+
constructor(
|
|
183
|
+
app: App,
|
|
184
|
+
plugin: PluginWithSettings<TSettings>,
|
|
185
|
+
subTabs: PluginSettingsSubTabRegistry<TSettings, TTabIds>,
|
|
186
|
+
defaultSubTabId: PluginSettingsSubTabId<TTabIds>
|
|
187
|
+
)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Properties:**
|
|
191
|
+
- `subTabs` – registry of all available sub-tabs
|
|
192
|
+
- `activeSubTabId` – currently active sub-tab identifier
|
|
193
|
+
|
|
194
|
+
**Methods:**
|
|
195
|
+
- `display()` – renders the navigation and the active sub-tab
|
|
196
|
+
- `renderNavigation(containerEl: HTMLElement)` – renders tab buttons
|
|
197
|
+
- `renderActiveSubTab(containerEl: HTMLElement)` – renders the currently active sub-tab
|
|
198
|
+
|
|
199
|
+
### `PluginSettingsSubTab<TSettings>`
|
|
200
|
+
|
|
201
|
+
Interface that each sub-tab must implement.
|
|
202
|
+
|
|
203
|
+
**Properties:**
|
|
204
|
+
- `header: string` – the title displayed in the navigation
|
|
205
|
+
|
|
206
|
+
**Methods:**
|
|
207
|
+
- `render(context: PluginSettingsRenderContext<TSettings>): void` – renders the sub-tab content into the given container
|
|
208
|
+
|
|
209
|
+
### `PluginSettingsRenderContext<TSettings>`
|
|
210
|
+
|
|
211
|
+
Provides a decoupled rendering context for sub-tabs.
|
|
212
|
+
|
|
213
|
+
**Properties:**
|
|
214
|
+
- `containerEl: HTMLElement` – DOM container for rendering
|
|
215
|
+
- `settings: TSettings` – plugin settings object
|
|
216
|
+
- `saveSettings(): Promise<void>` – triggers settings persistence
|
|
217
|
+
- `plugin: PluginWithSettings<TSettings>` – plugin reference
|
|
218
|
+
|
|
219
|
+
### `PluginSettingsSubTabRegistry<TSettings, TTabIds>`
|
|
220
|
+
|
|
221
|
+
A declarative mapping from sub-tab IDs to sub-tab instances.
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
type PluginSettingsSubTabRegistry<TSettings, TTabIds extends PluginSettingsSubTabIdMap> = {
|
|
225
|
+
[K in PluginSettingsSubTabId<TTabIds>]: PluginSettingsSubTab<TSettings>;
|
|
226
|
+
};
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### `PluginSettingsSubTabIdMap` / `PluginSettingsSubTabId<TTabIds>`
|
|
230
|
+
|
|
231
|
+
Compile-time registry and union type of valid sub-tab identifiers using module augmentation.
|
|
232
|
+
|
|
233
|
+
[TOP](#ts-obsidian-ui-settings) [CHANGELOG](CHANGELOG.md) [LICENSE](LICENSE) [ROADMAP](ROADMAP.md)
|
package/ROADMAP.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
[BOTTOM](#notes) [CHANGELOG](CHANGELOG.md) [LICENSE](LICENSE) [README](README.md)
|
|
2
|
+
|
|
3
|
+
# Roadmap
|
|
4
|
+
|
|
5
|
+
This roadmap outlines planned features, improvements, and long-term goals for the **ts-obsidian-ui-settings** framework.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Short-term Goals (Next Release Cycle)
|
|
10
|
+
|
|
11
|
+
- Complete unit test coverage for all core classes and types
|
|
12
|
+
- Add examples for complex sub-tab interactions
|
|
13
|
+
- Improve documentation and API reference
|
|
14
|
+
- Enhance TypeScript type safety for nested sub-tabs and settings
|
|
15
|
+
- Introduce helper utilities for common sub-tab patterns
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Mid-term Goals
|
|
20
|
+
|
|
21
|
+
- Provide built-in support for dynamic sub-tab loading
|
|
22
|
+
- Implement optional persistent tab state across plugin reloads
|
|
23
|
+
- Offer theming options for tab navigation buttons
|
|
24
|
+
- Add more examples and templates for plugin developers
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Long-term Goals
|
|
29
|
+
|
|
30
|
+
- Support fully pluggable sub-tab layouts
|
|
31
|
+
- Integrate with other Obsidian plugin ecosystems
|
|
32
|
+
- Provide a CLI or generator to scaffold settings tabs
|
|
33
|
+
- Enable advanced type-level validation for plugin settings and dependencies
|
|
34
|
+
- Explore support for internationalized sub-tab headers and dynamic translations
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Notes
|
|
39
|
+
|
|
40
|
+
- All development will maintain full TypeScript compatibility.
|
|
41
|
+
- No external runtime dependencies will be introduced, to remain fully bundleable in Obsidian plugins.
|
|
42
|
+
- Sub-tabs will remain isolated and reusable across plugins.
|
|
43
|
+
|
|
44
|
+
[TOP](#roadmap) [CHANGELOG](CHANGELOG.md) [LICENSE](LICENSE) [README](README.md)
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { PluginWithSettings } from 'ts-obsidian-plugin';
|
|
2
|
+
import { PluginSettingTab, Plugin, App } from 'obsidian';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Provides all dependencies required by a plugin settings sub-tab
|
|
6
|
+
* to render itself without owning navigation, lifecycle, or state
|
|
7
|
+
* management responsibilities.
|
|
8
|
+
*
|
|
9
|
+
* This context intentionally decouples rendering logic from the
|
|
10
|
+
* concrete PluginSettingTab implementation and avoids implicit
|
|
11
|
+
* access to global plugin state.
|
|
12
|
+
*
|
|
13
|
+
* @template TSettings
|
|
14
|
+
* The concrete settings type used by the plugin.
|
|
15
|
+
*/
|
|
16
|
+
interface PluginSettingsRenderContext<TSettings extends object> {
|
|
17
|
+
/**
|
|
18
|
+
* The DOM element into which the sub-tab must render its UI.
|
|
19
|
+
*
|
|
20
|
+
* This container is guaranteed to be empty before rendering
|
|
21
|
+
* and is owned by the parent settings tab controller.
|
|
22
|
+
*/
|
|
23
|
+
readonly containerEl: HTMLElement;
|
|
24
|
+
/**
|
|
25
|
+
* The current plugin settings instance.
|
|
26
|
+
*
|
|
27
|
+
* This object is mutable; however, persistence must be triggered
|
|
28
|
+
* explicitly via {@link saveSettings}.
|
|
29
|
+
*/
|
|
30
|
+
readonly settings: TSettings;
|
|
31
|
+
/**
|
|
32
|
+
* Persists the current settings state.
|
|
33
|
+
*
|
|
34
|
+
* Sub-tabs must call this method after mutating settings in order
|
|
35
|
+
* to ensure durability across reloads.
|
|
36
|
+
*/
|
|
37
|
+
saveSettings(): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Reference to the hosting plugin instance.
|
|
40
|
+
*
|
|
41
|
+
* The exposed type is intentionally restricted to
|
|
42
|
+
* {@link PluginWithSettings} in order to prevent sub-tabs
|
|
43
|
+
* from relying on concrete plugin implementations.
|
|
44
|
+
*/
|
|
45
|
+
readonly plugin: PluginWithSettings<TSettings>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Contract implemented by a single settings sub-tab.
|
|
50
|
+
*
|
|
51
|
+
* A sub-tab represents an isolated, self-contained rendering unit
|
|
52
|
+
* within a plugin settings page. It must not manage navigation,
|
|
53
|
+
* active state, or DOM cleanup outside of its assigned container.
|
|
54
|
+
*
|
|
55
|
+
* @template TSettings
|
|
56
|
+
* The concrete settings type used by the plugin.
|
|
57
|
+
*/
|
|
58
|
+
interface PluginSettingsSubTab<TSettings extends object> {
|
|
59
|
+
/**
|
|
60
|
+
* Human-readable tab header text.
|
|
61
|
+
*
|
|
62
|
+
* This value is typically derived from an i18n service and is
|
|
63
|
+
* expected to be stable for the lifetime of the tab instance.
|
|
64
|
+
*/
|
|
65
|
+
get header(): string;
|
|
66
|
+
/**
|
|
67
|
+
* Renders the complete UI for this sub-tab.
|
|
68
|
+
*
|
|
69
|
+
* Implementations must assume that the provided container is empty
|
|
70
|
+
* and must not retain references to DOM elements beyond this call.
|
|
71
|
+
*
|
|
72
|
+
* @param ctx
|
|
73
|
+
* Rendering context providing access to settings, persistence,
|
|
74
|
+
* and plugin services.
|
|
75
|
+
*/
|
|
76
|
+
render(ctx: PluginSettingsRenderContext<TSettings>): void;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Compile-time registry for all valid plugin settings sub-tab identifiers.
|
|
81
|
+
*
|
|
82
|
+
* This interface is intentionally empty and must be augmented via
|
|
83
|
+
* TypeScript module augmentation by consumers.
|
|
84
|
+
*
|
|
85
|
+
* The registry has no runtime representation and exists solely
|
|
86
|
+
* to enforce compile-time correctness and uniqueness of sub-tab IDs.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* // In a plugin-specific module (e.g. "my-plugin/subtabs")
|
|
91
|
+
* declare module "my-plugin/subtabs" {
|
|
92
|
+
* interface PluginSettingsSubTabIdMap {
|
|
93
|
+
* "general": true;
|
|
94
|
+
* "structure": true;
|
|
95
|
+
* "ribbons": true;
|
|
96
|
+
* }
|
|
97
|
+
* }
|
|
98
|
+
*
|
|
99
|
+
* type SubTabId = keyof PluginSettingsSubTabIdMap;
|
|
100
|
+
* // "general" | "structure" | "ribbons"
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
interface PluginSettingsSubTabIdMap {
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Union type of all registered plugin settings sub-tab identifiers.
|
|
108
|
+
*
|
|
109
|
+
* This type represents the complete set of valid sub-tab IDs that
|
|
110
|
+
* may be used for navigation and persistence.
|
|
111
|
+
*/
|
|
112
|
+
type PluginSettingsSubTabId<T extends PluginSettingsSubTabIdMap> = keyof T;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Declarative registry of settings sub-tabs.
|
|
116
|
+
*
|
|
117
|
+
* The keys of this registry are strongly typed sub-tab identifiers,
|
|
118
|
+
* guaranteed at compile time to be unique and valid.
|
|
119
|
+
*
|
|
120
|
+
* The registry is purely declarative and contains no UI state.
|
|
121
|
+
* Navigation, active state, and rendering orchestration are handled
|
|
122
|
+
* by the hosting settings tab controller.
|
|
123
|
+
*
|
|
124
|
+
* @template TSettings
|
|
125
|
+
* The concrete settings type used by the plugin.
|
|
126
|
+
*
|
|
127
|
+
* @template TTabIds
|
|
128
|
+
* The compile-time map of valid sub-tab identifiers, typically
|
|
129
|
+
* provided via TypeScript module augmentation.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```ts
|
|
133
|
+
* // 1. Declare valid sub-tab identifiers via module augmentation
|
|
134
|
+
* declare module "my-plugin/subtabs" {
|
|
135
|
+
* interface PluginSettingsSubTabIdMap {
|
|
136
|
+
* "general": true;
|
|
137
|
+
* "structure": true;
|
|
138
|
+
* "ribbons": true;
|
|
139
|
+
* }
|
|
140
|
+
* }
|
|
141
|
+
*
|
|
142
|
+
* // 2. Derive the union type of valid sub-tab identifiers
|
|
143
|
+
* type SubTabId = keyof PluginSettingsSubTabIdMap;
|
|
144
|
+
* // → "general" | "structure" | "ribbons"
|
|
145
|
+
*
|
|
146
|
+
* // 3. Define a registry mapping each sub-tab ID to its implementation
|
|
147
|
+
* const subTabs: PluginSettingsSubTabRegistry<
|
|
148
|
+
* MyPluginSettings,
|
|
149
|
+
* PluginSettingsSubTabIdMap
|
|
150
|
+
* > = {
|
|
151
|
+
* general: new GeneralSettingsSubTab(),
|
|
152
|
+
* structure: new StructureSettingsSubTab(),
|
|
153
|
+
* ribbons: new RibbonSettingsSubTab(),
|
|
154
|
+
* };
|
|
155
|
+
*
|
|
156
|
+
* // Any missing, misspelled, or duplicate key would result
|
|
157
|
+
* // in a compile-time error.
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
type PluginSettingsSubTabRegistry<TSettings, TTabIds extends PluginSettingsSubTabIdMap> = {
|
|
161
|
+
[K in PluginSettingsSubTabId<TTabIds>]: PluginSettingsSubTab<TSettings>;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Abstract settings tab controller with support for strongly typed sub-tabs.
|
|
166
|
+
*
|
|
167
|
+
* This class implements a declarative, extensible, and type-safe mechanism
|
|
168
|
+
* for rendering plugin settings using multiple sub-tabs.
|
|
169
|
+
*
|
|
170
|
+
* Responsibilities:
|
|
171
|
+
* - Rendering the navigation UI for sub-tabs
|
|
172
|
+
* - Managing the active sub-tab state
|
|
173
|
+
* - Providing a stable render context to sub-tabs
|
|
174
|
+
* - Orchestrating re-rendering on tab switches
|
|
175
|
+
*
|
|
176
|
+
* Non-responsibilities:
|
|
177
|
+
* - Rendering individual settings controls
|
|
178
|
+
* - Owning sub-tab DOM elements beyond the active container
|
|
179
|
+
* - Persisting settings directly (delegated to the plugin)
|
|
180
|
+
*
|
|
181
|
+
* @template TSettings
|
|
182
|
+
* The concrete settings object type used by the plugin.
|
|
183
|
+
*
|
|
184
|
+
* @template TTabIds
|
|
185
|
+
* The compile-time registry of valid sub-tab identifiers.
|
|
186
|
+
*/
|
|
187
|
+
declare abstract class PluginSettingsTabWithSubTabs<TSettings extends object, TTabIds extends PluginSettingsSubTabIdMap> extends PluginSettingTab {
|
|
188
|
+
protected readonly plugin: Plugin & PluginWithSettings<TSettings>;
|
|
189
|
+
/**
|
|
190
|
+
* Declarative registry of all available sub-tabs.
|
|
191
|
+
*
|
|
192
|
+
* The keys of this registry are compile-time validated sub-tab
|
|
193
|
+
* identifiers, ensuring correctness and uniqueness.
|
|
194
|
+
*/
|
|
195
|
+
protected readonly subTabs: PluginSettingsSubTabRegistry<TSettings, TTabIds>;
|
|
196
|
+
/**
|
|
197
|
+
* The currently active sub-tab identifier.
|
|
198
|
+
*
|
|
199
|
+
* This state is fully owned by the controller and never exposed
|
|
200
|
+
* to individual sub-tabs.
|
|
201
|
+
*/
|
|
202
|
+
protected activeSubTabId: PluginSettingsSubTabId<TTabIds>;
|
|
203
|
+
/**
|
|
204
|
+
* Protected constructor for a settings tab with multiple sub-tabs.
|
|
205
|
+
*
|
|
206
|
+
* This constructor initializes the navigation and active tab state.
|
|
207
|
+
*
|
|
208
|
+
* @template TSettings The concrete plugin settings type.
|
|
209
|
+
* @template TTabIds The compile-time map of valid sub-tab identifiers.
|
|
210
|
+
*
|
|
211
|
+
* @param app The Obsidian application instance.
|
|
212
|
+
* @param plugin The hosting plugin instance, implementing both
|
|
213
|
+
* `Plugin` and `PluginWithSettings<TSettings>`.
|
|
214
|
+
* @param subTabs A declarative registry of all sub-tabs.
|
|
215
|
+
* @param defaultSubTabId The default sub-tab to activate on display.
|
|
216
|
+
*/
|
|
217
|
+
protected constructor(app: App, plugin: Plugin & PluginWithSettings<TSettings>, subTabs: PluginSettingsSubTabRegistry<TSettings, TTabIds>, defaultSubTabId: PluginSettingsSubTabId<TTabIds>);
|
|
218
|
+
/**
|
|
219
|
+
* Called by Obsidian when the settings tab should be rendered.
|
|
220
|
+
*
|
|
221
|
+
* This method is final and must not be overridden by subclasses.
|
|
222
|
+
* Customization is achieved exclusively through sub-tab registration.
|
|
223
|
+
*/
|
|
224
|
+
display(): void;
|
|
225
|
+
/**
|
|
226
|
+
* Renders the navigation UI for all registered sub-tabs.
|
|
227
|
+
*
|
|
228
|
+
* @param containerEl
|
|
229
|
+
* The container element used to host the navigation controls.
|
|
230
|
+
*/
|
|
231
|
+
protected renderNavigation(containerEl: HTMLElement): void;
|
|
232
|
+
/**
|
|
233
|
+
* Renders the currently active sub-tab.
|
|
234
|
+
*
|
|
235
|
+
* @param containerEl
|
|
236
|
+
* The container element dedicated to sub-tab content.
|
|
237
|
+
*/
|
|
238
|
+
protected renderActiveSubTab(containerEl: HTMLElement): void;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export { PluginSettingsTabWithSubTabs };
|
|
242
|
+
export type { PluginSettingsRenderContext, PluginSettingsSubTab, PluginSettingsSubTabId, PluginSettingsSubTabIdMap, PluginSettingsSubTabRegistry };
|
package/index.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { PluginSettingTab } from "obsidian";
|
|
2
|
+
|
|
3
|
+
class PluginSettingsTabWithSubTabs extends PluginSettingTab {
|
|
4
|
+
plugin;
|
|
5
|
+
subTabs;
|
|
6
|
+
activeSubTabId;
|
|
7
|
+
constructor(app, plugin, subTabs, defaultSubTabId) {
|
|
8
|
+
super(app, plugin);
|
|
9
|
+
this.plugin = plugin;
|
|
10
|
+
this.subTabs = subTabs;
|
|
11
|
+
this.activeSubTabId = defaultSubTabId;
|
|
12
|
+
}
|
|
13
|
+
display() {
|
|
14
|
+
this.containerEl.empty();
|
|
15
|
+
const navigationEl = this.containerEl.createDiv({
|
|
16
|
+
cls: "plugin-settings-subtab-nav"
|
|
17
|
+
});
|
|
18
|
+
const contentEl = this.containerEl.createDiv({
|
|
19
|
+
cls: "plugin-settings-subtab-content"
|
|
20
|
+
});
|
|
21
|
+
this.renderNavigation(navigationEl);
|
|
22
|
+
this.renderActiveSubTab(contentEl);
|
|
23
|
+
}
|
|
24
|
+
renderNavigation(containerEl) {
|
|
25
|
+
Object.keys(this.subTabs).forEach(tabId => {
|
|
26
|
+
const subTab = this.subTabs[tabId];
|
|
27
|
+
const button = containerEl.createEl("button", {
|
|
28
|
+
text: subTab.header,
|
|
29
|
+
cls: tabId === this.activeSubTabId ? "is-active" : undefined
|
|
30
|
+
});
|
|
31
|
+
button.addEventListener("click", () => {
|
|
32
|
+
if (this.activeSubTabId !== tabId) {
|
|
33
|
+
this.activeSubTabId = tabId;
|
|
34
|
+
this.display();
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
renderActiveSubTab(containerEl) {
|
|
40
|
+
const subTab = this.subTabs[this.activeSubTabId];
|
|
41
|
+
const context = {
|
|
42
|
+
containerEl: containerEl,
|
|
43
|
+
settings: this.plugin.settings,
|
|
44
|
+
saveSettings: () => this.plugin.saveSettings(),
|
|
45
|
+
plugin: this.plugin
|
|
46
|
+
};
|
|
47
|
+
subTab.render(context);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export { PluginSettingsTabWithSubTabs };
|
|
52
|
+
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VzIjpbInNyYy9saWIvUGx1Z2luU2V0dGluZ3NUYWJXaXRoU3ViVGFicy50cyJdLCJuYW1lcyI6WyJQbHVnaW5TZXR0aW5nc1RhYldpdGhTdWJUYWJzIiwiUGx1Z2luU2V0dGluZ1RhYiIsInBsdWdpbiIsInN1YlRhYnMiLCJhY3RpdmVTdWJUYWJJZCIsImNvbnN0cnVjdG9yIiwiYXBwIiwiZGVmYXVsdFN1YlRhYklkIiwic3VwZXIiLCJ0aGlzIiwiZGlzcGxheSIsImNvbnRhaW5lckVsIiwiZW1wdHkiLCJuYXZpZ2F0aW9uRWwiLCJjcmVhdGVEaXYiLCJjbHMiLCJjb250ZW50RWwiLCJyZW5kZXJOYXZpZ2F0aW9uIiwicmVuZGVyQWN0aXZlU3ViVGFiIiwiT2JqZWN0Iiwia2V5cyIsImZvckVhY2giLCJ0YWJJZCIsInN1YlRhYiIsImJ1dHRvbiIsImNyZWF0ZUVsIiwidGV4dCIsImhlYWRlciIsInVuZGVmaW5lZCIsImFkZEV2ZW50TGlzdGVuZXIiLCJjb250ZXh0Iiwic2V0dGluZ3MiLCJzYXZlU2V0dGluZ3MiLCJyZW5kZXIiXSwibWFwcGluZ3MiOiI7O0FBaUNNLE1BQWdCQSxxQ0FHWkM7SUFvQ2FDO0lBNUJGQztJQVNUQztJQWlCVixXQUFBQyxDQUNFQyxLQUNtQkosUUFDbkJDLFNBQ0FJO1FBRUFDLE1BQU1GLEtBQUtKO1FBSlFPLEtBQUFQLFNBQUFBO1FBTW5CTyxLQUFLTixVQUFVQTtRQUNmTSxLQUFLTCxpQkFBaUJHO0FBQ3hCO0lBUVMsT0FBQUc7UUFDUEQsS0FBS0UsWUFBWUM7UUFFakIsTUFBTUMsZUFBZUosS0FBS0UsWUFBWUcsVUFBVTtZQUM5Q0MsS0FBSzs7UUFHUCxNQUFNQyxZQUFZUCxLQUFLRSxZQUFZRyxVQUFVO1lBQzNDQyxLQUFLOztRQUdQTixLQUFLUSxpQkFBaUJKO1FBQ3RCSixLQUFLUyxtQkFBbUJGO0FBQzFCO0lBUVUsZ0JBQUFDLENBQWlCTjtRQUN4QlEsT0FBT0MsS0FBS1gsS0FBS04sU0FFZmtCLFFBQVNDO1lBRVYsTUFBTUMsU0FBU2QsS0FBS04sUUFBUW1CO1lBRTVCLE1BQU1FLFNBQVNiLFlBQVljLFNBQVMsVUFBVTtnQkFDNUNDLE1BQU1ILE9BQU9JO2dCQUNiWixLQUFLTyxVQUFVYixLQUFLTCxpQkFDaEIsY0FDQXdCOztZQUdOSixPQUFPSyxpQkFBaUIsU0FBUztnQkFDL0IsSUFBSXBCLEtBQUtMLG1CQUFtQmtCLE9BQU87b0JBQ2pDYixLQUFLTCxpQkFBaUJrQjtvQkFDdEJiLEtBQUtDO0FBQ1A7OztBQUdOO0lBUVUsa0JBQUFRLENBQW1CUDtRQUMzQixNQUFNWSxTQUFTZCxLQUFLTixRQUFRTSxLQUFLTDtRQUVqQyxNQUFNMEIsVUFBa0Q7WUFDdERuQjtZQUNBb0IsVUFBVXRCLEtBQUtQLE9BQU82QjtZQUN0QkMsY0FBYyxNQUFNdkIsS0FBS1AsT0FBTzhCO1lBQ2hDOUIsUUFBUU8sS0FBS1A7O1FBR2ZxQixPQUFPVSxPQUFPSDtBQUNoQjs7OyJ9
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ts-obsidian-ui-settings",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Unified abstraction for obsidian setting ui with support für tabs",
|
|
5
|
+
"author": "Dirk Brenckmann <db.developer@gmx.de>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "index.js",
|
|
8
|
+
"module": "index.js",
|
|
9
|
+
"types": "index.d.ts",
|
|
10
|
+
"type": "module",
|
|
11
|
+
"sideEffects": false,
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./index.js",
|
|
15
|
+
"types": "./index.d.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "rollup --config .config/rollup.config.mjs --environment BUILD:production",
|
|
20
|
+
"coverage": "vitest run --coverage --config .config/vitest.config.ts",
|
|
21
|
+
"package": "npm run build && npm pack",
|
|
22
|
+
"test": "vitest --config .config/vitest.config.ts",
|
|
23
|
+
"test:watch": "vitest --watch --config .config/vitest.config.ts",
|
|
24
|
+
"test:ui": "vitest --ui --config .config/vitest.config.ts"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"index.js",
|
|
28
|
+
"index.d.ts",
|
|
29
|
+
"CHANGELOG.md",
|
|
30
|
+
"LICENSE",
|
|
31
|
+
"README.md",
|
|
32
|
+
"ROADMAP.md"
|
|
33
|
+
],
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"obsidian": ">=1.5.0"
|
|
36
|
+
},
|
|
37
|
+
"peerDependenciesMeta": {
|
|
38
|
+
"obsidian": {
|
|
39
|
+
"optional": false
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@rollup/plugin-commonjs": "^29.0.0",
|
|
44
|
+
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
45
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
46
|
+
"@rollup/plugin-typescript": "^12.3.0",
|
|
47
|
+
"@trivago/prettier-plugin-sort-imports": "^6.0.0",
|
|
48
|
+
"@types/node": "^24.10.0",
|
|
49
|
+
"@vitest/coverage-v8": "^4.0.15",
|
|
50
|
+
"@vitest/ui": "^4.0.15",
|
|
51
|
+
"jsdom": "^27.4.0",
|
|
52
|
+
"obsidian": "^1.10.2-1",
|
|
53
|
+
"prettier": "^3.7.4",
|
|
54
|
+
"prettier-plugin-align": "^0.0.1-alpha.1",
|
|
55
|
+
"rollup": "^4.53.1",
|
|
56
|
+
"rollup-plugin-dts": "^6.3.0",
|
|
57
|
+
"ts-obsidian-plugin": "^1.1.1",
|
|
58
|
+
"tslib": "^2.8.1",
|
|
59
|
+
"typescript": "^5.9.3",
|
|
60
|
+
"vitest": "^4.0.15"
|
|
61
|
+
}
|
|
62
|
+
}
|