feat: use ResizeObserver Api instead of window.resize (#1392)

* feat: use ResizeObserver Api instead of window.resize

* fix(module:select): fall back to window.resize in IE11.

* fix(module:domEventService): switch from IsIE11 to IsResizeObserverSupported

* fix: Console.WriteLine removed

Co-authored-by: James Yeung <shunjiey@hotmail.com>
This commit is contained in:
Andrzej Bakun 2021-04-27 08:03:39 +02:00 committed by GitHub
parent 47ebd7f076
commit b22f9495f4
11 changed files with 253 additions and 21 deletions

View File

@ -29,5 +29,7 @@ namespace AntDesign.Core.Extensions
public static ValueTask SetSelectionStartAsync(this IJSRuntime jSRuntime, ElementReference target, int selectionStart) =>
jSRuntime.InvokeVoidAsync(JSInteropConstants.SetSelectionStart, target, selectionStart);
public static ValueTask<bool> IsResizeObserverSupported(this IJSRuntime jSRuntime) => jSRuntime.InvokeAsync<bool>(JSInteropConstants.IsResizeObserverSupported);
}
}

View File

@ -3,6 +3,10 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using AntDesign.Core.Extensions;
using AntDesign.Core.JsInterop.ObservableApi;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace AntDesign.JsInterop
@ -12,6 +16,7 @@ namespace AntDesign.JsInterop
private ConcurrentDictionary<string, List<DomEventSubscription>> _domEventListeners = new ConcurrentDictionary<string, List<DomEventSubscription>>();
private readonly IJSRuntime _jsRuntime;
private bool? _isResizeObserverSupported = null;
public DomEventService(IJSRuntime jsRuntime)
{
@ -82,6 +87,70 @@ namespace AntDesign.JsInterop
});
}
public async ValueTask AddResizeObserver(ElementReference dom, Action<List<ResizeObserverEntry>> callback)
{
string key = FormatKey(dom.Id, nameof(JSInteropConstants.ObserverConstants.Resize));
if (!(await IsResizeObserverSupported()))
{
Action<JsonElement> action = (je) => callback.Invoke(new List<ResizeObserverEntry> { new ResizeObserverEntry() });
AddEventListener("window", "resize", action, false);
}
else
{
if (!_domEventListeners.ContainsKey(key))
{
_domEventListeners[key] = new List<DomEventSubscription>();
await _jsRuntime.InvokeVoidAsync(JSInteropConstants.ObserverConstants.Resize.Create, key, DotNetObjectReference.Create(new Invoker<string>((p) =>
{
for (var i = 0; i < _domEventListeners[key].Count; i++)
{
var subscription = _domEventListeners[key][i];
object tP = JsonSerializer.Deserialize(p, subscription.Type);
subscription.Delegate.DynamicInvoke(tP);
}
})));
await _jsRuntime.InvokeVoidAsync(JSInteropConstants.ObserverConstants.Resize.Observe, key, dom);
}
_domEventListeners[key].Add(new DomEventSubscription(callback, typeof(List<ResizeObserverEntry>)));
}
}
public async ValueTask RemoveResizeObserver(ElementReference dom, Action<List<ResizeObserverEntry>> callback)
{
string key = FormatKey(dom.Id, nameof(JSInteropConstants.ObserverConstants.Resize));
if (_domEventListeners.ContainsKey(key))
{
var subscription = _domEventListeners[key].SingleOrDefault(s => s.Delegate == (Delegate)callback);
if (subscription != null)
{
_domEventListeners[key].Remove(subscription);
}
}
}
public async ValueTask DisposeResizeObserver(ElementReference dom)
{
string key = FormatKey(dom.Id, nameof(JSInteropConstants.ObserverConstants.Resize));
if (await IsResizeObserverSupported())
{
await _jsRuntime.InvokeVoidAsync(JSInteropConstants.ObserverConstants.Resize.Dispose, key);
}
_domEventListeners.TryRemove(key, out _);
}
public async ValueTask DisconnectResizeObserver(ElementReference dom)
{
string key = FormatKey(dom.Id, nameof(JSInteropConstants.ObserverConstants.Resize));
if (await IsResizeObserverSupported())
{
await _jsRuntime.InvokeVoidAsync(JSInteropConstants.ObserverConstants.Resize.Disconnect, key);
}
if (_domEventListeners.ContainsKey(key))
{
_domEventListeners[key].Clear();
}
}
private static string FormatKey(object dom, string eventName) => $"{dom}-{eventName}";
public void RemoveEventListerner<T>(object dom, string eventName, Action<T> callback)
@ -96,6 +165,8 @@ namespace AntDesign.JsInterop
}
}
}
private async ValueTask<bool> IsResizeObserverSupported() => _isResizeObserverSupported ??= await _jsRuntime.IsResizeObserverSupported();
}
public class Invoker<T>

View File

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AntDesign.JsInterop
{

View File

@ -1,14 +1,10 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace AntDesign
namespace AntDesign
{
public static class JSInteropConstants
{
private const string FUNC_PREFIX = "AntDesign.interop.";
public static string IsResizeObserverSupported => $"{FUNC_PREFIX}isResizeObserverSupported";
public static string GetDomInfo => $"{FUNC_PREFIX}getDomInfo";
public static string TriggerEvent => $"{FUNC_PREFIX}triggerEvent";
@ -117,5 +113,21 @@ namespace AntDesign
public static string ResetModalPosition => $"{FUNC_PREFIX}resetModalPosition";
#endregion Draggable Modal
public static class ObserverConstants
{
private const string FUNC_PREFIX = JSInteropConstants.FUNC_PREFIX + "observable.";
public static class Resize
{
private const string FUNC_PREFIX = ObserverConstants.FUNC_PREFIX + "resize.";
public static string Create = $"{FUNC_PREFIX}create";
public static string Observe = $"{FUNC_PREFIX}observe";
public static string Unobserve = $"{FUNC_PREFIX}unobserve";
public static string Disconnect = $"{FUNC_PREFIX}disconnect";
public static string Dispose = $"{FUNC_PREFIX}dispose";
}
}
}
}

View File

@ -0,0 +1,13 @@
using System.Text.Json.Serialization;
namespace AntDesign.Core.JsInterop.ObservableApi
{
public class BoxSize
{
[JsonPropertyName("blockSize")]
public decimal BlockSize { get; set; }
[JsonPropertyName("inlineSize")]
public decimal InlineSize { get; set; }
}
}

View File

@ -0,0 +1,22 @@
using System.Text.Json.Serialization;
using AntDesign.JsInterop;
using Microsoft.AspNetCore.Components;
namespace AntDesign.Core.JsInterop.ObservableApi
{
public class ResizeObserverEntry
{
[JsonPropertyName("borderBoxSize")]
public BoxSize BorderBoxSize { get; set; }
[JsonPropertyName("contentBoxSize")]
public BoxSize ContentBoxSize { get; set; }
[JsonPropertyName("contentRect")]
public DomRect ContentRect { get; set; }
[JsonPropertyName("target")]
public ElementReference Target { get; set; }
}
}

View File

@ -0,0 +1,3 @@
import * as resize from './resizeObserver';
export { resize };

View File

@ -0,0 +1,101 @@
import { getDom } from '../interop';
const resizeObservers: Map<string, ResizeObserver> = new Map<string, ResizeObserver>();
export function create(key, invoker) {
const observer = new ResizeObserver((entries, observer) => observerCallBack(entries, observer, invoker));
resizeObservers.set(key, observer)
}
export function observe(key: string, element) {
const observer = resizeObservers.get(key);
if (observer) {
let dom = getDom(element);
observer.observe(dom);
}
}
export function disconnect(key: string): void {
const observer = this.resizeObservers.get(key)
if (observer) {
observer.disconnect()
}
}
export function unobserve(key: string, element: Element): void {
const observer = this.resizeObservers.get(key)
if (observer) {
let dom = getDom(element);
observer.unobserve(dom)
}
}
export function dispose(key: string): void {
console.log("dispose", key);
disconnect(key)
resizeObservers.delete(key)
}
class BoxSize {
public blockSize?: number
public inlineSize?: number
}
class DomRect {
x?: number
y?: number
width?: number
height?: number
top?: number
right?: number
bottom?: number
left?: number
}
class ResizeObserverEntry {
borderBoxSize?: BoxSize
contentBoxSize?: BoxSize
contentRect?: DomRect
target?: Element
}
function observerCallBack(entries, observer, invoker) {
if (invoker) {
const mappedEntries = new Array<ResizeObserverEntry>()
entries.forEach(entry => {
if (entry) {
const mEntry = new ResizeObserverEntry()
if (entry.borderBoxSize) {
mEntry.borderBoxSize = new BoxSize()
mEntry.borderBoxSize.blockSize = entry.borderBoxSize.blockSize
mEntry.borderBoxSize.inlineSize = entry.borderBoxSize.inlineSize
}
if (entry.contentBoxSize) {
mEntry.contentBoxSize = new BoxSize()
mEntry.contentBoxSize.blockSize = entry.contentBoxSize.blockSize
mEntry.contentBoxSize.inlineSize = entry.contentBoxSize.inlineSize
}
if (entry.contentRect) {
mEntry.contentRect = new DomRect()
mEntry.contentRect.x = entry.contentRect.x
mEntry.contentRect.y = entry.contentRect.y
mEntry.contentRect.width = entry.contentRect.width
mEntry.contentRect.height = entry.contentRect.height
mEntry.contentRect.top = entry.contentRect.top
mEntry.contentRect.right = entry.contentRect.right
mEntry.contentRect.bottom = entry.contentRect.bottom
mEntry.contentRect.left = entry.contentRect.left
}
mEntry.target = entry.target
mappedEntries.push(mEntry)
}
})
const entriesJson = JSON.stringify(mappedEntries)
invoker.invokeMethodAsync('Invoke', entriesJson)
}
}

View File

@ -1,3 +1,11 @@
import * as observable from './ObservableApi/observableApi';
export { observable };
export function isResizeObserverSupported(): boolean {
return "ResizeObserver" in window;
}
export function getDom(element) {
if (!element) {
element = document.body;
@ -223,7 +231,7 @@ export function copy(text) {
});
}
export function focus(selector, noScroll: boolean=false) {
export function focus(selector, noScroll: boolean = false) {
let dom = getDom(selector);
if (!(dom instanceof HTMLElement))
throw new Error("Unable to focus an invalid element.");

View File

@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using AntDesign.Core.Extensions;
using AntDesign.Core.JsInterop.ObservableApi;
using AntDesign.JsInterop;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
@ -121,7 +121,7 @@ namespace AntDesign.Select.Internal
_suffixElement = await Js.InvokeAsync<DomRect>(JSInteropConstants.GetBoundingClientRect, _suffixRef);
_suffixElement.Width += 7;
}
DomEventService.AddEventListener("window", "resize", OnWindowResize, false);
await DomEventService.AddResizeObserver(_overflow, OnOveralyResize);
await CalculateResponsiveTags();
}
DomEventService.AddEventListener(ParentSelect._inputRef, "focusout", OnBlurInternal, true);
@ -136,17 +136,20 @@ namespace AntDesign.Select.Internal
await base.OnAfterRenderAsync(firstRender);
}
protected async void OnWindowResize(JsonElement element)
protected async void OnOveralyResize(List<ResizeObserverEntry> entries)
{
await CalculateResponsiveTags();
await CalculateResponsiveTags(false, entries[0].ContentRect);
}
internal async Task CalculateResponsiveTags(bool forceInputFocus = false)
internal async Task CalculateResponsiveTags(bool forceInputFocus = false, DomRect entry = null)
{
if (!ParentSelect.IsResponsive)
return;
if (entry is null)
_overflowElement = await Js.InvokeAsync<DomRect>(JSInteropConstants.GetBoundingClientRect, _overflow);
else
_overflowElement = entry;
//distance between items is margin-inline-left=4px
decimal accumulatedWidth = _prefixElement.Width + _suffixElement.Width + (4 + (SearchValue?.Length ?? 0) * 8);
@ -391,6 +394,8 @@ namespace AntDesign.Select.Internal
_ = InvokeAsync(async () =>
{
await Task.Delay(100);
if (ParentSelect.IsResponsive)
await DomEventService.DisposeResizeObserver(_overflow);
await Js.InvokeVoidAsync(JSInteropConstants.RemovePreventKeys, ParentSelect._inputRef);
await Js.InvokeVoidAsync(JSInteropConstants.RemovePreventEnterOnOverlayVisible, ParentSelect._inputRef);
});
@ -398,8 +403,6 @@ namespace AntDesign.Select.Internal
DomEventService.RemoveEventListerner<JsonElement>(ParentSelect._inputRef, "focus", OnFocusInternal);
DomEventService.RemoveEventListerner<JsonElement>(ParentSelect._inputRef, "focusout", OnBlurInternal);
DomEventService.RemoveEventListerner<JsonElement>("window", "beforeunload", Reloading);
if (ParentSelect.IsResponsive)
DomEventService.RemoveEventListerner<JsonElement>("window", "resize", OnWindowResize);
if (IsDisposed) return;

View File

@ -33,6 +33,7 @@
"@commitlint/cli": "^11.0.0",
"@commitlint/config-conventional": "^11.0.0",
"@types/es6-promise": "^3.3.0",
"@types/resize-observer-browser": "^0.1.3",
"babel-core": "^6.26.3",
"babel-preset-es2015": "^6.24.1",
"babelify": "^8.0.0",