@lvce-editor/title-bar-worker 3.21.0 → 4.1.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.
@@ -1599,6 +1599,7 @@ const createDefaultState = (uid = DEFAULT_UID) => ({
1599
1599
  titleBarStyleCustom: true,
1600
1600
  titleBarTitleEnabled: true,
1601
1601
  titleTemplate: '${folderName}',
1602
+ titleWidth: 0,
1602
1603
  uid,
1603
1604
  width: 800,
1604
1605
  workspaceUri: '',
@@ -2729,10 +2730,6 @@ const mergeClassNames = (...classNames) => {
2729
2730
  return classNames.filter(Boolean).join(' ');
2730
2731
  };
2731
2732
 
2732
- const px = value => {
2733
- return `${value}px`;
2734
- };
2735
-
2736
2733
  const text = data => {
2737
2734
  return {
2738
2735
  childCount: 0,
@@ -2850,6 +2847,75 @@ const getMenuOffset = (x, clientX, iconWidth) => {
2850
2847
  return clientX - x - iconWidth;
2851
2848
  };
2852
2849
 
2850
+ const Ellipsis = 'Ellipsis';
2851
+
2852
+ const MoreEntryWidth = 38;
2853
+ const OverflowMenuId = 'title-bar-overflow';
2854
+ const getTotalWidth$1 = entries => {
2855
+ let total = 0;
2856
+ for (const entry of entries) {
2857
+ total += entry.width;
2858
+ }
2859
+ return total;
2860
+ };
2861
+ const getVisibleTitleBarEntries = (entries, width, focusedIndex, isMenuOpen) => {
2862
+ array(entries);
2863
+ number(width);
2864
+ let total = 0;
2865
+ const visible = [];
2866
+ for (let i = 0; i < entries.length; i++) {
2867
+ const entry = entries[i];
2868
+ const nextTotal = total + entry.width;
2869
+ if (nextTotal >= width) {
2870
+ break;
2871
+ }
2872
+ total = nextTotal;
2873
+ const visibleIndex = visible.length;
2874
+ const isOpen = visibleIndex === focusedIndex && isMenuOpen;
2875
+ const isFocused = visibleIndex === focusedIndex;
2876
+ visible.push({
2877
+ ...entry,
2878
+ isFocused,
2879
+ isOpen
2880
+ });
2881
+ }
2882
+ const hasOverflow = visible.length < entries.length;
2883
+ if (hasOverflow) {
2884
+ while (visible.length > 0 && getTotalWidth$1(visible) + MoreEntryWidth > width) {
2885
+ visible.pop();
2886
+ }
2887
+ const hiddenEntries = entries.slice(visible.length);
2888
+ const overflowIndex = visible.length;
2889
+ visible.push({
2890
+ ariaLabel: moreDot(),
2891
+ hiddenEntries,
2892
+ icon: Ellipsis,
2893
+ id: OverflowMenuId,
2894
+ isFocused: overflowIndex === focusedIndex,
2895
+ isOpen: overflowIndex === focusedIndex && isMenuOpen,
2896
+ label: '...',
2897
+ width: MoreEntryWidth
2898
+ });
2899
+ }
2900
+ return visible;
2901
+ };
2902
+
2903
+ const hasOverflowEntry = entries => {
2904
+ return entries.some(entry => entry?.id === OverflowMenuId);
2905
+ };
2906
+ const getNavigableTitleBarEntries = state => {
2907
+ const {
2908
+ focusedIndex,
2909
+ isMenuOpen,
2910
+ titleBarEntries,
2911
+ width
2912
+ } = state;
2913
+ if (hasOverflowEntry(titleBarEntries)) {
2914
+ return titleBarEntries;
2915
+ }
2916
+ return getVisibleTitleBarEntries(titleBarEntries, width, focusedIndex, isMenuOpen);
2917
+ };
2918
+
2853
2919
  const getTitleBarIndexFromPosition = (titleBarEntries, x, y) => {
2854
2920
  let currentX = 0;
2855
2921
  for (let i = 0; i < titleBarEntries.length; i++) {
@@ -2954,23 +3020,34 @@ const getIndexToFocusNext = menu => {
2954
3020
 
2955
3021
  // TODO more tests
2956
3022
 
3023
+ const getOverflowMenuItems = hiddenEntries => {
3024
+ return hiddenEntries.map(entry => ({
3025
+ command: '',
3026
+ flags: SubMenu$1,
3027
+ id: entry.id,
3028
+ label: entry.label
3029
+ }));
3030
+ };
2957
3031
  const openMenuAtIndex = async (state, index, shouldBeFocused) => {
2958
3032
  const {
2959
3033
  iconWidth,
2960
3034
  platform,
2961
- titleBarEntries,
2962
3035
  titleBarHeight,
2963
3036
  x
2964
3037
  } = state;
3038
+ const titleBarEntries = getNavigableTitleBarEntries(state);
2965
3039
  // TODO race conditions
2966
3040
  // TODO send renderer process
2967
3041
  // 1. open menu, items to show
2968
3042
  // 2. focus menu
2969
3043
  const titleBarEntry = titleBarEntries[index];
3044
+ if (!titleBarEntry) {
3045
+ return state;
3046
+ }
2970
3047
  const {
2971
3048
  id
2972
3049
  } = titleBarEntry;
2973
- const items = await getMenuEntries2(state, {
3050
+ const items = id === OverflowMenuId ? getOverflowMenuItems(titleBarEntry.hiddenEntries || []) : await getMenuEntries2(state, {
2974
3051
  menuId: id,
2975
3052
  platform
2976
3053
  });
@@ -2987,7 +3064,7 @@ const openMenuAtIndex = async (state, index, shouldBeFocused) => {
2987
3064
  const menu = {
2988
3065
  focusedIndex: menuFocusedIndex,
2989
3066
  height,
2990
- id,
3067
+ id: id === OverflowMenuId ? undefined : id,
2991
3068
  items,
2992
3069
  level: 0,
2993
3070
  width,
@@ -3048,9 +3125,9 @@ const handleMouseOut = ifElse(handleMouseOutMenuOpen, handleMouseOutMenuClosed);
3048
3125
  const handlePointerOut = (state, clientX, clientY) => {
3049
3126
  const {
3050
3127
  iconWidth,
3051
- titleBarEntries,
3052
3128
  x
3053
3129
  } = state;
3130
+ const titleBarEntries = getNavigableTitleBarEntries(state);
3054
3131
  const menuX = getMenuOffset(x, clientX, iconWidth);
3055
3132
  const index = getTitleBarIndexFromPosition(titleBarEntries, menuX);
3056
3133
  if (index === -1) {
@@ -3073,9 +3150,7 @@ const handleMouseOverMenuOpen = async (state, index) => {
3073
3150
  const handleMouseOver = ifElse(handleMouseOverMenuOpen, handleMouseOverMenuClosed);
3074
3151
 
3075
3152
  const handlePointerOver = (state, name) => {
3076
- const {
3077
- titleBarEntries
3078
- } = state;
3153
+ const titleBarEntries = getNavigableTitleBarEntries(state);
3079
3154
  const index = titleBarEntries.findIndex(item => item.label === name);
3080
3155
  if (index === -1) {
3081
3156
  return state;
@@ -3124,45 +3199,6 @@ const getTitle = (workspaceUri, titleTemplate, appName) => {
3124
3199
  return parseTitleTemplate(titleTemplate, folderName, appName);
3125
3200
  };
3126
3201
 
3127
- // TODO in the future, it could also be a multi-root workspace
3128
- const handleWorkspaceChange = async (state, uri) => {
3129
- const title = getTitle(uri, state.titleTemplate, state.appName);
3130
- return {
3131
- ...state,
3132
- title,
3133
- workspaceUri: uri
3134
- };
3135
- };
3136
-
3137
- const hideCommandCenter = async state => {
3138
- return {
3139
- ...state,
3140
- commandCenterEnabled: false
3141
- };
3142
- };
3143
-
3144
- const hideMenuBar = async state => {
3145
- return {
3146
- ...state,
3147
- titleBarMenuBarEnabled: false
3148
- };
3149
- };
3150
-
3151
- const createTextMeasureContext = (letterSpacing, font) => {
3152
- const canvas = new OffscreenCanvas(0, 0);
3153
- const ctx = canvas.getContext('2d');
3154
- if (!ctx) {
3155
- throw new Error('Failed to get canvas context 2d');
3156
- }
3157
- ctx.letterSpacing = letterSpacing;
3158
- ctx.font = font;
3159
- return ctx;
3160
- };
3161
-
3162
- const getFontString = (fontWeight, fontSize, fontFamily) => {
3163
- return `${fontWeight} ${fontSize}px ${fontFamily}`;
3164
- };
3165
-
3166
3202
  const launchTextMeasurementWorker = async () => {
3167
3203
  const rpc = await create$4({
3168
3204
  commandMap: {},
@@ -3189,37 +3225,51 @@ const measureTextWidths2 = async (texts, fontWeight, fontSize, fontFamily, lette
3189
3225
  return result;
3190
3226
  };
3191
3227
 
3192
- const measureTextWidthsOld = async (texts, fontWeight, fontSize, fontFamily, letterSpacing) => {
3193
- if (typeof letterSpacing !== 'number') {
3194
- throw new TypeError('letterSpacing must be of type number');
3195
- }
3196
- const letterSpacingString = px(letterSpacing);
3197
- const fontString = getFontString(fontWeight, fontSize, fontFamily);
3198
- const ctx = createTextMeasureContext(letterSpacingString, fontString);
3199
- const widths = [];
3200
- for (const text of texts) {
3201
- const metrics = ctx.measureText(text);
3202
- const {
3203
- width
3204
- } = metrics;
3205
- widths.push(width);
3206
- }
3207
- return widths;
3228
+ const measureTextWidths = async (texts, fontWeight, fontSize, fontFamily, letterSpacing) => measureTextWidths2(texts, fontWeight, fontSize, fontFamily, letterSpacing);
3229
+
3230
+ const measureTitleWidth = async (title, fontWeight, fontSize, fontFamily, letterSpacing) => {
3231
+ const [titleWidth = 0] = await measureTextWidths([title], fontWeight, fontSize, fontFamily, letterSpacing);
3232
+ return titleWidth;
3208
3233
  };
3209
- const measureTextWidths = async (texts, fontWeight, fontSize, fontFamily, letterSpacing) => {
3210
- try {
3211
- return measureTextWidths2(texts, fontWeight, fontSize, fontFamily, letterSpacing);
3212
- } catch {
3213
- return measureTextWidthsOld(texts, fontWeight, fontSize, fontFamily, letterSpacing);
3214
- }
3234
+
3235
+ // TODO in the future, it could also be a multi-root workspace
3236
+ const handleWorkspaceChange = async (state, uri) => {
3237
+ const {
3238
+ appName,
3239
+ labelFontFamily,
3240
+ labelFontSize,
3241
+ labelFontWeight,
3242
+ labelLetterSpacing,
3243
+ titleTemplate
3244
+ } = state;
3245
+ const title = getTitle(uri, titleTemplate, appName);
3246
+ const titleWidth = await measureTitleWidth(title, labelFontWeight, labelFontSize, labelFontFamily, labelLetterSpacing);
3247
+ return {
3248
+ ...state,
3249
+ title,
3250
+ titleWidth,
3251
+ workspaceUri: uri
3252
+ };
3253
+ };
3254
+
3255
+ const hideCommandCenter = async state => {
3256
+ return {
3257
+ ...state,
3258
+ commandCenterEnabled: false
3259
+ };
3260
+ };
3261
+
3262
+ const hideMenuBar = async state => {
3263
+ return {
3264
+ ...state,
3265
+ titleBarMenuBarEnabled: false
3266
+ };
3215
3267
  };
3216
3268
 
3217
3269
  const getLabel = entry => {
3218
3270
  return entry.label;
3219
3271
  };
3220
- const addWidths = async (entries, labelPadding, fontWeight, fontSize, fontFamily, letterSpacing) => {
3221
- const labels = entries.map(getLabel);
3222
- const widths = await measureTextWidths(labels, fontWeight, fontSize, fontFamily, letterSpacing);
3272
+ const getWithWidths = (entries, widths, labelPadding) => {
3223
3273
  const withWidths = [];
3224
3274
  for (let i = 0; i < entries.length; i++) {
3225
3275
  const entry = entries[i];
@@ -3232,6 +3282,16 @@ const addWidths = async (entries, labelPadding, fontWeight, fontSize, fontFamily
3232
3282
  }
3233
3283
  return withWidths;
3234
3284
  };
3285
+ const addWidths = async (entries, labelPadding, fontWeight, fontSize, fontFamily, letterSpacing) => {
3286
+ const labels = entries.map(getLabel);
3287
+ const widths = await measureTextWidths(labels, fontWeight, fontSize, fontFamily, letterSpacing);
3288
+ const withWidths = getWithWidths(entries, widths, labelPadding);
3289
+ return withWidths;
3290
+ };
3291
+
3292
+ const getTitleBarMenuBarWidth = (windowWidth, menuBarX, iconWidth, titleWidth) => {
3293
+ return windowWidth / 2 - titleWidth / 2 - menuBarX - iconWidth;
3294
+ };
3235
3295
 
3236
3296
  const getWorkspaceUri = () => {
3237
3297
  return invoke('Workspace.getUri');
@@ -3253,7 +3313,9 @@ const loadContent2 = async state => {
3253
3313
  const buttons = getTitleBarButtons(platform, controlsOverlayEnabled, titleBarStyleCustom);
3254
3314
  const workspaceUri = await getWorkspaceUri();
3255
3315
  const title = getTitle(workspaceUri, state.titleTemplate, state.appName);
3316
+ const titleWidth = await measureTitleWidth(title, labelFontWeight, labelFontSize, labelFontFamily, labelLetterSpacing);
3256
3317
  const iconWidth = 30;
3318
+ const width = getTitleBarMenuBarWidth(state.width, state.x, iconWidth, titleWidth);
3257
3319
 
3258
3320
  // TODO load preferences here
3259
3321
 
@@ -3266,7 +3328,9 @@ const loadContent2 = async state => {
3266
3328
  iconWidth,
3267
3329
  title,
3268
3330
  titleBarButtons: buttons,
3269
- titleBarEntries: withWidths
3331
+ titleBarEntries: withWidths,
3332
+ titleWidth,
3333
+ width
3270
3334
  };
3271
3335
  };
3272
3336
 
@@ -3606,6 +3670,7 @@ const getTitleBarIconVirtualDom = (titleBarIconEnabled, iconSrc) => {
3606
3670
  const getItemVirtualDom = item => {
3607
3671
  // @ts-ignore
3608
3672
  const {
3673
+ ariaLabel,
3609
3674
  isFocused,
3610
3675
  isOpen,
3611
3676
  keyboardShortCut,
@@ -3619,6 +3684,7 @@ const getItemVirtualDom = item => {
3619
3684
  ariaExpanded: isOpen,
3620
3685
  ariaHasPopup: true,
3621
3686
  ariaKeyShortcuts: keyboardShortCut,
3687
+ ariaLabel: ariaLabel || label,
3622
3688
  ariaOwns: isOpen ? 'Menu-0' : undefined,
3623
3689
  childCount: 1,
3624
3690
  className,
@@ -3670,46 +3736,6 @@ const getTitleVirtualDom = (titleBarTitleEnabled, title) => {
3670
3736
  return [parentNode, text(title)];
3671
3737
  };
3672
3738
 
3673
- const Ellipsis = 'Ellipsis';
3674
-
3675
- const getVisibleTitleBarEntries = (entries, width, focusedIndex, isMenuOpen) => {
3676
- array(entries);
3677
- number(width);
3678
- let total = 0;
3679
- const visible = [];
3680
- for (let i = 0; i < entries.length; i++) {
3681
- const entry = entries[i];
3682
- total += entry.width;
3683
- if (total >= width) {
3684
- break;
3685
- }
3686
- const isOpen = i === focusedIndex && isMenuOpen;
3687
- const isFocused = i === focusedIndex;
3688
- visible.push({
3689
- ...entry,
3690
- isFocused,
3691
- isOpen
3692
- });
3693
- }
3694
- const hasOverflow = visible.length < entries.length;
3695
- if (hasOverflow) {
3696
- const padding = 8;
3697
- const moreIconWidth = 22;
3698
- const totalPadding = padding * 2;
3699
- const hasStillOverflow = total + moreIconWidth + totalPadding > width;
3700
- if (hasStillOverflow) {
3701
- visible.pop();
3702
- }
3703
- visible.push({
3704
- ariaLabel: moreDot(),
3705
- icon: Ellipsis,
3706
- label: '',
3707
- width: moreIconWidth + totalPadding
3708
- });
3709
- }
3710
- return visible;
3711
- };
3712
-
3713
3739
  const getTitleBarVirtualDom = state => {
3714
3740
  const {
3715
3741
  assetDir,
@@ -3823,17 +3849,14 @@ const renderEventListeners = () => {
3823
3849
  }];
3824
3850
  };
3825
3851
 
3826
- const getTitleBarMenuBarWidth = (width, menuBarX, titleBarButtonsWidth) => {
3827
- const remainingWidth = width - menuBarX - titleBarButtonsWidth;
3828
- return remainingWidth;
3829
- };
3830
3852
  const resize = async (state, dimensions) => {
3831
3853
  const {
3832
- titleBarButtonsWidth
3854
+ iconWidth,
3855
+ titleWidth
3833
3856
  } = state;
3834
3857
  const menuBarX = dimensions.x;
3835
3858
  const menuBarY = dimensions.y;
3836
- const menuBarWidth = getTitleBarMenuBarWidth(dimensions.width, menuBarX, titleBarButtonsWidth);
3859
+ const menuBarWidth = getTitleBarMenuBarWidth(dimensions.width, menuBarX, iconWidth, titleWidth);
3837
3860
  const menuBarHeight = dimensions.height;
3838
3861
  return {
3839
3862
  ...state,
@@ -3861,12 +3884,34 @@ const setPlatform = (state, platform) => {
3861
3884
  };
3862
3885
  };
3863
3886
 
3864
- const setTitleTemplate = (state, titleTemplate) => {
3865
- const title = getTitle(state.workspaceUri, titleTemplate, state.appName);
3887
+ const setTitleTemplate = async (state, titleTemplate) => {
3888
+ const {
3889
+ appName,
3890
+ labelFontFamily,
3891
+ labelFontSize,
3892
+ labelFontWeight,
3893
+ labelLetterSpacing,
3894
+ workspaceUri
3895
+ } = state;
3896
+ const title = getTitle(workspaceUri, titleTemplate, appName);
3897
+ const titleWidth = await measureTitleWidth(title, labelFontWeight, labelFontSize, labelFontFamily, labelLetterSpacing);
3866
3898
  return {
3867
3899
  ...state,
3868
3900
  title,
3869
- titleTemplate
3901
+ titleTemplate,
3902
+ titleWidth
3903
+ };
3904
+ };
3905
+
3906
+ const setWidth = (state, width) => {
3907
+ const {
3908
+ iconWidth,
3909
+ titleWidth,
3910
+ x
3911
+ } = state;
3912
+ return {
3913
+ ...state,
3914
+ width: getTitleBarMenuBarWidth(width, x, iconWidth, titleWidth)
3870
3915
  };
3871
3916
  };
3872
3917
 
@@ -3947,27 +3992,25 @@ const focus = state => {
3947
3992
  };
3948
3993
 
3949
3994
  const focusLast = state => {
3950
- const {
3951
- titleBarEntries
3952
- } = state;
3995
+ const titleBarEntries = getNavigableTitleBarEntries(state);
3953
3996
  const indexToFocus = last(titleBarEntries);
3954
3997
  return focusIndex(state, indexToFocus);
3955
3998
  };
3956
3999
 
3957
4000
  const focusNext = state => {
3958
4001
  const {
3959
- focusedIndex,
3960
- titleBarEntries
4002
+ focusedIndex
3961
4003
  } = state;
4004
+ const titleBarEntries = getNavigableTitleBarEntries(state);
3962
4005
  const indexToFocus = next(titleBarEntries, focusedIndex);
3963
4006
  return focusIndex(state, indexToFocus);
3964
4007
  };
3965
4008
 
3966
4009
  const focusPrevious = state => {
3967
4010
  const {
3968
- focusedIndex,
3969
- titleBarEntries
4011
+ focusedIndex
3970
4012
  } = state;
4013
+ const titleBarEntries = getNavigableTitleBarEntries(state);
3971
4014
  const indexToFocus = previous(titleBarEntries, focusedIndex);
3972
4015
  return focusIndex(state, indexToFocus);
3973
4016
  };
@@ -3998,9 +4041,9 @@ const handleClick = async (state, button, index) => {
3998
4041
  const handleClickAt = async (state, button, eventX, eventY) => {
3999
4042
  const {
4000
4043
  iconWidth,
4001
- titleBarEntries,
4002
4044
  x
4003
4045
  } = state;
4046
+ const titleBarEntries = getNavigableTitleBarEntries(state);
4004
4047
  const menuOffset = getMenuOffset(x, eventX, iconWidth);
4005
4048
  const index = getTitleBarIndexFromPosition(titleBarEntries, menuOffset);
4006
4049
  if (index === -1) {
@@ -4512,6 +4555,7 @@ const commandMap = {
4512
4555
  'TitleBar.saveState': wrapGetter(saveState),
4513
4556
  'TitleBar.setPlatform': wrapCommand(setPlatform),
4514
4557
  'TitleBar.setTitleTemplate': wrapCommand(setTitleTemplate),
4558
+ 'TitleBar.setWidth': wrapCommand(setWidth),
4515
4559
  'TitleBar.showCommandCenter': wrapCommand(showCommandCenter),
4516
4560
  'TitleBar.showMenuBar': wrapCommand(showMenuBar),
4517
4561
  'TitleBar.terminate': terminate,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/title-bar-worker",
3
- "version": "3.21.0",
3
+ "version": "4.1.0",
4
4
  "description": "Title Bar Worker",
5
5
  "repository": {
6
6
  "type": "git",