@simple-reporting/base 1.0.33 → 1.0.35

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 (58) hide show
  1. package/dev/package.json +2 -1
  2. package/dev/src/assets/scss/app.scss +5 -0
  3. package/dev/src/assets/scss/pdf.scss +1 -1
  4. package/dev/vite.config.ts +4 -1
  5. package/devTools/SrlDevTools.vue +234 -0
  6. package/devTools/assets/Svg/Check.vue +7 -0
  7. package/devTools/assets/Svg/Close.vue +5 -0
  8. package/devTools/assets/Svg/Eye.vue +16 -0
  9. package/devTools/assets/Svg/Info.vue +7 -0
  10. package/devTools/assets/Svg/Settings.vue +5 -0
  11. package/devTools/assets/Svg/Uncheck.vue +7 -0
  12. package/devTools/box/Content.vue +93 -0
  13. package/devTools/components/BoxPanel.vue +23 -0
  14. package/devTools/components/Content.vue +17 -0
  15. package/devTools/config.ts +34 -0
  16. package/devTools/dialog/Colors.vue +33 -0
  17. package/devTools/dialog/Grid.vue +111 -0
  18. package/devTools/dialog/Settings.vue +62 -0
  19. package/devTools/dialog/Spacer.vue +110 -0
  20. package/devTools/dialog/ViewPort.vue +33 -0
  21. package/devTools/panel/Content.vue +50 -0
  22. package/devTools/settings.ts +28 -0
  23. package/devTools/utils/index.ts +7 -0
  24. package/devTools/utils/wheelResizeHandler.ts +19 -0
  25. package/livingdocs/010.Titles/020.title-h2/scss/general.scss +1 -16
  26. package/livingdocs/010.Titles/020.title-h2/scss/pdf.scss +16 -0
  27. package/livingdocs/010.Titles/020.title-h2/scss/web.scss +16 -0
  28. package/livingdocs/010.Titles/030.title-h3/scss/general.scss +0 -15
  29. package/livingdocs/010.Titles/030.title-h3/scss/pdf.scss +16 -0
  30. package/livingdocs/010.Titles/030.title-h3/scss/web.scss +16 -0
  31. package/livingdocs/010.Titles/040.title-h4/scss/general.scss +0 -15
  32. package/livingdocs/010.Titles/040.title-h4/scss/pdf.scss +16 -0
  33. package/livingdocs/010.Titles/040.title-h4/scss/web.scss +16 -0
  34. package/livingdocs/040.Media/010.table/scss/general.scss +6 -3
  35. package/livingdocs/110.PDF/100.pdf-toc-item/scss/general.scss +6 -1
  36. package/livingdocs/110.PDF/100.pdf-toc-item/scss/pdf.scss +6 -0
  37. package/package.json +8 -3
  38. package/plugins/viteSrlPlugin.js +19 -6
  39. package/scripts/build.d.ts +2 -0
  40. package/scripts/build.js +68 -3
  41. package/scripts/css/stripMediaFromCss.d.ts +17 -0
  42. package/scripts/css/stripMediaFromCss.js +147 -0
  43. package/scripts/vue/components.js +9 -2
  44. package/srl/.srl/App.vue +7 -1
  45. package/srl/.srl/components/Srl/Article/Accordion.vue +1 -0
  46. package/srl/.srl/components/Srl/Article/Root.vue +4 -4
  47. package/srl/.srl/components/Srl/Category/Accordion/Toggle.vue +2 -1
  48. package/srl/.srl/components/Srl/Menu/Item.vue +58 -24
  49. package/srl/.srl/components/Srl/Menu.vue +43 -17
  50. package/srl/.srl/composables/config.ts +4 -3
  51. package/srl/.srl/composables/index.ts +3 -0
  52. package/srl/.srl/composables/menu.ts +6 -3
  53. package/srl/.srl/composables/srlConfig.ts +3 -0
  54. package/srl/.srl/types/global.d.ts +11 -0
  55. package/srl/.srl/types/nswow.d.ts +5 -0
  56. package/srl/.srl/utils/html.ts +2 -2
  57. package/srl/.srl/utils/index.ts +27 -25
  58. package/srl/.srl/utils/object.ts +60 -0
package/scripts/build.js CHANGED
@@ -20,12 +20,14 @@ import {
20
20
  writeLivingDocsJson,
21
21
  } from './utils.js';
22
22
  import { nsWowInternalLddUrl } from './config.js';
23
- import folders from './folders.js';
23
+ import folders, { srlSystem } from './folders.js';
24
24
  import { mapLdd } from './ldd/mapLdd.js';
25
25
  import { LivingdocsDesignValidator } from './ldd/LivingdocsDesignValidator.js';
26
26
  import { camelCase } from './utils.js';
27
+ import { stripMediaFromCssFile } from './css/stripMediaFromCss.js';
27
28
  import { buildVariables } from './build/variables.js';
28
29
  import './dotenv.js';
30
+ import { readJson } from 'fs-extra/esm';
29
31
 
30
32
  const placeholderId = '6297EAFB-33A0-48B8-8D64-E61CDC3E9035';
31
33
  const nswowPath = folders.srlImports;
@@ -360,6 +362,11 @@ async function buildXbrl() {
360
362
  }
361
363
  }
362
364
 
365
+ async function finalizeXbrl() {
366
+ console.log('\n\nFinalize XBRL');
367
+ await stripMediaFromCssFile(join(folders.srlOutput, 'xbrl', 'xbrl.css'));
368
+ }
369
+
363
370
  /**
364
371
  * Builds Living Documentation (LDD) for a project.
365
372
  *
@@ -370,7 +377,7 @@ async function buildXbrl() {
370
377
  async function buildLdd(version) {
371
378
  console.log('\n\nBuild Livingdocs');
372
379
  buildVariables.system.environment = 'production';
373
- buildVariables.system.build = 'ldd';
380
+ buildVariables.system.build = 'editor';
374
381
  buildVariables.system['size-unit'] = 'rem';
375
382
 
376
383
  await checkFolders();
@@ -851,6 +858,10 @@ async function build(version, options = {}) {
851
858
  await buildPdfCustomer(options.customer);
852
859
  }
853
860
 
861
+ if (has('xbrl') || has('xhtml')) {
862
+ await finalizeXbrl();
863
+ }
864
+
854
865
  if (has('ldd')) {
855
866
  await zipLdd();
856
867
  }
@@ -886,6 +897,60 @@ function cleanupScssAlias(string) {
886
897
  return string.replace(/[^a-zA-Z0-9]/g, '');
887
898
  }
888
899
 
900
+ async function generateUseSrlConfig() {
901
+ const content = await readJson(join(folders.root, 'srl.config.json'));
902
+ const c = [
903
+ `import { ref } from 'vue';`,
904
+ `const srlConfig = ref(${JSON.stringify(content, null, 2)})`,
905
+ 'export default function useSrlConfig() {',
906
+ ` return srlConfig`,
907
+ '}'
908
+ ]
909
+
910
+ writeFileSync(
911
+ join(folders.srlComposables, 'srlConfig.ts'),
912
+ c.join('\n'),
913
+ );
914
+ }
915
+
916
+ async function mapIndexScss() {
917
+ const files = await glob(join(folders.srlAssets, 'scss', 'placeholders', '**', '*.scss'), {
918
+ withFileTypes: true,
919
+ nosort: false,
920
+ });
921
+
922
+ files.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
923
+
924
+ const relativePathToRoot = join('..', '/');
925
+
926
+ const map = [];
927
+
928
+ for (let x = 0; x < files.length; x++) {
929
+ const alias = cleanupScssAlias(files[x].relativePosix());
930
+ map.push(`@use "${relativePathToRoot}${files[x].relativePosix()}" as ${alias};`);
931
+ }
932
+
933
+ const c = [
934
+ `@use './config';`,
935
+ ...map,
936
+ `@forward './system' as system-*;`,
937
+ `@forward './fonts' as fonts-*;`,
938
+ `@forward './grid' as grid-*;`,
939
+ `@forward './colors' as colors-*;`,
940
+ `@forward './typography' as typography-*;`,
941
+ `@forward './helpers' as helpers-*;`,
942
+ `@forward './spacer' as spacer-*;`,
943
+ `@forward './meta';`,
944
+ ]
945
+
946
+ writeFileSync(
947
+ join(folders.srlSystem, 'index.scss'),
948
+ c.join('\n'),
949
+ );
950
+
951
+ return true;
952
+ }
953
+
889
954
  /**
890
955
  * Maps SCSS files and generates import statements for different output files.
891
956
  *
@@ -1157,4 +1222,4 @@ async function map() {
1157
1222
  return true;
1158
1223
  }
1159
1224
 
1160
- export { build, ddev, map, mapScss, mapLdd, mapJs };
1225
+ export { build, ddev, map, generateUseSrlConfig, mapIndexScss, mapScss, mapLdd, mapJs };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Entfernt @media aus exakt einer CSS-Datei und ersetzt diese Datei durch das Ergebnis.
3
+ * @param {string} cssPath absoluter oder relativer Pfad zur .css Datei
4
+ */
5
+ export function stripMediaFromCssFile(cssPath: string): Promise<{
6
+ file: string;
7
+ removed: number;
8
+ }>;
9
+ export type RemoveMediaPayload = {
10
+ params: string;
11
+ atRule: import("postcss").AtRule;
12
+ fullRuleText: string;
13
+ };
14
+ export type RemoveAllMediaPluginOptions = {
15
+ onRemoveMedia?: (payload: RemoveMediaPayload) => void;
16
+ shouldLogRemovedMedia?: (params: string, atRule: import("postcss").AtRule) => boolean;
17
+ };
@@ -0,0 +1,147 @@
1
+ import { readFile, writeFile, stat } from 'node:fs/promises';
2
+ import postcss from 'postcss';
3
+
4
+ /**
5
+ * Entfernt alle @media-At-Rules aus CSS.
6
+ * Wichtig: bewusst AST-basiert (PostCSS), damit keine False-Positives entstehen.
7
+ */
8
+
9
+ const normalizeMediaParams = (params) =>
10
+ (params ?? '')
11
+ .trim()
12
+ // Whitespace normalisieren
13
+ .replace(/\s+/g, ' ')
14
+ // rund um Doppelpunkt vereinheitlichen
15
+ .replace(/\s*:\s*/g, ': ')
16
+ // "and" sauber trennen, falls Minifier Leerzeichen frisst
17
+ .replace(/\)\s*and\s*\(/gi, ') and (')
18
+ .replace(/\)and\(/gi, ') and (');
19
+
20
+ const isPrefersReducedMotionReduce = (params) => {
21
+ const p = normalizeMediaParams(params).toLowerCase();
22
+ return p === '(prefers-reduced-motion: reduce)';
23
+ };
24
+
25
+ /**
26
+ * @typedef {{
27
+ * params: string,
28
+ * atRule: import('postcss').AtRule,
29
+ * fullRuleText: string
30
+ * }} RemoveMediaPayload
31
+ */
32
+
33
+ /**
34
+ * @typedef {{
35
+ * onRemoveMedia?: (payload: RemoveMediaPayload) => void,
36
+ * shouldLogRemovedMedia?: (params: string, atRule: import('postcss').AtRule) => boolean
37
+ * }} RemoveAllMediaPluginOptions
38
+ */
39
+
40
+ /**
41
+ * @param {RemoveAllMediaPluginOptions} [opts]
42
+ */
43
+ const removeAllMediaPlugin = (opts = {}) => {
44
+ const {
45
+ onRemoveMedia = () => {},
46
+ shouldLogRemovedMedia = () => true,
47
+ } = opts;
48
+
49
+ return {
50
+ postcssPlugin: 'remove-all-media',
51
+ AtRule: {
52
+ media: (atRule) => {
53
+ const params = atRule.params;
54
+
55
+ // erst loggen, dann entfernen
56
+ try {
57
+ if (shouldLogRemovedMedia(params, atRule)) {
58
+ // Wichtig: vor dem remove() stringifizieren, sonst ist der Knoten weg
59
+ const fullRuleText = atRule.toString();
60
+ onRemoveMedia({ params, atRule, fullRuleText });
61
+ }
62
+ } finally {
63
+ atRule.remove();
64
+ }
65
+ },
66
+ },
67
+ };
68
+ };
69
+ removeAllMediaPlugin.postcss = true;
70
+
71
+ async function fileExists(path) {
72
+ try {
73
+ const s = await stat(path);
74
+ return s.isFile();
75
+ } catch {
76
+ return false;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Entfernt @media aus exakt einer CSS-Datei und ersetzt diese Datei durch das Ergebnis.
82
+ * @param {string} cssPath absoluter oder relativer Pfad zur .css Datei
83
+ */
84
+ export async function stripMediaFromCssFile(cssPath) {
85
+ const css = await readFile(cssPath, 'utf8');
86
+ const mapPath = `${cssPath}.map`;
87
+ const hasMap = await fileExists(mapPath);
88
+
89
+ const prevMap = hasMap ? JSON.parse(await readFile(mapPath, 'utf8')) : false;
90
+
91
+ /** @type {string[]} */
92
+ const removedEntries = [];
93
+
94
+ const result = await postcss([
95
+ removeAllMediaPlugin({
96
+ shouldLogRemovedMedia: (params) => !isPrefersReducedMotionReduce(params),
97
+ onRemoveMedia: ({ params, fullRuleText }) => {
98
+ const normalized = normalizeMediaParams(params);
99
+ // Eine Zeile pro Eintrag (inkl. Inhalt). Wir hängen den kompletten Block an,
100
+ // aber ohne das doppelte "@media ..." Prefix.
101
+ const body = fullRuleText.replace(/^@media\s+[^{}]+\s*/i, '');
102
+ removedEntries.push(`@media ${normalized} ${body}`);
103
+ },
104
+ }),
105
+ ]).process(css, {
106
+ from: cssPath,
107
+ to: cssPath,
108
+ map: hasMap
109
+ ? {
110
+ inline: false,
111
+ annotation: true,
112
+ prev: prevMap,
113
+ }
114
+ : false,
115
+ });
116
+
117
+ await writeFile(cssPath, result.css ?? '', 'utf8');
118
+
119
+ if (hasMap && result.map) {
120
+ await writeFile(mapPath, result.map.toString(), 'utf8');
121
+ }
122
+
123
+ // Output nur, wenn wirklich etwas entfernt wurde
124
+ if (removedEntries.length > 0) {
125
+ console.log('[strip-media]');
126
+ console.log('');
127
+ for (const line of removedEntries) console.log(line);
128
+ console.log('');
129
+ console.log(`[strip-media] result: removed ${removedEntries.length} @media block(s)`);
130
+ }
131
+
132
+ return { file: cssPath, removed: removedEntries.length };
133
+ }
134
+
135
+ // CLI Nutzung: node scripts/stripMediaFromCss.mjs <cssFilePath>
136
+ if (import.meta.url === `file://${process.argv[1]}`) {
137
+ const cssFilePath = process.argv[2];
138
+ if (!cssFilePath) {
139
+ console.error('Usage: node scripts/stripMediaFromCss.mjs <cssFilePath>');
140
+ process.exit(1);
141
+ }
142
+
143
+ stripMediaFromCssFile(cssFilePath).catch((e) => {
144
+ console.error(e);
145
+ process.exit(1);
146
+ });
147
+ }
@@ -39,8 +39,10 @@ export async function vueComponents() {
39
39
  const appComponents = readVueDir(folders.srlSrc, '@/');
40
40
  const srlComponents = readVueDir(folders.srlRoot, '#');
41
41
 
42
- const components = []
43
- const types = []
42
+ const components = [
43
+ `app.component('SrlDevTools', defineAsyncComponent(() => import('@simple-reporting/base/devTools/SrlDevTools.vue')));`,
44
+ ]
45
+ const types = [];
44
46
 
45
47
  for (const [name, info] of Object.entries(appComponents)) {
46
48
  components.push(
@@ -66,6 +68,11 @@ export async function vueComponents() {
66
68
  }
67
69
  }
68
70
 
71
+ types.push({
72
+ name: 'SrlDevTools',
73
+ type: ` type SrlDevTools = typeof import('@simple-reporting/base/devTools/SrlDevTools.vue')['default'];`,
74
+ });
75
+
69
76
  writeFileSync(join(folders.srlPlugins, 'asyncSrlComponents.ts'),
70
77
  `import { defineAsyncComponent, type App } from 'vue';
71
78
  export default function asyncSrlComponents(app: App): void {
package/srl/.srl/App.vue CHANGED
@@ -36,7 +36,7 @@
36
36
  * to other components in the application.
37
37
  */
38
38
  import App from '@/App.vue';
39
- import { onMounted, watch } from 'vue'
39
+ import { computed, onMounted, watch } from 'vue'
40
40
  import { useCssStyles } from '#composables'
41
41
  import { setMounted } from '#utils'
42
42
 
@@ -51,6 +51,11 @@ watch(
51
51
  },
52
52
  { immediate: true },
53
53
  );
54
+
55
+ const devToolsEnabled = computed(() => {
56
+ return import.meta.env.DEV && import.meta.env.VITE_DISABLE_SRL_DEVTOOLS !== 'true'
57
+ })
58
+
54
59
  onMounted(() => {
55
60
  setMounted(true)
56
61
  });
@@ -60,4 +65,5 @@ onMounted(() => {
60
65
  <suspense>
61
66
  <App />
62
67
  </suspense>
68
+ <SrlDevTools v-if="devToolsEnabled" />
63
69
  </template>
@@ -53,6 +53,7 @@ onMounted(() => {
53
53
  content.value.id = id
54
54
  content.value.setAttribute('tabindex', '-1')
55
55
  toggle.value.setAttribute('aria-controls', id)
56
+ toggle.value.setAttribute('aria-expanded', 'false')
56
57
 
57
58
  toggle.value.addEventListener('click', () => {
58
59
  toggle.value?.getAttribute('aria-expanded') === 'true' ? close() : open()
@@ -44,13 +44,13 @@ const article = useArticle();
44
44
 
45
45
  if (article.value) {
46
46
  const file = `./html/${locale}/${article.value.name}.html`;
47
- const publicationTitle = config.value.settings.publicationName[locale];
48
-
47
+ const publicationTitle = config.value.settings?.publicationName?[locale] : null;
49
48
  try {
50
49
  const req = await fetch(file);
51
50
  let text = await req.text();
52
-
53
- document.title = `${article.value.translatedTitle} - ${publicationTitle}`;
51
+ const title = [article.value.translatedTitle];
52
+ publicationTitle ? title.push(publicationTitle as string) : null;
53
+ document.title = title.join(' - ');
54
54
  content.value = prepareHtmlContent(text);
55
55
  } catch (error) {
56
56
  console.error(`Failed to load article content from ${file}:`, error);
@@ -6,7 +6,8 @@ const props = defineProps<{
6
6
  toggle: () => void;
7
7
  open: () => void;
8
8
  close: () => void;
9
- };
9
+ }
10
+ level: number
10
11
  }>()
11
12
 
12
13
  </script>
@@ -49,17 +49,25 @@ const breadcrumb = defineModel<NsWowNavigationItem[]>('breadcrumb', {
49
49
  required: true,
50
50
  }) as Ref<NsWowNavigationItem[]>
51
51
 
52
- const emit = defineEmits([
53
- 'toggle',
54
- 'open',
55
- 'close',
56
- 'link',
57
- 'routerChange',
58
- 'next',
59
- 'prev',
60
- 'tab',
61
- 'back',
62
- ])
52
+ const emit = defineEmits<{
53
+ (e: 'toggle', payload: {
54
+ element: HTMLAnchorElement | HTMLButtonElement
55
+ itemEl: HTMLLIElement
56
+ menu: SrlMenu
57
+ menuEl: HTMLUListElement
58
+ opened: boolean
59
+ props: typeof props
60
+ }): void
61
+ (e: 'open', payload: { index: number | string }): void
62
+ (e: 'close', payload: { index: number | string }): void
63
+ (e: 'link'): void
64
+ (e: 'routerChange'): void
65
+ (e: 'next', payload: { index: number | string }): void
66
+ (e: 'prev', payload: { index: number | string }): void
67
+ (e: 'tab'): void
68
+ (e: 'back'): void
69
+ }>()
70
+
63
71
  const id = ref<number | string | undefined>()
64
72
  if (props.item.children) {
65
73
  id.value = useId()
@@ -68,7 +76,7 @@ if (props.item.children) {
68
76
  const currentBackPath = computed<NsWowNavigationItem[]>(() => {
69
77
  if (!props.item?.attributes?.class?.includes('srl-menu__link--back')) {
70
78
  const i: NsWowNavigationItem = {
71
- label: props.item.label
79
+ label: props.item.label,
72
80
  }
73
81
  props.item.title ? i.title = props.item.title : null
74
82
  props.item.img ? i.img = props.item.img : null
@@ -87,13 +95,20 @@ const currentBackPath = computed<NsWowNavigationItem[]>(() => {
87
95
  })
88
96
 
89
97
  const currentPath = computed<NsWowNavigationItem[]>(() => {
90
- return [...props.path, props.item]
98
+ return [...props.path, {
99
+ ...props.item,
100
+ element: $el.value,
101
+ menu: menu.value,
102
+ menuEl: menu.value?.$el,
103
+ itemEl: li.value,
104
+ }]
91
105
  })
92
106
 
93
107
  const external = ref(props.item.href && isExternalPath(props.item.href))
94
108
 
95
- const menu = ref()
96
- const $el = ref<HTMLElement | null>(null)
109
+ const menu = ref<SrlMenu | null>(null)
110
+ const li = ref<HTMLLIElement>()
111
+ const $el = ref<HTMLAnchorElement | HTMLButtonElement>()
97
112
  const opened = ref(false)
98
113
 
99
114
 
@@ -109,6 +124,7 @@ function toggleAction() {
109
124
  } else {
110
125
  menu.value.closeAll()
111
126
  }
127
+ !props.item.callback || props.item.callback($el.value , li.value, menu.value.$el)
112
128
  toggle()
113
129
  }
114
130
  function close() {
@@ -124,7 +140,18 @@ function closeSub() {
124
140
  }
125
141
 
126
142
  function toggle() {
127
- emit('toggle')
143
+ emit('toggle', {
144
+ element: $el.value!,
145
+ itemEl: li.value!,
146
+ menu: menu.value,
147
+ menuEl: menu.value.$el,
148
+ opened: opened.value,
149
+ props: props,
150
+ })
151
+ }
152
+
153
+ function toggleBridge(data) {
154
+ emit('toggle', data)
128
155
  }
129
156
 
130
157
  function next() {
@@ -154,7 +181,7 @@ function back(event: KeyboardEvent) {
154
181
  }
155
182
 
156
183
  function link() {
157
- !props.item.callback || props.item.callback()
184
+ !props.item.callback || props.item.callback($el.value, li.value)
158
185
  emit('link')
159
186
  }
160
187
 
@@ -173,7 +200,13 @@ function closeItem() {
173
200
  function findPath(): NsWowNavigationItem[] | null {
174
201
  if (opened.value) {
175
202
  if (props.item?.children?.length) {
176
- let res = [...props.path, props.item]
203
+ let res = [...props.path, {
204
+ ...props.item,
205
+ element: $el.value,
206
+ menu: menu.value,
207
+ menuEl: menu.value?.$el,
208
+ itemEl: li.value,
209
+ }]
177
210
  menu.value.items.forEach((item) => {
178
211
  item.opened && (res = item.findPath() || res)
179
212
  })
@@ -194,12 +227,12 @@ defineExpose({
194
227
  })
195
228
 
196
229
  function internalLinkClick() {
197
- !props.item.callback || props.item.callback()
230
+ !props.item.callback || props.item.callback($el.value , li.value)
198
231
  routerChange()
199
232
  }
200
233
 
201
234
  function externalLinkClick() {
202
- !props.item.callback || props.item.callback()
235
+ !props.item.callback || props.item.callback($el.value , li.value)
203
236
  link()
204
237
  }
205
238
 
@@ -237,7 +270,7 @@ const classListItem = computed(() => {
237
270
  </script>
238
271
 
239
272
  <template>
240
- <li v-if="!item.children && props.item.href" role="none" :class="classListLi">
273
+ <li v-if="!item.children && props.item.href" ref="li" role="none" :class="classListLi">
241
274
  <router-link
242
275
  v-if="!external"
243
276
  ref="$el"
@@ -289,7 +322,7 @@ const classListItem = computed(() => {
289
322
  />
290
323
  </a>
291
324
  </li>
292
- <li v-else-if="props.item.callback" role="none" :class="classListLi">
325
+ <li v-else-if="!item.children && props.item.callback" ref="li" role="none" :class="classListLi">
293
326
  <button
294
327
  type="button"
295
328
  ref="$el"
@@ -315,7 +348,7 @@ const classListItem = computed(() => {
315
348
  />
316
349
  </button>
317
350
  </li>
318
- <li v-else :class="classListLi" role="none">
351
+ <li v-else :class="classListLi" ref="li" role="none">
319
352
  <button
320
353
  type="button"
321
354
  ref="$el"
@@ -365,7 +398,8 @@ const classListItem = computed(() => {
365
398
  @routerChange="emit('routerChange')"
366
399
  @tab="tab"
367
400
  @closeSub="closeSub"
368
- @toggle="toggle"
401
+ @toggle="toggleBridge"
402
+ @backButtonClick="toggle"
369
403
  />
370
404
  </li>
371
405
  </template>
@@ -77,6 +77,30 @@ type BackButtonItem = {
77
77
  }
78
78
  }
79
79
 
80
+ type SrlMenuItemProps = {
81
+ name: string
82
+ item: NsWowNavigationItem
83
+ index: number | string
84
+ disableTab: boolean
85
+ disableTabIndex: boolean
86
+ initOpen: number
87
+ depth: number
88
+ disableClasses: boolean
89
+ backButtonEnabled: boolean
90
+ backButtonLabel: (backPath: NsWowNavigationItem[]) => string
91
+ backButtonItem?: BackButtonItem
92
+ backPath: NsWowNavigationItem[]
93
+ path: NsWowNavigationItem[]
94
+ }
95
+
96
+ type TogglePayload = {
97
+ element: HTMLAnchorElement | HTMLButtonElement
98
+ itemEl: HTMLLIElement
99
+ menu: SrlMenu
100
+ opened: boolean
101
+ props: SrlMenuItemProps
102
+ }
103
+
80
104
  const props = withDefaults(
81
105
  defineProps<{
82
106
  name: string
@@ -108,17 +132,19 @@ const props = withDefaults(
108
132
  },
109
133
  )
110
134
 
111
- const emit = defineEmits([
112
- 'toggle',
113
- 'close',
114
- 'closeSub',
115
- 'link',
116
- 'routerChange',
117
- 'nextExceeded',
118
- 'prevExceeded',
119
- 'tab',
120
- 'back',
121
- ])
135
+ const emit = defineEmits<{
136
+ (e: 'toggle', payload: TogglePayload): void
137
+ (e: 'backButtonClick'): void
138
+ (e: 'close'): void
139
+ (e: 'closeSub'): void
140
+ (e: 'link'): void
141
+ (e: 'routerChange'): void
142
+ (e: 'nextExceeded'): void
143
+ (e: 'prevExceeded'): void
144
+ (e: 'tab'): void
145
+ (e: 'back'): void
146
+ }>()
147
+
122
148
  const items = ref<SrlMenuItem[]>([])
123
149
 
124
150
  const opened = defineModel('opened', { type: Boolean, default: true })
@@ -140,7 +166,6 @@ function close() {
140
166
  } else {
141
167
  emit('close')
142
168
  }
143
- toggle()
144
169
  }
145
170
 
146
171
  function next(event: { index: number }) {
@@ -183,8 +208,8 @@ function routerChange() {
183
208
  emit('routerChange')
184
209
  }
185
210
 
186
- function toggle() {
187
- emit('toggle')
211
+ function toggle(data: TogglePayload) {
212
+ emit('toggle', data)
188
213
  }
189
214
 
190
215
  function closeAll(keep?: number | string) {
@@ -254,7 +279,10 @@ const menuItems = computed<NsWowNavigationItem[]>(() => {
254
279
  'aria-controls': props.id ?? '',
255
280
  'aria-expanded': String(opened.value)
256
281
  }
257
- backItem.callback = close
282
+ backItem.callback = () => {
283
+ close()
284
+ emit('backButtonClick')
285
+ }
258
286
  return [backItem, ...props.menu]
259
287
  }
260
288
  return props.menu
@@ -320,5 +348,3 @@ defineExpose({
320
348
  </template>
321
349
  </ul>
322
350
  </template>
323
-
324
- <style scoped lang="scss"></style>
@@ -39,11 +39,12 @@
39
39
  * const config = useConfig()
40
40
  */
41
41
  import { ref, type Ref } from 'vue';
42
+ import { objectDeepAssign } from '../utils/object.ts'
42
43
 
43
44
  const config = ref<NsWowConfig>({
44
45
  locale: 'de',
45
46
  settings: {
46
- languages: ['de', 'en'],
47
+ languages: ['de'],
47
48
  defaultLanguage: 'de',
48
49
  shortBreadcrumb: false,
49
50
  search: {
@@ -74,7 +75,7 @@ async function setConfig(): Promise<Ref<NsWowConfig>> {
74
75
 
75
76
  const path = `/src/locales/${locale}.json`;
76
77
  if (defaultMessages[path]) {
77
- config.value.translations[locale] = Object.assign(
78
+ config.value.translations[locale] = objectDeepAssign(
78
79
  defaultMessages[path],
79
80
  config.value.translations[locale],
80
81
  );
@@ -88,7 +89,7 @@ async function loadSettings() {
88
89
  try {
89
90
  const response: Response = await fetch(file);
90
91
  const data: NsWowSettings = await response.json();
91
- config.value.settings = Object.assign(config.value.settings, data);
92
+ config.value.settings = objectDeepAssign(config.value.settings, data);
92
93
  config.value.locale = data.defaultLanguage;
93
94
  document.documentElement.lang = data.defaultLanguage;
94
95
  } catch (e) {
@@ -1,4 +1,5 @@
1
1
  import _instance from './instance';
2
+ import _useSrlConfig from './srlConfig'
2
3
  import _useArticle from './article';
3
4
  import _useArticles from './articles';
4
5
  import _useConfig from './config';
@@ -12,6 +13,7 @@ import _useLanguageSwitch from './languageSwitch';
12
13
  import * as cssStyles from './cssStyles.ts'
13
14
 
14
15
  export const setInstance = _instance.setInstance;
16
+ export const useSrlConfig = _useSrlConfig;
15
17
  export const useInstance = _instance.useInstance;
16
18
  export const useArticle = _useArticle;
17
19
  export const useArticles = _useArticles;
@@ -29,6 +31,7 @@ export const useCssStyles = cssStyles.useCssStyles;
29
31
  export default {
30
32
  setInstance,
31
33
  useInstance,
34
+ useSrlConfig,
32
35
  useArticle,
33
36
  useArticles,
34
37
  useConfig,