mirror of
https://gitee.com/ant-design-blazor/ant-design-blazor.git
synced 2024-11-29 18:48:50 +08:00
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:
parent
47ebd7f076
commit
b22f9495f4
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AntDesign.JsInterop
|
||||
{
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
13
components/core/JsInterop/ObservableApi/BoxSize.cs
Normal file
13
components/core/JsInterop/ObservableApi/BoxSize.cs
Normal 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; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
|
||||
}
|
||||
}
|
3
components/core/JsInterop/ObservableApi/observableApi.ts
Normal file
3
components/core/JsInterop/ObservableApi/observableApi.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import * as resize from './resizeObserver';
|
||||
|
||||
export { resize };
|
101
components/core/JsInterop/ObservableApi/resizeObserver.ts
Normal file
101
components/core/JsInterop/ObservableApi/resizeObserver.ts
Normal 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)
|
||||
}
|
||||
}
|
@ -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;
|
||||
@ -165,7 +173,7 @@ export function addDomEventListener(element, eventName, preventDefault, invoker)
|
||||
if (v instanceof Node) return 'Node';
|
||||
if (v instanceof Window) return 'Window';
|
||||
return v;
|
||||
}, ' ');
|
||||
}, ' ');
|
||||
setTimeout(function () { invoker.invokeMethodAsync('Invoke', json) }, 0);
|
||||
if (preventDefault === true) {
|
||||
args.preventDefault();
|
||||
@ -223,8 +231,8 @@ export function copy(text) {
|
||||
});
|
||||
}
|
||||
|
||||
export function focus(selector, noScroll: boolean=false) {
|
||||
let dom = getDom(selector);
|
||||
export function focus(selector, noScroll: boolean = false) {
|
||||
let dom = getDom(selector);
|
||||
if (!(dom instanceof HTMLElement))
|
||||
throw new Error("Unable to focus an invalid element.");
|
||||
dom.focus({
|
||||
|
@ -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;
|
||||
|
||||
_overflowElement = await Js.InvokeAsync<DomRect>(JSInteropConstants.GetBoundingClientRect, _overflow);
|
||||
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;
|
||||
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user