@vnejs/plugins.views.settings 0.1.3 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +6 -0
  3. package/dist/modules/controller.d.ts +44 -0
  4. package/dist/modules/controller.js +169 -0
  5. package/dist/modules/view.d.ts +11 -0
  6. package/dist/modules/view.js +10 -0
  7. package/dist/types.d.ts +10 -0
  8. package/dist/types.js +1 -0
  9. package/dist/utils/settings.d.ts +25 -0
  10. package/dist/utils/settings.js +1 -0
  11. package/dist/view/SettingsExampleText.d.ts +9 -0
  12. package/dist/view/SettingsExampleText.js +25 -0
  13. package/dist/view/SettingsTabContent.d.ts +15 -0
  14. package/dist/view/SettingsTabContent.js +32 -0
  15. package/dist/view/index.d.ts +3 -0
  16. package/dist/view/index.js +18 -0
  17. package/package.json +52 -7
  18. package/src/index.ts +9 -0
  19. package/src/modules/controller.ts +221 -0
  20. package/src/modules/view.ts +22 -0
  21. package/src/tests/controller.test.ts +240 -0
  22. package/src/tests/setup.ts +44 -0
  23. package/src/types.ts +20 -0
  24. package/src/utils/settings.ts +28 -0
  25. package/src/view/SettingsExampleText.tsx +61 -0
  26. package/src/view/SettingsTabContent.tsx +118 -0
  27. package/src/view/index.tsx +63 -0
  28. package/tsconfig.json +10 -0
  29. package/const/events.js +0 -7
  30. package/const/params.js +0 -43
  31. package/index.js +0 -9
  32. package/modules/controller.js +0 -132
  33. package/modules/view.js +0 -14
  34. package/view/components/SettingsControls.jsx +0 -22
  35. package/view/components/SettingsCross.jsx +0 -13
  36. package/view/components/SettingsExampleText.jsx +0 -40
  37. package/view/components/SettingsTabContent.jsx +0 -66
  38. package/view/components/index.js +0 -4
  39. package/view/index.jsx +0 -42
@@ -0,0 +1,221 @@
1
+ import { ModuleController } from "@vnejs/module.components";
2
+ import type { SettingsField } from "@vnejs/contracts.views.settings";
3
+
4
+ import type { SettingsViewPluginConstants, SettingsViewPluginEvents, SettingsViewPluginParams, SettingsViewPluginSettings } from "../types.js";
5
+ import type { SettingsViewPluginState, SettingsViewTabContent, SettingsViewTabPayload } from "../utils/settings.js";
6
+
7
+ export class SettingsViewController extends ModuleController<
8
+ SettingsViewPluginEvents,
9
+ SettingsViewPluginConstants,
10
+ SettingsViewPluginSettings,
11
+ SettingsViewPluginParams,
12
+ SettingsViewPluginState
13
+ > {
14
+ name = "settings.fields.controller";
15
+
16
+ updateEvent = this.EVENTS.SETTINGS_VIEW.UPDATE;
17
+ controlsIndex = this.PARAMS.SETTINGS_VIEW.ZINDEX;
18
+
19
+ tabKeyOf = ({ tabKey, tab }: SettingsViewTabPayload = {}) => tabKey || tab?.titleLocKey || "default";
20
+
21
+ tabContentOf = (tab: SettingsViewTabPayload["tab"], locs?: Record<string, string>): SettingsViewTabContent => ({
22
+ fields: tab?.fields ?? [],
23
+ showTextExample: Boolean(tab?.showTextExample),
24
+ locs,
25
+ });
26
+
27
+ applyTab = (payload: SettingsViewTabPayload = {}) => {
28
+ const { tab, locs } = payload;
29
+ const fields = tab?.fields ?? [];
30
+ const key = this.tabKeyOf(payload);
31
+
32
+ this.maxCurrentItem = Math.max((fields.length ?? 1) - 1, 0);
33
+
34
+ const tabsContent = payload.tabs
35
+ ? Object.fromEntries(
36
+ Object.entries(payload.tabs).map(([tabKey, settingsTab]) => [tabKey, this.tabContentOf(settingsTab, locs)]),
37
+ )
38
+ : { ...(this.state.tabsContent ?? {}), [key]: this.tabContentOf(tab, locs) };
39
+
40
+ return {
41
+ tab,
42
+ tabKey: key,
43
+ tabsContent,
44
+ fields,
45
+ locs,
46
+ showTextExample: Boolean(tab?.showTextExample),
47
+ currentItemInner: 0,
48
+ };
49
+ };
50
+
51
+ onShowFields = (payload: SettingsViewTabPayload = {}) => this.onShow(payload);
52
+
53
+ onTabSet = async (payload: SettingsViewTabPayload = {}) => {
54
+ if (!this.state.isShow) return this.onShow(payload);
55
+
56
+ const isForce = Boolean(payload.isForce || this.shared.viewForceAnimationSources?.isNotEmpty());
57
+
58
+ this.currentItem = null;
59
+
60
+ const next = { ...this.applyTab(payload), currentItem: null as null };
61
+
62
+ if (isForce) return this.updateStateAndViewForce(next);
63
+
64
+ const key = next.tabKey!;
65
+ const alreadyMounted = Boolean(this.state.tabsContent?.[key]);
66
+
67
+ // Mount the incoming tab at opacity 0 first so CSS can crossfade both layers together.
68
+ if (!alreadyMounted) {
69
+ await this.updateStateAndViewFast({ tabsContent: next.tabsContent });
70
+ await this.waitRerender();
71
+ }
72
+
73
+ return this.updateStateAndView(next);
74
+ };
75
+
76
+ getFocusedField = () => {
77
+ const { currentItem, fields = [] } = this.state;
78
+
79
+ if (currentItem == null || !fields.length) return;
80
+
81
+ return fields[currentItem];
82
+ };
83
+
84
+ innerIndexOfField = (field?: SettingsField) => {
85
+ const options = field?.options ?? [];
86
+ if (!options.length) return 0;
87
+
88
+ const settingKey = field?.settingKey;
89
+ if (!settingKey) return 0;
90
+
91
+ const index = options.findIndex((option) => option.value === this.shared.settings[settingKey]);
92
+
93
+ return index >= 0 ? index : 0;
94
+ };
95
+
96
+ optionIndexOf = (length: number, index = 0) => {
97
+ if (!length) return 0;
98
+
99
+ return ((index % length) + length) % length;
100
+ };
101
+
102
+ shiftOptions = (direction: -1 | 1) => {
103
+ const field = this.getFocusedField();
104
+ const options = field?.options ?? [];
105
+
106
+ if (!options.length) return;
107
+
108
+ const currentItemInner = this.optionIndexOf(options.length, (this.state.currentItemInner ?? 0) + direction);
109
+ const option = options[currentItemInner];
110
+ const settingKey = field?.settingKey;
111
+
112
+ void this.updateStateAndViewFast({ currentItemInner });
113
+
114
+ if (settingKey && option && !option.action) {
115
+ void this.emit(this.EVENTS.SETTINGS.SET, { name: settingKey, value: option.value });
116
+ }
117
+ };
118
+
119
+ setCurrentItem = async (value: number, defaultValue: number) => {
120
+ if (this.currentItem === null) {
121
+ this.currentItem = defaultValue;
122
+ } else {
123
+ this.currentItem = value;
124
+ if (this.currentItem > this.maxCurrentItem) this.currentItem = 0;
125
+ if (this.currentItem < 0) this.currentItem = this.maxCurrentItem;
126
+ }
127
+
128
+ const currentItemInner = this.innerIndexOfField(this.state.fields?.[this.currentItem]);
129
+
130
+ await this.updateStateAndViewFast({ currentItem: this.currentItem, currentItemInner });
131
+ };
132
+
133
+ onArrowLeft = () => {
134
+ const field = this.getFocusedField();
135
+
136
+ if (!field) return;
137
+
138
+ if (field.options?.length) return this.shiftOptions(-1);
139
+
140
+ const { settingKey = "", step = 0, min = 0, type = "" } = field;
141
+ if (!settingKey) return;
142
+
143
+ if (type === "toggle") {
144
+ return this.shared.settings[settingKey] === true && void this.emit(this.EVENTS.SETTINGS.SET, { name: settingKey, value: false });
145
+ }
146
+
147
+ if (type === "range") {
148
+ const newValue = Number(Number(Number(this.shared.settings[settingKey]) - step).toFixed(4));
149
+
150
+ return newValue >= min && void this.emit(this.EVENTS.SETTINGS.SET, { name: settingKey, value: newValue });
151
+ }
152
+ };
153
+
154
+ onArrowRight = () => {
155
+ const field = this.getFocusedField();
156
+
157
+ if (!field) return;
158
+
159
+ if (field.options?.length) return this.shiftOptions(1);
160
+
161
+ const { settingKey = "", step = 0, max = 0, type = "" } = field;
162
+ if (!settingKey) return;
163
+
164
+ if (type === "toggle") {
165
+ return this.shared.settings[settingKey] === false && void this.emit(this.EVENTS.SETTINGS.SET, { name: settingKey, value: true });
166
+ }
167
+
168
+ if (type === "range") {
169
+ const newValue = Number(Number(Number(this.shared.settings[settingKey]) + step).toFixed(4));
170
+
171
+ return newValue <= max && void this.emit(this.EVENTS.SETTINGS.SET, { name: settingKey, value: newValue });
172
+ }
173
+ };
174
+
175
+ onInteract = () => {
176
+ const field = this.getFocusedField();
177
+
178
+ if (!field) return;
179
+
180
+ const { settingKey = "", type = "", options = [] } = field;
181
+ const { locs = {} } = this.state;
182
+
183
+ if (options?.length) {
184
+ const option = options[this.optionIndexOf(options.length, this.state.currentItemInner ?? 0)];
185
+
186
+ if (!option) return;
187
+
188
+ if (option.action) return this.PARAMS.SETTINGS_VIEW.ACTIONS[option.action](this, locs);
189
+ if (settingKey) return void this.emit(this.EVENTS.SETTINGS.SET, { name: settingKey, value: option.value });
190
+
191
+ return;
192
+ }
193
+
194
+ if (!settingKey) return;
195
+
196
+ if (type === "toggle") return void this.emit(this.EVENTS.SETTINGS.SET, { name: settingKey, value: !this.shared.settings[settingKey] });
197
+ };
198
+
199
+ controls = {
200
+ [this.CONST.CONTROLS.BUTTONS.INTERACT]: () => void this.onInteract(),
201
+ [this.CONST.CONTROLS.BUTTONS.ARROW_LEFT]: () => void this.onArrowLeft(),
202
+ [this.CONST.CONTROLS.BUTTONS.ARROW_RIGHT]: () => void this.onArrowRight(),
203
+ [this.CONST.CONTROLS.BUTTONS.ARROW_UP]: this.decCurrentItem,
204
+ [this.CONST.CONTROLS.BUTTONS.ARROW_BOTTOM]: this.incCurrentItem,
205
+ };
206
+ controlsCheckNext = true;
207
+
208
+ subscribe = () => {
209
+ this.on(this.EVENTS.SETTINGS_VIEW.SHOW, this.onShowFields);
210
+ this.on(this.EVENTS.SETTINGS_VIEW.HIDE, this.onHide);
211
+ this.on(this.EVENTS.SETTINGS_VIEW.TAB_SET, this.onTabSet);
212
+
213
+ this.on(this.EVENTS.SETTINGS.CHANGED, this.onSettingChange);
214
+ this.on(this.EVENTS.COMPONENTS.EXTERNAL_UPDATE, this.onSettingChange);
215
+ };
216
+
217
+ beforeShow = async (payload: SettingsViewTabPayload = {}) => this.updateState(this.applyTab(payload));
218
+ afterHide = () => this.setDefaultState();
219
+
220
+ onSettingChange = () => this.state.isShow && this.updateViewFast();
221
+ }
@@ -0,0 +1,22 @@
1
+ import { ModuleView } from "@vnejs/module.components";
2
+
3
+ import { render } from "../view/index.js";
4
+ import type { SettingsViewPluginConstants, SettingsViewPluginEvents, SettingsViewPluginParams, SettingsViewPluginSettings } from "../types.js";
5
+ import type { SettingsViewPluginState } from "../utils/settings.js";
6
+
7
+ export class SettingsView extends ModuleView<
8
+ SettingsViewPluginEvents,
9
+ SettingsViewPluginConstants,
10
+ SettingsViewPluginSettings,
11
+ SettingsViewPluginParams,
12
+ SettingsViewPluginState
13
+ > {
14
+ name = "settings.fields.view";
15
+
16
+ locLabel = this.PARAMS.SETTINGS_VIEW.LOC_LABEL;
17
+ animationTime = this.PARAMS.SETTINGS_VIEW.TRANSITION;
18
+ updateEvent = this.EVENTS.SETTINGS_VIEW.UPDATE;
19
+
20
+ renderFunc = render;
21
+ updateHandler = this.onUpdateStoreComponent;
22
+ }
@@ -0,0 +1,240 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+
3
+ import { spyEvent } from "@vnejs/test-utils";
4
+ import { SUBSCRIBE_EVENTS as SETTINGS_EVENTS } from "@vnejs/contracts.core.settings";
5
+
6
+ import { SettingsViewController } from "../modules/controller.js";
7
+ import { createSettingsViewTestModule, registerSettingsViewPluginVne, SUBSCRIBE_EVENTS } from "./setup.js";
8
+
9
+ describe("SettingsViewController", () => {
10
+ beforeEach(() => {
11
+ registerSettingsViewPluginVne();
12
+ });
13
+
14
+ it("SHOW stores fields from tab and maxCurrentItem", async () => {
15
+ const { module, observer } = createSettingsViewTestModule(SettingsViewController);
16
+
17
+ await observer.emit(SUBSCRIBE_EVENTS.SHOW, {
18
+ tabKey: "general",
19
+ tab: {
20
+ titleLocKey: "general",
21
+ fields: [
22
+ { settingKey: "a", type: "toggle" },
23
+ { settingKey: "b", type: "toggle" },
24
+ ],
25
+ },
26
+ });
27
+
28
+ expect((module as SettingsViewController).state.fields).toHaveLength(2);
29
+ expect((module as SettingsViewController).maxCurrentItem).toBe(1);
30
+ expect((module as SettingsViewController).state.isShow).toBe(true);
31
+ expect((module as SettingsViewController).state.tabKey).toBe("general");
32
+ expect(Object.keys((module as SettingsViewController).state.tabsContent ?? {})).toEqual(["general"]);
33
+ });
34
+
35
+ it("ARROW_RIGHT toggles setting when focused", async () => {
36
+ const { module, observer } = createSettingsViewTestModule(SettingsViewController, {
37
+ shared: { settings: { "test.toggle": false } },
38
+ });
39
+
40
+ await observer.emit(SUBSCRIBE_EVENTS.SHOW, {
41
+ tab: { titleLocKey: "general", fields: [{ settingKey: "test.toggle", type: "toggle" }] },
42
+ });
43
+
44
+ (module as SettingsViewController).currentItem = 0;
45
+ (module as SettingsViewController).updateState({ currentItem: 0 });
46
+
47
+ const settingsSet = spyEvent(observer, SETTINGS_EVENTS.SET);
48
+
49
+ (module as SettingsViewController).onArrowRight();
50
+
51
+ expect(settingsSet).toHaveBeenCalledExactlyOnceWith({ name: "test.toggle", value: true });
52
+ });
53
+
54
+ it("TAB_SET while visible replaces fields with fade", async () => {
55
+ const { module, observer } = createSettingsViewTestModule(SettingsViewController);
56
+
57
+ await observer.emit(SUBSCRIBE_EVENTS.SHOW, {
58
+ tabKey: "general",
59
+ tabs: {
60
+ general: { titleLocKey: "general", fields: [{ settingKey: "a", type: "toggle" }] },
61
+ audio: {
62
+ titleLocKey: "audio",
63
+ showTextExample: true,
64
+ fields: [
65
+ { settingKey: "b", type: "toggle" },
66
+ { settingKey: "c", type: "toggle" },
67
+ ],
68
+ },
69
+ },
70
+ tab: { titleLocKey: "general", fields: [{ settingKey: "a", type: "toggle" }] },
71
+ });
72
+
73
+ await observer.emit(SUBSCRIBE_EVENTS.TAB_SET, {
74
+ isForce: true,
75
+ tabKey: "audio",
76
+ tab: {
77
+ titleLocKey: "audio",
78
+ showTextExample: true,
79
+ fields: [
80
+ { settingKey: "b", type: "toggle" },
81
+ { settingKey: "c", type: "toggle" },
82
+ ],
83
+ },
84
+ });
85
+
86
+ expect((module as SettingsViewController).state.fields).toHaveLength(2);
87
+ expect((module as SettingsViewController).state.showTextExample).toBe(true);
88
+ expect((module as SettingsViewController).maxCurrentItem).toBe(1);
89
+ expect((module as SettingsViewController).state.currentItemInner).toBe(0);
90
+ expect((module as SettingsViewController).state.tabKey).toBe("audio");
91
+ expect(Object.keys((module as SettingsViewController).state.tabsContent ?? {}).sort()).toEqual(["audio", "general"]);
92
+ });
93
+
94
+ it("ARROW_RIGHT on selector_texts cycles inner and SET", async () => {
95
+ const { module, observer } = createSettingsViewTestModule(SettingsViewController, {
96
+ shared: { settings: { "test.lang": "ru" } },
97
+ });
98
+
99
+ await observer.emit(SUBSCRIBE_EVENTS.SHOW, {
100
+ tab: {
101
+ titleLocKey: "general",
102
+ fields: [
103
+ {
104
+ settingKey: "test.lang",
105
+ type: "selector_texts",
106
+ options: [
107
+ { text: "ru", value: "ru" },
108
+ { text: "en", value: "en" },
109
+ { text: "de", value: "de" },
110
+ ],
111
+ },
112
+ ],
113
+ },
114
+ });
115
+
116
+ const controller = module as SettingsViewController;
117
+
118
+ controller.currentItem = 0;
119
+ controller.updateState({ currentItem: 0, currentItemInner: 0 });
120
+
121
+ const settingsSet = spyEvent(observer, SETTINGS_EVENTS.SET);
122
+
123
+ controller.onArrowRight();
124
+
125
+ expect(controller.state.currentItemInner).toBe(1);
126
+ expect(settingsSet).toHaveBeenCalledExactlyOnceWith({ name: "test.lang", value: "en" });
127
+ });
128
+
129
+ it("ARROW_LEFT on selector_texts wraps inner and SET", async () => {
130
+ const { module, observer } = createSettingsViewTestModule(SettingsViewController, {
131
+ shared: { settings: { "test.lang": "ru" } },
132
+ });
133
+
134
+ await observer.emit(SUBSCRIBE_EVENTS.SHOW, {
135
+ tab: {
136
+ titleLocKey: "general",
137
+ fields: [
138
+ {
139
+ settingKey: "test.lang",
140
+ type: "selector_texts",
141
+ options: [
142
+ { text: "ru", value: "ru" },
143
+ { text: "en", value: "en" },
144
+ { text: "de", value: "de" },
145
+ ],
146
+ },
147
+ ],
148
+ },
149
+ });
150
+
151
+ const controller = module as SettingsViewController;
152
+
153
+ controller.currentItem = 0;
154
+ controller.updateState({ currentItem: 0, currentItemInner: 0 });
155
+
156
+ const settingsSet = spyEvent(observer, SETTINGS_EVENTS.SET);
157
+
158
+ controller.onArrowLeft();
159
+
160
+ expect(controller.state.currentItemInner).toBe(2);
161
+ expect(settingsSet).toHaveBeenCalledExactlyOnceWith({ name: "test.lang", value: "de" });
162
+ });
163
+
164
+ it("ARROW_* on actions without settingKey only moves inner", async () => {
165
+ const { module, observer } = createSettingsViewTestModule(SettingsViewController);
166
+ const controller = module as SettingsViewController;
167
+ const doA = vi.fn();
168
+ const doB = vi.fn();
169
+
170
+ controller.PARAMS.SETTINGS_VIEW.ACTIONS.do_a = doA;
171
+ controller.PARAMS.SETTINGS_VIEW.ACTIONS.do_b = doB;
172
+
173
+ await observer.emit(SUBSCRIBE_EVENTS.SHOW, {
174
+ tab: {
175
+ titleLocKey: "general",
176
+ fields: [
177
+ {
178
+ type: "actions",
179
+ options: [
180
+ { locKey: "a", action: "do_a" },
181
+ { locKey: "b", action: "do_b" },
182
+ ],
183
+ },
184
+ ],
185
+ },
186
+ });
187
+
188
+ controller.currentItem = 0;
189
+ controller.updateState({ currentItem: 0, currentItemInner: 0 });
190
+
191
+ const settingsSet = spyEvent(observer, SETTINGS_EVENTS.SET);
192
+
193
+ controller.onArrowLeft();
194
+
195
+ expect(controller.state.currentItemInner).toBe(1);
196
+ expect(doA).not.toHaveBeenCalled();
197
+ expect(doB).not.toHaveBeenCalled();
198
+ expect(settingsSet).not.toHaveBeenCalled();
199
+
200
+ controller.onInteract();
201
+
202
+ expect(doB).toHaveBeenCalledOnce();
203
+ expect(doA).not.toHaveBeenCalled();
204
+ });
205
+
206
+ it("setCurrentItem syncs currentItemInner to the current option", async () => {
207
+ const { module, observer } = createSettingsViewTestModule(SettingsViewController, {
208
+ shared: { settings: { "test.lang": "en" } },
209
+ });
210
+
211
+ await observer.emit(SUBSCRIBE_EVENTS.SHOW, {
212
+ tab: {
213
+ titleLocKey: "general",
214
+ fields: [
215
+ { settingKey: "a", type: "toggle" },
216
+ {
217
+ settingKey: "test.lang",
218
+ type: "selector_texts",
219
+ options: [
220
+ { text: "ru", value: "ru" },
221
+ { text: "en", value: "en" },
222
+ { text: "de", value: "de" },
223
+ ],
224
+ },
225
+ ],
226
+ },
227
+ });
228
+
229
+ const controller = module as SettingsViewController;
230
+
231
+ controller.currentItem = 0;
232
+ controller.updateState({ currentItem: 0, currentItemInner: 99 });
233
+
234
+ await controller.setCurrentItem(1, 0);
235
+
236
+ expect(controller.currentItem).toBe(1);
237
+ expect(controller.state.currentItem).toBe(1);
238
+ expect(controller.state.currentItemInner).toBe(1);
239
+ });
240
+ });
@@ -0,0 +1,44 @@
1
+ import { CONSTANTS as CONTROLS_CONST, PLUGIN_NAME as CONTROLS, SUBSCRIBE_EVENTS as CONTROLS_EVENTS } from "@vnejs/contracts.controls";
2
+ import { PARAMS, PLUGIN_NAME, SUBSCRIBE_EVENTS } from "@vnejs/contracts.views.settings";
3
+ import { SourcesSet } from "@vnejs/sources-set";
4
+ import {
5
+ createTestModule as baseCreateTestModule,
6
+ registerCoreVne,
7
+ registerVnePlugin,
8
+ stubSilentEvent,
9
+ } from "@vnejs/test-utils";
10
+
11
+ export const registerSettingsViewPluginVne = () => {
12
+ registerCoreVne();
13
+ registerVnePlugin(CONTROLS, { constants: CONTROLS_CONST, events: CONTROLS_EVENTS });
14
+ registerVnePlugin(PLUGIN_NAME, { events: SUBSCRIBE_EVENTS, params: PARAMS });
15
+ };
16
+
17
+ export const createSettingsViewTestModule = <T extends Parameters<typeof baseCreateTestModule>[0]>(
18
+ ModuleClass: T,
19
+ options: Parameters<typeof baseCreateTestModule>[1] = {},
20
+ ) => {
21
+ const { shared: sharedOption, state: stateOption, ...rest } = options;
22
+
23
+ const result = baseCreateTestModule(ModuleClass, {
24
+ ...rest,
25
+ state: {
26
+ "settings.fields.controller": { isShow: false, fields: [], currentItem: null, currentItemInner: 0 },
27
+ ...(stateOption ?? {}),
28
+ },
29
+ shared: {
30
+ ...(sharedOption ?? {}),
31
+ settings: { ...(sharedOption?.settings ?? {}) },
32
+ viewForceAnimationSources: new SourcesSet(),
33
+ },
34
+ });
35
+
36
+ stubSilentEvent(result.observer, SUBSCRIBE_EVENTS.SHOW);
37
+ stubSilentEvent(result.observer, SUBSCRIBE_EVENTS.HIDE);
38
+ stubSilentEvent(result.observer, SUBSCRIBE_EVENTS.UPDATE);
39
+ stubSilentEvent(result.observer, SUBSCRIBE_EVENTS.TAB_SET);
40
+
41
+ return result;
42
+ };
43
+
44
+ export { SUBSCRIBE_EVENTS, PARAMS, PLUGIN_NAME };
package/src/types.ts ADDED
@@ -0,0 +1,20 @@
1
+ import type { ModuleComponentsConstants, ModuleComponentsEvents, ModuleComponentsParams, ModuleComponentsSettings } from "@vnejs/module.components";
2
+ import type { Constants as ControlsConstants, PluginName as ControlsPluginName } from "@vnejs/contracts.controls";
3
+ import type { PluginName as SettingsPluginName, SubscribeEvents as SettingsSubscribeEvents } from "@vnejs/contracts.core.settings";
4
+ import type { SettingsKeys as TextsizeSettingsKeys, PluginName as TextsizePluginName } from "@vnejs/contracts.settings.textsize";
5
+ import type { Params as TextSmoothParams, PluginName as TextSmoothPluginName, SettingsKeys as TextSmoothSettingsKeys } from "@vnejs/contracts.text.smooth";
6
+ import type { Params, PluginName, SubscribeEvents } from "@vnejs/contracts.views.settings";
7
+
8
+ export type SettingsViewPluginEvents = ModuleComponentsEvents &
9
+ Record<PluginName, SubscribeEvents> &
10
+ Record<SettingsPluginName, SettingsSubscribeEvents>;
11
+
12
+ export type SettingsViewPluginConstants = ModuleComponentsConstants & Record<ControlsPluginName, ControlsConstants>;
13
+
14
+ export type SettingsViewPluginSettings = ModuleComponentsSettings &
15
+ Record<TextSmoothPluginName, TextSmoothSettingsKeys> &
16
+ Record<TextsizePluginName, TextsizeSettingsKeys>;
17
+
18
+ export type SettingsViewPluginParams = ModuleComponentsParams &
19
+ Record<PluginName, Params> &
20
+ Record<TextSmoothPluginName, TextSmoothParams>;
@@ -0,0 +1,28 @@
1
+ import type { SettingsField, SettingsTab } from "@vnejs/contracts.views.settings";
2
+
3
+ export type SettingsViewTabContent = {
4
+ fields?: SettingsField[];
5
+ showTextExample?: boolean;
6
+ locs?: Record<string, string>;
7
+ };
8
+
9
+ export type SettingsViewPluginState = {
10
+ isShow: boolean;
11
+ isForce: boolean;
12
+ tab?: SettingsTab;
13
+ tabKey?: string;
14
+ tabsContent?: Record<string, SettingsViewTabContent>;
15
+ fields?: SettingsField[];
16
+ locs?: Record<string, string>;
17
+ showTextExample?: boolean;
18
+ currentItem?: number | null;
19
+ currentItemInner?: number | null;
20
+ };
21
+
22
+ export type SettingsViewTabPayload = {
23
+ tab?: SettingsTab;
24
+ tabs?: Record<string, SettingsTab>;
25
+ tabKey?: string;
26
+ locs?: Record<string, string>;
27
+ isForce?: boolean;
28
+ };
@@ -0,0 +1,61 @@
1
+ import { tokenizeText } from "@vnejs/helpers";
2
+ import type { ReactComponentProps } from "@vnejs/uis.react";
3
+ import { SmoothText, useEffect, useMemo, useState } from "@vnejs/uis.react";
4
+
5
+ import type { SettingsViewPluginConstants, SettingsViewPluginEvents, SettingsViewPluginParams, SettingsViewPluginSettings } from "../types.js";
6
+ import type { SettingsViewPluginState } from "../utils/settings.js";
7
+
8
+ type SettingsExampleTextProps = ReactComponentProps<
9
+ SettingsViewPluginEvents,
10
+ SettingsViewPluginConstants,
11
+ SettingsViewPluginSettings,
12
+ SettingsViewPluginParams,
13
+ SettingsViewPluginState
14
+ > & {
15
+ isShow?: boolean;
16
+ locs?: Record<string, string>;
17
+ };
18
+
19
+ type SmoothTextTokens = NonNullable<Parameters<typeof SmoothText>[0]["tokens"]>;
20
+
21
+ export const SettingsExampleText = ({ isShow = false, locs = {}, PARAMS, shared, SETTINGS }: SettingsExampleTextProps) => {
22
+ const [tokens, setTokens] = useState<SmoothTextTokens>([]);
23
+
24
+ const isForce = shared.textProcessSkipSources.isNotEmpty();
25
+
26
+ const text = useMemo(() => locs["fields.text.example"] ?? "", [locs]);
27
+
28
+ useEffect(() => {
29
+ if (isShow) setTokens(tokenizeText(text) as SmoothTextTokens);
30
+ }, [isShow, text]);
31
+
32
+ useEffect(() => {
33
+ setTokens((prev) => (prev.length ? [...prev] : prev));
34
+ }, [isForce]);
35
+
36
+ useEffect(() => {
37
+ if (isShow || isForce) return;
38
+
39
+ const tm = setTimeout(() => setTokens([]), PARAMS.SETTINGS_VIEW.TRANSITION);
40
+
41
+ return () => clearTimeout(tm);
42
+ }, [isShow, isForce, PARAMS.SETTINGS_VIEW.TRANSITION]);
43
+
44
+ const charDelay = isForce
45
+ ? 0
46
+ : PARAMS.TEXT_SMOOTH.TOKEN_RENDER_DELAY / Number(shared.settings[SETTINGS.TEXT_SMOOTH.TOKEN_RENDER_SPEED]);
47
+
48
+ return (
49
+ <SmoothText
50
+ tokens={tokens}
51
+ isForce={isForce}
52
+ charDelay={charDelay}
53
+ tokensVisibleForce={isForce ? tokens.length : null}
54
+ transition={PARAMS.SETTINGS_VIEW.VIEW_PROPS.exampleText.transition}
55
+ size={PARAMS.SETTINGS_VIEW.VIEW_PROPS.exampleText.size}
56
+ color={PARAMS.SETTINGS_VIEW.VIEW_PROPS.exampleText.color}
57
+ height={PARAMS.SETTINGS_VIEW.VIEW_PROPS.exampleText.height}
58
+ scrollBarTextSize={Number(shared.settings[SETTINGS.TEXTSIZE.VALUE])}
59
+ />
60
+ );
61
+ };