@lvce-editor/title-bar-worker 3.21.0 → 4.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.
@@ -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,12 @@ 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
+ };
3235
3291
 
3236
3292
  const getWorkspaceUri = () => {
3237
3293
  return invoke('Workspace.getUri');
@@ -3253,6 +3309,7 @@ const loadContent2 = async state => {
3253
3309
  const buttons = getTitleBarButtons(platform, controlsOverlayEnabled, titleBarStyleCustom);
3254
3310
  const workspaceUri = await getWorkspaceUri();
3255
3311
  const title = getTitle(workspaceUri, state.titleTemplate, state.appName);
3312
+ const titleWidth = await measureTitleWidth(title, labelFontWeight, labelFontSize, labelFontFamily, labelLetterSpacing);
3256
3313
  const iconWidth = 30;
3257
3314
 
3258
3315
  // TODO load preferences here
@@ -3266,7 +3323,8 @@ const loadContent2 = async state => {
3266
3323
  iconWidth,
3267
3324
  title,
3268
3325
  titleBarButtons: buttons,
3269
- titleBarEntries: withWidths
3326
+ titleBarEntries: withWidths,
3327
+ titleWidth
3270
3328
  };
3271
3329
  };
3272
3330
 
@@ -3606,6 +3664,7 @@ const getTitleBarIconVirtualDom = (titleBarIconEnabled, iconSrc) => {
3606
3664
  const getItemVirtualDom = item => {
3607
3665
  // @ts-ignore
3608
3666
  const {
3667
+ ariaLabel,
3609
3668
  isFocused,
3610
3669
  isOpen,
3611
3670
  keyboardShortCut,
@@ -3619,6 +3678,7 @@ const getItemVirtualDom = item => {
3619
3678
  ariaExpanded: isOpen,
3620
3679
  ariaHasPopup: true,
3621
3680
  ariaKeyShortcuts: keyboardShortCut,
3681
+ ariaLabel: ariaLabel || label,
3622
3682
  ariaOwns: isOpen ? 'Menu-0' : undefined,
3623
3683
  childCount: 1,
3624
3684
  className,
@@ -3670,46 +3730,6 @@ const getTitleVirtualDom = (titleBarTitleEnabled, title) => {
3670
3730
  return [parentNode, text(title)];
3671
3731
  };
3672
3732
 
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
3733
  const getTitleBarVirtualDom = state => {
3714
3734
  const {
3715
3735
  assetDir,
@@ -3823,17 +3843,18 @@ const renderEventListeners = () => {
3823
3843
  }];
3824
3844
  };
3825
3845
 
3826
- const getTitleBarMenuBarWidth = (width, menuBarX, titleBarButtonsWidth) => {
3827
- const remainingWidth = width - menuBarX - titleBarButtonsWidth;
3828
- return remainingWidth;
3846
+ const getTitleBarMenuBarWidth = (windowWidth, menuBarX, iconWidth, titleWidth) => {
3847
+ return windowWidth / 2 - titleWidth / 2 - menuBarX - iconWidth;
3829
3848
  };
3849
+
3830
3850
  const resize = async (state, dimensions) => {
3831
3851
  const {
3832
- titleBarButtonsWidth
3852
+ iconWidth,
3853
+ titleWidth
3833
3854
  } = state;
3834
3855
  const menuBarX = dimensions.x;
3835
3856
  const menuBarY = dimensions.y;
3836
- const menuBarWidth = getTitleBarMenuBarWidth(dimensions.width, menuBarX, titleBarButtonsWidth);
3857
+ const menuBarWidth = getTitleBarMenuBarWidth(dimensions.width, menuBarX, iconWidth, titleWidth);
3837
3858
  const menuBarHeight = dimensions.height;
3838
3859
  return {
3839
3860
  ...state,
@@ -3861,12 +3882,34 @@ const setPlatform = (state, platform) => {
3861
3882
  };
3862
3883
  };
3863
3884
 
3864
- const setTitleTemplate = (state, titleTemplate) => {
3865
- const title = getTitle(state.workspaceUri, titleTemplate, state.appName);
3885
+ const setTitleTemplate = async (state, titleTemplate) => {
3886
+ const {
3887
+ appName,
3888
+ labelFontFamily,
3889
+ labelFontSize,
3890
+ labelFontWeight,
3891
+ labelLetterSpacing,
3892
+ workspaceUri
3893
+ } = state;
3894
+ const title = getTitle(workspaceUri, titleTemplate, appName);
3895
+ const titleWidth = await measureTitleWidth(title, labelFontWeight, labelFontSize, labelFontFamily, labelLetterSpacing);
3866
3896
  return {
3867
3897
  ...state,
3868
3898
  title,
3869
- titleTemplate
3899
+ titleTemplate,
3900
+ titleWidth
3901
+ };
3902
+ };
3903
+
3904
+ const setWidth = (state, width) => {
3905
+ const {
3906
+ iconWidth,
3907
+ titleWidth,
3908
+ x
3909
+ } = state;
3910
+ return {
3911
+ ...state,
3912
+ width: getTitleBarMenuBarWidth(width, x, iconWidth, titleWidth)
3870
3913
  };
3871
3914
  };
3872
3915
 
@@ -3947,27 +3990,25 @@ const focus = state => {
3947
3990
  };
3948
3991
 
3949
3992
  const focusLast = state => {
3950
- const {
3951
- titleBarEntries
3952
- } = state;
3993
+ const titleBarEntries = getNavigableTitleBarEntries(state);
3953
3994
  const indexToFocus = last(titleBarEntries);
3954
3995
  return focusIndex(state, indexToFocus);
3955
3996
  };
3956
3997
 
3957
3998
  const focusNext = state => {
3958
3999
  const {
3959
- focusedIndex,
3960
- titleBarEntries
4000
+ focusedIndex
3961
4001
  } = state;
4002
+ const titleBarEntries = getNavigableTitleBarEntries(state);
3962
4003
  const indexToFocus = next(titleBarEntries, focusedIndex);
3963
4004
  return focusIndex(state, indexToFocus);
3964
4005
  };
3965
4006
 
3966
4007
  const focusPrevious = state => {
3967
4008
  const {
3968
- focusedIndex,
3969
- titleBarEntries
4009
+ focusedIndex
3970
4010
  } = state;
4011
+ const titleBarEntries = getNavigableTitleBarEntries(state);
3971
4012
  const indexToFocus = previous(titleBarEntries, focusedIndex);
3972
4013
  return focusIndex(state, indexToFocus);
3973
4014
  };
@@ -3998,9 +4039,9 @@ const handleClick = async (state, button, index) => {
3998
4039
  const handleClickAt = async (state, button, eventX, eventY) => {
3999
4040
  const {
4000
4041
  iconWidth,
4001
- titleBarEntries,
4002
4042
  x
4003
4043
  } = state;
4044
+ const titleBarEntries = getNavigableTitleBarEntries(state);
4004
4045
  const menuOffset = getMenuOffset(x, eventX, iconWidth);
4005
4046
  const index = getTitleBarIndexFromPosition(titleBarEntries, menuOffset);
4006
4047
  if (index === -1) {
@@ -4512,6 +4553,7 @@ const commandMap = {
4512
4553
  'TitleBar.saveState': wrapGetter(saveState),
4513
4554
  'TitleBar.setPlatform': wrapCommand(setPlatform),
4514
4555
  'TitleBar.setTitleTemplate': wrapCommand(setTitleTemplate),
4556
+ 'TitleBar.setWidth': wrapCommand(setWidth),
4515
4557
  'TitleBar.showCommandCenter': wrapCommand(showCommandCenter),
4516
4558
  'TitleBar.showMenuBar': wrapCommand(showMenuBar),
4517
4559
  '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.0.0",
4
4
  "description": "Title Bar Worker",
5
5
  "repository": {
6
6
  "type": "git",