From 27acbaf7f7e3643841bff735b09f6af546a1ff14 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sat, 4 Jan 2025 15:23:02 -0300 Subject: [PATCH 01/64] v3.0.0-beta.1 - CI: test with `dart2js` and `dart2wasm` (on Chrome). - sdk: '>=3.6.0 <4.0.0' - js_interop_utils: ^1.0.1 - web_utils: ^1.0.1 - web: ^1.1.0 - collection: ^1.19.0 - lints: ^5.1.1 --- .github/workflows/dart.yml | 12 +++++++----- CHANGELOG.md | 12 ++++++++++++ dart_test.yaml | 1 + pubspec.yaml | 11 +++++++---- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 3894e72..e54569e 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dart-lang/setup-dart@v1 - name: Dart version run: | @@ -37,7 +37,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dart-lang/setup-dart@v1 - name: Dart version run: | @@ -55,7 +55,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dart-lang/setup-dart@v1 - name: Dart version run: | @@ -65,8 +65,10 @@ jobs: run: dart pub get - name: Upgrade dependencies run: dart pub upgrade - - name: Run tests (Chrome) - run: dart test --platform chrome --coverage=./coverage + - name: Run tests (Chrome Wasm) + run: dart test --platform chrome --compiler dart2wasm + - name: Run tests (Chrome JS) + run: dart test --platform chrome --compiler dart2js --coverage=./coverage - name: Generate coverage report run: | dart pub global activate coverage diff --git a/CHANGELOG.md b/CHANGELOG.md index 1841262..9249608 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## 3.0.0-beta.1 + +- CI: test with `dart2js` and `dart2wasm` (on Chrome). + +- sdk: '>=3.6.0 <4.0.0' + +- js_interop_utils: ^1.0.1 +- web_utils: ^1.0.1 +- web: ^1.1.0 +- collection: ^1.19.0 +- lints: ^5.1.1 + ## 2.5.13 - `UIComponent`: diff --git a/dart_test.yaml b/dart_test.yaml index 8bee834..539b9d4 100644 --- a/dart_test.yaml +++ b/dart_test.yaml @@ -20,3 +20,4 @@ define_platforms: linux: firefox-esr platforms: [chrome] +compilers: [dart2js, dart2wasm] diff --git a/pubspec.yaml b/pubspec.yaml index ab29f58..f26235c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,16 +1,19 @@ name: bones_ui description: Bones_UI - An intuitive and user-friendly Web User Interface framework for Dart. -version: 2.5.13 +version: 3.0.0-beta.1 homepage: https://github.com/Colossus-Services/bones_ui environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' executables: bones_ui: bones_ui_test: dependencies: + js_interop_utils: ^1.0.1 + web_utils: ^1.0.1 + web: ^1.1.0 intl_messages: ^2.3.4 dom_tools: ^2.3.2 dom_builder: ^2.2.7 @@ -29,7 +32,7 @@ dependencies: enum_to_string: ^2.0.1 yaml: ^3.1.3 archive: ^3.6.1 - collection: ^1.18.0 + collection: ^1.19.0 args: ^2.6.0 logging: ^1.3.0 path: ^1.9.1 @@ -43,7 +46,7 @@ dependencies: dev_dependencies: build_web_compilers: ^4.1.0 build_runner: ^2.4.14 - lints: ^4.0.0 + lints: ^5.1.1 dependency_validator: ^4.1.2 From 656d3594b59ae758a317bbf1c3221da15209f4a7 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Mon, 17 Feb 2025 21:16:32 -0300 Subject: [PATCH 02/64] v3.0.0-beta.1 - CI: test with `dart2js` and `dart2wasm` (on Chrome). - sdk: '>=3.6.0 <4.0.0' - js_interop_utils: ^1.0.5 - web_utils: ^1.0.5 - dom_tools: ^3.0.0-beta.3 - dom_builder: ^3.0.0-beta.1 - web: ^1.1.0 - collection: ^1.19.0 - test: ^1.25.15 - stream_channel: ^2.1.4 - lints: ^5.1.1 - build_web_compilers: ^4.1.1 - build_runner: ^2.4.15 --- CHANGELOG.md | 11 +- example/main.dart | 6 +- lib/bones_ui.dart | 2 +- lib/bones_ui_kit.dart | 6 +- lib/bones_ui_test.dart | 5 +- lib/bones_ui_test_clit.dart | 2 +- lib/src/bones_ui_async_content.dart | 22 +- lib/src/bones_ui_base.dart | 67 +- lib/src/bones_ui_component.dart | 372 ++++++----- lib/src/bones_ui_content.dart | 4 +- lib/src/bones_ui_document.dart | 12 +- lib/src/bones_ui_explorer.dart | 42 +- lib/src/bones_ui_extension.dart | 52 +- lib/src/bones_ui_generator.dart | 61 +- lib/src/bones_ui_internal.dart | 16 +- lib/src/bones_ui_layout.dart | 48 +- lib/src/bones_ui_log.dart | 68 +- lib/src/bones_ui_navigator.dart | 20 +- lib/src/bones_ui_root.dart | 17 +- lib/src/bones_ui_test_tools.dart | 688 +++++++++++++++----- lib/src/bones_ui_utils.dart | 7 +- lib/src/bones_ui_web.dart | 52 +- lib/src/component/bui.dart | 129 ++-- lib/src/component/button.dart | 43 +- lib/src/component/calendar.dart | 8 +- lib/src/component/capture.dart | 140 ++-- lib/src/component/clip_image.dart | 31 +- lib/src/component/color_picker.dart | 89 +-- lib/src/component/controlled_component.dart | 26 +- lib/src/component/data_source.dart | 14 +- lib/src/component/dialog.dart | 38 +- lib/src/component/dialog_edit_image.dart | 77 ++- lib/src/component/infos_table.dart | 40 +- lib/src/component/input_config.dart | 175 ++--- lib/src/component/json_render.dart | 35 + lib/src/component/loading.dart | 25 +- lib/src/component/masonry.dart | 50 +- lib/src/component/menu.dart | 46 +- lib/src/component/multi_selection.dart | 129 ++-- lib/src/component/svg.dart | 47 +- lib/src/component/template.dart | 26 +- pubspec.yaml | 27 +- test/bones_ui_test.dart | 45 +- 43 files changed, 1677 insertions(+), 1143 deletions(-) create mode 100644 lib/src/component/json_render.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 9249608..43f0cf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,18 @@ - sdk: '>=3.6.0 <4.0.0' -- js_interop_utils: ^1.0.1 -- web_utils: ^1.0.1 +- js_interop_utils: ^1.0.5 +- web_utils: ^1.0.5 +- dom_tools: ^3.0.0-beta.3 +- dom_builder: ^3.0.0-beta.1 - web: ^1.1.0 - collection: ^1.19.0 +- test: ^1.25.15 +- stream_channel: ^2.1.4 + - lints: ^5.1.1 +- build_web_compilers: ^4.1.1 +- build_runner: ^2.4.15 ## 2.5.13 diff --git a/example/main.dart b/example/main.dart index b6474d2..5c48d69 100644 --- a/example/main.dart +++ b/example/main.dart @@ -1,10 +1,8 @@ -import 'dart:html'; - import 'package:bones_ui/bones_ui_kit.dart'; void main() async { // Create `bones_ui` root and initialize it: - var root = MyRoot(querySelector('#output')); + var root = MyRoot(document.querySelector('#output')); root.initialize(); } @@ -162,7 +160,7 @@ class MyComponents extends UIComponent { '
', UIButton(content, 'UIButton') ..onClick.listen((event) => _showAlert('UIButton Clicked:', - 'x: ${event.client.x}
y: ${event.client.y}')), + 'x: ${event.clientX}
y: ${event.clientY}')), '
', UIInputTable(content, [ InputConfig('name', 'Name', type: 'text'), diff --git a/lib/bones_ui.dart b/lib/bones_ui.dart index c3c2788..6ac31f9 100644 --- a/lib/bones_ui.dart +++ b/lib/bones_ui.dart @@ -1,7 +1,7 @@ /// Bone UI /// /// Easy and simple Web User Interface Framework for Dart. -library bones_ui; +library; export 'dart:async'; diff --git a/lib/bones_ui_kit.dart b/lib/bones_ui_kit.dart index 71c2bc5..c67e08e 100644 --- a/lib/bones_ui_kit.dart +++ b/lib/bones_ui_kit.dart @@ -1,14 +1,16 @@ /// Bone UI - Kit /// /// Exports `bones_ui` + extra useful packages. -library bones_ui_kit; +library; export 'package:dom_builder/dom_builder.dart'; export 'package:dom_tools/dom_tools.dart'; export 'package:dynamic_call/dynamic_call.dart'; export 'package:html_unescape/html_unescape.dart'; -export 'package:json_object_mapper/json_object_mapper.dart'; +export 'package:js_interop_utils/js_interop_utils.dart'; export 'package:mercury_client/mercury_client.dart'; export 'package:swiss_knife/swiss_knife.dart'; +export 'package:web_utils/web_utils.dart' + hide CSS, HttpStatus, HttpRequest, Credential, MimeType, Geolocation; export 'bones_ui.dart'; diff --git a/lib/bones_ui_test.dart b/lib/bones_ui_test.dart index 357cbe3..395a359 100644 --- a/lib/bones_ui_test.dart +++ b/lib/bones_ui_test.dart @@ -1,11 +1,12 @@ /// Bone UI - Test Tools -library bones_ui_test; +library; export 'dart:async'; -export 'dart:html'; export 'package:dom_builder/dom_builder.dart'; export 'package:dom_tools/dom_tools.dart'; +export 'package:web_utils/web_utils.dart' + hide CSS, HttpStatus, HttpRequest, MimeType; export 'bones_ui.dart'; export 'src/bones_ui_test_tools.dart'; diff --git a/lib/bones_ui_test_clit.dart b/lib/bones_ui_test_clit.dart index a3bc8f9..7e105b5 100644 --- a/lib/bones_ui_test_clit.dart +++ b/lib/bones_ui_test_clit.dart @@ -1,5 +1,5 @@ /// Bone UI - Test CLI -library bones_ui_test_cli; +library; export 'bones_ui.dart'; export 'src/bones_ui_test_cli.dart'; diff --git a/lib/src/bones_ui_async_content.dart b/lib/src/bones_ui_async_content.dart index f00d1dc..8861cd6 100644 --- a/lib/src/bones_ui_async_content.dart +++ b/lib/src/bones_ui_async_content.dart @@ -1,15 +1,14 @@ import 'dart:async'; -import 'dart:html'; import 'package:dom_tools/dom_tools.dart'; import 'package:intl_messages/intl_messages.dart'; import 'package:swiss_knife/swiss_knife.dart'; +import 'package:web_utils/web_utils.dart'; import 'bones_ui_base.dart'; import 'bones_ui_component.dart'; import 'bones_ui_log.dart'; import 'bones_ui_utils.dart'; -import 'bones_ui_web.dart'; typedef AsyncContentProvider = Future? Function(); @@ -366,23 +365,24 @@ class UIAsyncContent { } } -dynamic _ensureElementForDOM(dynamic element) { +dynamic _ensureElementForDOM(Object? element) { if (_isElementForDOM(element)) { return element; } if (element is String) { if (element.contains('<') && element.contains('>')) { - var div = createDivInline(element); - if (div.childNodes.isEmpty) return div; + var div = createDivInline(html: element); + var childNodes = div.childNodes; + if (childNodes.isEmpty) return div; - if (div.childNodes.length == 1) { - return div.childNodes.first; + if (childNodes.length == 1) { + return childNodes.toIterable().first; } else { div; } } else { - var span = SpanElement(); + var span = HTMLSpanElement(); setElementInnerHTML(span, element); return span; } @@ -391,10 +391,8 @@ dynamic _ensureElementForDOM(dynamic element) { return element; } -bool _isElementForDOM(dynamic element) { - if (element is UIElement) { - return true; - } else if (element is UINode) { +bool _isElementForDOM(Object? element) { + if (element.isNode) { return true; } else if (element is UIComponent) { return true; diff --git a/lib/src/bones_ui_base.dart b/lib/src/bones_ui_base.dart index 95ec454..22305f0 100644 --- a/lib/src/bones_ui_base.dart +++ b/lib/src/bones_ui_base.dart @@ -1,8 +1,7 @@ -import 'dart:html'; - -import 'package:dom_builder/dom_builder_dart_html.dart'; +import 'package:dom_builder/dom_builder_web.dart'; import 'package:dom_tools/dom_tools.dart'; import 'package:intl_messages/intl_messages.dart'; +import 'package:web_utils/web_utils.dart' hide CSS; import 'bones_ui_async_content.dart'; import 'bones_ui_component.dart'; @@ -86,18 +85,18 @@ class UIDeviceOrientation extends EventHandlerPrivate { /// Returns [true] if device is in landscape orientation. static bool isLandscape() { - var orientation = window.orientation; - if (orientation == null) return false; - return orientation == -90 || orientation == 90; + var orientation = window.screen.orientation; + var angle = orientation.angle; + return angle == -90 || angle == 90; } } /// Returns [true] if a `Bones_UI` component is in DOM. -bool isComponentInDOM(dynamic element) { +bool isComponentInDOM(Object? element) { if (element == null) return false; - if (element is UINode) { - return document.body!.contains(element); + if (element.isNode) { + return document.body!.contains(element as UINode); } else if (element is UIComponent) { return isComponentInDOM(element.renderedElements); } else if (element is UIAsyncContent) { @@ -114,10 +113,10 @@ bool isComponentInDOM(dynamic element) { } /// Returns [true] if [element] type is able to be in DOM. -bool canBeInDOM(dynamic element) { +bool canBeInDOM(Object? element) { if (element == null) return false; - if (element is UINode) { + if (element.isNode) { return true; } else if (element is UIComponent) { return true; @@ -130,7 +129,7 @@ bool canBeInDOM(dynamic element) { return false; } -typedef FilterRendered = bool Function(dynamic elem); +typedef FilterRendered = bool Function(Object? elem); typedef FilterElement = bool Function(UIElement elem); typedef ForEachElement = void Function(UIElement elem); typedef ForEachComponent = void Function(Object elem); @@ -139,7 +138,9 @@ typedef ParametersProvider = Map Function(); /// For a [UIComponent] that is a field (has a value). abstract class UIField { String get fieldName; + V? getFieldValue(); + void setFieldValue(V? value); } @@ -175,7 +176,7 @@ class TextProvider { : this.fromIntlKey(IntlKey(intlMessages, key, variables: variables, variablesProvider: variablesProvider)); - static TextProvider? from(dynamic text) { + static TextProvider? from(Object? text) { if (text == null) return null; if (text is TextProvider) return text; @@ -207,9 +208,9 @@ class TextProvider { bool singleCall = false; - String? get text { + String get text { if (_text != null) { - return _text; + return _text!; } dynamic value; @@ -236,9 +237,7 @@ class TextProvider { } @override - String toString() { - return text!; - } + String toString() => text; } class ElementProvider { @@ -258,21 +257,27 @@ class ElementProvider { ElementProvider.fromDOMNode(this._domNode); - static ElementProvider? from(dynamic element) { + static ElementProvider? from(Object? element) { if (element == null) return null; + if (element is ElementProvider) return element; if (element is String) return ElementProvider.fromHTML(element); - if (element is UIElement) return ElementProvider.fromElement(element); + + if (element.isElement) { + return ElementProvider.fromElement(element as UIElement); + } + if (element is UIComponent) return ElementProvider.fromUIComponent(element); if (element is DOMNode) return ElementProvider.fromDOMNode(element); + return null; } - static bool accepts(dynamic element) { + static bool accepts(Object? element) { if (element == null) return false; if (element is ElementProvider) return true; if (element is String) return true; - if (element is UIElement) return true; + if (element.isElement) return true; if (element is UIComponent) return true; if (element is DOMNode) return true; return false; @@ -280,7 +285,7 @@ class ElementProvider { String? get elementAsHTML { var elem = element; - return elem != null ? element!.outerHtml : null; + return elem?.outerHTML.dartify()?.toString(); } UIElement? get element { @@ -289,7 +294,7 @@ class ElementProvider { } if (_html != null) { - _element = createHTML(_html); + _element = createHTML(html: _html); return _element; } @@ -319,7 +324,7 @@ class ElementProvider { } class CSSProvider { - UIElement? _element; + Element? _element; String? _html; @@ -335,21 +340,21 @@ class CSSProvider { CSSProvider.fromDOMNode(this._domNode); - static CSSProvider? from(dynamic provider) { + static CSSProvider? from(Object? provider) { if (provider == null) return null; if (provider is CSSProvider) return provider; if (provider is String) return CSSProvider.fromHTML(provider); - if (provider is UIElement) return CSSProvider.fromElement(provider); + if (provider.isElement) return CSSProvider.fromElement(provider as Element); if (provider is UIComponent) return CSSProvider.fromUIComponent(provider); if (provider is DOMNode) return CSSProvider.fromDOMNode(provider); return null; } - static bool accepts(dynamic element) { + static bool accepts(Object? element) { if (element == null) return false; if (element is CSSProvider) return true; if (element is String) return true; - if (element is UIElement) return true; + if (element.isElement) return true; if (element is UIComponent) return true; if (element is DOMNode) return true; return false; @@ -363,7 +368,7 @@ class CSSProvider { } if (_html != null) { - _element = createHTML(_html); + _element = createHTML(html: _html); return cssFromElement(_element!); } @@ -391,7 +396,7 @@ class CSSProvider { if (isNodeInDOM(element)) { return CSS(element.getComputedStyle().cssText); } else { - return CSS(element.style.cssText); + return CSS(element.style?.cssText); } } diff --git a/lib/src/bones_ui_component.dart b/lib/src/bones_ui_component.dart index fcc84c0..2449294 100644 --- a/lib/src/bones_ui_component.dart +++ b/lib/src/bones_ui_component.dart @@ -1,15 +1,14 @@ import 'dart:async'; import 'dart:collection'; -import 'package:bones_ui/src/bones_ui_utils.dart'; import 'package:collection/collection.dart' show IterableExtension; import 'package:dom_builder/dom_builder.dart'; import 'package:dom_tools/dom_tools.dart'; import 'package:dynamic_call/dynamic_call.dart'; -import 'package:json_render/json_render.dart'; import 'package:statistics/statistics.dart' show Decimal, DynamicInt, DynamicNumber; import 'package:swiss_knife/swiss_knife.dart'; +import 'package:web_utils/web_utils.dart'; import 'bones_ui_async_content.dart'; import 'bones_ui_base.dart'; @@ -21,8 +20,10 @@ import 'bones_ui_layout.dart'; import 'bones_ui_log.dart'; import 'bones_ui_navigator.dart'; import 'bones_ui_root.dart'; +import 'bones_ui_utils.dart'; import 'bones_ui_web.dart'; import 'component/input_config.dart'; +import 'component/json_render.dart'; /// [UIComponent] behavior to clear the component. enum UIComponentClearParent { onConstruct, onInitialRender, onRender } @@ -53,7 +54,7 @@ abstract class UIComponent extends UIEventHandler { UIElement? _parent; final UIComponentClearParent? clearParent; - UIElement? _content; + HTMLElement? _content; bool? _constructing; @@ -87,8 +88,8 @@ abstract class UIComponent extends UIEventHandler { _resolveParentUIComponent(parent); if (_parent == null) { - if (parent is UIElement) { - _parent = parent; + if (parent.isElement) { + _parent = parent as Element; } else if (parent is UIComponent) { _parent = parent.content; } @@ -147,7 +148,7 @@ abstract class UIComponent extends UIEventHandler { configureStyle(style, style2, componentStyle); if (clearParent == UIComponentClearParent.onConstruct) { - _parent?.nodes.clear(); + _parent?.clear(); } _parent?.append(_content!); @@ -172,7 +173,7 @@ abstract class UIComponent extends UIEventHandler { _refreshInternal, ); - UIElement? _getContent() => _content; + HTMLElement? _getContent() => _content; static final Expando _contentsUIComponents = Expando('_content:UIComponent'); @@ -180,7 +181,7 @@ abstract class UIComponent extends UIEventHandler { static UIComponent? getContentUIComponent(UIElement content) => _contentsUIComponents[content]; - void _setContent(UIElement content) { + void _setContent(HTMLElement content) { var prev = _content; if (prev != null && !identical(prev, content)) { _contentsUIComponents[prev] = null; @@ -196,7 +197,7 @@ abstract class UIComponent extends UIEventHandler { void insertTo(int index, UIElement parent, {UIComponent? parentUIComponent}) { _setParentImpl(parent, parentUIComponent, true); - parent.nodes.insert(index, content!); + parent.insertChild(index, content!); } /// Called by constructor to register this component in the [UIRoot] tree. @@ -227,12 +228,12 @@ abstract class UIComponent extends UIEventHandler { if (_content != null) { if (identical(_parent, parent)) { - if (!identical(_content!.parent, _parent)) { + if (!identical(_content!.parentElement, _parent)) { _parent!.append(_content!); } _resolveParentUIComponent(parentUIComponent ?? parent); return _parent; - } else if (identical(_content!.parent, parent)) { + } else if (identical(_content!.parentElement, parent)) { _resolveParentUIComponent(parentUIComponent ?? parent); return _parent; } else { @@ -272,7 +273,7 @@ abstract class UIComponent extends UIEventHandler { if (prevParent == null) { var content = _content; - if (identical(content?.parent, parentContent)) { + if (identical(content?.parentElement, parentContent)) { _parent = parentContent; } } @@ -297,9 +298,9 @@ abstract class UIComponent extends UIEventHandler { return parent; } - if (parent is! UIElement) return null; + if (!parent.isElement) return null; - var foundUIParent = getContentUIComponent(parent) ?? + var foundUIParent = getContentUIComponent(parent as Element) ?? _getUIComponentByContent(parent) ?? _getUIComponentByChild(parent); @@ -320,19 +321,21 @@ abstract class UIComponent extends UIEventHandler { return; } - if (elementUIComponent == null && element is UIElement) { - elementUIComponent = _resolveNodeUIComponent(element); + if (elementUIComponent == null && element.isElement) { + elementUIComponent = _resolveNodeUIComponent(element as Element); } if (elementUIComponent == null) { - if (recursive && element is UIElement && element.hasUIChildren) { - if (parentUIComponent == null && parent is UIElement) { - parentUIComponent = - _resolveNodeUIComponent(parent, getUIComponentByChild: true); + if (recursive && + element.isElement && + (element as Element).hasUIChildren) { + if (parentUIComponent == null && parent.isElement) { + parentUIComponent = _resolveNodeUIComponent(parent as Element, + getUIComponentByChild: true); } if (parentUIComponent != null) { - for (var child in element.children) { + for (var child in element.children.toIterable()) { resolveParentUIComponent( parent: parent, parentUIComponent: parentUIComponent, @@ -348,9 +351,9 @@ abstract class UIComponent extends UIEventHandler { return; } - if (parentUIComponent == null && parent is UIElement) { - parentUIComponent = - _resolveNodeUIComponent(parent, getUIComponentByChild: true); + if (parentUIComponent == null && parent.isElement) { + parentUIComponent = _resolveNodeUIComponent(parent as Element, + getUIComponentByChild: true); } if (parentUIComponent != null) { @@ -363,9 +366,9 @@ abstract class UIComponent extends UIEventHandler { static UIComponent? _resolveNodeUIComponent(UINode node, {bool getUIComponentByChild = false}) { - if (node is! UIElement) return null; + if (!node.isElement) return null; - var component = UIComponent.getContentUIComponent(node); + var component = UIComponent.getContentUIComponent(node as Element); if (component == null) { var uiRoot = UIRoot.getInstance(); @@ -414,7 +417,7 @@ abstract class UIComponent extends UIEventHandler { /// Hide component. void hide() { - _content!.hidden = true; + _content!.hidden = true.toJS; if (_showing) { _displayOnHidden = _content!.style.display; @@ -426,10 +429,10 @@ abstract class UIComponent extends UIEventHandler { /// Show component. void show() { - _content!.hidden = false; + _content!.hidden = false.toJS; if (!_showing) { - _content!.style.display = _displayOnHidden; + _content!.style.display = _displayOnHidden ?? ''; _displayOnHidden = null; } @@ -498,11 +501,11 @@ abstract class UIComponent extends UIEventHandler { void configureClasses(Object? classes1, [Object? classes2, Object? componentClasses]) { var content = this.content!; - content.classes.add('ui-component'); + content.classList.add('ui-component'); if (componentClasses != null) { var classesNamesComponent = parseClasses(componentClasses); - content.classes.addAll(classesNamesComponent); + content.classList.addAll(classesNamesComponent); } appendClasses(classes1, classes2); @@ -533,10 +536,10 @@ abstract class UIComponent extends UIEventHandler { } var content = this.content!; - content.classes.addAll(classesNames); + content.classList.addAll(classesNames); if (classesNamesRemove.isNotEmpty) { - content.classes.removeAll(classesNamesRemove); + content.classList.removeAll(classesNamesRemove); } } @@ -585,7 +588,7 @@ abstract class UIComponent extends UIEventHandler { final content = this.content!; - var cssText = content.style.cssText ?? ''; + var cssText = content.style.cssText; if (cssText == '') { cssText = allStylesLine; } else { @@ -635,18 +638,18 @@ abstract class UIComponent extends UIEventHandler { } } - UIElement createContentElement(bool inline) { - return createDiv(inline); + HTMLElement createContentElement(bool inline) { + return createDiv(inline: inline); } UIElement? get parent => _parent; - UIElement? get content => _content; + HTMLElement? get content => _content; List getContentChildren( {FilterElement? filter, bool deep = true}) { return _getContentChildrenImpl( - _content!.children, [], deep, filter); + _content!.children.toList(), [], deep, filter); } List _getContentChildrenImpl(List list, @@ -663,7 +666,7 @@ abstract class UIComponent extends UIEventHandler { if (deep) { for (var elem in list) { - _getContentChildrenImpl(elem.children, dst, true, filter); + _getContentChildrenImpl(elem.children.toList(), dst, true, filter); } } @@ -671,7 +674,8 @@ abstract class UIComponent extends UIEventHandler { } UIElement? findInContentChildDeep(FilterElement filter) => - _findInContentChildDeepImpl(_content?.children ?? [], filter); + _findInContentChildDeepImpl( + _content?.children.toList() ?? [], filter); UIElement? _findInContentChildDeepImpl( List list, FilterElement filter) { @@ -682,7 +686,7 @@ abstract class UIComponent extends UIEventHandler { } for (var elem in list) { - var found = _findInContentChildDeepImpl(elem.children, filter); + var found = _findInContentChildDeepImpl(elem.children.toList(), filter); if (found != null) return found; } @@ -691,7 +695,7 @@ abstract class UIComponent extends UIEventHandler { List findChildDeep(FilterElement filter) { var list = []; - _findChildDeepImpl(_content!.children, filter, list); + _findChildDeepImpl(_content!.children.toList(), filter, list); return list; } @@ -706,12 +710,12 @@ abstract class UIComponent extends UIEventHandler { } for (var elem in list) { - _findChildDeepImpl(elem.children, filter, dst); + _findChildDeepImpl(elem.children.toList(), filter, dst); } } MapEntry? findChildrenDeep(String fieldName) => - _findChildrenDeepImpl(_content!.children, fieldName); + _findChildrenDeepImpl(_content!.children.toList(), fieldName); MapEntry? _findChildrenDeepImpl( List list, String fieldName) { @@ -723,7 +727,7 @@ abstract class UIComponent extends UIEventHandler { } for (var elem in list) { - var found = _findChildrenDeepImpl(elem.children, fieldName); + var found = _findChildrenDeepImpl(elem.children.toList(), fieldName); if (found != null) return found; } @@ -736,7 +740,7 @@ abstract class UIComponent extends UIEventHandler { Map getFieldsComponentsMap( {List? fields, List? ignoreFields}) { var map = Map.fromEntries( - _listFieldsEntriesInContentDeepImpl(_content!.children)); + _listFieldsEntriesInContentDeepImpl(_content!.children.toList())); if (fields != null) { map.removeWhere((key, value) => !fields.contains(key)); @@ -767,7 +771,7 @@ abstract class UIComponent extends UIEventHandler { String? _renderedElementsLocale; - List? _renderedElements; + List? _renderedElements; List? get renderedElements => _renderedElements != null ? List.unmodifiable(_renderedElements!) : null; @@ -853,7 +857,7 @@ abstract class UIComponent extends UIEventHandler { getRenderedElement( (elem) => (elem is UIComponent && elem.id == id) || - (elem is UIElement && elem.id == id), + (elem.isElement && (elem as Element).id == id), deep); T? getRenderedUIComponentById(dynamic id, @@ -893,8 +897,8 @@ abstract class UIComponent extends UIEventHandler { for (var e in _renderedElements!) { if (e is UIComponent) { e.delete(); - } else if (e is UIElement) { - e.remove(); + } else if (e.isElement) { + (e as Element).remove(); } } } @@ -909,7 +913,7 @@ abstract class UIComponent extends UIEventHandler { e.remove(); } - content.children.clear(); + content.clear(); if (removeFromParent) { content.remove(); @@ -1147,7 +1151,7 @@ abstract class UIComponent extends UIEventHandler { if (uiComp != null) return uiComp; } - for (var elem in _content!.children) { + for (var elem in _content!.children.toIterable()) { if (identical(content, elem)) { return this; } @@ -1160,7 +1164,7 @@ abstract class UIComponent extends UIEventHandler { if (child == null) return null; if (identical(child, _content)) return this; - for (var elem in _content!.children) { + for (var elem in _content!.children.toIterable()) { if (identical(child, elem)) { return this; } @@ -1304,12 +1308,12 @@ abstract class UIComponent extends UIEventHandler { _renderCount == 1)) { // content not added to parent: if (!identical(content.parentNode, parent)) { - parent.nodes.clear(); - parent.append(content); + parent.clear(); + parent.appendChild(content); } // content already added, remove other nodes: else { - var nodes = List.from(parent.nodes); + var nodes = parent.childNodes.toList(); var containsContent = false; for (var node in nodes) { @@ -1456,7 +1460,7 @@ abstract class UIComponent extends UIEventHandler { _finalizeRender(); try { - _parseAttributesPosRender(content!.children); + _parseAttributesPosRender(content!.children.toList()); } catch (e, s) { UIConsole.error('$this _parseAttributesPosRender(...) error', e, s); } @@ -1472,16 +1476,16 @@ abstract class UIComponent extends UIEventHandler { setTreeElementsBackgroundBlur(content!, 'bg-blur'); } - void _ensureAllRendered(List elements) { + void _ensureAllRendered(List elements) { if (elements.isEmpty) return; for (var e in elements) { if (e is UIComponent) { e.ensureRendered(); - } else if (e is UIElement) { + } else if (e.isElement) { var subElements = []; - for (var child in e.children) { + for (var child in (e as Element).children.toList()) { var uiComponent = _getUIComponentByContent(child); if (uiComponent != null) { @@ -1640,7 +1644,7 @@ abstract class UIComponent extends UIEventHandler { /// - [String], parsed as `HTML`. /// - [Map] (rendered as JSON). /// - [List] with previous types (recursively). - /// - [Function] that returns any previous type. Including [Function], allowing `async` functions. + /// - [Function] that returns any previous type. Including [Function]<[Future]>, allowing `async` functions. dynamic render(); /// Called after [render]. @@ -1664,7 +1668,7 @@ abstract class UIComponent extends UIEventHandler { connectDataSource(); } - List toContentElements(dynamic rendered, + List toContentElements(dynamic rendered, {bool append = false, bool parseAttributes = true}) { try { var domContext = UIComponentDOMContext(this, domGenerator.domContext); @@ -1672,20 +1676,20 @@ abstract class UIComponent extends UIEventHandler { var list = _toContentElementsImpl(rendered, append, domContext); if (parseAttributes) { - _parseAttributes(content!.children); + _parseAttributes(content!.children.toList()); } return list; } catch (e, s) { logger.error( 'Error converting rendered to content elements: $rendered', e, s); - return []; + return []; } } - static bool isRenderable(dynamic element) { + static bool isRenderable(Object? element) { if (element == null) return false; - return element is UIElement || + return element.isElement || element is UIContent || element is UIAsyncContent; } @@ -1739,7 +1743,7 @@ abstract class UIComponent extends UIEventHandler { return renderableList; } - List _toContentElementsImpl( + List _toContentElementsImpl( Object? rendered, bool append, DOMContext? domContext) { var renderableList = toRenderableList(rendered, domContext); @@ -1747,10 +1751,10 @@ abstract class UIComponent extends UIEventHandler { if (renderableList == null || renderableList.isEmpty) { if (!append) { - content.nodes.clear(); + content.clear(); } - return []; + return []; } if (isListOfStrings(renderableList)) { @@ -1769,24 +1773,24 @@ abstract class UIComponent extends UIEventHandler { .toList(); if (append) { - content.nodes.addAll(nodes); + content.appendAll(nodes); } else { - content.nodes.clear(); - content.nodes.addAll(nodes); + content.clear(); + content.appendAll(nodes); } - var renderedList = List.from(content.childNodes); + var renderedList = List.from(content.childNodes.toList()); return renderedList; } else { - if (isListValuesIdentical(renderableList, content.nodes.toList())) { - return List.from(renderableList); + if (isListValuesIdentical(renderableList, content.childNodes.toList())) { + return List.from(renderableList); } for (var value in renderableList) { _removeFromContent(value); } - var renderedList2 = []; + var renderedList2 = []; var prevElemIndex = -1; @@ -1800,7 +1804,7 @@ abstract class UIComponent extends UIEventHandler { } dynamic _normalizeRenderListValue( - UIElement? content, value, DOMContext? domContext) { + UIElement? content, Object? value, DOMContext? domContext) { if (value is DOMNode) { return value.buildDOM( generator: domGenerator, parent: content, context: domContext); @@ -1809,7 +1813,7 @@ abstract class UIComponent extends UIEventHandler { return nodes; } else if (value is List) { return value; - } else if (value is UINode) { + } else if (value.isNode) { return value; } else if (value is Function) { return value; @@ -1840,25 +1844,22 @@ abstract class UIComponent extends UIEventHandler { generator: domGenerator, parent: content, context: domContext); } else if (value is Map || (value is List && listMatchesAll(value, (dynamic e) => e is Map))) { - var jsonRender = JSONRender.fromJSON(value) - ..renderMode = JSONRenderMode.view - ..addAllKnownTypeRenders(); - return jsonRender.render(); + return UIJsonRender(null, json: value); } else { return value; } } - int _buildRenderList(dynamic value, List renderedList, + int _buildRenderList(Object? value, List renderedList, int prevElemIndex, DOMContext? domContext) { if (value == null) return prevElemIndex; var content = this.content; value = _normalizeRenderListValue(content, value, domContext); - if (value is UINode) { - prevElemIndex = - _addElementToRenderList(value, value, renderedList, prevElemIndex); + if (value.isNode) { + prevElemIndex = _addElementToRenderList( + value, value as Node, renderedList, prevElemIndex); } else if (value is UIComponent) { if (value.parent != content) { value._setParentImpl(content, this, false); @@ -1952,11 +1953,11 @@ abstract class UIComponent extends UIEventHandler { int? maxContentIdx; final content = this.content!; - for (var node in content.nodes.toList()) { + for (var node in content.childNodes.toList()) { if (prevElements.contains(node)) { - var idx = content.nodes.indexOf(node); + var idx = content.childNodes.indexOf(node); if (idx >= 0) { - content.nodes.removeAt(idx); + content.removeNodeAt(idx); if (maxContentIdx == null || idx > maxContentIdx) { maxContentIdx = idx; } @@ -1966,20 +1967,20 @@ abstract class UIComponent extends UIEventHandler { var loadedContent = asyncContent.content; - var renderedList = []; - if (maxContentIdx == null || maxContentIdx >= content.nodes.length) { - _buildRenderList( - loadedContent, renderedList, content.nodes.length - 1, domContext); + var renderedList = []; + if (maxContentIdx == null || maxContentIdx >= content.childNodes.length) { + _buildRenderList(loadedContent, renderedList, + content.childNodes.length - 1, domContext); renderedElements.addAll(renderedList); } else { - var tail = content.nodes.sublist(maxContentIdx).toList(); + var tail = content.childNodes.toList().sublist(maxContentIdx).toList(); for (var e in tail) { - content.nodes.remove(e); + content.removeChild(e); } - _buildRenderList( - loadedContent, renderedList, content.nodes.length - 1, domContext); + _buildRenderList(loadedContent, renderedList, + content.childNodes.length - 1, domContext); if (tail.isNotEmpty) { var renderedComponents = renderedList @@ -1990,7 +1991,7 @@ abstract class UIComponent extends UIEventHandler { tail.removeWhere( (e) => renderedList.contains(e) || renderedComponents.contains(e)); - content.nodes.addAll(tail); + content.appendAll(tail); } minRenderedElementsIdx ??= 0; @@ -2000,8 +2001,8 @@ abstract class UIComponent extends UIEventHandler { _ensureAllRendered(renderedList); try { - _parseAttributes(content.children); - _parseAttributesPosRender(content.children); + _parseAttributes(content.children.toList()); + _parseAttributesPosRender(content.children.toList()); } catch (e, s) { UIConsole.error('$this _parseAttributesPosRender(...) error', e, s); } @@ -2046,15 +2047,15 @@ abstract class UIComponent extends UIEventHandler { dynamic value, UINode element, List renderedList, int prevElemIndex) { var content = this.content!; - var idx = content.nodes.indexOf(element); + var idx = content.childNodes.indexOf(element); if (idx < 0) { content.append(element); - idx = content.nodes.indexOf(element); + idx = content.childNodes.indexOf(element); } else if (idx < prevElemIndex) { element.remove(); content.append(element); - idx = content.nodes.indexOf(element); + idx = content.childNodes.indexOf(element); } prevElemIndex = idx; @@ -2063,11 +2064,11 @@ abstract class UIComponent extends UIEventHandler { return prevElemIndex; } - void _removeFromContent(dynamic value) { + void _removeFromContent(Object? value) { if (value == null) return; - if (value is UIElement) { - value.remove(); + if (value.isElement) { + (value as Element).remove(); } else if (value is UIComponent) { if (value.isRendered) { value.content!.remove(); @@ -2082,30 +2083,30 @@ abstract class UIComponent extends UIEventHandler { } } - void _parseAttributes(List list) { + void _parseAttributes(List list) { if (list.isEmpty) return; - for (var elem in list) { - if (elem is UIElement) { - _parseNavigate(elem); - _parseAction(elem); - _parseEvents(elem); - _parseDataSource(elem); + for (var elem in list.whereElement()) { + _parseNavigate(elem); + _parseAction(elem); + _parseEvents(elem); + _parseDataSource(elem); - try { - _parseAttributes(elem.children); - } catch (e) { - UIConsole.error('Error parsing attributes for element: $elem', e); - } + try { + _parseAttributes(elem.children.toList()); + } catch (e) { + UIConsole.error('Error parsing attributes for element: $elem', e); } } } - void _parseAttributesPosRender(List list) { + void _parseAttributesPosRender(List list) { if (list.isEmpty) return; for (var elem in list) { - if (elem is UIElement) { + if (elem.isHTMLElement) { + elem = elem as HTMLElement; + try { _parseUiLayout(elem); } catch (e) { @@ -2113,7 +2114,7 @@ abstract class UIComponent extends UIEventHandler { } try { - _parseAttributesPosRender(elem.children); + _parseAttributesPosRender(elem.children.toList()); } catch (e) { UIConsole.error('Error parsing attributes for element: $elem', e); } @@ -2234,7 +2235,7 @@ abstract class UIComponent extends UIEventHandler { return anySet; } - dynamic getAttribute(String? name) { + String? getAttribute(String? name) { name = _normalizeComponentAttributeName(name); if (name == null) return null; @@ -2242,7 +2243,7 @@ abstract class UIComponent extends UIEventHandler { case 'style': return content!.style.cssText; case 'class': - return content!.classes.join(' '); + return content!.classList.value; case 'navigate': return UINavigator.getNavigateOnClick(content!); case 'data-source': @@ -2272,8 +2273,8 @@ abstract class UIComponent extends UIEventHandler { } case 'class': { - content!.classes.clear(); - content!.classes.addAll(parseAttributeValueAsStringList(value)!); + content!.classList.clear(); + content!.classList.addAll(parseAttributeValueAsStringList(value)!); return true; } case 'navigate': @@ -2352,7 +2353,7 @@ abstract class UIComponent extends UIEventHandler { } case 'class': { - content!.classes.clear(); + content!.classList.clear(); return true; } case 'navigate': @@ -2377,10 +2378,16 @@ abstract class UIComponent extends UIEventHandler { } } - E? getFieldElement(String? fieldName) { + Element? getFieldElementNonTyped(String? fieldName) { var elem = findInContentChildDeep((e) => getElementFieldName(e) == fieldName); - return elem is E ? elem : null; + return elem; + } + + E? getFieldElementTyped( + String? fieldName, Web webType) { + var elem = getFieldElementNonTyped(fieldName); + return elem.asElementOf(webType); } Object? getFieldComponent(String? fieldName) { @@ -2398,8 +2405,8 @@ abstract class UIComponent extends UIEventHandler { String? getComponentFieldName(Object obj) { if (obj is UIField) { return obj.fieldName; - } else if (obj is UIElement) { - return getElementFieldName(obj); + } else if (obj.isElement) { + return getElementFieldName(obj as Element); } else { return null; } @@ -2462,35 +2469,69 @@ abstract class UIComponent extends UIEventHandler { } /// Alias to [content.querySelector]. - E? querySelector(String? selectors) { + Element? querySelectorNonTyped(String? selectors) { + if (selectors == null || selectors.isEmpty) return null; + return content?.querySelector(selectors); + } + + /// Alias to [content.querySelector]. + E? querySelectorTyped(String? selectors, Web webType) { if (selectors == null || selectors.isEmpty) return null; - if (E == UIElement) { - var elem = content?.querySelector(selectors); - return elem is E ? elem : null; + if (webType == Web.Element) { + // Faster: + return content?.querySelector(selectors) as E?; } else { - return content?.querySelectorAll(selectors).whereType().firstOrNull; + return content?.querySelectorAllTyped(selectors, webType).firstOrNull; } } /// Alias to [content.querySelector]. - E? selectElement(String? selectors) => - querySelector(selectors); + Element? selectElementNonTyped(String? selectors) => + querySelectorNonTyped(selectors); + + /// Alias to [content.querySelector]. + E? selectElementTyped( + String? selectors, Web webType) => + querySelectorTyped(selectors, webType); /// Alias to [content.querySelectorAll]. - List querySelectorAll(String? selectors) { + List querySelectorAllNonTyped(String? selectors) { + if (selectors == null || selectors.isEmpty) return []; + return content?.querySelectorAll(selectors).toElements() ?? []; + } + + /// Alias to [content.querySelectorAll]. + List querySelectorAllTyped( + String? selectors, Web webType) { if (selectors == null || selectors.isEmpty) return []; - return content?.querySelectorAll(selectors).whereType().toList() ?? - []; + return content?.querySelectorAllTyped(selectors, webType) ?? []; } /// Alias to [content.querySelectorAll]. - List selectElements(String? selectors) => - querySelectorAll(selectors); + List selectElementsNonTyped(String? selectors) => + querySelectorAllNonTyped(selectors); + + /// Alias to [content.querySelectorAll]. + List selectElementsTyped( + String? selectors, Web webType) => + querySelectorAllTyped(selectors, webType); + + /// Alias to [content.querySelectorAll]. + Map selectElementsNonTypedValues(String? selectors) { + var entries = selectElementsNonTyped(selectors).map((e) { + var k = e.getAttribute('name')?.trim(); + if (k == null || k.isEmpty) { + k = e.id.trim(); + } + return MapEntry(k, e.elementValue); + }); + return Map.fromEntries(entries); + } /// Alias to [content.querySelectorAll]. - Map selectElementsValues( - String? selectors) { - var entries = selectElements(selectors).map((e) { + Map selectElementsTypedValues( + String? selectors, Web webType) { + var entries = selectElementsTyped(selectors, webType).map((e) { var k = e.getAttribute('name')?.trim(); if (k == null || k.isEmpty) { k = e.id.trim(); @@ -2501,23 +2542,26 @@ abstract class UIComponent extends UIEventHandler { } bool clearContent() { - content!.nodes.clear(); + content!.clear(); return true; } bool setContentNodes(List nodes) { - content!.nodes.clear(); - content!.nodes.addAll(nodes); + content!.clear(); + content!.appendAll(nodes); return true; } bool appendToContent(List nodes) { - content!.nodes.addAll(nodes); + content!.appendAll(nodes); return true; } List getFieldsElements() => _getContentChildrenImpl( - content!.children, [], true, (e) => getElementFieldName(e) != null); + content!.children.toList(), + [], + true, + (e) => getElementFieldName(e) != null); String? parseChildElementValue(UIElement? childElement, {UIComponent? childUiComponent, bool allowTextAsValue = true}) => @@ -2604,7 +2648,7 @@ abstract class UIComponent extends UIEventHandler { _renderedFieldsValues != null ? _renderedFieldsValues![fieldName!] : null; void setField(String fieldName, dynamic value) { - var fieldElem = getFieldElement(fieldName); + var fieldElem = getFieldElementNonTyped(fieldName); if (fieldElem == null) return; var valueStr = value != null ? '$value' : null; @@ -2616,7 +2660,7 @@ abstract class UIComponent extends UIEventHandler { } void updateRenderedFieldValue(String fieldName) { - var fieldElem = getFieldElement(fieldName); + var fieldElem = getFieldElementNonTyped(fieldName); if (fieldElem == null) return; var value = parseChildElementValue(fieldElem); @@ -2656,8 +2700,8 @@ abstract class UIComponent extends UIEventHandler { var val = parseChildElementValue(fieldComponent.content, childUiComponent: fieldComponent); return val as V? ?? def; - } else if (fieldComponent is UIElement) { - var val = parseChildElementValue(fieldComponent); + } else if (fieldComponent.isElement) { + var val = parseChildElementValue(fieldComponent as Element); return val as V? ?? def; } else { return fieldComponent.toString() as V? ?? def; @@ -2877,18 +2921,22 @@ abstract class UIComponent extends UIEventHandler { var component = getFieldComponent(fieldName); - if (component is UIElement) { - component.focus(); + if (component.asJSAny.isA()) { + (component as HTMLElement).focus(); return true; } else if (component is UIComponent) { var input = findInContentChildDeep((e) => e.isTextInput); if (input != null) { - input.focus(); - return true; + return input.focus(); } - component.content?.focus(); - return true; + var content = component.content; + if (content != null) { + content.focus(); + return true; + } else { + return false; + } } return false; @@ -2921,7 +2969,7 @@ abstract class UIComponent extends UIEventHandler { var list = getEmptyFields(); for (var fieldName in list) { - var elem = getFieldElement(fieldName); + var elem = getFieldElementNonTyped(fieldName); if (elem != null) { f(elem); count++; @@ -2959,7 +3007,7 @@ abstract class UIComponent extends UIEventHandler { UIConsole.log('action: $action'); } - void _parseUiLayout(UIElement elem) { + void _parseUiLayout(HTMLElement elem) { var uiLayout = getElementAttribute(elem, 'uiLayout'); if (uiLayout != null) { diff --git a/lib/src/bones_ui_content.dart b/lib/src/bones_ui_content.dart index 9f619b9..4154b75 100644 --- a/lib/src/bones_ui_content.dart +++ b/lib/src/bones_ui_content.dart @@ -1,7 +1,7 @@ +import 'package:dom_tools/dom_tools_kit.dart'; import 'package:swiss_knife/swiss_knife.dart'; import 'bones_ui_component.dart'; -import 'bones_ui_web.dart'; /// Base class for content components. abstract class UIContent extends UIComponent { @@ -21,7 +21,7 @@ abstract class UIContent extends UIComponent { List allRendered = []; if (topMargin > 0) { - var divTopMargin = UIElement.div(); + var divTopMargin = HTMLDivElement(); divTopMargin.style.width = '100%'; divTopMargin.style.height = '${topMargin}px'; diff --git a/lib/src/bones_ui_document.dart b/lib/src/bones_ui_document.dart index bc16829..824134f 100644 --- a/lib/src/bones_ui_document.dart +++ b/lib/src/bones_ui_document.dart @@ -1,11 +1,11 @@ import 'package:dom_tools/dom_tools.dart'; -import 'package:json_render/json_render.dart'; import 'package:swiss_knife/swiss_knife.dart'; import 'bones_ui_component.dart'; import 'bones_ui_generator.dart'; -import 'component/component_async.dart'; import 'bones_ui_web.dart'; +import 'component/component_async.dart'; +import 'component/json_render.dart'; /// Represents an [url] link, with an optional [target]. class URLLink { @@ -99,7 +99,7 @@ class UIDocument extends UIComponentAsync { } else { var type = attributes['type']?.toString() ?? '.md'; resourceContent = - ResourceContent.fromURI('file.$type', contentHolder?.text); + ResourceContent.fromURI('file.$type', contentHolder?.textContent); } return UIDocument(parent, resourceContent); @@ -175,11 +175,7 @@ class UIDocument extends UIComponentAsync { div.style.overflowWrap = 'break-word'; return div; } else if (language == 'json') { - var jsonRender = JSONRender.fromJSONAsString(docContent); - jsonRender.addAllKnownTypeRenders(); - var div = jsonRender.render(); - div.style.overflowWrap = 'break-word'; - return div; + return UIJsonRender(null, json: docContent); } } diff --git a/lib/src/bones_ui_explorer.dart b/lib/src/bones_ui_explorer.dart index f84efad..5488621 100644 --- a/lib/src/bones_ui_explorer.dart +++ b/lib/src/bones_ui_explorer.dart @@ -1,11 +1,12 @@ import 'dart:convert' as dart_convert; +import 'package:bones_ui/src/component/json_render.dart'; import 'package:dom_builder/dom_builder.dart'; import 'package:dom_tools/dom_tools.dart'; import 'package:intl_messages/intl_messages.dart'; -import 'package:json_render/json_render.dart'; import 'package:mercury_client/mercury_client.dart'; import 'package:swiss_knife/swiss_knife.dart'; +import 'package:web_utils/web_utils.dart'; import 'package:yaml/yaml.dart'; import 'bones_ui_component.dart'; @@ -346,7 +347,7 @@ class UIExplorer extends UIComponentAsync { var modelType = model.modelType; if (modelType.isEmpty) return null; - content!.classes.add('$componentClass-$modelType'); + content!.classList.add('$componentClass-$modelType'); if (modelType == 'document') { return await renderDocument(); @@ -411,11 +412,7 @@ class UIExplorer extends UIComponentAsync { div.style.overflowWrap = 'break-word'; return div; } else if (language == 'json') { - var jsonRender = JSONRender.fromJSONAsString(urlContent); - jsonRender.addAllKnownTypeRenders(); - var div = jsonRender.render(); - div.style.overflowWrap = 'break-word'; - return div; + return UIJsonRender(null, json: urlContent); } return urlContent; @@ -545,7 +542,7 @@ class _UIExplorerCatalog extends UIComponent { var response = await httpRequester.doRequest(); if (response == null) { - var elementError = getFieldElement('send-error')!; + var elementError = getFieldElementNonTyped('send-error')!; elementError.hidden = false; } else { refresh(); @@ -658,16 +655,16 @@ class _ViewerRender { _ViewerRender(this.config); Future render( - UIElement? output, String? contentType, dynamic content) async { + UIElement? output, String? contentType, Object? content) async { var type = config.getPropertyAsStringTrimLC('type', 'html'); if (type == 'html') { - if (content is UINode) { + if (content.isNode) { return content; } else if (content is DOMElement) { return content; } else { - return createHTML('$content'); + return createHTML(html: '$content'); } } else if (type == 'json') { return renderJson(output, contentType, content); @@ -677,27 +674,6 @@ class _ViewerRender { } dynamic renderJson(UIElement? output, String? contentType, dynamic content) { - var jsonRender = content is String - ? JSONRender.fromJSONAsString(content) - : JSONRender.fromJSON(content); - - var mode = config.getPropertyAsStringTrimLC('mode'); - - if (mode == 'input') { - jsonRender.renderMode = JSONRenderMode.input; - } else if (mode == 'view') { - jsonRender.renderMode = JSONRenderMode.view; - } - - jsonRender.addAllKnownTypeRenders(); - - var showNodeArrow = config.getPropertyAsBool('show_node_arrow', true)!; - var showNodeOpenerAndCloser = - config.getPropertyAsBool('show_node_opener_and_closer', true)!; - - jsonRender.showNodeArrow = showNodeArrow; - jsonRender.showNodeOpenerAndCloser = showNodeOpenerAndCloser; - - return jsonRender.render(); + return UIJsonRender(null, json: content); } } diff --git a/lib/src/bones_ui_extension.dart b/lib/src/bones_ui_extension.dart index 7dda730..87be1dc 100644 --- a/lib/src/bones_ui_extension.dart +++ b/lib/src/bones_ui_extension.dart @@ -1,13 +1,12 @@ -import 'dart:html'; - import 'package:swiss_knife/swiss_knife.dart'; +import 'package:web_utils/web_utils.dart'; import 'bones_ui_base.dart'; import 'bones_ui_component.dart'; import 'bones_ui_root.dart'; import 'bones_ui_web.dart'; -extension ElementExtension on UIElement { +extension UIElementExtension on UIElement { /// Resolves the [UIComponent] of this [UIElement]. UIComponent? resolveUIComponent({UIComponent? parentUIComponent}) { if (parentUIComponent != null) { @@ -24,9 +23,9 @@ extension ElementExtension on UIElement { bool allowTextAsValue = true}) { var self = this; - if (self is InputElementBase || - self is TextAreaElement || - self is SelectElement) { + if (self.isA() || + self.isA() || + self.isA()) { return resolveInputElementValue(); } @@ -46,7 +45,7 @@ extension ElementExtension on UIElement { var value = self.getAttribute('field_value'); if (isEmptyObject(value) && allowTextAsValue) { - value = self.text; + value = self.textContent; } return value; } @@ -54,20 +53,21 @@ extension ElementExtension on UIElement { String? resolveInputElementValue() { var self = this; - if (self is TextAreaElement) { - return self.value; - } else if (self is SelectElement) { - var selected = self.selectedOptionsSafe; + if (self.isA()) { + return (self as HTMLTextAreaElement).value; + } else if (self.isA()) { + var selected = (self as HTMLSelectElement).selectedOptionsSafe; if (selected.isEmpty) return ''; return MapProperties.toStringValue(selected.map((opt) => opt.value)); - } else if (self is InputElement) { - var type = self.type; + } else if (self.isA()) { + var type = (self as HTMLInputElement).type; switch (type) { case 'checkbox': case 'radio': return parseBool(self.checked, false)! ? self.value : null; case 'file': - return MapProperties.toStringValue(self.files!.map((f) => f.name)); + return MapProperties.toStringValue( + self.files!.toList().map((f) => f.name)); default: return self.value; } @@ -93,34 +93,12 @@ extension ElementExtension on UIElement { var value = elementValue; return value == null || value.trim().isEmpty; } - - bool dispatchChangeEvent() { - var event = Event.eventType('Event', 'change'); - return dispatchEvent(event); - } } -extension IterableElementExtension on Iterable { +extension UIIterableElementExtension on Iterable { /// Returns a [List] of [UIComponent] of this [Iterable] of [UIElement]s. List get uiComponents => map((e) => e.uiComponent).toList(); /// Returns a [List] of values of this [Iterable] of [UIElement]s. List get elementsValues => map((e) => e.elementValue).toList(); } - -extension SelectElementExtension on SelectElement { - /// Selects an [index] and triggers the `change` event. - /// See [dispatchChangeEvent] and [selectedIndex]. - bool selectIndex(int index) { - selectedIndex = index; - return dispatchChangeEvent(); - } - - List get selectedOptionsSafe { - try { - return selectedOptions; - } catch (_) { - return []; - } - } -} diff --git a/lib/src/bones_ui_generator.dart b/lib/src/bones_ui_generator.dart index 2b65cbc..e1e526c 100644 --- a/lib/src/bones_ui_generator.dart +++ b/lib/src/bones_ui_generator.dart @@ -1,9 +1,9 @@ -import 'dart:html'; - -import 'package:dom_builder/dom_builder_dart_html.dart'; +import 'package:dom_builder/dom_builder_web.dart'; import 'package:dom_tools/dom_tools.dart'; import 'package:intl_messages/intl_messages.dart'; +import 'package:statistics/statistics.dart'; import 'package:swiss_knife/swiss_knife.dart'; +import 'package:web_utils/web_utils.dart'; import 'bones_ui_async_content.dart'; import 'bones_ui_component.dart'; @@ -149,10 +149,11 @@ class UIComponentGenerator @override bool isGeneratedElement(UINode element) { - if (element is UIElement) { - var tag = element.tagName.toLowerCase(); + final element2 = element.asElementChecked; + if (element2 != null) { + var tag = element2.tagName.toLowerCase(); if (tag != generatedTag) return false; - var classes = element.classes; + var classes = element2.classList.toList(); var match = classes.containsAll(componentClass.asAttributeValues!); print(classes); print('$componentClass -> $match'); @@ -201,9 +202,8 @@ class UIComponentGenerator DOMElement? domParent, UIElement? parent, UIElement? node) { - var attributes = node != null - ? Map.fromEntries(node.attributes.entries) - : {}; + var attributes = + node != null ? node.attributes.toMap() : {}; var classes = _parseNodeClass(attributes); var style = _parseNodeStyle(attributes); @@ -278,23 +278,23 @@ class UIComponentGenerator abstract class ElementGeneratorBase extends ElementGenerator { void setElementAttributes( UIElement element, Map attributes) { - element.classes.add(tag); + element.classList.add(tag); var attrClass = attributes['class']; if (attrClass != null && attrClass.valueLength > 0) { - element.classes.addAll(attrClass.values!); + element.classList.addAll(attrClass.values!); } var attrStyle = attributes['style']; if (attrStyle != null && attrStyle.valueLength > 0) { - var prevCssText = element.style.cssText; + var prevCssText = element.style?.cssText; if (prevCssText == '') { - element.style.cssText = attrStyle.value; + element.style?.cssText = attrStyle.value ?? ''; } else { var cssText2 = '$prevCssText; ${attrStyle.value} ;'; - element.style.cssText = cssText2; + element.style?.cssText = cssText2; } } } @@ -314,7 +314,7 @@ class UIComponentDOMContext extends DOMContext { /// A [DOMGenerator] (from package `dom_builder`) /// able to generate [UIElement] (from `dart:html`). -class UIDOMGenerator extends DOMGeneratorDartHTMLImpl { +class UIDOMGenerator extends DOMGeneratorWebImpl { UIDOMGenerator() { registerElementGenerator(BUIElementGenerator()); registerElementGenerator(UITemplateElementGenerator()); @@ -407,23 +407,24 @@ class UIDOMGenerator extends DOMGeneratorDartHTMLImpl { var component = externalElement; var componentContent = component.content; - if (element is UIElement) { - element.append(componentContent!); - component.setParent(element); - _resolveParentUIComponent(element, component.content, + final element2 = element.asElementChecked; + if (element2 != null) { + element2.appendChild(componentContent!); + component.setParent(element2); + _resolveParentUIComponent(element2, component.content, childUIComponent: component); component.ensureRendered(); return [componentContent]; } else { - _resolveParentUIComponent(element, component.content, + _resolveParentUIComponent(element2, component.content, childUIComponent: component); return null; } } else if (externalElement is MessageBuilder) { var text = externalElement.build(); - var span = SpanElement(); + var span = HTMLSpanElement(); setElementInnerHTML(span, text); - element.append(span); + element.appendChild(span); return [span]; } @@ -443,20 +444,20 @@ class UIDOMGenerator extends DOMGeneratorDartHTMLImpl { UINode? parent, DOMNode domElement, UINode? templateElement, - futureElementResolved, + Object? futureElementResolved, DOMTreeMap treeMap, DOMContext? context) { super.attachFutureElement(domParent, parent, domElement, templateElement, futureElementResolved, treeMap, context); - if (futureElementResolved is UIElement) { + if (futureElementResolved.isElement) { UIComponent? parentComponent; if (context is UIComponentDOMContext) { parentComponent = context.uiComponent; } else { - parentComponent = - UIRoot.getInstance()!.findUIComponentByChild(futureElementResolved); + parentComponent = UIRoot.getInstance()! + .findUIComponentByChild(futureElementResolved as UIElement); } if (parentComponent != null) { @@ -466,7 +467,7 @@ class UIDOMGenerator extends DOMGeneratorDartHTMLImpl { .ensureAllRendered([futureElementResolved]); _resolveParentUIComponent( - parentComponent.content, futureElementResolved, + parentComponent.content, futureElementResolved as UIElement, parentUIComponent: parentComponent); } } @@ -511,8 +512,8 @@ class UIDOMGenerator extends DOMGeneratorDartHTMLImpl { @override void finalizeGeneratedTree(DOMTreeMap treeMap) { var rootElement = treeMap.rootElement; - if (rootElement is UIElement) { - setElementsBGBlur(rootElement); + if (rootElement.isElement) { + setElementsBGBlur(rootElement as Element); setElementsDivCentered(rootElement); } } @@ -527,7 +528,7 @@ class UIDOMGenerator extends DOMGeneratorDartHTMLImpl { } } -class UIDOMActionExecutor extends DOMActionExecutorDartHTML { +class UIDOMActionExecutor extends DOMActionExecutorWebHTML { @override UINode? callLocale( UINode? target, List parameters, DOMContext? context) { diff --git a/lib/src/bones_ui_internal.dart b/lib/src/bones_ui_internal.dart index dc837d4..474cb9c 100644 --- a/lib/src/bones_ui_internal.dart +++ b/lib/src/bones_ui_internal.dart @@ -1,12 +1,13 @@ import 'bones_ui_component.dart'; import 'bones_ui_web.dart'; +import 'package:web/web.dart'; class UIComponentInternals { final UIComponent component; - final void Function(List list) _parseAttributes; + final void Function(List list) _parseAttributes; - final void Function(List elements) _ensureAllRendered; + final void Function(List elements) _ensureAllRendered; final void Function() _refreshInternal; @@ -22,9 +23,9 @@ class UIComponentInternals { bool renderOnConstruction, ) _construct; - final UIElement? Function() _getContent; + final HTMLElement? Function() _getContent; - final void Function(UIElement content) _setContent; + final void Function(HTMLElement content) _setContent; UIComponentInternals( this.component, @@ -36,9 +37,10 @@ class UIComponentInternals { this._refreshInternal, ); - void parseAttributes(List list) => _parseAttributes(list); + void parseAttributes(List list) => _parseAttributes(list); - void ensureAllRendered(List elements) => _ensureAllRendered(elements); + void ensureAllRendered(List elements) => + _ensureAllRendered(elements); void refreshInternal() => _refreshInternal(); @@ -58,5 +60,5 @@ class UIComponentInternals { UIElement? getContent() => _getContent(); - void setContent(UIElement content) => _setContent(content); + void setContent(HTMLElement content) => _setContent(content); } diff --git a/lib/src/bones_ui_layout.dart b/lib/src/bones_ui_layout.dart index 22ae64c..000dc0b 100644 --- a/lib/src/bones_ui_layout.dart +++ b/lib/src/bones_ui_layout.dart @@ -1,6 +1,5 @@ -import 'dart:html'; - import 'package:expressions/expressions.dart'; +import 'package:web_utils/web_utils.dart'; import 'bones_ui_component.dart'; import 'bones_ui_web.dart'; @@ -111,8 +110,8 @@ class UILayoutEvaluator extends ExpressionEvaluator { return elem; } - dynamic _requestElementProperty(dynamic element, String propery) { - var resolved = elementPropertyResolver(element, propery); + dynamic _requestElementProperty(dynamic element, String property) { + var resolved = elementPropertyResolver(element, property); if (resolved == null) return null; _providedElements.add(resolved); return resolved; @@ -504,7 +503,7 @@ class UILayoutEvaluator extends ExpressionEvaluator { class UILayout { final UIComponent parent; - final UIElement element; + final HTMLElement element; final String layout; @@ -529,12 +528,13 @@ class UILayout { } } - dynamic _getElementProperty(dynamic elem, String? property) { + dynamic _getElementProperty(Object? elem, String? property) { if (property == null) return null; - if (elem is UIElement) { - property = property.toLowerCase(); + if (elem.asJSAny.isHTMLElement) { + elem = elem as HTMLElement; + property = property.toLowerCase(); if (property == 'x') { return elem.offsetLeft; } else if (property == 'y') { @@ -543,8 +543,7 @@ class UILayout { return elem.offsetWidth; } else if (property == 'height') { return elem.offsetHeight; - } - if (property == 'center') { + } else if (property == 'center') { return _getElementCenter(elem); } else if (property == 'index') { return _getElementIndex(elem); @@ -557,19 +556,20 @@ class UILayout { } int _getElementIndex(UIElement elem) { - var idx = elem.parent!.children.indexOf(elem); + var idx = elem.parentElement!.children.indexOf(elem); return idx; } int _getElementIndexByID(UIElement elem) { var elemID = elem.id; - List elemsSameID = elem.parent!.querySelectorAll('#$elemID'); + List elemsSameID = + elem.parentElement!.querySelectorAll('#$elemID').toElements(); if (elemsSameID.isEmpty) return -1; var idx = elemsSameID.indexOf(elem); return idx; } - Map _getElementCenter(UIElement elem) { + Map _getElementCenter(HTMLElement elem) { var x = elem.offsetLeft; var y = elem.offsetTop; var w = elem.offsetWidth; @@ -731,7 +731,7 @@ class UILayout { return _valueXY( value, (pw, ph, w, h) => '${((pw / 2) - (w / 2)).toStringAsFixed(0)}px', - (e) => e.style.left); + (e) => e.style?.left ?? ''); } void _commandFunctionY(String value) { @@ -743,7 +743,7 @@ class UILayout { return _valueXY( value, (pw, ph, w, h) => '${((ph / 2) - (h / 2)).toStringAsFixed(0)}px', - (e) => e.style.top); + (e) => e.style?.top ?? ''); } void _commandFunctionCenterX(String value) { @@ -775,14 +775,14 @@ class UILayout { String _valueXY(String value, ElementCoordsValue valueCenter, ValueFromElement valueFromElement) { if (_patternElementID.hasMatch(value)) { - var sel = element.parent!.querySelector(value); + var sel = element.parentElement!.querySelector(value); if (sel == null) return '0px'; return valueFromElement(sel); } else if (value == '*') { var w = element.offsetWidth; var h = element.offsetHeight; - var parent = element.offsetParent!; + var parent = element.offsetParent!.asHTMLElement; var pw = parent.offsetWidth; var ph = parent.offsetHeight; @@ -802,7 +802,7 @@ class UILayout { value, (pw, ph, w, h) => '${pw}px', (pw, ph, p) => '${(pw * p).toStringAsFixed(0)}px', - (e) => e.style.width); + (e) => e.style?.width ?? ''); element.style.width = width; } @@ -811,7 +811,7 @@ class UILayout { value, (pw, ph, w, h) => '${ph}px', (pw, ph, p) => '${(ph * p).toStringAsFixed(0)}px', - (e) => e.style.height); + (e) => e.style?.height ?? ''); element.style.height = height; } @@ -825,12 +825,12 @@ class UILayout { var w = element.offsetWidth; var h = element.offsetHeight; - var parent = element.offsetParent!; + var parent = element.offsetParent!.asHTMLElement; var pw = parent.offsetWidth; var ph = parent.offsetHeight; if (_patternElementID.hasMatch(value)) { - var sel = element.parent!.querySelector(value); + var sel = element.parentElement!.querySelector(value); if (sel == null) return '0px'; return valueFromElement(sel); } else if (value == '*') { @@ -855,10 +855,10 @@ class UILayout { String _evaluateValue(String value, ValueFromElement valueFromElement) { var context = _buildEvaluationContext(); - var evaluated = + Object? evaluated = _uiLayoutEvaluator.processLayout(value, context, 'px', '0px'); - if (evaluated is Element) { - return valueFromElement(evaluated); + if (evaluated.isElement) { + return valueFromElement(evaluated as Element); } return '$evaluated'; } diff --git a/lib/src/bones_ui_log.dart b/lib/src/bones_ui_log.dart index 1740cd1..69ae04a 100644 --- a/lib/src/bones_ui_log.dart +++ b/lib/src/bones_ui_log.dart @@ -1,8 +1,8 @@ import 'dart:async'; -import 'dart:html'; import 'package:dom_tools/dom_tools.dart'; import 'package:logging/logging.dart' as logging; +import 'package:web_utils/web_utils.dart'; import 'bones_ui_web.dart'; @@ -314,7 +314,7 @@ class UIConsole { } bool _isShowing() { - return querySelector('#UIConsole') != null; + return document.querySelector('#UIConsole') != null; } static void hide() { @@ -322,7 +322,7 @@ class UIConsole { } void _hide() { - var prevConsoleDiv = querySelector('#UIConsole'); + var prevConsoleDiv = document.querySelector('#UIConsole'); if (prevConsoleDiv != null) { prevConsoleDiv.remove(); @@ -333,14 +333,14 @@ class UIConsole { return get()!._show(); } - DivElement? _contentClipboard; + HTMLDivElement? _contentClipboard; void _show() { _enable(); _hide(); - var consoleDiv = DivElement(); + var consoleDiv = HTMLDivElement(); consoleDiv.id = 'UIConsole'; consoleDiv.style @@ -361,40 +361,44 @@ class UIConsole { ..height = '0px' ..lineHeight = '0px'; - consoleDiv.children.add(contentClipboard); + consoleDiv.appendChild(contentClipboard); var allLogs = _allLogs(); - var consoleButtons = DivElement(); + var consoleButtons = HTMLDivElement(); - var buttonClose = UIElement.span()..text = '[X]'; + var buttonClose = HTMLSpanElement()..text = '[X]'; buttonClose.style.cursor = 'pointer'; buttonClose.onClick.listen((m) => hide()); - var buttonCopy = UIElement.span()..text = '[Copy All]'; + var buttonCopy = HTMLSpanElement()..text = '[Copy All]'; buttonCopy.style.cursor = 'pointer'; buttonCopy.onClick.listen((m) => copy()); - var buttonZoomIn = UIElement.span()..text = '[ + ]'; + var buttonZoomIn = HTMLSpanElement()..text = '[ + ]'; buttonZoomIn.style.cursor = 'zoom-in'; - var buttonZoomOut = UIElement.span()..text = '[ - ]'; + var buttonZoomOut = HTMLSpanElement()..text = '[ - ]'; buttonZoomOut.style.cursor = 'zoom-out'; - var buttonClear = UIElement.span()..text = '[Clear]'; + var buttonClear = HTMLSpanElement()..text = '[Clear]'; buttonClear.style.cursor = 'pointer'; - consoleButtons.children.add(buttonClose); - consoleButtons.children.add(UIElement.span()..innerHtml = '  '); - consoleButtons.children.add(buttonCopy); - consoleButtons.children.add(UIElement.span()..innerHtml = '  '); - consoleButtons.children.add(buttonZoomIn); - consoleButtons.children.add(UIElement.span()..innerHtml = '  '); - consoleButtons.children.add(buttonZoomOut); - consoleButtons.children.add(UIElement.span()..innerHtml = '  '); - consoleButtons.children.add(buttonClear); - - var consoleText = DivElement(); + consoleButtons.appendChild(buttonClose); + consoleButtons.children + .add(HTMLSpanElement()..innerHTML = '  '.toJS); + consoleButtons.appendChild(buttonCopy); + consoleButtons.children + .add(HTMLSpanElement()..innerHTML = '  '.toJS); + consoleButtons.appendChild(buttonZoomIn); + consoleButtons.children + .add(HTMLSpanElement()..innerHTML = '  '.toJS); + consoleButtons.appendChild(buttonZoomOut); + consoleButtons.children + .add(HTMLSpanElement()..innerHTML = '  '.toJS); + consoleButtons.appendChild(buttonClear); + + var consoleText = HTMLDivElement(); consoleText.style.fontSize = '$_fontSize%'; consoleText.style.overflow = 'scroll'; @@ -419,17 +423,17 @@ class UIConsole { _changeFontSize(consoleText, 0.95); }); - consoleDiv.children.add(consoleButtons); - consoleDiv.children.add(consoleText); + consoleDiv.appendChild(consoleButtons); + consoleDiv.appendChild(consoleText); _contentClipboard = contentClipboard; - document.documentElement!.children.add(consoleDiv); + document.documentElement!.appendChild(consoleDiv); } double _fontSize = 100.0; - void _changeFontSize(DivElement consoleText, double change) { + void _changeFontSize(HTMLDivElement consoleText, double change) { var fontSizeProp = consoleText.style.fontSize; var fontSize = fontSizeProp.isEmpty @@ -468,7 +472,7 @@ class UIConsole { var allLogs = _allLogs(); if (_contentClipboard != null) { - _contentClipboard!.innerHtml = '
$allLogs
'; + _contentClipboard!.innerHTML = '
$allLogs
'.toJS; _copyElementToClipboard(_contentClipboard!); _contentClipboard!.text = ''; } @@ -489,10 +493,10 @@ class UIConsole { static final String buttonId = 'UIConsole_button'; - static DivElement button([double opacity = 0.20]) { + static HTMLDivElement button([double opacity = 0.20]) { enable(); - var elem = createDivInline('[>_]'); + var elem = createDivInline(html: '[>_]'); elem.id = buttonId; @@ -508,7 +512,7 @@ class UIConsole { } static void displayButton() { - var prevElem = querySelector('#$buttonId'); + var prevElem = document.querySelector('#$buttonId'); if (prevElem != null) return; var elem = button(1.0); @@ -522,6 +526,6 @@ class UIConsole { ..transform = 'translateY(-15px)' ..zIndex = '999999'; - document.body!.children.add(elem); + document.body!.appendChild(elem); } } diff --git a/lib/src/bones_ui_navigator.dart b/lib/src/bones_ui_navigator.dart index f4f9405..79b0fe8 100644 --- a/lib/src/bones_ui_navigator.dart +++ b/lib/src/bones_ui_navigator.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:swiss_knife/swiss_knife.dart'; +import 'package:web_utils/web_utils.dart'; import 'bones_ui_base.dart'; import 'bones_ui_component.dart'; @@ -497,10 +498,10 @@ class UINavigator { /// Returns [List] that are from navigable components. /// /// [element] If null uses [document] to select sub elements. - List selectNavigables([UIElement? element]) { + List selectNavigables([UIElement? element]) { return element != null - ? element.querySelectorAll(_navigableComponentSelector) - : documentQuerySelectorAll(_navigableComponentSelector); + ? element.querySelectorAll(_navigableComponentSelector).toElements() + : documentQuerySelectorAll(_navigableComponentSelector).toList(); } /// Find in [element] tree nodes with attribute `navigate`. @@ -522,7 +523,7 @@ class UINavigator { !routes.contains(navigateRoute)) { routes.add(navigateRoute); } - _findElementNavigableRoutes(elem.children, routes); + _findElementNavigableRoutes(elem.children.toList(), routes); } } @@ -547,7 +548,7 @@ class UINavigator { /// Register a `onClick` listener in [element] to navigate to [route] /// with [parameters]. - static StreamSubscription? navigateOnClick(UIElement element, String? route, + static StreamSubscription? navigateOnClick(Element element, String? route, [Map? parameters, ParametersProvider? parametersProvider, bool force = false]) { @@ -579,8 +580,9 @@ class UINavigator { subscriptionHolder.add(subscription); - if (element.style.cursor.isEmpty) { - element.style.cursor = 'pointer'; + var cursor = element.style?.cursor ?? ''; + if (cursor.isEmpty) { + element.style?.cursor = 'pointer'; } return subscription; @@ -589,7 +591,7 @@ class UINavigator { return null; } - static bool clearNavigateOnClick(UIElement element) { + static bool clearNavigateOnClick(HTMLElement element) { var attrRoute = element.getAttribute('__navigate__route'); element.removeAttribute('__navigate__route'); element.removeAttribute('__navigate__parameters'); @@ -950,7 +952,7 @@ abstract class UINavigableContent extends UINavigableComponent { List allRendered = []; if (topMargin > 0) { - var divTopMargin = UIElement.div(); + var divTopMargin = HTMLDivElement(); divTopMargin.style.width = '100%'; divTopMargin.style.height = '${topMargin}px'; diff --git a/lib/src/bones_ui_root.dart b/lib/src/bones_ui_root.dart index 39a82c6..dc78c1d 100644 --- a/lib/src/bones_ui_root.dart +++ b/lib/src/bones_ui_root.dart @@ -1,12 +1,12 @@ import 'dart:async'; -import 'dart:html'; -import 'package:dom_builder/dom_builder_dart_html.dart'; +import 'package:dom_builder/dom_builder_web.dart'; import 'package:dom_tools/dom_tools.dart'; import 'package:intl/date_symbol_data_local.dart'; import 'package:intl/intl.dart'; import 'package:intl_messages/intl_messages.dart'; import 'package:swiss_knife/swiss_knife.dart'; +import 'package:web_utils/web_utils.dart'; import 'bones_ui_component.dart'; import 'bones_ui_document.dart'; @@ -189,7 +189,7 @@ abstract class UIRoot extends UIRootComponent { } /// Returns `true` if this instance is running from `bones_ui test` CLI. - bool get isTest => content?.classes.contains('__bones_ui_test__') ?? false; + bool get isTest => content?.classList.contains('__bones_ui_test__') ?? false; /// Returns this [UIRoot] instance. @override @@ -235,9 +235,9 @@ abstract class UIRoot extends UIRootComponent { } // ignore: use_function_type_syntax_for_parameters - SelectElement? buildLanguageSelector(refreshOnChange()) { + HTMLSelectElement? buildLanguageSelector(refreshOnChange()) { return _localesManager!.buildLanguageSelector(refreshOnChange) - as SelectElement?; + as HTMLSelectElement?; } Future _callInitializeLocale(String locale) { @@ -453,7 +453,12 @@ void _initializeAll() { void _configure() { Dimension.parsers.add((v) { - return v is Screen ? Dimension(v.width!, v.height!) : null; + if (v.asJSAny.isA()) { + final screen = v as Screen; + return Dimension(screen.width, screen.height); + } else { + return null; + } }); } diff --git a/lib/src/bones_ui_test_tools.dart b/lib/src/bones_ui_test_tools.dart index 2d4f49f..71f1daa 100644 --- a/lib/src/bones_ui_test_tools.dart +++ b/lib/src/bones_ui_test_tools.dart @@ -1,26 +1,17 @@ -import 'dart:async'; import 'dart:convert' as dart_convert; -import 'dart:html' as dart_html; -import 'dart:html'; -import 'dart:js' as js; import 'dart:math'; import 'package:archive/archive.dart'; +import 'package:bones_ui/bones_ui_kit.dart'; import 'package:collection/collection.dart'; import 'package:stack_trace/stack_trace.dart'; import 'package:stream_channel/stream_channel.dart'; import 'package:test/test.dart' as pkg_test; import 'package:test/test.dart'; - // ignore: implementation_imports import 'package:test_api/src/backend/invoker.dart' as pkg_test_invoker; +import 'package:web_utils/web_utils.dart' as dart_html; -import 'bones_ui.dart'; -import 'bones_ui_base.dart'; -import 'bones_ui_component.dart'; -import 'bones_ui_extension.dart'; -import 'bones_ui_navigator.dart'; -import 'bones_ui_root.dart'; import 'bones_ui_web.dart'; const bonesUiTestToolTitle = 'Bones_UI/${BonesUI.version} - Test'; @@ -75,10 +66,13 @@ Future _initializeTestUIRootImpl( clearTestUIOutputDiv(outputDivID); - document.body!.appendHtml('
'); + document.body!.appendHTML('
'); + + var output = document.querySelector('#$outputDivID') ?? + (throw StateError( + "Can't select #$outputDivID: the appended output DIV added to body is missing!")); - var output = querySelector('#$outputDivID'); - output!.children.clear(); + output.clear(); // Reset the URL route/fragment. // Usually the browser test plugin adds some parameters in the URL fragment. @@ -92,7 +86,8 @@ Future _initializeTestUIRootImpl( ? locationUrl.removeFragment().toString() : locationHref; - window.history.pushState({}, 'Test UIRoot: init', locationUrlReset); + window.history + .pushState(JSObject(), 'Test UIRoot: init', locationUrlReset); print('-- Removed URL fragment> ${Uri.decodeFull(fragment)}'); } @@ -101,7 +96,7 @@ Future _initializeTestUIRootImpl( var uiRoot = uiRootInstantiator(output); print('-- Instantiated: $uiRoot'); - uiRoot.content!.classes.add('__bones_ui_test__'); + uiRoot.content!.classList.add('__bones_ui_test__'); uiRoot.initialize(); @@ -125,8 +120,8 @@ Future _initializeTestUIRootImpl( /// Used by [initializeTestUIRoot]. /// See [testUI]. void clearTestUIOutputDiv(String outputDivID) { - var prevOutputs = querySelectorAll('#$outputDivID'); - for (var e in prevOutputs) { + var prevOutputs = document.querySelectorAll('#$outputDivID'); + for (var e in prevOutputs.toList()) { e.remove(); } } @@ -325,8 +320,10 @@ Future testUISleepUntilElement(Object? root, String selectors, bool expected = false}) { root ??= document.documentElement; - if (root is! UIElement && root is! UIComponent) { - throw ArgumentError("`root` is not an `UIElement` or `UIComponent`"); + var rootJSAny = root.asJSAny; + + if (!rootJSAny.isA() && root is! UIComponent) { + throw ArgumentError("`root` is not an `web.Element` or `UIComponent`"); } var stackTrace = StackTrace.current; @@ -385,7 +382,7 @@ class SpawnHybrid { @override String toString() { - return 'SpawnHibrid{uri: $uri, message: $message}'; + return 'SpawnHybrid{uri: $uri, message: $message}'; } } @@ -421,7 +418,7 @@ Future testMultipleUI(Map Function()> testsMain, var random = Random(shuffleSeed); entries.shuffle(random); - print('testMultipleUI> shuflle(shuffleSeed: $shuffleSeed)'); + print('testMultipleUI> shuffle(shuffleSeed: $shuffleSeed)'); } print('testMultipleUI:'); @@ -441,15 +438,15 @@ Future testMultipleUI(Map Function()> testsMain, } } -/// Executes a group of tests using an instnatiated [UIRoot]. +/// Executes a group of tests using an instantiated [UIRoot]. /// /// - [testUIName] is the name of the test group. -/// - [uiRootInstantiator] is the function that isntantiates the [UIRoot]. +/// - [uiRootInstantiator] is the function that instantiates the [UIRoot]. /// - [body] is the function that will declare the [test]s for the [UIRoot]. /// - [outputDivID] defines the ID of the `div` that will render the [UIRoot]. /// - [initialRenderTimeout] is the timeout for the initial render. See [initializeTestUIRoot]. /// - If [spawnHybrid] is provided it will spawn a VM isolate for the given [uri]. See function [spawnHybridUri]. -/// - [preSetup] and [posSetup] are optinal and are called before and after the group [setUpAll]. +/// - [preSetup] and [posSetup] are optional and are called before and after the group [setUpAll]. /// - [teardown] is called after the group [tearDownAll]. /// - See [UITestContext]. /// @@ -657,11 +654,9 @@ class UITestContext { var title = parts.where((e) => e.isNotEmpty).join(' '); try { - js.context.callMethod("eval", [ - ''' + evalJS(''' window.top.document.title = "$title"; - ''' - ]); + '''); } catch (_) {} } @@ -807,11 +802,13 @@ abstract class UITestChain< Element? elem; if (e is UIComponent) { - elem = e.querySelector(selectors); - } else if (e is Element) { - elem = selectors != null ? e.querySelector(selectors) : null; + elem = e.querySelectorNonTyped(selectors); + } else if (e.isElement) { + elem = selectors != null + ? (e.asJSAny as Element).querySelector(selectors) + : null; } else { - elem = uiRoot.querySelector(selectors); + elem = uiRoot.querySelectorNonTyped(selectors); } if (expected) { @@ -823,22 +820,61 @@ abstract class UITestChain< } /// Alias to [UIComponent.querySelectorAll]. - UITestChainNode, T> querySelectorAll( + UITestChainNode, T> querySelectorAllNonTyped( String? selectors, {bool expected = false}) { var e = element; + List elems; + if (e is UIComponent) { + elems = e.querySelectorAllNonTyped(selectors); + } else if (e.isElement) { + elems = selectors != null + ? (e.asJSAny as Element).querySelectorAll(selectors).toElements() + : []; + } else if (e is Iterable) { + elems = selectors != null + ? e + .expand((e) => e.querySelectorAll(selectors).toIterable()) + .toElements() + : []; + } else { + elems = uiRoot.querySelectorAllNonTyped(selectors); + } + + if (expected) { + expect(elems.isNotEmpty, isTrue, + reason: "Can't find selected elements: $selectors"); + } + + return UITestChainNode(testChainRoot, elems, this as T); + } + + /// Alias to [UIComponent.querySelectorAll]. + UITestChainNode, T> querySelectorAllTyped( + String? selectors, Web webType, + {bool expected = false}) { + var e = element; + List elems; if (e is UIComponent) { - elems = e.querySelectorAll(selectors); - } else if (e is Element) { - elems = selectors != null ? e.querySelectorAll(selectors) : []; + elems = e.querySelectorAllTyped(selectors, webType); + } else if (e.isElement) { + elems = selectors != null + ? (e.asJSAny as Element) + .querySelectorAll(selectors) + .whereElementType(webType) + .toList() + : []; } else if (e is Iterable) { elems = selectors != null - ? e.expand((e) => e.querySelectorAll(selectors)).toList() + ? e + .expand((e) => e.querySelectorAll(selectors).toIterable()) + .whereElementType(webType) + .toList() : []; } else { - elems = uiRoot.querySelectorAll(selectors); + elems = uiRoot.querySelectorAllTyped(selectors, webType); } if (expected) { @@ -862,16 +898,37 @@ abstract class UITestChain< } /// Alias to [querySelectorAll]. - UITestChainNode, T> selectAll(String? selectors, + UITestChainNode, T> selectAllNonTyped(String? selectors, + {bool expected = false}) => + querySelectorAllNonTyped(selectors, expected: expected); + + /// Alias to [querySelectorAll]. + UITestChainNode, T> selectAllTyped( + String? selectors, Web webType, {bool expected = false}) => - querySelectorAll(selectors, expected: expected); + querySelectorAllTyped(selectors, webType, expected: expected); /// Alias to [querySelectorAll] + `where`. - UITestChainNode, T> selectWhere( + UITestChainNode, T> selectWhereNonTyped( String? selectors, bool Function(Element element) test, {bool expected = false}) { - var sel = querySelectorAll(selectors); - var elems = sel.element.whereType().where(test).toList(); + var sel = querySelectorAllNonTyped(selectors); + var elems = sel.element.where(test).toList(); + + if (expected) { + expect(elems.isNotEmpty, isTrue, + reason: "Can't find selected elements: $selectors"); + } + + return UITestChainNode(testChainRoot, elems, this as T); + } + + /// Alias to [querySelectorAll] + `where`. + UITestChainNode, T> selectWhereTyped( + String? selectors, Web webType, bool Function(Element element) test, + {bool expected = false}) { + var sel = querySelectorAllTyped(selectors, webType); + var elems = sel.element.where(test).toList(); if (expected) { expect(elems.isNotEmpty, isTrue, @@ -882,10 +939,25 @@ abstract class UITestChain< } /// Alias to [querySelectorAll] + `firstWhereOrNull`. - UITestChainNode selectFirstWhere( + UITestChainNode selectFirstWhereNonTyped( String? selectors, bool Function(Element element) test, {bool expected = false}) { - var sel = querySelectorAll(selectors); + var sel = querySelectorAllNonTyped(selectors); + var elem = sel.element.firstWhereOrNull(test); + + if (expected) { + expect(elem, pkg_test.isNotNull, + reason: "Can't find selected element: $selectors"); + } + + return UITestChainNode(testChainRoot, elem, this as T); + } + + /// Alias to [querySelectorAll] + `firstWhereOrNull`. + UITestChainNode selectFirstWhereTyped( + String? selectors, Web webType, bool Function(Element element) test, + {bool expected = false}) { + var sel = querySelectorAllTyped(selectors, webType); var elem = sel.element.whereType().firstWhereOrNull(test); if (expected) { @@ -897,7 +969,7 @@ abstract class UITestChain< } /// Alias to [sleepUntilElement] + [querySelectorAll] + `where`. - Future, T>> selectWhereUntil( + Future, T>> selectWhereUntilNonTyped( String? selectors, bool Function(Element element) test, {int? timeoutMs, int? intervalMs, @@ -910,20 +982,52 @@ abstract class UITestChain< minMs: minMs, mapper: mapper, validator: (elems) => elems.any(test)) - .selectWhere(selectors, test) + .selectWhereNonTyped(selectors, test) .thenChain((o) { if (expected) { - var sel = - selectAll(selectors).element.map((e) => e.simplify()).toList(); + var sel = selectAllNonTyped(selectors) + .element + .map((e) => e.simplify()) + .toList(); expect(o.element, pkg_test.isNotEmpty, reason: "Can't find any selected element: $selectors -> $sel"); } - return o as UITestChainNode, T>; + return o as UITestChainNode, T>; }); + /// Alias to [sleepUntilElement] + [querySelectorAll] + `where`. + Future, T>> + selectWhereUntilTyped(String? selectors, + Web webType, bool Function(Element element) test, + {int? timeoutMs, + int? intervalMs, + int? minMs, + Iterable Function(List elems)? mapper, + bool expected = false}) => + sleepUntilElement(selectors ?? '*', + timeoutMs: timeoutMs, + intervalMs: intervalMs, + minMs: minMs, + mapper: mapper, + validator: (elems) => elems.any(test)) + .selectWhereTyped(selectors, webType, test) + .thenChain((o) { + if (expected) { + var sel = selectAllTyped(selectors, webType) + .element + .map((e) => e.simplify()) + .toList(); + + expect(o.element, pkg_test.isNotEmpty, + reason: + "Can't find any selected element: $selectors -> $sel"); + } + return o as UITestChainNode, T>; + }); + /// Alias to [sleepUntilElement] + [querySelectorAll] + `firstWhereOrNull`. - Future> selectFirstWhereUntil( + Future> selectFirstWhereUntilNonTyped( String? selectors, bool Function(Element element) test, {int? timeoutMs, int? intervalMs, @@ -935,23 +1039,73 @@ abstract class UITestChain< minMs: minMs, mapper: mapper, validator: (elems) => elems.any(test)) - .selectFirstWhere(selectors, test) + .selectFirstWhereNonTyped(selectors, test) .thenChain((o) { var elem = o.element; if (elem == null) { - var sel = - selectAll(selectors).element.map((e) => e.simplify()).toList(); + var sel = selectAllNonTyped(selectors) + .element + .map((e) => e.simplify()) + .toList(); expect(elem, pkg_test.isNotNull, reason: "Can't find selected element: $selectors -> $sel"); } - return UITestChainNode( + return UITestChainNode( o.testChainRoot as UITestChainRoot, elem!, this as T); }); + /// Alias to [sleepUntilElement] + [querySelectorAll] + `firstWhereOrNull`. + Future> + selectFirstWhereUntilTyped(String? selectors, + Web webType, bool Function(Element element) test, + {int? timeoutMs, + int? intervalMs, + int? minMs, + Iterable Function(List elems)? mapper}) => + sleepUntilElement(selectors ?? '*', + timeoutMs: timeoutMs, + intervalMs: intervalMs, + minMs: minMs, + mapper: mapper, + validator: (elems) => elems.any(test)) + .selectFirstWhereTyped(selectors, webType, test) + .thenChain((o) { + var elem = o.element; + if (elem == null) { + var sel = selectAllTyped(selectors, webType) + .element + .map((e) => e.simplify()) + .toList(); + + expect(elem, pkg_test.isNotNull, + reason: "Can't find selected element: $selectors -> $sel"); + } + return UITestChainNode( + o.testChainRoot as UITestChainRoot, elem!, this as T); + }); + /// Alias to [sleepUntilElement] + [querySelectorAll] - Future> selectUntil( - String? selectors, + Future> selectUntilNonTyped(String? selectors, + {int? timeoutMs, + int? intervalMs, + int? minMs, + Iterable Function(List elems)? mapper}) => + sleepUntilElement(selectors ?? '*', + timeoutMs: timeoutMs, + intervalMs: intervalMs, + minMs: minMs, + mapper: mapper) + .selectExpected(selectors) + .then((o) { + var elem = o.element; + return UITestChainNode( + o.testChainRoot as UITestChainRoot, elem, this as T); + }); + + /// Alias to [sleepUntilElement] + [querySelectorAll] + Future> selectUntilTyped( + String? selectors, Web webType, {int? timeoutMs, int? intervalMs, int? minMs, @@ -964,9 +1118,9 @@ abstract class UITestChain< .selectExpected(selectors) .then((o) { var elem = o.element; - if (elem is! O) { + if (!elem.isElementOf(webType)) { expect(elem, pkg_test.isA(), - reason: "Selected element not of type `$O`: $elem"); + reason: "Selected element not of type `$webType`: $elem"); } return UITestChainNode( @@ -1034,7 +1188,7 @@ abstract class UITestChain< .trim() .replaceAll(RegExp(r'\s'), '_'); - var outerHtml = context.document.element.outerHtml ?? ''; + var outerHtml = context.document.element.outerHTML.asString; var timeMs = DateTime.now().millisecondsSinceEpoch; String? msg; @@ -1198,14 +1352,14 @@ extension UITestChainElementExtension< T extends UITestChain> on T { String? get text => element?.text; - String? get innerHtml => element?.innerHtml; + String? get innerHtml => element?.innerHTML.asString; - String? get outerHtml => element?.outerHtml; + String? get outerHtml => element?.outerHTML.asString; } extension UITestChainSelectElementExtension< U extends UIRoot, - E extends SelectElement?, + E extends HTMLSelectElement?, O, P extends UITestChain, T extends UITestChain> on T { @@ -1419,10 +1573,17 @@ extension FutureUITestChainExtension< {bool expected = false}) => thenChain((o) => o.querySelector(selectors, expected: expected)); - Future, T>> querySelectorAll( + Future, T>> querySelectorAllNonTyped( String? selectors, {bool expected = false}) => - thenChain((o) => o.querySelectorAll(selectors, expected: expected)); + thenChain( + (o) => o.querySelectorAllNonTyped(selectors, expected: expected)); + + Future, T>> + querySelectorAllTyped( + String? selectors, Web webType, {bool expected = false}) => + thenChain((o) => o.querySelectorAllTyped(selectors, webType, + expected: expected)); Future> select(String? selectors, {bool expected = false}) => @@ -1431,48 +1592,100 @@ extension FutureUITestChainExtension< Future> selectExpected(String? selectors) => thenChain((o) => o.selectExpected(selectors)); - Future, T>> selectAll( + Future, T>> selectAllNonTyped( String? selectors, {bool expected = false}) => - thenChain((o) => o.selectAll(selectors, expected: expected)); + thenChain((o) => o.selectAllNonTyped(selectors, expected: expected)); - Future, T>> selectWhere( - String? selectors, bool Function(Element element) test, + Future, T>> selectAllTyped( + String? selectors, Web webType, {bool expected = false}) => + thenChain( + (o) => o.selectAllTyped(selectors, webType, expected: expected)); + + Future, T>> + selectWhereNonTyped( + String? selectors, bool Function(Element element) test, + {bool expected = false}) => + thenChain((o) => + o.selectWhereNonTyped(selectors, test, expected: expected)); + + Future, T>> selectWhereTyped( + String? selectors, + Web webType, + bool Function(Element element) test, {bool expected = false}) => - thenChain((o) => o.selectWhere(selectors, test, expected: expected)); + thenChain((o) => + o.selectWhereTyped(selectors, webType, test, expected: expected)); - Future> selectFirstWhere( + Future> selectFirstWhereNonTyped( String? selectors, bool Function(Element element) test, {bool expected = false}) => - thenChain( - (o) => o.selectFirstWhere(selectors, test, expected: expected)); + thenChain((o) => + o.selectFirstWhereNonTyped(selectors, test, expected: expected)); + + Future> selectFirstWhereTyped( + String? selectors, + Web webType, + bool Function(Element element) test, + {bool expected = false}) => + thenChain((o) => o.selectFirstWhereTyped(selectors, webType, test, + expected: expected)); - Future, T>> selectWhereUntil( + Future, T>> selectWhereUntilNonTyped( String? selectors, bool Function(Element element) test, {int? timeoutMs, int? intervalMs, int? minMs, Iterable Function(List elems)? mapper, bool expected = false}) => - thenChain((o) => o.selectWhereUntil(selectors, test, + thenChain((o) => o.selectWhereUntilNonTyped(selectors, test, timeoutMs: timeoutMs, intervalMs: intervalMs, minMs: minMs, mapper: mapper, expected: expected)); - Future> selectFirstWhereUntil( + Future, T>> + selectWhereUntilTyped(String? selectors, + Web webType, bool Function(Element element) test, + {int? timeoutMs, + int? intervalMs, + int? minMs, + Iterable Function(List elems)? mapper, + bool expected = false}) => + thenChain((o) => o.selectWhereUntilTyped(selectors, webType, test, + timeoutMs: timeoutMs, + intervalMs: intervalMs, + minMs: minMs, + mapper: mapper, + expected: expected)); + + Future> selectFirstWhereUntilNonTyped( String? selectors, bool Function(Element element) test, {int? timeoutMs, int? intervalMs, int? minMs, Iterable Function(List elems)? mapper}) => - thenChain((o) => o.selectFirstWhereUntil(selectors, test, + thenChain((o) => o.selectFirstWhereUntilNonTyped(selectors, test, timeoutMs: timeoutMs, intervalMs: intervalMs, minMs: minMs, mapper: mapper)); + Future> + selectFirstWhereUntilTyped(String? selectors, + Web webType, bool Function(Element element) test, + {int? timeoutMs, + int? intervalMs, + int? minMs, + Iterable Function(List elems)? mapper}) => + thenChain((o) => o.selectFirstWhereUntilTyped( + selectors, webType, test, + timeoutMs: timeoutMs, + intervalMs: intervalMs, + minMs: minMs, + mapper: mapper)); + Future> map(R Function(E e) mapper) => then((o) => o.map(mapper)); @@ -1555,11 +1768,17 @@ extension FutureUITestChainNodeExtension< thenChain((o) => o.querySelector(selectors, expected: expected) as UITestChainNode); - Future, T>> querySelectorAll( + Future, T>> querySelectorAllNonTyped( String? selectors, {bool expected = false}) => - thenChain((o) => o.querySelectorAll(selectors, expected: expected) - as UITestChainNode, T>); + thenChain((o) => o.querySelectorAllNonTyped(selectors, expected: expected) + as UITestChainNode, T>); + + Future, T>> + querySelectorAllTyped( + String? selectors, Web webType, {bool expected = false}) => + thenChain((o) => o.querySelectorAllTyped(selectors, webType, + expected: expected) as UITestChainNode, T>); Future> select(String? selectors, {bool expected = false}) => @@ -1570,60 +1789,124 @@ extension FutureUITestChainNodeExtension< thenChain( (o) => o.selectExpected(selectors) as UITestChainNode); - Future, T>> selectAll( + Future, T>> selectAllNonTyped( String? selectors, {bool expected = false}) => - thenChain((o) => o.selectAll(selectors, expected: expected) - as UITestChainNode, T>); + thenChain((o) => o.selectAllNonTyped(selectors, expected: expected) + as UITestChainNode, T>); - Future, T>> selectWhere( + Future, T>> selectAllTyped( + String? selectors, Web webType, {bool expected = false}) => + thenChain((o) => o.selectAllTyped(selectors, webType, + expected: expected) as UITestChainNode, T>); + + Future, T>> selectWhereNonTyped( String? selectors, bool Function(Element element) test, {bool expected = false}) => - thenChain((o) => o.selectWhere(selectors, test, expected: expected) - as UITestChainNode, T>); + thenChain((o) => o.selectWhereNonTyped(selectors, test, + expected: expected) as UITestChainNode, T>); - Future> selectFirstWhere( + Future, T>> selectWhereTyped( + String? selectors, + Web webType, + bool Function(Element element) test, + {bool expected = false}) => + thenChain((o) => o.selectWhereTyped(selectors, webType, test, + expected: expected) as UITestChainNode, T>); + + Future> selectFirstWhereNonTyped( String? selectors, bool Function(Element element) test, {bool expected = false}) => - thenChain((o) => o.selectFirstWhere(selectors, test, + thenChain((o) => o.selectFirstWhereNonTyped(selectors, test, + expected: expected) as UITestChainNode); + + Future> selectFirstWhereTyped( + String? selectors, + Web webType, + bool Function(Element element) test, + {bool expected = false}) => + thenChain((o) => o.selectFirstWhereTyped(selectors, webType, test, expected: expected) as UITestChainNode); - Future, T>> selectWhereUntil( + Future, T>> selectWhereUntilNonTyped( String? selectors, bool Function(Element element) test, {int? timeoutMs, int? intervalMs, int? minMs, Iterable Function(List elems)? mapper, bool expected = false}) => - thenChain((o) => o.selectWhereUntil(selectors, test, + thenChain((o) => o.selectWhereUntilNonTyped(selectors, test, timeoutMs: timeoutMs, intervalMs: intervalMs, minMs: minMs, mapper: mapper, - expected: expected) as UITestChainNode, T>); + expected: expected) as UITestChainNode, T>); + + Future, T>> + selectWhereUntilTyped(String? selectors, + Web webType, bool Function(Element element) test, + {int? timeoutMs, + int? intervalMs, + int? minMs, + Iterable Function(List elems)? mapper, + bool expected = false}) => + thenChain((o) => o.selectWhereUntilTyped(selectors, webType, test, + timeoutMs: timeoutMs, + intervalMs: intervalMs, + minMs: minMs, + mapper: mapper, + expected: expected) as UITestChainNode, T>); - Future> selectFirstWhereUntil( + Future> selectFirstWhereUntilNonTyped( String? selectors, bool Function(Element element) test, {int? timeoutMs, int? intervalMs, int? minMs, Iterable Function(List elems)? mapper}) => thenChain((o) => o - .selectFirstWhereUntil(selectors, test, + .selectFirstWhereUntilNonTyped(selectors, test, timeoutMs: timeoutMs, intervalMs: intervalMs, minMs: minMs, mapper: mapper) - .then((o) => o as UITestChainNode)); + .then((o) => o as UITestChainNode)); + + Future> + selectFirstWhereUntilTyped(String? selectors, + Web webType, bool Function(Element element) test, + {int? timeoutMs, + int? intervalMs, + int? minMs, + Iterable Function(List elems)? mapper}) => + thenChain((o) => o + .selectFirstWhereUntilTyped(selectors, webType, test, + timeoutMs: timeoutMs, + intervalMs: intervalMs, + minMs: minMs, + mapper: mapper) + .then((o) => o as UITestChainNode)); + + Future> selectUntilNonTyped(String? selectors, + {int? timeoutMs, + int? intervalMs, + int? minMs, + Iterable Function(List elems)? mapper}) => + thenChain((o) => o + .selectUntilNonTyped(selectors, + timeoutMs: timeoutMs, + intervalMs: intervalMs, + minMs: minMs, + mapper: mapper) + .then((o) => o as UITestChainNode)); - Future> selectUntil( - String? selectors, + Future> selectUntilTyped( + String? selectors, Web webType, {int? timeoutMs, int? intervalMs, int? minMs, Iterable Function(List elems)? mapper}) => thenChain((o) => o - .selectUntil(selectors, + .selectUntilTyped(selectors, webType, timeoutMs: timeoutMs, intervalMs: intervalMs, minMs: minMs, @@ -1660,14 +1943,14 @@ extension FutureUITestChainNodeElementExtension< T extends UITestChainNode> on Future> { Future get text => then((o) => o.element?.text); - Future get outerHtml => then((o) => o.element?.outerHtml); + Future get outerHtml => then((o) => o.element?.outerHTML.asString); - Future get innerHtml => then((o) => o.element?.innerHtml); + Future get innerHtml => then((o) => o.element?.innerHTML.asString); } extension FutureUITestChainNodeSelectElementExtension< U extends UIRoot, - E extends SelectElement?, + E extends HTMLSelectElement?, P extends UITestChain, T extends UITestChainNode> on Future> { Future selectIndex(int index) => thenChain((o) { @@ -1721,10 +2004,16 @@ extension TestElementExtension on Element? { return e; } - List selectAll(String? selectors) { + List selectAllNonTyped(String? selectors) { + var self = this; + if (self == null || selectors == null || selectors.isEmpty) return []; + return self.querySelectorAll(selectors).toElements(); + } + + List selectAllTyped(String? selectors, Web webType) { var self = this; if (self == null || selectors == null || selectors.isEmpty) return []; - return self.querySelectorAll(selectors); + return self.querySelectorAllTyped(selectors, webType); } } @@ -1740,21 +2029,31 @@ extension TestFutureElementExtension on Future { Future selectExpected(String? selectors) => thenChain((e) => e.selectExpected(selectors)); - Future> selectAll(String? selectors) => - then((e) => e.selectAll(selectors)); + Future> selectAllNonTyped(String? selectors) => + then((e) => e.selectAllNonTyped(selectors)); + + Future> selectAllTyped( + String? selectors, Web webType) => + then((e) => e.selectAllTyped(selectors, webType)); } extension TestUIComponentNullableExtension on UIComponent? { - E? select(String? selectors) { + Element? selectNonTyped(String? selectors) { var self = this; if (self == null || selectors == null || selectors.isEmpty) return null; - return self.querySelector(selectors); + return self.querySelectorNonTyped(selectors); } - E selectExpected(String? selectors) { + E? selectTyped(String? selectors, Web webType) { + var self = this; + if (self == null || selectors == null || selectors.isEmpty) return null; + return self.querySelectorTyped(selectors, webType); + } + + Element selectExpectedNonTyped(String? selectors) { var self = this; var e = selectors != null && selectors.isNotEmpty - ? self?.querySelector(selectors) + ? self?.querySelectorNonTyped(selectors) : null; if (e == null) { throw TestFailure("Can't find element: `$selectors`"); @@ -1762,20 +2061,38 @@ extension TestUIComponentNullableExtension on UIComponent? { return e; } - List selectAll(String? selectors) { + E selectExpectedTyped( + String? selectors, Web webType) { + var self = this; + var e = selectors != null && selectors.isNotEmpty + ? self?.querySelectorTyped(selectors, webType) + : null; + if (e == null) { + throw TestFailure("Can't find element: `$selectors`"); + } + return e; + } + + List selectAllNonTyped(String? selectors) { + var self = this; + if (self == null || selectors == null || selectors.isEmpty) return []; + return self.querySelectorAllNonTyped(selectors); + } + + List selectAllTyped(String? selectors, Web webType) { var self = this; if (self == null || selectors == null || selectors.isEmpty) return []; - return self.querySelectorAll(selectors); + return self.querySelectorAllTyped(selectors, webType); } String simplify( {bool trim = true, - bool collapseSapces = true, + bool collapseSpaces = true, bool lowerCase = true, String nullValue = ''}) => this?.content.simplify( trim: trim, - collapseSapces: collapseSapces, + collapseSpaces: collapseSpaces, lowerCase: lowerCase, nullValue: nullValue) ?? ''; @@ -1797,25 +2114,37 @@ extension TestFutureUIComponentExtension on Future { return elem; }); - Future select(String? selectors) => - then((e) => e.select(selectors)); + Future selectNonTyped(String? selectors) => + then((e) => e.selectNonTyped(selectors)); - Future selectExpected(String? selectors) => - thenChain((e) => e.selectExpected(selectors)); + Future selectTyped( + String? selectors, Web webType) => + then((e) => e.selectTyped(selectors, webType)); - Future> selectAll(String? selectors) => - then((e) => e.selectAll(selectors)); + Future selectExpectedNonTyped(String? selectors) => + thenChain((e) => e.selectExpectedNonTyped(selectors)); + + Future selectExpectedTyped( + String? selectors, Web webType) => + thenChain((e) => e.selectExpectedTyped(selectors, webType)); + + Future> selectAllNonTyped(String? selectors) => + then((e) => e.selectAllNonTyped(selectors)); + + Future> selectAllTyped( + String? selectors, Web webType) => + then((e) => e.selectAllTyped(selectors, webType)); } extension TestNodeExtension on UINode? { String simplify( {bool trim = true, - bool collapseSapces = true, + bool collapseSpaces = true, bool lowerCase = true, String nullValue = ''}) => - this?.text.simplify( + this?.textContent.simplify( trim: trim, - collapseSapces: collapseSapces, + collapseSpaces: collapseSpaces, lowerCase: lowerCase, nullValue: nullValue) ?? ''; @@ -1824,13 +2153,13 @@ extension TestNodeExtension on UINode? { extension TestIterableNodeExtension on Iterable? { List simplify( {bool trim = true, - bool collapseSapces = true, + bool collapseSpaces = true, bool lowerCase = true, String nullValue = ''}) => this ?.map((e) => e.simplify( trim: trim, - collapseSapces: collapseSapces, + collapseSpaces: collapseSpaces, lowerCase: lowerCase, nullValue: nullValue)) .toList() ?? @@ -1838,14 +2167,14 @@ extension TestIterableNodeExtension on Iterable? { String simplifyAll( {bool trim = true, - bool collapseSapces = true, + bool collapseSpaces = true, bool lowerCase = true, String nullValue = '', String separator = ' , '}) => this ?.map((e) => e.simplify( trim: trim, - collapseSapces: collapseSapces, + collapseSpaces: collapseSpaces, lowerCase: lowerCase, nullValue: nullValue)) .join(separator) ?? @@ -1853,14 +2182,14 @@ extension TestIterableNodeExtension on Iterable? { String simplifyAt(int index, {bool trim = true, - bool collapseSapces = true, + bool collapseSpaces = true, bool lowerCase = true, String nullValue = ''}) { var self = this; return self != null && index < self.length ? self.elementAt(index).simplify( trim: trim, - collapseSapces: collapseSapces, + collapseSpaces: collapseSpaces, lowerCase: lowerCase, nullValue: nullValue) : ''; @@ -1868,24 +2197,24 @@ extension TestIterableNodeExtension on Iterable? { String simplifyFirst( {bool trim = true, - bool collapseSapces = true, + bool collapseSpaces = true, bool lowerCase = true, String nullValue = ''}) => this?.firstOrNull.simplify( trim: trim, - collapseSapces: collapseSapces, + collapseSpaces: collapseSpaces, lowerCase: lowerCase, nullValue: nullValue) ?? ''; String simplifyLast( {bool trim = true, - bool collapseSapces = true, + bool collapseSpaces = true, bool lowerCase = true, String nullValue = ''}) => this?.lastOrNull.simplify( trim: trim, - collapseSapces: collapseSapces, + collapseSpaces: collapseSpaces, lowerCase: lowerCase, nullValue: nullValue) ?? ''; @@ -1895,7 +2224,9 @@ extension TestIterableElementExtension on Iterable? { List selectAll(String? selectors) { var self = this; return selectors != null && self != null - ? self.expand((e) => e.querySelectorAll(selectors)).toList() + ? self + .expand((e) => e.querySelectorAll(selectors).toElements()) + .toList() : []; } } @@ -1903,13 +2234,13 @@ extension TestIterableElementExtension on Iterable? { extension TestStringExtension on String? { String simplify( {bool trim = true, - bool collapseSapces = true, + bool collapseSpaces = true, bool lowerCase = true, String nullValue = ''}) { var self = this; if (self == null) return nullValue; - if (collapseSapces) { + if (collapseSpaces) { self = self.replaceAll(RegExp(r'\s+'), ' '); } @@ -1928,13 +2259,13 @@ extension TestStringExtension on String? { extension TestIterableStringExtension on Iterable? { List simplify( {bool trim = true, - bool collapseSapces = true, + bool collapseSpaces = true, bool lowerCase = true, String nullValue = ''}) => this ?.map((e) => e.simplify( trim: trim, - collapseSapces: collapseSapces, + collapseSpaces: collapseSpaces, lowerCase: lowerCase, nullValue: nullValue)) .toList() ?? @@ -1942,14 +2273,14 @@ extension TestIterableStringExtension on Iterable? { String simplifyAll( {bool trim = true, - bool collapseSapces = true, + bool collapseSpaces = true, bool lowerCase = true, String nullValue = '', String separator = ' , '}) => this ?.map((e) => e.simplify( trim: trim, - collapseSapces: collapseSapces, + collapseSpaces: collapseSpaces, lowerCase: lowerCase, nullValue: nullValue)) .join(separator) ?? @@ -1957,14 +2288,14 @@ extension TestIterableStringExtension on Iterable? { String simplifyAt(int index, {bool trim = true, - bool collapseSapces = true, + bool collapseSpaces = true, bool lowerCase = true, String nullValue = ''}) { var self = this; return self != null && index < self.length ? self.elementAt(index).simplify( trim: trim, - collapseSapces: collapseSapces, + collapseSpaces: collapseSpaces, lowerCase: lowerCase, nullValue: nullValue) : ''; @@ -1972,24 +2303,24 @@ extension TestIterableStringExtension on Iterable? { String simplifyFirst( {bool trim = true, - bool collapseSapces = true, + bool collapseSpaces = true, bool lowerCase = true, String nullValue = ''}) => this?.firstOrNull.simplify( trim: trim, - collapseSapces: collapseSapces, + collapseSpaces: collapseSpaces, lowerCase: lowerCase, nullValue: nullValue) ?? ''; String simplifyLast( {bool trim = true, - bool collapseSapces = true, + bool collapseSpaces = true, bool lowerCase = true, String nullValue = ''}) => this?.lastOrNull.simplify( trim: trim, - collapseSapces: collapseSapces, + collapseSpaces: collapseSpaces, lowerCase: lowerCase, nullValue: nullValue) ?? ''; @@ -2011,23 +2342,23 @@ bool _existsElement( String selectors, Iterable Function(List elems)? mapper, bool Function(List elems)? validator) { - List elem; - if (root is Element) { - elem = root.querySelectorAll(selectors); + List elements; + if (root.isElement) { + elements = (root as Element).querySelectorAll(selectors).toElements(); } else if (root is UIComponent) { - elem = root.querySelectorAll(selectors); + elements = root.querySelectorAllNonTyped(selectors); } else { throw StateError("`root` is not an `Element` or `UIComponent`"); } if (mapper != null) { - elem = mapper(elem).toList(); + elements = mapper(elements).toList(); } if (validator != null) { - return validator(elem); + return validator(elements); } else { - return elem.isNotEmpty; + return elements.isNotEmpty; } } @@ -2040,8 +2371,8 @@ void _click(Object root, Object? o, {String? selectors, bool expected = true}) { reason = "$root"; } - if (o is Element) { - o.click(); + if (o.isElement) { + (o as Element).click(); } else if (o is UIComponent) { o.click(); } else if (expected) { @@ -2059,16 +2390,27 @@ void _setValue(Object root, Object? elem, String? value, reason = "$root"; } - if (elem is InputElement) { - elem.value = value; - } else if (elem is TextAreaElement) { - elem.value = value; - } else if (elem is Element) { - elem.text = value; + var elemJSAny = elem.asJSAny; + + if (elemJSAny != null) { + if (elemJSAny.isA()) { + (elemJSAny as HTMLInputElement).value = value ?? ''; + return; + } else if (elemJSAny.isA()) { + (elemJSAny as HTMLTextAreaElement).value = value ?? ''; + return; + } else if (elemJSAny.isA()) { + (elemJSAny as Element).textContent = value; + return; + } } else if (elem is UIField) { elem.setFieldValue(value); - } else if (expected) { - throw TestFailure("Can't set value on `null` element. Reason: $reason"); + return; + } + + if (expected) { + throw TestFailure( + "Can't set value on `null` element ($elem). Reason: $reason"); } } @@ -2082,8 +2424,8 @@ void _selectIndex(Object root, Object? o, int index, reason = "$root"; } - if (o is SelectElement) { - o.selectIndex(index); + if (o.asJSAny.isA()) { + (o as HTMLSelectElement).selectIndex(index); } else if (expected) { throw TestFailure( "Can't call selectIndex(i) on `null` element. Reason: $reason"); @@ -2100,18 +2442,24 @@ void _checkbox(Object root, Object? o, bool checked, reason = "$root"; } - if (o is CheckboxInputElement) { - o.checked = checked; - } else if (expected) { + if (o.asJSAny.isA()) { + var input = o as HTMLInputElement; + if (input.type == 'checkbox') { + input.checked = checked; + return; + } + } + + if (expected) { throw TestFailure("Can't set `checked` on `null` element. Reason: $reason"); } } Element? _querySelect(Object? elem, String selectors) { - if (elem is Element) { - return elem.querySelector(selectors); + if (elem.isElement) { + return (elem as Element).querySelector(selectors); } else if (elem is UIComponent) { - return elem.querySelector(selectors); + return elem.querySelectorNonTyped(selectors); } else { return null; } diff --git a/lib/src/bones_ui_utils.dart b/lib/src/bones_ui_utils.dart index 4f6d69b..eae7d7d 100644 --- a/lib/src/bones_ui_utils.dart +++ b/lib/src/bones_ui_utils.dart @@ -1,6 +1,5 @@ import 'package:dom_builder/dom_builder.dart'; - -import 'bones_ui_web.dart'; +import 'package:web_utils/web_utils.dart'; /// Returns a [StackTrace] ensuring that no error will be thrown. StackTrace stackTraceSafe() { @@ -42,8 +41,8 @@ String? resolveToText(Object? o) { } else if (o is Iterable) { var l = o.nonNulls.map(resolveToText).nonNulls.toList(); return l.isEmpty ? null : l.join(); - } else if (o is UIElement) { - return o.text; + } else if (o.isElement) { + return (o as Element).textContent; } else if (o is DOMElement) { return o.text; } else { diff --git a/lib/src/bones_ui_web.dart b/lib/src/bones_ui_web.dart index 99954ab..b9e3a6d 100644 --- a/lib/src/bones_ui_web.dart +++ b/lib/src/bones_ui_web.dart @@ -1,13 +1,14 @@ -import 'dart:html' as web; import 'package:collection/collection.dart'; import 'package:dom_tools/dom_tools.dart'; import 'package:swiss_knife/swiss_knife.dart'; +import 'package:web/web.dart' as web; +import 'package:web_utils/web_utils.dart'; typedef UIElement = web.Element; typedef UINode = web.Node; extension UIElementExtension on UIElement { - List get uiChildren => children; + List get uiChildren => children.toList(); bool get hasUIChildren => children.isNotEmpty; @@ -22,10 +23,10 @@ extension UIElementExtension on UIElement { } } - if (element is web.InputElement || - element is web.TextAreaElement || - element is web.ButtonElement || - element is web.SelectElement) { + if (element.isA() || + element.isA() || + element.isA() || + element.isA()) { fieldName = getElementAttributeStr(element, 'name'); if (fieldName != null) { @@ -42,19 +43,21 @@ extension UIElementExtension on UIElement { void setValue(String? value) { final element = this; - if (element is web.InputElement) { - var type = element.type; + if (element.isA()) { + final inputElement = element as web.HTMLInputElement; + var type = inputElement.type; if (type == 'checkbox') { var checked = parseBool(value) ?? false; - element.checked = checked; + inputElement.checked = checked; } else { - element.value = value; + inputElement.value = value ?? ''; } - } else if (element is web.SelectElement) { + } else if (element.isA()) { + final selectElement = element as web.HTMLSelectElement; if (value == null) { - element.selectedIndex = -1; + selectElement.selectedIndex = -1; } else { - var options = element.options; + var options = selectElement.options.toList(); var opt = options.firstWhereOrNull((op) => op.value == value); @@ -63,23 +66,23 @@ extension UIElementExtension on UIElement { opt ??= options.firstWhereOrNull((op) { var label = op.label; - return label != null && - equalsIgnoreAsciiCase(label.trim(), value.trim()); + return equalsIgnoreAsciiCase(label.trim(), value.trim()); }); - element.selectedIndex = opt?.index ?? -1; + selectElement.selectedIndex = opt?.index ?? -1; } } else { element.text = value; } } - bool get isTextInput => - this is web.InputElement || this is web.TextAreaElement; + bool get isTextInput { + return isA() || isA(); + } } void navigationHistoryPush(String routeTitle, String locationUrl) { - web.window.history.pushState({}, routeTitle, locationUrl); + web.window.history.pushState(JSObject(), routeTitle, locationUrl); } String navigationURL() => web.window.location.href; @@ -87,20 +90,21 @@ String navigationURL() => web.window.location.href; void navigationOnChangeRoute( void Function(String? oldURL, String? newURL) listener) { web.window.onHashChange.listen((e) { - if (e is web.HashChangeEvent) { - listener(e.oldUrl, e.newUrl); + if (e.isA()) { + var hashEvent = e as web.HashChangeEvent; + listener(hashEvent.oldURL, hashEvent.newURL); } else { listener(null, null); } }); } -bool navigationIsOnline() => web.window.navigator.onLine ?? false; +bool navigationIsOnline() => web.window.navigator.onLine; -bool navigationIsSecureContext() => web.window.isSecureContext ?? false; +bool navigationIsSecureContext() => web.window.isSecureContext; UIElement? documentQuerySelector(String selectors) => web.document.querySelector(selectors); List documentQuerySelectorAll(String selectors) => - web.document.querySelectorAll(selectors); + web.document.querySelectorAll(selectors).toElements(); diff --git a/lib/src/component/bui.dart b/lib/src/component/bui.dart index d9307d1..eee33c4 100644 --- a/lib/src/component/bui.dart +++ b/lib/src/component/bui.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'dart:html'; import 'dart:typed_data'; import 'package:archive/archive.dart'; @@ -8,6 +7,7 @@ import 'package:dom_tools/dom_tools.dart'; import 'package:dynamic_call/dynamic_call.dart'; import 'package:intl_messages/intl_messages.dart'; import 'package:swiss_knife/swiss_knife.dart'; +import 'package:web_utils/web_utils.dart' hide MimeType; import 'package:yaml/yaml.dart'; import '../bones_ui_base.dart'; @@ -24,7 +24,7 @@ class BUIElementGenerator extends ElementGeneratorBase { final String tag = 'bui'; @override - DivElement generate( + HTMLDivElement generate( DOMGenerator domGenerator, DOMTreeMap treeMap, String? tag, @@ -35,34 +35,36 @@ class BUIElementGenerator extends ElementGeneratorBase { UINode? contentHolder, List? contentNodes, DOMContext? context) { - var buiElement = DivElement(); + var buiElement = HTMLDivElement(); setElementAttributes(buiElement, attributes); - buiElement.nodes.addAll(contentHolder!.nodes); + buiElement.appendAll(contentHolder!.childNodes.toList()); return buiElement; } @override bool isGeneratedElement(UINode element) { - return element is DivElement && element.classes.contains(tag); + return element.isA() && + (element as HTMLDivElement).classList.contains(tag); } @override DOMElement? revert(DOMGenerator domGenerator, DOMTreeMap? treeMap, DOMElement? domParent, UINode? parent, UINode? node) { - if (node is DivElement) { + if (node.isA()) { + var div = node as HTMLDivElement; var bui = - $tag(tag, classes: node.classes.join(' '), style: node.style.cssText); + $tag(tag, classes: div.classList.value, style: div.style.cssText); if (treeMap != null) { - var mappedDOMNode = treeMap.getMappedDOMNode(node); + var mappedDOMNode = treeMap.getMappedDOMNode(div); if (mappedDOMNode != null) { bui.add(mappedDOMNode.content); } } else { - bui.add(node.text); + bui.add(div.text); } return bui; @@ -291,7 +293,7 @@ class BUIRender extends UINavigableComponent { Element? renderContainer; - DivElement? get renderViewportElement => content as DivElement?; + HTMLDivElement? get renderViewportElement => content as HTMLDivElement?; @override void preRenderClear() {} @@ -321,13 +323,15 @@ class BUIRender extends UINavigableComponent { dynamic _render() { updateSourcesFromViewProvider(); + var renderContainer = this.renderContainer; + if (renderContainer == null) { - renderContainer = createDivInlineBlock(); - renderContainer!.style.cssText = ''; - renderContainer!.style.width = '100%'; - renderContainer!.style.height = '100%'; + this.renderContainer = renderContainer = createDivInlineBlock(); + renderContainer.style?.cssText = ''; + renderContainer.style?.width = '100%'; + renderContainer.style?.height = '100%'; } else { - var nodes = List.from(renderContainer!.nodes); + var nodes = renderContainer.childNodes.toList(); for (var node in nodes) { if (node != _navbarElement) { @@ -336,8 +340,9 @@ class BUIRender extends UINavigableComponent { } } - if (!content!.contains(renderContainer)) { - content!.append(renderContainer!); + final content = this.content!; + if (!content.contains(renderContainer)) { + content.append(renderContainer); } _renderNavbar(); @@ -365,7 +370,7 @@ class BUIRender extends UINavigableComponent { _renderedTreeMap = treeMap; _renderedRoot = treeMap.rootElement as Element?; - _renderedRoot!.classes.add('bui-root'); + _renderedRoot!.classList.add('bui-root'); return renderContainer; } @@ -385,15 +390,15 @@ class BUIRender extends UINavigableComponent { context.namedElementProvider = _namedElementProvider; context.onPreElementCreated = (treeMap, domElement, element, context) { - if (element is Element && treeMap.rootDOMNode == domElement) { - element.classes.add('ui-render-navbar'); + if (element.isElement && treeMap.rootDOMNode == domElement) { + element.asElement.classList.add('ui-render-navbar'); } }; context.preFinalizeGeneratedTree = (treeMap) { var navbarRoot = treeMap.rootElement; - if (navbarRoot is Element) { - navbarRoot.classes.add('ui-render-navbar'); + if (navbarRoot.isElement) { + navbarRoot!.asElement.classList.add('ui-render-navbar'); } }; @@ -429,10 +434,10 @@ class BUIRender extends UINavigableComponent { UINode? _namedElementProvider( String name, - DOMGenerator? domGenerator, - DOMTreeMap treeMap, + DOMGenerator? domGenerator, + DOMTreeMap treeMap, DOMElement? domParent, - dynamic parent, + Object? parent, String? tag, Map attributes) { if (viewProvider == null) return null; @@ -446,8 +451,7 @@ class BUIRender extends UINavigableComponent { if (view == null) return null; - var domContext = DOMContext( - parent: domGenerator!.domContext as DOMContext?); + var domContext = DOMContext(parent: domGenerator!.domContext); var buiCode = view.buiCode; @@ -462,10 +466,10 @@ class BUIRender extends UINavigableComponent { } return domGenerator.generateFromHTML(buiCode!, - parent: parent, context: domContext, finalizeTree: false); + parent: parent as Node?, context: domContext, finalizeTree: false); } - Future renderThumbnail( + Future renderThumbnail( {String? renderedHTML, bool includeDocumentStyles = true, String? styles, @@ -478,7 +482,7 @@ class BUIRender extends UINavigableComponent { if (includeDocumentStyles) { var rules = getAllCssStyleSheet() .map((e) => e.rules) - .whereType>() + .whereType>() .expand((e) => e) .toList(); @@ -929,7 +933,7 @@ class BUIRenderSource { BUIRenderSource(this.domGenerator, this.renderContainer, this.notifySourceChange, this.refresh); - dynamic _source; + Object? _source; bool get isNull => _source == null; @@ -965,11 +969,12 @@ class BUIRenderSource { } String? get intlPath { - if (_source is BUIView) { - BUIView view = _source; + final source = _source; + if (source is BUIView) { + BUIView view = source; return view.intl; - } else if (_source is String) { - return parseBUIAttribute(_source, 'intl'); + } else if (source is String) { + return parseBUIAttribute(source, 'intl'); } else { return null; } @@ -1026,8 +1031,9 @@ class BUIRenderSource { IntlMessagesLoader? messagesLoader; - if (_source is BUIView) { - BUIView view = _source; + final source = _source; + if (source is BUIView) { + BUIView view = source; messagesLoader = view.intlMessagesLoader; } else { messagesLoader = IntlMessagesLoader('/bui/', intlPath); @@ -1060,46 +1066,49 @@ class BUIRenderSource { } else if (_source is DOMElement) { var dom = _source as DOMElement; return dom.buildHTML(withIndent: true); - } else if (_source is Element) { + } else if (_source.isElement) { var elem = _source as Element; - return elem.outerHtml!; + return elem.outerHTML.asString; } else { throw StateError("Can't convert source to HTML: $_source"); } } Element? get sourceAsElement { - if (_source == null) { + final source = _source; + + if (source == null) { return null; - } else if (_source is String || _source is BUIView) { - return createHTML(_resolveBUICode(_source)); - } else if (_source is num || _source is bool) { - return createHTML('$_source'); - } else if (_source is DOMElement) { - var dom = _source as DOMElement; + } else if (source is String || source is BUIView) { + return createHTML(html: _resolveBUICode(source)); + } else if (source is num || source is bool) { + return createHTML(html: '$source'); + } else if (source is DOMElement) { + var dom = source; return domGenerator.generate(dom) as Element?; - } else if (_source is Element) { - var elem = _source as Element?; + } else if (source.isElement) { + var elem = source as Element?; return elem; } else { - throw StateError("Can't convert source to Element: $_source"); + throw StateError("Can't convert source to Element: $source"); } } DOMElement? get sourceAsDOMElement { - if (_source == null) { + final source = _source; + if (source == null) { return null; - } else if (_source is String || _source is BUIView) { - return $htmlRoot(_resolveBUICode(_source)); - } else if (_source is num || _source is bool) { - return $htmlRoot('$_source'); - } else if (_source is DOMElement) { - return _source as DOMElement?; - } else if (_source is Element) { - var elem = _source as Element; - return $htmlRoot(elem.outerHtml); + } else if (source is String || source is BUIView) { + return $htmlRoot(_resolveBUICode(source)); + } else if (source is num || source is bool) { + return $htmlRoot('$source'); + } else if (source is DOMElement) { + return source as DOMElement?; + } else if (source.isElement) { + var elem = source as Element; + return $htmlRoot(elem.outerHTML); } else { - throw StateError("Can't convert source to Element: $_source"); + throw StateError("Can't convert source to Element: $source"); } } diff --git a/lib/src/component/button.dart b/lib/src/component/button.dart index 453ba4b..5bda28b 100644 --- a/lib/src/component/button.dart +++ b/lib/src/component/button.dart @@ -1,5 +1,6 @@ import 'dart:async'; -import 'dart:html'; +import 'dart:math'; +import 'package:web_utils/web_utils.dart' hide CSS; import 'package:dom_builder/dom_builder.dart'; import 'package:dom_tools/dom_tools.dart'; @@ -75,7 +76,7 @@ abstract class UIButtonBase extends UIComponent { void fireClickEvent(MouseEvent event, [List? params]) { if (disabled) return; - var p = event.page; + var p = event.pagePoint; var time = event.timeStamp; if (_prevClickEvent_time == time && _prevClickEvent_point == p) return; @@ -112,12 +113,12 @@ abstract class UIButtonBase extends UIComponent { // ignore: non_constant_identifier_names bool _content_onClick_listening = false; - void _onClickListen(List renderedElements) { + void _onClickListen(List renderedElements) { var clickSet = false; for (var elem in renderedElements) { - if (elem is Element) { - elem.onClick.listen((e) => fireClickEvent(e)); + if (elem.isElement) { + (elem as Element).onClick.listen((e) => fireClickEvent(e)); clickSet = true; } } @@ -166,7 +167,7 @@ class UIButton extends UIButtonBase { 'ui-button', '', (parent, attributes, contentHolder, contentNodes) => - UIButton(parent, contentHolder?.text), + UIButton(parent, contentHolder?.textContent), [ UIComponentAttributeHandler('text', parser: parseString, @@ -235,8 +236,8 @@ class UIButton extends UIButtonBase { } @override - Element createContentElement(bool inline) { - return ButtonElement(); + HTMLElement createContentElement(bool inline) { + return HTMLButtonElement(); } @override @@ -259,7 +260,7 @@ class UIButton extends UIButtonBase { } void setNormalButton() { - content!.style.width = null; + content!.style.width = ''; } } @@ -417,9 +418,9 @@ class UIButtonLoader extends UIButtonBase { dynamic _button; - DivElement? _loadingDiv; + HTMLDivElement? _loadingDiv; - DivElement? _loadedMessage; + HTMLDivElement? _loadedMessage; @override dynamic renderButton() { @@ -429,7 +430,7 @@ class UIButtonLoader extends UIButtonBase { content!.style.opacity = ''; } - _loadingDiv ??= UILoading.asDivElement(UILoadingType.ring, + _loadingDiv ??= UILoading.asHTMLDivElement(UILoadingType.ring, zoom: 0.50, textZoom: 1.5, cssContext: content, @@ -439,7 +440,7 @@ class UIButtonLoader extends UIButtonBase { _button ??= renderButtonElement(); - _loadedMessage ??= DivElement()..style.display = 'none'; + _loadedMessage ??= HTMLDivElement()..style.display = 'none'; _setLoadedMessageStyle(); @@ -462,7 +463,7 @@ class UIButtonLoader extends UIButtonBase { } if (isNotEmptyString(style, trim: true)) { - loadedMessage.style.cssText = style; + loadedMessage.style.cssText = style ?? ''; } var classesError = (_loadedTextErrorClass?.text ?? '') @@ -474,11 +475,11 @@ class UIButtonLoader extends UIButtonBase { ..removeWhere((e) => e.isEmpty); if (error) { - loadedMessage.classes.removeAll(classesOk); - loadedMessage.classes.addAll(classesError); + loadedMessage.classList.removeAll(classesOk); + loadedMessage.classList.addAll(classesError); } else { - loadedMessage.classes.removeAll(classesError); - loadedMessage.classes.addAll(classesOk); + loadedMessage.classList.removeAll(classesError); + loadedMessage.classList.addAll(classesOk); } } @@ -499,7 +500,7 @@ class UIButtonLoader extends UIButtonBase { var button = _button; var buttonElement = - (button is DOMElement ? button.runtimeNode : button) as Element; + (button is DOMElement ? button.runtimeNode : button) as HTMLElement; buttonElement.style.display = 'none'; _loadedMessage!.style.display = 'none'; @@ -519,7 +520,7 @@ class UIButtonLoader extends UIButtonBase { var button = _button; var buttonElement = - (button is DOMElement ? button.runtimeNode : button) as Element?; + (button is DOMElement ? button.runtimeNode : button) as HTMLElement?; if (loadOK == null) { _setLoadedMessageStyle(); @@ -559,7 +560,7 @@ class UIButtonLoader extends UIButtonBase { void setProgress(double? ratio) { var progressDiv = _loadingDiv!.querySelector('.ui-loading-progress'); if (progressDiv == null) { - progressDiv = DivElement()..classes.add('ui-loading-progress'); + progressDiv = HTMLDivElement()..classList.add('ui-loading-progress'); var loadingDiv = _loadingDiv!.querySelector('.ui-loading')!; loadingDiv.append(progressDiv); return; diff --git a/lib/src/component/calendar.dart b/lib/src/component/calendar.dart index 73381f1..0df2b13 100644 --- a/lib/src/component/calendar.dart +++ b/lib/src/component/calendar.dart @@ -1,4 +1,4 @@ -import 'dart:html'; +import 'package:web_utils/web_utils.dart'; import 'dart:math' as math; import 'package:collection/collection.dart'; @@ -359,9 +359,9 @@ class UICalendar extends UIComponent implements UIField> { ] ]) ]) - ..onGenerate.listen((element) { - if (element is Element) { - blockVerticalScrollTraverse(element); + ..onGenerate.listen((Object? element) { + if (element.isHTMLElement) { + blockVerticalScrollTraverse(element as HTMLElement); } }) ]); diff --git a/lib/src/component/capture.dart b/lib/src/component/capture.dart index fcd1fcd..d7c82e3 100644 --- a/lib/src/component/capture.dart +++ b/lib/src/component/capture.dart @@ -1,11 +1,11 @@ import 'dart:async'; import 'dart:convert' as data_convert; -import 'dart:html'; import 'dart:math' as math; import 'dart:typed_data'; import 'package:dom_tools/dom_tools.dart'; import 'package:swiss_knife/swiss_knife.dart'; +import 'package:web_utils/web_utils.dart' hide MimeType; import '../bones_ui_base.dart'; import '../bones_ui_log.dart'; @@ -62,8 +62,8 @@ enum CaptureDataFormat { url, } -typedef CapturePhotoEditor = FutureOr Function( - ImageElement image); +typedef CapturePhotoEditor = FutureOr Function( + HTMLImageElement image); /// Base class for capture components. /// See [UIButtonCapture] and [UIButtonCapturePhoto]. @@ -218,26 +218,26 @@ abstract class UICapture extends UIButtonBase implements UIField { void posRender() { super.posRender(); - var fieldCapture = getInputCapture() as FileUploadInputElement; + var fieldCapture = getInputCapture() as HTMLInputElement; fieldCapture.onChange.listen((e) => _callOnCapture(fieldCapture, e)); } final EventStream onCapture = EventStream(); - void _callOnCapture(FileUploadInputElement input, Event event) async { + void _callOnCapture(HTMLInputElement input, Event event) async { await _readFile(input); onCaptureFile(input, event); onCapture.add(this); } - void onCaptureFile(FileUploadInputElement input, Event event) { + void onCaptureFile(HTMLInputElement input, Event event) { var file = getInputFile(); if (file != null) { UIConsole.log('onCapture> $input > $event > ${event.type} > $file'); UIConsole.log( - 'file> ${file.name} ; ${file.type} ; ${file.lastModified} ; ${file.relativePath}'); + 'file> ${file.name} ; ${file.type} ; ${file.lastModified} ; ${file.webkitRelativePath}'); } } @@ -340,9 +340,9 @@ abstract class UICapture extends UIButtonBase implements UIField { /// This is useful to avoid issues with device orientation and image rotation. bool removeExifFromImage = true; - Future _readFile(FileUploadInputElement input) async { + Future _readFile(HTMLInputElement input) async { if (input.files!.isNotEmpty) { - var file = input.files!.first; + var file = input.files!.item(0); _selectedFile = file; @@ -374,9 +374,9 @@ abstract class UICapture extends UIButtonBase implements UIField { if (captureType.isPhoto) { var fileURL = capturedData.dataAsURL(); - var imageElement = ImageElement()..src = fileURL; + var imageElement = HTMLImageElement()..src = fileURL; - if (imageElement.complete ?? false) { + if (imageElement.complete) { return _filterCapturedPhoto(capturedData, imageElement); } else { return imageElement.onLoad.first @@ -401,7 +401,7 @@ abstract class UICapture extends UIButtonBase implements UIField { // This is only called after load [image]. Future<_CapturedData> _filterCapturedPhoto( - _CapturedData capturedData, ImageElement image) async { + _CapturedData capturedData, HTMLImageElement image) async { if (editCapture) { var photoEditor = this.photoEditor; if (photoEditor != null) { @@ -418,7 +418,7 @@ abstract class UICapture extends UIButtonBase implements UIField { } } - if (!(image.complete ?? false)) { + if (!(image.complete)) { await image.onLoad.first; } @@ -442,7 +442,9 @@ abstract class UICapture extends UIButtonBase implements UIField { assert(imgH2 <= imgH); if (imgW2 != imgW || imgH2 != imgH) { - var canvas = CanvasElement(width: imgW2, height: imgH2); + var canvas = HTMLCanvasElement() + ..width = imgW2 + ..height = imgH2; var ctx = canvas.context2D; ctx.imageSmoothingEnabled = true; @@ -452,7 +454,13 @@ abstract class UICapture extends UIButtonBase implements UIField { var x = (imgW2 - imgW) ~/ 2; var y = (imgH2 - imgH) ~/ 2; - ctx.drawImageScaled(image, x, y, imgW, imgH); + ctx.drawImageScaled( + image, + x.toDouble(), + y.toDouble(), + imgW.toDouble(), + imgH.toDouble(), + ); imgW = imgW2; imgH = imgH2; @@ -477,7 +485,9 @@ abstract class UICapture extends UIButtonBase implements UIField { var canvasW = (imgW * r).toInt(); var canvasH = (imgH * r).toInt(); - var canvas = CanvasElement(width: canvasW, height: canvasH); + var canvas = HTMLCanvasElement() + ..width = canvasW + ..height = canvasH; var ctx = canvas.context2D; @@ -485,7 +495,13 @@ abstract class UICapture extends UIButtonBase implements UIField { ctx.imageSmoothingQuality = 'high'; ctx.clearRect(0, 0, canvasW, canvasH); - ctx.drawImageScaled(imgSrc, 0, 0, canvasW, canvasH); + ctx.drawImageScaled( + imgSrc, + 0, + 0, + canvasW.toDouble(), + canvasH.toDouble(), + ); var photoScaleMimeType = this.photoScaleMimeType; @@ -505,7 +521,7 @@ abstract class UICapture extends UIButtonBase implements UIField { return capturedData2; } - Future _readFileInput(FileUploadInputElement input) async { + Future _readFileInput(HTMLInputElement input) async { switch (captureDataFormat) { case CaptureDataFormat.arrayBuffer: return await readFileInputElementAsArrayBuffer( @@ -529,16 +545,18 @@ abstract class UICapture extends UIButtonBase implements UIField { @override void onClickEvent(event, List? params) { - var input = getInputCapture() as FileUploadInputElement; - input.value = null; + var input = getInputCapture() as HTMLInputElement; + input.value = ''; input.click(); } - Element? getInputCapture() => getFieldElement(fieldName); + Element? getInputCapture() => getFieldElementNonTyped(fieldName); File? getInputFile() { - var input = getInputCapture() as FileUploadInputElement?; - return input != null && input.files!.isNotEmpty ? input.files![0] : null; + var input = getInputCapture() as HTMLInputElement?; + if (input == null) return null; + var files = input.files!; + return files.isNotEmpty ? files.item(0) : null; } bool isFileImage() { @@ -847,11 +865,22 @@ class URLFileReader { }); fileReader.onLoad.listen((e) { - var dataURL = fileReader.result as String; + var dataURL = fileReader.result.dartify()?.toString(); _notifyOnLoad(dataURL); }); - fileReader.readAsDataUrl(_file); + fileReader.onLoadEnd.listen((event) { + final error = fileReader.error; + + if (error != null) { + _notifyOnLoad(null); + } else { + var dataURL = fileReader.result.dartify()?.toString(); + _notifyOnLoad(dataURL); + } + }); + + fileReader.readAsDataURL(_file); } void _notifyOnLoad(String? dataURL) { @@ -878,11 +907,16 @@ class ImageFileReader extends URLFileReader { @override void onLoad(String? dataURL, String type) { - var img = ImageElement(src: dataURL); + var img = HTMLImageElement(); + + if (dataURL != null && dataURL.isNotEmpty) { + img.src = dataURL; + } + onLoadImage.add(img); } - final EventStream onLoadImage = EventStream(); + final EventStream onLoadImage = EventStream(); } class VideoFileReader extends URLFileReader { @@ -890,19 +924,19 @@ class VideoFileReader extends URLFileReader { @override void onLoad(String? dataURL, String type) { - var video = VideoElement(); + var video = HTMLVideoElement(); video.controls = true; - var sourceElement = SourceElement(); + var sourceElement = HTMLSourceElement(); sourceElement.src = dataURL!; sourceElement.type = type; - video.children.add(sourceElement); + video.appendChild(sourceElement); onLoadVideo.add(video); } - final EventStream onLoadVideo = EventStream(); + final EventStream onLoadVideo = EventStream(); } class AudioFileReader extends URLFileReader { @@ -910,19 +944,19 @@ class AudioFileReader extends URLFileReader { @override void onLoad(String? dataURL, String type) { - var audio = AudioElement(); + var audio = HTMLAudioElement(); audio.controls = true; - var sourceElement = SourceElement(); + var sourceElement = HTMLSourceElement(); sourceElement.src = dataURL!; sourceElement.type = type; - audio.children.add(sourceElement); + audio.appendChild(sourceElement); onLoadAudio.add(audio); } - final EventStream onLoadAudio = EventStream(); + final EventStream onLoadAudio = EventStream(); } /// A Button that captures a photo. @@ -1006,7 +1040,7 @@ class UIButtonCapturePhoto extends UICapture { String? selectedImageStyle; @override - void onCaptureFile(FileUploadInputElement input, Event event) { + void onCaptureFile(HTMLInputElement input, Event event) { if (showSelectedImageInButton) { showSelectedImage(); } @@ -1020,16 +1054,15 @@ class UIButtonCapturePhoto extends UICapture { var content = this.content!; - for (var e in _selectedImageElements) { - content.children.remove(e); - } + content.removeNodes(_selectedImageElements); if (onlyShowSelectedImageInButton) { - content.children.removeWhere((e) => !e.hidden); + content.removeNodeWhere((e) => !(e.asElementChecked?.hidden ?? false)); } - var img = ImageElement(src: dataURL) - ..classes.add('ui-capture-img') + var img = HTMLImageElement() + ..src = dataURL + ..classList.add('ui-capture-img') ..style.margin = '2px 4px' ..style.maxHeight = '100%'; @@ -1042,22 +1075,22 @@ class UIButtonCapturePhoto extends UICapture { } if (isNotEmptyObject(selectedImageClasses)) { - img.classes.addAll(selectedImageClasses!); + img.classList.addAll(selectedImageClasses!); } if (isNotEmptyString(selectedImageStyle, trim: true)) { - img.style.cssText = '${img.style.cssText ?? ''}; $selectedImageStyle'; + img.style.cssText = '${img.style.cssText}; $selectedImageStyle'; } _selectedImageElements.clear(); if (!onlyShowSelectedImageInButton) { - _selectedImageElements.add(BRElement()); + _selectedImageElements.add(HTMLBRElement()); } _selectedImageElements.add(img); img.onClick.listen((e) => fireClickEvent(e)); - content.children.addAll(_selectedImageElements); + content.appendNodes(_selectedImageElements); } void setWideButton() { @@ -1065,7 +1098,7 @@ class UIButtonCapturePhoto extends UICapture { } void setNormalButton() { - content!.style.width = null; + content!.style.width = ''; } } @@ -1134,7 +1167,7 @@ class UIButtonCapture extends UICapture { bool showSelectedFileInButton = true; @override - void onCaptureFile(FileUploadInputElement input, Event event) { + void onCaptureFile(HTMLInputElement input, Event event) { if (showSelectedFileInButton) { showSelectedFile(); } @@ -1144,13 +1177,16 @@ class UIButtonCapture extends UICapture { var dataURL = selectedFileDataAsDataURLBase64; if (dataURL == null) return; - content!.children.removeWhere((e) => (e is SpanElement || e is BRElement)); + final content = this.content; + + content!.removeNodeWhere( + (e) => (e.isA() || e.isA())); var fileName = selectedFile?.name; if (fileName != null && fileName.isNotEmpty) { - content!.children.add(BRElement()); - content!.children.add(SpanElement()..text = fileName); + content.appendChild(HTMLBRElement()); + content.appendChild(HTMLSpanElement()..text = fileName); } } @@ -1159,6 +1195,6 @@ class UIButtonCapture extends UICapture { } void setNormalButton() { - content!.style.width = null; + content!.style.width = ''; } } diff --git a/lib/src/component/clip_image.dart b/lib/src/component/clip_image.dart index 11d9bc3..0f3dfeb 100644 --- a/lib/src/component/clip_image.dart +++ b/lib/src/component/clip_image.dart @@ -1,4 +1,6 @@ -import 'dart:html'; +import 'dart:math'; + +import 'package:web_utils/web_utils.dart'; import 'package:swiss_knife/swiss_knife.dart'; @@ -62,7 +64,7 @@ class ImageClip { /// Component to clip an image. class UIClipImage extends UIComponent { - final ImageElement _img; + final HTMLImageElement _img; int imgWidth; @@ -108,12 +110,12 @@ class UIClipImage extends UIComponent { } Point? parsePoint(Event e) { - if (e is TouchEvent) { - var p = e.changedTouches!.first.client; - return Point(p.x - _img.offset.left, p.y - _img.offset.top); - } else if (e is MouseEvent) { - var p = e.page; - return Point(p.x - _img.offset.left, p.y - _img.offset.top); + if (e.isA()) { + var p = (e as TouchEvent).changedTouches.item(0)!.clientPoint; + return Point(p.x - _img.offsetLeft, p.y - _img.offsetTop); + } else if (e.isA()) { + var p = (e as MouseEvent).pagePoint; + return Point(p.x - _img.offsetLeft, p.y - _img.offsetTop); } return null; } @@ -137,7 +139,7 @@ class UIClipImage extends UIComponent { var rect = _createRect(_start, p)!; _divRectDrag = _createRectDiv(rect); - content!.children.add(_divRectDrag!); + content!.appendChild(_divRectDrag!); } void _clearRects() { @@ -173,7 +175,7 @@ class UIClipImage extends UIComponent { _divRect = _createRectDiv(clipRect); - content!.children.add(_divRect!); + content!.appendChild(_divRect!); onChangeClip.add(_clipRect); onChange.add(this); @@ -199,8 +201,9 @@ class UIClipImage extends UIComponent { ImageClip? get imageClip => _imageClip; - DivElement _createRectDiv(Rectangle rect) { - var div = DivElement(); + HTMLDivElement _createRectDiv(Rectangle rect) { + var div = HTMLDivElement(); + div.style ..width = '${rect.width}px' ..height = '${rect.height}px' @@ -240,8 +243,8 @@ class UIClipImage extends UIComponent { var w = (x2 - x1).toInt(); var h = (y2 - y1).toInt(); - var left = (_img.offset.left + x).toInt(); - var top = (_img.offset.top + y).toInt(); + var left = (_img.offsetLeft + x).toInt(); + var top = (_img.offsetTop + y).toInt(); return Rectangle(left, top, w, h); } diff --git a/lib/src/component/color_picker.dart b/lib/src/component/color_picker.dart index 1adbef6..a3bb4fa 100644 --- a/lib/src/component/color_picker.dart +++ b/lib/src/component/color_picker.dart @@ -1,8 +1,7 @@ -import 'dart:html'; - import 'package:dom_builder/dom_builder.dart'; import 'package:dom_tools/dom_tools.dart'; import 'package:swiss_knife/swiss_knife.dart'; +import 'package:web_utils/web_utils.dart'; import '../bones_ui_base.dart'; import '../bones_ui_component.dart'; @@ -39,7 +38,7 @@ class UIColorPickerInput extends UIComponent implements UIField { int get pickerHeight => _pickerHeight; - InputElement? _input; + HTMLInputElement? _input; @override dynamic render() { @@ -120,7 +119,7 @@ class UIColorPickerInput extends UIComponent implements UIField { } void _updateColorFromPicker( - Object? color, InputElement input, DivElement colorButton) { + Object? color, HTMLInputElement input, HTMLDivElement colorButton) { if (color is! Color) return; var pickerColor = CSSColor.from([color.red, color.green, color.blue]); @@ -148,11 +147,11 @@ class UIColorPickerInput extends UIComponent implements UIField { EventStream onFocus = EventStream(); - void _dispatchInputChange(InputElement input) { + void _dispatchInputChange(HTMLInputElement input) { input.dispatchEvent(Event('change')); } - void _updateColorFromInput(InputElement input, UIColorPicker picker) { + void _updateColorFromInput(HTMLInputElement input, UIColorPicker picker) { var inputColor = CSSColor.from(input.value); var color = picker.color!; @@ -163,7 +162,7 @@ class UIColorPickerInput extends UIComponent implements UIField { } } - void _switchColorInputType(InputElement input, UIColorPicker picker) { + void _switchColorInputType(HTMLInputElement input, UIColorPicker picker) { var inputColor = CSSColor.from(input.value); if (inputColor != null) { @@ -180,13 +179,19 @@ class UIColorPickerInput extends UIComponent implements UIField { } } - InputElement _renderGenericInput(String inputType, inputValue) { - var inputHtml = ''' - - '''; + HTMLInputElement _renderGenericInput(String inputType, inputValue) { + // var inputHtml = ''' + // + // '''; + + var input = HTMLInputElement() + ..style.cssText = 'width: calc(100% - 20px)' + ..type = inputType + ..value = inputValue ?? ''; - var input = createHTML(inputHtml); - return input as InputElement; + //var input = createHTML(html: inputHtml); + //return input as HTMLInputElement; + return input; } } @@ -272,31 +277,31 @@ class UIColorPicker extends UIComponent { HSLColor? get hslColor => _hslColor; // ignore: non_constant_identifier_names - DivElement? _panel_all; + HTMLDivElement? _panel_all; // ignore: non_constant_identifier_names - DivElement? _panel_ViewColor_Saturation; + HTMLDivElement? _panel_ViewColor_Saturation; // ignore: non_constant_identifier_names - DivElement? _panel_Saturation_Luma_Square; + HTMLDivElement? _panel_Saturation_Luma_Square; - DivElement? _viewColor; + HTMLDivElement? _viewColor; - DivElement? _saturation; + HTMLDivElement? _saturation; - DivElement? _luma; + HTMLDivElement? _luma; - DivElement? _square; + HTMLDivElement? _square; - DivElement? _hue; + HTMLDivElement? _hue; - DivElement? _point; + HTMLDivElement? _point; - DivElement? _saturationBar; + HTMLDivElement? _saturationBar; - DivElement? _lumaBar; + HTMLDivElement? _lumaBar; - DivElement? _hueBar; + HTMLDivElement? _hueBar; bool _squarePressed = false; @@ -344,8 +349,8 @@ class UIColorPicker extends UIComponent { _disableTransitions(_saturation!); - _panel_ViewColor_Saturation!.children.add(_viewColor!); - _panel_ViewColor_Saturation!.children.add(_saturation!); + _panel_ViewColor_Saturation!.appendChild(_viewColor!); + _panel_ViewColor_Saturation!.appendChild(_saturation!); _luma = createDivInline() ..style.width = '${barSize}px' @@ -367,7 +372,7 @@ class UIColorPicker extends UIComponent { _disableTransitions(_point!); - _square!.children.add(_point!); + _square!.appendChild(_point!); _lumaBar = createDiv() ..style.width = '${barSize}px' @@ -376,7 +381,7 @@ class UIColorPicker extends UIComponent { _disableTransitions(_lumaBar!); - _luma!.children.add(_lumaBar!); + _luma!.appendChild(_lumaBar!); _saturationBar = createDiv() ..style.width = '1px' @@ -386,11 +391,11 @@ class UIColorPicker extends UIComponent { _disableTransitions(_saturationBar!); - _saturation!.children.add(_saturationBar!); + _saturation!.appendChild(_saturationBar!); - _panel_Saturation_Luma_Square!.children.add(_panel_ViewColor_Saturation!); - _panel_Saturation_Luma_Square!.children.add(_luma!); - _panel_Saturation_Luma_Square!.children.add(_square!); + _panel_Saturation_Luma_Square!.appendChild(_panel_ViewColor_Saturation!); + _panel_Saturation_Luma_Square!.appendChild(_luma!); + _panel_Saturation_Luma_Square!.appendChild(_square!); _hue = createDiv() ..style.backgroundColor = 'red' @@ -408,10 +413,10 @@ class UIColorPicker extends UIComponent { _disableTransitions(_hueBar!); - _hue!.children.add(_hueBar!); + _hue!.appendChild(_hueBar!); - _panel_all!.children.add(_panel_Saturation_Luma_Square!); - _panel_all!.children.add(_hue!); + _panel_all!.appendChild(_panel_Saturation_Luma_Square!); + _panel_all!.appendChild(_hue!); // @@ -513,8 +518,8 @@ class UIColorPicker extends UIComponent { void _squareClick(MouseEvent event) { var target = event.target; if (target == _square) { - var x = event.offset.x.toInt(); - var y = event.offset.y.toInt(); + var x = event.offsetX.toInt(); + var y = event.offsetY.toInt(); _adjustPoint(x, y); } } @@ -534,7 +539,7 @@ class UIColorPicker extends UIComponent { void _lumaClick(MouseEvent event) { var target = event.target; if (target == _luma || target == _square) { - var y = event.offset.y.toInt(); + var y = event.offsetY.toInt(); _adjustLumaBar(y); } } @@ -550,7 +555,7 @@ class UIColorPicker extends UIComponent { void _saturationClick(MouseEvent event) { var target = event.target; if (target == _saturation || target == _square) { - var x = event.offset.x.toInt(); + var x = event.offsetX.toInt(); _adjustSaturationBar(x); } } @@ -564,7 +569,7 @@ class UIColorPicker extends UIComponent { void _hueClick(MouseEvent event) { var target = event.target; if (target == _hue || target == _square || target == _luma) { - var x = event.offset.x.toInt(); + var x = event.offsetX.toInt(); if (target == _square) { x += _barSize; } @@ -691,6 +696,6 @@ class UIColorPicker extends UIComponent { void _disableTransitions(Element element) { element.style - ..transition = 'none' + ?..transition = 'none' ..animation = 'none'; } diff --git a/lib/src/component/controlled_component.dart b/lib/src/component/controlled_component.dart index 717ab71..1eafa4f 100644 --- a/lib/src/component/controlled_component.dart +++ b/lib/src/component/controlled_component.dart @@ -1,5 +1,5 @@ import 'dart:async'; -import 'dart:html'; +import 'package:web_utils/web_utils.dart'; import 'package:dom_tools/dom_tools.dart'; import 'package:swiss_knife/swiss_knife.dart'; @@ -78,15 +78,15 @@ abstract class UIControlledComponent extends UIComponent { for (var entry in _controllers!.entries) { var key = entry.key; - var value = entry.value; + var value = entry.value as Object?; if (value is UIField) { value = value.getFieldValue(); } else if (value is UIComponent) { dynamic fields = value.getFields(); value = asMapOfString(fields); - } else if (value is Element) { - value = parseChildElementValue(value); + } else if (value.isElement) { + value = parseChildElementValue(value as Element); } mapProperties.put(key, value); @@ -162,10 +162,12 @@ abstract class UIControlledComponent extends UIComponent { return false; } - Future listenControllers(Map controllers) async { + Future listenControllers(Map controllers) async { for (var control in controllers.values) { - if (control is Element) { - control.onChange.listen((e) => callOnChangeControllers(control)); + if (control.isElement) { + (control as Element) + .onChange + .listen((e) => callOnChangeControllers(control)); } else if (control is UIComponent) { control.onChange.listen((e) => callOnChangeControllers(control)); } else if (control is UIAsyncContent) { @@ -243,10 +245,10 @@ abstract class UIControlledComponent extends UIComponent { if (entry.key.endsWith('_label')) continue; if (list.isNotEmpty) { - var separatorH = createSpan(' '); - var separatorV = DivElement() - ..classes.add('w-100') - ..classes.add('d-md-none') + var separatorH = createSpan(html: ' '); + var separatorV = HTMLDivElement() + ..classList.add('w-100') + ..classList.add('d-md-none') //..classes.add('d-lg-block') ; list.add(separatorH); @@ -257,7 +259,7 @@ abstract class UIControlledComponent extends UIComponent { var controller = entry.value; if (label is String) { - var span = createLabel('$label:   '); + var span = createLabel(html: '$label:   '); list.add(span); } else { list.add(label); diff --git a/lib/src/component/data_source.dart b/lib/src/component/data_source.dart index 313234e..cff5a77 100644 --- a/lib/src/component/data_source.dart +++ b/lib/src/component/data_source.dart @@ -1,4 +1,4 @@ -import 'dart:html'; +import 'package:web_utils/web_utils.dart'; import 'package:dom_tools/dom_tools.dart'; import 'package:dynamic_call/dynamic_call.dart'; @@ -14,7 +14,7 @@ class UIDataSource extends UIComponent { 'ui-data-source', '', (parent, attributes, contentHolder, contentNodes) => - UIDataSource(parent, contentHolder?.text), + UIDataSource(parent, contentHolder?.textContent), [ UIComponentAttributeHandler('data-source', getter: (c) => c._dataSource!.toJson(true), @@ -33,9 +33,9 @@ class UIDataSource extends UIComponent { super(componentClass: 'ui-data-source'); @override - Element createContentElement(bool inline) { - var div = createDiv(inline); - div.hidden = true; + HTMLElement createContentElement(bool inline) { + var div = createDiv(inline: inline); + div.hidden = true.toJS; div.style.display = 'node'; div.style.visibility = 'hidden'; return div; @@ -52,12 +52,12 @@ class UIDataSource extends UIComponent { @override void configure() { - content!.hidden = true; + content!.hidden = true.toJS; } @override dynamic render() { var json = dataSource!.toJson(true); - return PreElement().text = '\n$json\n'; + return HTMLPreElement.pre()..text = '\n$json\n'; } } diff --git a/lib/src/component/dialog.dart b/lib/src/component/dialog.dart index 7e7ea76..ffd117e 100644 --- a/lib/src/component/dialog.dart +++ b/lib/src/component/dialog.dart @@ -1,5 +1,5 @@ import 'dart:async'; -import 'dart:html'; +import 'package:web_utils/web_utils.dart'; import 'package:dom_builder/dom_builder.dart'; import 'package:dom_tools/dom_tools.dart'; @@ -112,7 +112,7 @@ abstract class UIDialogBase extends UIRootComponent { : '.$dialogButtonClass, button'; var sel = content!.querySelectorAll(selectors); - var buttons = sel.where(isDialogButton).toList(); + var buttons = sel.whereElement().where(isDialogButton).toList(); return buttons; } @@ -164,13 +164,13 @@ abstract class UIDialogBase extends UIRootComponent { } bool _isElementOfClass(Element element, String className) { - if (element.classes.contains(className)) { + if (element.classList.contains(className)) { return true; } var elementsWithClass = content!.querySelectorAll('.$className'); - for (var elem in elementsWithClass) { + for (var elem in elementsWithClass.toIterable()) { if (elem == element || elem.contains(element)) { return true; } @@ -210,10 +210,14 @@ abstract class UIDialogBase extends UIRootComponent { @override bool get isShowing { - return rootParent!.contains(content) && - !content!.hidden && - content!.style.display != 'none' && - content!.style.visibility != 'hidden'; + final rootParent = UIDialogBase.rootParent; + final content = this.content; + return rootParent != null && + content != null && + rootParent.contains(content) && + !content.isHidden && + content.style.display != 'none' && + content.style.visibility != 'hidden'; } String? _styleDisplayPrevValue; @@ -229,20 +233,21 @@ abstract class UIDialogBase extends UIRootComponent { rootParent!.append(parent); } + final content = this.content; if (!parent.contains(content)) { parent.append(content!); } - if (content!.hidden) { - content!.hidden = false; + if (content!.isHidden) { + content.hidden = null; } - if (content!.style.display == 'none') { - content!.style.display = _styleDisplayPrevValue ?? ''; + if (content.style.display == 'none') { + content.style.display = _styleDisplayPrevValue ?? ''; } - if (content!.style.visibility == 'hidden') { - content!.style.visibility = _styleVisibilityPrevValue ?? ''; + if (content.style.visibility == 'hidden') { + content.style.visibility = _styleVisibilityPrevValue ?? ''; } ensureRendered(); @@ -254,7 +259,7 @@ abstract class UIDialogBase extends UIRootComponent { void hide() { var showing = isShowing; - content!.hidden = true; + content!.hidden = true.toJS; if (content!.style.display != 'none') { _styleDisplayPrevValue = content!.style.display; @@ -336,6 +341,7 @@ class UIDialog extends UIDialogBase { var dialogs = window.document.querySelectorAll('.ui-dialog'); return dialogs + .whereElement() .map(UIComponent.getContentUIComponent) .whereType() .toList(); @@ -345,7 +351,7 @@ class UIDialog extends UIDialogBase { static void removeAllDialogs() { var dialogs = window.document.querySelectorAll('.ui-dialog'); - for (var d in dialogs) { + for (var d in dialogs.whereElement()) { var component = UIComponent.getContentUIComponent(d); if (component is UIDialogBase) { component.hide(); diff --git a/lib/src/component/dialog_edit_image.dart b/lib/src/component/dialog_edit_image.dart index 66c6891..d6ed270 100644 --- a/lib/src/component/dialog_edit_image.dart +++ b/lib/src/component/dialog_edit_image.dart @@ -1,16 +1,17 @@ import 'dart:async'; -import 'dart:html'; import 'dart:math' as math; +import 'dart:math' show Point; import 'package:dom_builder/dom_builder.dart'; import 'package:dom_tools/dom_tools.dart'; +import 'package:web_utils/web_utils.dart'; import 'dialog.dart'; /// An [UIDialog] that edits an [image]. class UIDialogEditImage extends UIDialog { /// The original image. - final ImageElement image; + final HTMLImageElement image; final int marginHorizontal; final int marginVertical; @@ -66,14 +67,14 @@ class UIDialogEditImage extends UIDialog { /// The edited image. /// See [editedImageDataURL]. - ImageElement? get editedImage { + HTMLImageElement? get editedImage { var imageDataURL = editedImageDataURL; if (imageDataURL == null || !imageDataURL.startsWith('data:')) return null; //print(imageDataURL); //downloadDataURL(DataURLBase64.parse(imageDataURL)!, 'edited-image.jpeg'); - return ImageElement(src: imageDataURL); + return HTMLImageElement()..src = imageDataURL; } } @@ -100,8 +101,8 @@ class _CanvasEditImage extends ExternalElementNode { _elementResize.track(canvas, (_) => _onResize(false)); window.onResize.listen((_) => _onResize(true)); - canvas.onMouseDown.listen((evt) => _onMouseDown1(evt.offset)); - canvas.onMouseMove.listen((evt) => _onMouseMove(evt.offset)); + canvas.onMouseDown.listen((evt) => _onMouseDown1(evt.offsetPoint)); + canvas.onMouseMove.listen((evt) => _onMouseMove(evt.offsetPoint)); canvas.onMouseUp.listen((evt) => _onMouseUp()); canvas.onMouseLeave.listen((evt) => _onMouseUp()); @@ -111,13 +112,13 @@ class _CanvasEditImage extends ExternalElementNode { canvas.onTouchLeave.listen((evt) => _onMouseUp()); } - int innerWidth = math.max(1, window.innerWidth ?? 1); + int innerWidth = math.max(1, window.innerWidth); - int innerHeight = math.max(1, window.innerHeight ?? 1); + int innerHeight = math.max(1, window.innerHeight); void _onResize(bool windowResize) { - var innerWidth = window.innerWidth ?? 0; - var innerHeight = window.innerHeight ?? 0; + var innerWidth = window.innerWidth; + var innerHeight = window.innerHeight; if (innerWidth < 1 || innerHeight < 1) return; @@ -140,23 +141,25 @@ class _CanvasEditImage extends ExternalElementNode { static void _pointHandler( TouchEvent event, void Function(Point p1, [Point? p2]) f) { - var canvasTouches = - event.touches?.where((t) => t.target is CanvasElement).toList() ?? []; + var canvasTouches = event.touches + .toIterable() + .where((t) => t.target.isA()) + .toList(); if (canvasTouches.isEmpty) { return; } else if (canvasTouches.length == 1) { event.preventDefault(); - var point1 = canvasTouches[0].client; + var point1 = canvasTouches[0].clientPoint; //print('!!! touch> $event > $point1 >> ${event.touches} > ${event.touches?.map((e) => e.target)} '); f(point1); } else if (canvasTouches.length == 2) { event.preventDefault(); - var point1 = canvasTouches[0].client; - var point2 = canvasTouches[1].client; + var point1 = canvasTouches[0].clientPoint; + var point2 = canvasTouches[1].clientPoint; //print('!!! touch> $event > $point1 & $point2 >> ${event.touches} > ${event.touches?.map((e) => e.target)} '); f(point1, point2); @@ -226,14 +229,20 @@ class _CanvasEditImage extends ExternalElementNode { requestRender(); } - static CanvasElement _buildCanvas(CanvasImageSource img, int imgNaturalWidth, - int imgNaturalHeight, int marginHorizontal, int marginVertical) { + static HTMLCanvasElement _buildCanvas( + CanvasImageSource img, + int imgNaturalWidth, + int imgNaturalHeight, + int marginHorizontal, + int marginVertical) { var d = _calcCanvasDimension( imgNaturalWidth, imgNaturalHeight, marginHorizontal, marginVertical); var w = d[0]; var h = d[1]; - return CanvasElement(width: w, height: h) + return HTMLCanvasElement() + ..width = w + ..height = h ..style.boxShadow = '0px 1px 18px 5px rgba(0, 0, 0, 0.65)' ..style.borderRadius = '12px'; } @@ -251,8 +260,8 @@ class _CanvasEditImage extends ExternalElementNode { static List _calcCanvasDimension(int imgNaturalWidth, int imgNaturalHeight, int marginHorizontal, int marginVertical) { - var innerWidth = window.innerWidth! - (marginHorizontal * 2); - var innerHeight = window.innerHeight! - (marginVertical * 2); + var innerWidth = window.innerWidth - (marginHorizontal * 2); + var innerHeight = window.innerHeight - (marginVertical * 2); var zoom = _calcZoom(imgNaturalWidth, imgNaturalHeight, innerWidth, innerHeight); @@ -359,7 +368,7 @@ class _CanvasEditImage extends ExternalElementNode { requestRender(); } - CanvasElement get canvas => externalElement as CanvasElement; + HTMLCanvasElement get canvas => externalElement as HTMLCanvasElement; int get renderWidth => (imgNaturalWidth * _zoom).toInt(); @@ -377,9 +386,9 @@ class _CanvasEditImage extends ExternalElementNode { return t != null ? y + t.y.toInt() : y; } - int get canvasWidth => canvas.width!; + int get canvasWidth => canvas.width; - int get canvasHeight => canvas.height!; + int get canvasHeight => canvas.height; Future? _rendering; @@ -395,13 +404,19 @@ class _CanvasEditImage extends ExternalElementNode { var context2d = canvas.context2D; context2d.clearRect(0, 0, canvasWidth, canvasHeight); - context2d.drawImageScaled(img, renderX, renderY, renderWidth, renderHeight); + context2d.drawImageScaled( + img, + renderX.toDouble(), + renderY.toDouble(), + renderWidth.toDouble(), + renderHeight.toDouble(), + ); if (_showGrid) { var wDiv4 = canvasWidth ~/ 3; var hDiv4 = canvasHeight ~/ 3; - context2d.fillStyle = 'rgba(0,0,0, 0.20)'; + context2d.fillStyle = 'rgba(0,0,0, 0.20)'.toJS; context2d.fillRect(wDiv4, 0, 2, canvasHeight); context2d.fillRect(canvasWidth - wDiv4, 0, 2, canvasHeight); @@ -429,7 +444,9 @@ class _CanvasEditImage extends ExternalElementNode { var canvasWidth = imgNaturalWidth; var canvasHeight = imgNaturalHeight; - var canvas = CanvasElement(width: canvasWidth, height: canvasHeight); + var canvas = HTMLCanvasElement() + ..width = canvasWidth + ..height = canvasHeight; var imgW = (imgNaturalWidth * zoom).toInt(); var imgH = (imgNaturalHeight * zoom).toInt(); @@ -446,7 +463,13 @@ class _CanvasEditImage extends ExternalElementNode { context2d.imageSmoothingEnabled = true; context2d.imageSmoothingQuality = 'high'; - context2d.drawImageScaled(img, x, y, imgW, imgH); + context2d.drawImageScaled( + img, + x.toDouble(), + y.toDouble(), + imgW.toDouble(), + imgH.toDouble(), + ); return canvas.toDataUrl('image/jpeg', 0.98); } diff --git a/lib/src/component/infos_table.dart b/lib/src/component/infos_table.dart index 653ff77..afac6eb 100644 --- a/lib/src/component/infos_table.dart +++ b/lib/src/component/infos_table.dart @@ -1,13 +1,11 @@ -import 'dart:html'; - import 'package:swiss_knife/swiss_knife.dart'; +import 'package:web_utils/web_utils.dart'; import '../bones_ui_component.dart'; -import '../bones_ui_web.dart'; /// Component that renders a table with information. class UIInfosTable extends UIComponent { - final Map _infos; + final Map _infos; final List? headerColumnsNames; final String? headerColor; @@ -33,7 +31,7 @@ class UIInfosTable extends UIComponent { rowsColors.add(''); } - var table = TableElement(); + var table = HTMLTableElement(); table.setAttribute('border', '0'); table.setAttribute('align', 'center'); @@ -45,9 +43,10 @@ class UIInfosTable extends UIComponent { } var tHead = table.createTHead(); - var headRow = tHead.addRow(); + var headRow = tHead.appendRow(); - if (isNotEmptyObject(rowsStyles)) { + final rowsStyles = this.rowsStyles; + if (rowsStyles != null && rowsStyles.isNotEmpty) { headRow.style.cssText = rowsStyles; } @@ -56,8 +55,9 @@ class UIInfosTable extends UIComponent { } for (var columnName in headerColumnsNames!) { - var cel = headRow.addCell(); - if (isNotEmptyObject(cellsStyles)) { + var cel = headRow.appendCell(); + final cellsStyles = this.cellsStyles; + if (cellsStyles != null && cellsStyles.isNotEmpty) { cel.style.cssText = cellsStyles; } @@ -72,9 +72,10 @@ class UIInfosTable extends UIComponent { var color = rowsColors[i++ % rowsColors.length]; - var row = table.addRow(); + var row = table.appendRow(); - if (isNotEmptyObject(rowsStyles)) { + final rowsStyles = this.rowsStyles; + if (rowsStyles != null && rowsStyles.isNotEmpty) { row.style.cssText = rowsStyles; } @@ -82,25 +83,26 @@ class UIInfosTable extends UIComponent { row.style.backgroundColor = color; } - var cell1 = row.addCell(); - if (isNotEmptyObject(cellsStyles)) { + var cell1 = row.appendCell(); + final cellsStyles = this.cellsStyles; + if (cellsStyles != null && cellsStyles.isNotEmpty) { cell1.style.cssText = cellsStyles; } cell1.setAttribute('align', 'right'); - cell1.innerHtml = '$k: '; + cell1.innerHTML = '$k: '.toJS; - var cell2 = row.addCell(); - if (isNotEmptyObject(cellsStyles)) { + var cell2 = row.appendCell(); + if (cellsStyles != null && cellsStyles.isNotEmpty) { cell2.style.cssText = cellsStyles; } cell2.setAttribute('align', 'center'); - if (v is UIElement) { - cell2.children.add(v); + if (v.isElement) { + cell2.appendChild(v as Element); } else { - cell2.innerHtml = v.toString(); + cell2.innerHTML = v.toString().toJS; } } diff --git a/lib/src/component/input_config.dart b/lib/src/component/input_config.dart index 5928c29..51fe568 100644 --- a/lib/src/component/input_config.dart +++ b/lib/src/component/input_config.dart @@ -1,11 +1,10 @@ -import 'dart:html'; - import 'package:collection/collection.dart'; import 'package:dom_builder/dom_builder.dart'; import 'package:dom_tools/dom_tools.dart'; import 'package:extended_type/extended_type.dart'; import 'package:intl_messages/intl_messages.dart'; import 'package:swiss_knife/swiss_knife.dart'; +import 'package:web_utils/web_utils.dart'; import '../bones_ui_component.dart'; import '../bones_ui_utils.dart'; @@ -241,18 +240,17 @@ class InputConfig { Element? element; if (_inputRender != null) { - var obj = _inputRender(this); + var obj = _inputRender(this) as Object?; + if (obj == null) return null; - if (obj == null) { - return null; - } else if (obj is UIComponent) { + if (obj is UIComponent) { inputComponent = obj; - } else if (obj is InputElement) { - inputElement = obj; + } else if (obj.asJSAny.isA()) { + inputElement = obj as HTMLInputElement; } else if (obj is DOMElement) { domeElement = obj; - } else if (obj is Element) { - element = obj; + } else if (obj.isElement) { + element = obj as Element; } else { throw StateError( "Can't handle input rendered object type: ${obj.runtimeType} > $obj"); @@ -275,7 +273,7 @@ class InputConfig { } else if (inputType == 'path') { element = _renderInputPath(inputID, inputValue); } else if (inputType == 'html') { - inputElement = createHTML(inputValue); + inputElement = createHTML(html: inputValue); inputElement.onClick.listen((event) { var value = inputElement!.getAttribute('element_value'); if (isNotEmptyObject(value)) { @@ -325,7 +323,7 @@ class InputConfig { if (containsIntlMessage(valText)) { var domSpan = $span(content: valText); var dom = domSpan.buildDOM(generator: UIComponent.domGenerator); - var text = dom?.text; + var text = dom?.textContent; return text; } @@ -334,13 +332,14 @@ class InputConfig { void _configureElementStyle(Element inputElement) { if (isNotEmptyObject(classes)) { - inputElement.classes.addAll(classes); + inputElement.classList.addAll(classes); } - if (isNotEmptyObject(style)) { - var cssText = inputElement.style.cssText; - inputElement.style.cssText = - isNotEmptyObject(cssText) ? '$cssText ; $style' : style; + final style = this.style; + if (style != null && style.isNotEmpty) { + var cssText = inputElement.style?.cssText; + inputElement.style?.cssText = + cssText != null && cssText.isNotEmpty ? '$cssText ; $style' : style; } } @@ -365,7 +364,7 @@ class InputConfig { } } - DivElement? _renderInputPath(String fieldName, String? inputValue) { + HTMLDivElement? _renderInputPath(String fieldName, String? inputValue) { var input = $input(style: 'width: auto', value: inputValue); DOMElement? button; @@ -386,29 +385,29 @@ class InputConfig { } value ??= ''; - var element = input.runtime.node as InputElement; + var element = input.runtime.node as HTMLInputElement; element.value = '$value'; - element.dispatchEvent(Event('change')); + element.dispatchChangeEvent(); }); } var div = $div(content: [input, button]) .buildDOM(generator: UIComponent.domGenerator); - return div as DivElement?; + return div as HTMLDivElement?; } - TextAreaElement _renderTextArea(Object? inputValue) { + HTMLTextAreaElement _renderTextArea(Object? inputValue) { var valText = _resolveValueText(inputValue); - var textArea = TextAreaElement()..style.width = '100%'; - textArea.value = valText; + var textArea = HTMLTextAreaElement()..style.width = '100%'; + textArea.value = valText ?? ''; return textArea; } Element _renderGenericInput(String? inputType, Object? inputValue) { var valText = _resolveValueText(inputValue); - var input = InputElement() + var input = HTMLInputElement() ..type = inputType ?? 'text' ..value = valText ?? '' ..style.width = '100%'; @@ -424,8 +423,8 @@ class InputConfig { return input; } - SelectElement _renderSelect(Object? inputValue) { - var select = SelectElement()..style.maxWidth = '100%'; + HTMLSelectElement _renderSelect(Object? inputValue) { + var select = HTMLSelectElement()..style.maxWidth = '100%'; if (options != null && options!.isNotEmpty) { var inputValueStr = inputValue?.toString(); @@ -447,7 +446,9 @@ class InputConfig { optVal = optKey; } - var optionElement = OptionElement(data: optVal, value: optKey); + var optionElement = HTMLOptionElement() + ..text = optVal + ..value = optKey; if (selected) { optionElement.selected = selected; @@ -458,7 +459,7 @@ class InputConfig { } else if (inputValue != null) { var s = _resolveValueText(inputValue); if (s != null && s.isNotEmpty) { - select.innerHtml = s; + select.innerHTML = s.toJS; } } @@ -525,7 +526,7 @@ class UIInputTable extends UIComponent { var highlightClass = this.highlightClass; return forEachEmptyFieldElement( - (fieldElement) => fieldElement.classes.add(highlightClass)); + (fieldElement) => fieldElement.classList.add(highlightClass)); } int unhighlightErrorInputs() { @@ -534,7 +535,7 @@ class UIInputTable extends UIComponent { var highlightClass = this.highlightClass; return forEachFieldElement((fieldElement) { - fieldElement.classes.remove(highlightClass); + fieldElement.classList.remove(highlightClass); var fieldName = getElementFieldName(fieldElement); _hideInvalidMessage(fieldName); }); @@ -543,10 +544,10 @@ class UIInputTable extends UIComponent { bool highlightField(String? fieldName, {String? invalidValueMessage}) { if (canHighlightInputs()) return false; - var fieldElement = getFieldElement(fieldName); + var fieldElement = getFieldElementNonTyped(fieldName); if (fieldElement == null) return false; - fieldElement.classes.add(highlightClass); + fieldElement.classList.add(highlightClass); if (showInvalidMessages && !isEmptyValue(invalidValueMessage)) { var msg = content!.querySelector('#__invalid_msg__$fieldName'); @@ -562,10 +563,10 @@ class UIInputTable extends UIComponent { bool unhighlightField(String fieldName) { if (canHighlightInputs()) return false; - var fieldElement = getFieldElement(fieldName); + var fieldElement = getFieldElementNonTyped(fieldName); if (fieldElement == null) return false; - fieldElement.classes.remove(highlightClass); + fieldElement.classList.remove(highlightClass); _hideInvalidMessage(fieldName); @@ -601,9 +602,11 @@ class UIInputTable extends UIComponent { highlightField(fieldName, invalidValueMessage: invalidValueMessage); if (ok) { - var element = getFieldElement(fieldName); - if (element != null && scrollToInvalidElement) { - scrollToElement(element); + var element = getFieldElementNonTyped(fieldName); + if (element != null && + element.isHTMLElement && + scrollToInvalidElement) { + scrollToElement(element as HTMLElement); } } @@ -633,15 +636,15 @@ class UIInputTable extends UIComponent { @override dynamic render() { - var form = FormElement(); + var form = HTMLFormElement(); form.style.width = '100%'; form.autocomplete = 'off'; - var table = TableElement(); + var table = HTMLTableElement(); table.style.width = '100%'; if (_tableClasses.isNotEmpty) { - table.classes.addAll(_tableClasses); + table.classList.addAll(_tableClasses); } if (_tableStyle?.isNotEmpty ?? false) { @@ -651,7 +654,7 @@ class UIInputTable extends UIComponent { var tBody = table.createTBody(); for (var input in _inputs) { - var row = tBody.addRow(); + var row = tBody.appendRow(); if (showLabels) { var label = input.label ?? ''; @@ -661,7 +664,7 @@ class UIInputTable extends UIComponent { verticalAlign = 'top'; } - var cell = row.addCell() + var cell = row.appendCell() ..style.verticalAlign = verticalAlign ..style.textAlign = 'right'; @@ -680,32 +683,35 @@ class UIInputTable extends UIComponent { style: 'font-weight: bold', content: [label, ':', ' ']); var dom = domLabel.buildDOM(generator: UIComponent.domGenerator) - as LabelElement; - cell.children.add(dom); + as HTMLLabelElement; + cell.appendChild(dom); } else { - cell.appendHtml( + cell.appendHTML( ''); } } } - var celInput = row.addCell()..style.textAlign = 'left'; + var celInput = row.appendCell()..style.textAlign = 'left'; + + var inputRendered = + input.renderInput(getPreviousRenderedFieldValue) as Object?; - var inputRendered = input.renderInput(getPreviousRenderedFieldValue); + if (inputRendered.isElement) { + var inputRenderedElement = inputRendered as Element; - if (inputRendered is Element) { if (_inputsClasses.isNotEmpty) { - inputRendered.classes.addAll(_inputsClasses); + (inputRenderedElement).classList.addAll(_inputsClasses); } - celInput.children.add(inputRendered); + celInput.appendChild(inputRenderedElement); } else if (inputRendered is UIComponent) { if (_inputsClasses.isNotEmpty) { - inputRendered.content?.classes.addAll(_inputsClasses); + inputRendered.content?.classList.addAll(_inputsClasses); } var div = createDiv(); - celInput.children.add(div); + celInput.appendChild(div); inputRendered.setParent(div, parentUIComponent: this); inputRendered.ensureRendered(); @@ -721,16 +727,16 @@ class UIInputTable extends UIComponent { } if (showInvalidMessages) { - var msg = DivElement() + var msg = HTMLDivElement() ..id = '__invalid_msg__${input.fieldName}' - ..hidden = true; + ..hidden = true.toJS; var invalidValueClass = this.invalidValueClass; if (invalidValueClass != null) { - msg.classes.add(invalidValueClass); + msg.classList.add(invalidValueClass); } - celInput.children.add(msg); + celInput.appendChild(msg); } } @@ -738,24 +744,24 @@ class UIInputTable extends UIComponent { var row = _resolveRow(r); if (row == null) continue; - if (row is TableRowElement) { - _addTableRow(table, row); - } else if (row is List) { + if (row.asJSAny.isA()) { + _addTableRow(table, row as HTMLTableRowElement); + } else if (row is List) { for (var r in row) { _addTableRow(table, r); } - } else if (row is List) { - var tr = table.addRow(); + } else if (row is List) { + var tr = table.appendRow(); for (var cell in row) { _addTableRowCell(tr, cell); } } else if (row is List) { - var tr = table.addRow(); + var tr = table.appendRow(); for (var cell in row) { - var td = tr.addCell(); - td.children.add(cell); + var td = tr.appendCell(); + td.appendChild(cell); } } } @@ -765,24 +771,26 @@ class UIInputTable extends UIComponent { return form; } - void _addTableRow(TableElement table, TableRowElement row) { - var tr = table.addRow(); + void _addTableRow(HTMLTableElement table, HTMLTableRowElement row) { + var tr = table.appendRow(); - tr.attributes.addAll(row.attributes); + tr.setAttributes(row.attributes.toMap()); - for (var cell in row.cells) { - _addTableRowCell(tr, cell); + for (var cell in row.cells.toList()) { + if (cell.isA()) { + _addTableRowCell(tr, cell as HTMLTableCellElement); + } } } - void _addTableRowCell(TableRowElement tr, TableCellElement cell) { - var td = tr.addCell(); - td.attributes.addAll(cell.attributes); + void _addTableRowCell(HTMLTableRowElement tr, HTMLTableCellElement cell) { + var td = tr.appendCell(); + td.setAttributes(cell.attributes.toMap()); var children = cell.children.toList(); - cell.children.clear(); + cell.clear(); - td.children.addAll(children); + td.appendNodes(children); for (var element in children) { UIComponent.resolveParentUIComponent( @@ -832,15 +840,16 @@ class UIInputTable extends UIComponent { } if (table != null) { - var dom = - table.buildDOM(generator: UIComponent.domGenerator) as TableElement; + var dom = table.buildDOM(generator: UIComponent.domGenerator) + as HTMLTableElement; var trs = dom.rows.toList(); if (trs.isEmpty) return null; return trs.length == 1 ? trs.first : trs; } var div = $div(content: nodes); - var dom = div.buildDOM(generator: UIComponent.domGenerator) as DivElement; + var dom = + div.buildDOM(generator: UIComponent.domGenerator) as HTMLDivElement; return dom.children.toList(); } @@ -892,7 +901,7 @@ class UIInputTable extends UIComponent { elem.onFocus.listen((event) { var element = event.target; - if (element is Element) { + if (element.isElement) { onInputFocus.add(element); } }); @@ -935,10 +944,10 @@ class UIInputTable extends UIComponent { var onActionListener = inputConfig?.onActionListener; if (onActionListener != null) { - if (elem is ButtonElement) { + if (elem.isA()) { elem.onClick.listen(onActionListener); - } else if (elem is InputElement) { - var type = elem.type; + } else if (elem.isA()) { + var type = (elem as HTMLInputElement).type; if (type == 'submit' || type == 'reset' || type == 'checkbox') { elem.onClick.listen(onActionListener); @@ -953,7 +962,7 @@ class UIInputTable extends UIComponent { interactionCompleter.onComplete.listen(onActionListener); } } - } else if (elem is TextAreaElement) { + } else if (elem.isA()) { interactionCompleter.onComplete.listen(onActionListener); } else { elem.onClick.listen(onActionListener); diff --git a/lib/src/component/json_render.dart b/lib/src/component/json_render.dart new file mode 100644 index 0000000..e25d698 --- /dev/null +++ b/lib/src/component/json_render.dart @@ -0,0 +1,35 @@ +import 'dart:convert'; + +import 'package:bones_ui/bones_ui.dart'; +import 'package:dom_builder/dom_builder.dart'; + +class UIJsonRender extends UIComponent { + final Object? json; + + UIJsonRender(super.parent, + {required this.json, + super.classes, + super.classes2, + super.style, + super.style2, + super.inline = true, + super.id}) + : super(componentClass: 'ui-json-render'); + + @override + Object? render() { + final json = this.json; + + if (json == null) { + return $tag('pre', content: 'null'); + } else if (json is String) { + var s = HtmlEscape().convert(json); + return $tag('pre', content: '"$s"'); + } else if (json is num) { + return $tag('pre', content: '$json'); + } else { + var j = JsonEncoder.withIndent(' ').convert(json); + return $tag('pre', content: j); + } + } +} diff --git a/lib/src/component/loading.dart b/lib/src/component/loading.dart index 0d95501..a8a2d51 100644 --- a/lib/src/component/loading.dart +++ b/lib/src/component/loading.dart @@ -1,9 +1,7 @@ -import 'dart:html'; - import 'package:dom_builder/dom_builder.dart'; import 'package:dom_tools/dom_tools.dart'; -import 'package:enum_to_string/enum_to_string.dart'; import 'package:swiss_knife/swiss_knife.dart'; +import 'package:web_utils/web_utils.dart'; import '../bones_ui_base.dart'; import '../bones_ui_component.dart'; @@ -410,7 +408,7 @@ String? _loadCSS(String loadingClass, String? color) { enum UILoadingType { ring, dualRing, roller, spinner, ripple, blocks, ellipsis } -UILoadingType? getUILoadingType(dynamic type) { +UILoadingType? getUILoadingType(Object? type) { if (type == null) return null; if (type is UILoadingType) return type; @@ -491,15 +489,15 @@ abstract class UILoading { var loadingClass = getUILoadingTypeClass(type); if (loadingClass == null) return; - var sel = root!.querySelectorAll('.$loadingClass'); + var sel = root!.querySelectorAll('.$loadingClass').toHTMLElements(); for (var elem in sel) { var color = ensureNotEmptyString(elem.style.color, trim: true); var text = ensureNotEmptyString(elem.text, trim: true); - var loading = asDivElement(type, color: color, text: text); + var loading = asHTMLDivElement(type, color: color, text: text); - elem.nodes.clear(); + elem.clear(); elem.append(loading); } } @@ -514,7 +512,7 @@ abstract class UILoading { bool? withProgress, UILoadingConfig? config}) { if (config != null) { - if (config.type != null) type = config.type; + type = config.type; if (isNotEmptyString(config.color, trim: true)) color = config.color; if (config.zoom != null) zoom = config.zoom; if (config.text != null) text = config.text; @@ -581,7 +579,7 @@ abstract class UILoading { return div; } - static DivElement asDivElement(UILoadingType? type, + static HTMLDivElement asHTMLDivElement(UILoadingType? type, {bool inline = true, String? color, double? zoom, @@ -599,12 +597,12 @@ abstract class UILoading { cssContext: cssContext, withProgress: withProgress, config: config); - return div.buildDOM(generator: UIComponent.domGenerator) as DivElement; + return div.buildDOM(generator: UIComponent.domGenerator) as HTMLDivElement; } } class UILoadingConfig implements AsDOMElement { - final UILoadingType? type; + final UILoadingType type; final bool? inline; final TextProvider? _color; final double? zoom; @@ -674,7 +672,8 @@ class UILoadingConfig implements AsDOMElement { DIVElement asDIVElement() => UILoading.asDIVElement(type, config: this); - DivElement asDivElement() => UILoading.asDivElement(type, config: this); + HTMLDivElement asDivElement() => + UILoading.asHTMLDivElement(type, config: this); @override DOMElement get asDOMElement => asDIVElement(); @@ -684,7 +683,7 @@ class UILoadingConfig implements AsDOMElement { var text = _text?.text; return [ - 'type: ${EnumToString.convertToString(type)}', + 'type: ${type.name}', if (inline != null) 'inline: $inline', if (isNotEmptyString(color, trim: true)) 'color: $color', if (zoom != null) 'zoom: $zoom', diff --git a/lib/src/component/masonry.dart b/lib/src/component/masonry.dart index c411b23..f218ad4 100644 --- a/lib/src/component/masonry.dart +++ b/lib/src/component/masonry.dart @@ -1,4 +1,4 @@ -import 'dart:html'; +import 'package:web_utils/web_utils.dart'; import 'package:collection/collection.dart' show IterableExtension; import 'package:dom_builder/dom_builder.dart'; @@ -16,9 +16,9 @@ class MasonryItem { MasonryItem(this.element, this._width, this._height); - factory MasonryItem.from(dynamic element) { - if (element is Element) { - return MasonryItem.fromElement(element); + factory MasonryItem.from(Object? element) { + if (element.isElement) { + return MasonryItem.fromElement(element as Element); } else if (element is DOMElement) { return MasonryItem.fromDOMElement(element); } else if (element is UIComponent) { @@ -60,10 +60,10 @@ class MasonryItem { } } -int? _getElementWidth(dynamic element) { +int? _getElementWidth(Object? element) { if (element == null) return null; - if (element is Element) { - return getElementWidth(element); + if (element.isHTMLElement) { + return getElementWidth(element as HTMLElement); } else if (element is UIComponent) { return getElementWidth(element.content!); } else if (element is DOMElement) { @@ -73,10 +73,10 @@ int? _getElementWidth(dynamic element) { return 0; } -int? _getElementHeight(dynamic element) { +int? _getElementHeight(Object? element) { if (element == null) return null; - if (element is Element) { - return getElementHeight(element); + if (element.isHTMLElement) { + return getElementHeight(element as HTMLElement); } else if (element is UIComponent) { return getElementHeight(element.content!); } else if (element is DOMElement) { @@ -281,8 +281,8 @@ class UIMasonry extends UIComponent { dynamic render() { content! ..style.textAlign = 'center' - ..style.width = width - ..style.height = height + ..style.width = width ?? '' + ..style.height = height ?? '' ..style.overflowY = 'auto'; if (isNotEmptyObject(scrollbarColors)) { @@ -635,8 +635,8 @@ class _MasonryLine { var w = masonryWidth * masonryWidthSizeWithItemsMargin; var h = maxMasonryHeight * masonryHeightSizeWithItemsMargin; - var div = DivElement() - ..classes.add('ui-masonry-line') + var div = HTMLDivElement() + ..classList.add('ui-masonry-line') ..style.textAlign = 'center' ..style.margin = '0 auto' ..style.width = '${w}px' @@ -902,8 +902,8 @@ class _MasonryRenderGroup extends _MasonryRenderable { @override Element render() { - var div = DivElement() - ..classes.add('ui-masonry-group') + var div = HTMLDivElement() + ..classList.add('ui-masonry-group') ..style.display = 'inline-block' ..style.overflow = 'hidden' ..style.verticalAlign = 'top'; @@ -913,7 +913,7 @@ class _MasonryRenderGroup extends _MasonryRenderable { var item = _items[i]; if (lineWidth >= maxWidth) { - div.append(BRElement()); + div.append(HTMLBRElement()); lineWidth = 0; } @@ -982,20 +982,20 @@ class _MasonryRenderItem extends _MasonryRenderable { var w = w2 - (itemsMargin! * 1); var h = h2 - (itemsMargin! * 1); - var div1 = DivElement() - ..classes.add('ui-masonry-block') + var div1 = HTMLDivElement() + ..classList.add('ui-masonry-block') ..style.cssText = 'display: inline-block; vertical-align: top; text-align: center; overflow: hidden; width: ${w2}px; height: ${h2}px; margin: 0px;'; - var div2 = DivElement() + var div2 = HTMLDivElement() ..style.cssText = 'display: table; width: 100%; height: 100%;'; - var div3 = DivElement() + var div3 = HTMLDivElement() ..style.cssText = 'display: table-cell; text-align: center; vertical-align: middle;'; - var div4 = DivElement() - ..classes.add('ui-masonry-item') + var div4 = HTMLDivElement() + ..classList.add('ui-masonry-item') ..style.cssText = 'display: inline-block; max-width: ${w}px; max-height: ${h}px'; @@ -1017,8 +1017,8 @@ class _MasonryRenderItem extends _MasonryRenderable { htmlRoot?.buildDOM(generator: UIComponent.domGenerator, parent: div4); } - if (element is Element) { - div4.append(element); + if (element.isElement) { + div4.appendChild(element as Element); } return div1; diff --git a/lib/src/component/menu.dart b/lib/src/component/menu.dart index 4fdc9dd..c67e4d6 100644 --- a/lib/src/component/menu.dart +++ b/lib/src/component/menu.dart @@ -1,8 +1,9 @@ -import 'dart:html'; +import 'dart:math'; import 'package:dom_builder/dom_builder.dart'; import 'package:dom_tools/dom_tools.dart'; import 'package:swiss_knife/swiss_knife.dart'; +import 'package:web_utils/web_utils.dart'; import '../bones_ui_base.dart'; import '../bones_ui_component.dart'; @@ -50,28 +51,26 @@ class MenuEntry

extends MenuItem { TextProvider? get name => _name; - set name(dynamic name) { - _name = TextProvider.from(name); - if (_name == null || _name!.text == null) { - throw ArgumentError.notNull('name'); - } - } + set name(Object? name) => + _name = TextProvider.from(name) ?? (throw ArgumentError.notNull('name')); - String? get nameText => _name != null ? _name!.text : ''; + String? get nameText { + final name = _name; + return name != null ? name.text : ''; + } TextProvider? get title => _title; - set title(dynamic title) { - _title = TextProvider.from(title); - } + set title(Object? title) => _title = TextProvider.from(title); - String? get titleText => _title != null ? _title!.text : ''; + String get titleText { + final title = _title; + return title != null ? title.text : ''; + } ElementProvider? get icon => _icon; - set icon(dynamic icon) { - _icon = ElementProvider.from(icon); - } + set icon(dynamic icon) => _icon = ElementProvider.from(icon); Element? get iconElement => _icon?.element; @@ -95,9 +94,8 @@ class MenuEntry

extends MenuItem { bool get hasAction => action != null; @override - String toString() { - return 'MenuEntry{_name: $_name, _subMenu: $_subMenu, payload: $payload}'; - } + String toString() => + 'MenuEntry{_name: $_name, _subMenu: $_subMenu, payload: $payload}'; } class UIMenu extends UIComponent { @@ -150,16 +148,15 @@ class UIMenu extends UIComponent { setElementBackgroundBlur(content!, backgroundBlur); } - if (isNotEmptyObject(zIndex)) { + final zIndex = this.zIndex; + if (zIndex != null && zIndex.isNotEmpty) { content!.style.zIndex = zIndex; } } bool? get vertical => _vertical; - set vertical(bool? value) { - _vertical = value ?? false; - } + set vertical(bool? value) => _vertical = value ?? false; @override dynamic render() { @@ -200,7 +197,7 @@ class UIMenu extends UIComponent { dynamic nameText = menuEntry.nameText; var titleText = menuEntry.titleText; - var iconElement = menuEntry.iconElement; + var iconElement = menuEntry.iconElement.asHTMLElementChecked; var iconSeparator = iconElement != null ? ' ' : null; UISVG? dropDownIcon; @@ -214,8 +211,7 @@ class UIMenu extends UIComponent { iconElement.title = titleText; } - nameText = - $span(attributes: {'title': titleText!}, content: nameText); + nameText = $span(attributes: {'title': titleText}, content: nameText); } var menuEntryDiv = $divInline( diff --git a/lib/src/component/multi_selection.dart b/lib/src/component/multi_selection.dart index 671a75d..c097e66 100644 --- a/lib/src/component/multi_selection.dart +++ b/lib/src/component/multi_selection.dart @@ -1,10 +1,11 @@ -import 'dart:html'; +import 'dart:math'; import 'package:collection/collection.dart' show IterableExtension; import 'package:dom_builder/dom_builder.dart'; import 'package:dom_tools/dom_tools.dart'; import 'package:intl_messages/intl_messages.dart'; import 'package:swiss_knife/swiss_knife.dart'; +import 'package:web_utils/web_utils.dart'; import '../bones_ui_base.dart'; import '../bones_ui_component.dart'; @@ -17,7 +18,7 @@ class UIMultiSelection extends UIComponent implements UIField> { UIComponentGenerator('ui-multi-selection', 'div', 'ui-multi-selection', 'display: inline-block', (parent, attributes, contentHolder, contentNodes) { - var jsonConfig = parseJSON(contentHolder?.text, {}); + var jsonConfig = parseJSON(contentHolder?.textContent, {}); if (jsonConfig is! Map) jsonConfig = {}; return UIMultiSelection(parent, jsonConfig['options']); }, [ @@ -171,10 +172,10 @@ class UIMultiSelection extends UIComponent implements UIField> { } } - InputElement? _input; - DivElement? _optionsPanel; + HTMLInputElement? _input; + HTMLDivElement? _optionsPanel; - List _checkElements = []; + List _checkElements = []; bool? isCheckedByID(dynamic id) { if (id == null) return null; @@ -310,15 +311,15 @@ class UIMultiSelection extends UIComponent implements UIField> { } } - List _getSelectedElements() { + List _getSelectedElements() { return _checkElements.where((e) => _isChecked(e) ?? false).toList(); } - List _getUnSelectedElements() { + List _getUnSelectedElements() { return _checkElements.where((e) => !(_isChecked(e) ?? false)).toList(); } - InputElementBase? _getCheckElement(String id) { + HTMLInputElement? _getCheckElement(String id) { return _checkElements.firstWhereOrNull((e) => e.value == id); } @@ -382,7 +383,7 @@ class UIMultiSelection extends UIComponent implements UIField> { } } - void _autoAdjustInputFont(InputElement input, String text) { + void _autoAdjustInputFont(HTMLInputElement input, String text) { if (!autoInputFontShrink) return; input.style.fontSize = ''; @@ -422,8 +423,8 @@ class UIMultiSelection extends UIComponent implements UIField> { @override dynamic render() { if (_input == null) { - var input = _input = InputElement() - ..classes.add('ui-multi-selection-input') + var input = _input = HTMLInputElement() + ..classList.add('ui-multi-selection-input') ..type = 'text'; input @@ -434,7 +435,7 @@ class UIMultiSelection extends UIComponent implements UIField> { //style="cursor: pointer; padding: 5px 10px; border: 1px solid #ccc; width: 100%" - _optionsPanel = DivElement() + _optionsPanel = HTMLDivElement() ..style.display = 'none' ..style.backgroundColor = 'rgba(255,255,255, 0.90)' ..style.position = 'absolute' @@ -443,9 +444,9 @@ class UIMultiSelection extends UIComponent implements UIField> { ..style.textAlign = 'left' ..style.padding = '4px'; - _optionsPanel!.classes.add('shadow'); - _optionsPanel!.classes.add('p-2'); - _optionsPanel!.classes.add('ui-multi-selection-options-menu'); + _optionsPanel!.classList.add('shadow'); + _optionsPanel!.classList.add('p-2'); + _optionsPanel!.classList.add('ui-multi-selection-options-menu'); window.onResize.listen((e) => _updateDivOptionsPosition()); @@ -484,8 +485,8 @@ class UIMultiSelection extends UIComponent implements UIField> { window.onTouchStart.listen((e) { if (_optionsPanel == null) return; - var overDivOptions = nodeTreeContainsAny( - _optionsPanel!, e.targetTouches!.map((t) => t.target as UINode)); + var overDivOptions = nodeTreeContainsAny(_optionsPanel!, + e.targetTouches.toIterable().map((t) => t.target as UINode)); if (!overDivOptions && _isShowing()) { _toggleDivOptions(true); } @@ -554,7 +555,7 @@ class UIMultiSelection extends UIComponent implements UIField> { } void _updateDivOptionsPosition() { - var inputVPRect = _input!.getBoundingClientRect(); + var inputVPRect = _input!.getBoundingClientRect().toRectangle(); var inputRect = _appendScrollCoords(inputVPRect); var inputW = inputRect.width; @@ -569,7 +570,7 @@ class UIMultiSelection extends UIComponent implements UIField> { var y = inputY + inputHeight; var freeViewportHeightUpward = inputVPY - 10; - var freeViewportHeightBelow = (window.innerHeight! - inputVPY) - 10; + var freeViewportHeightBelow = (window.innerHeight - inputVPY) - 10; _optionsPanel! ..style.position = 'absolute' @@ -611,23 +612,33 @@ class UIMultiSelection extends UIComponent implements UIField> { bool _isShowing() => _optionsPanel!.style.display == '' || _optionsPanel!.style.display == ''; - bool? _setCheck(InputElementBase? elem, bool? check) { - if (elem is CheckboxInputElement) { - return elem.checked = check; - } else if (elem is RadioButtonInputElement) { - return elem.checked = check; - } else { - return null; + bool? _setCheck(HTMLInputElement? elem, bool? check) { + if (elem == null) return null; + + switch (elem.type.toLowerCase()) { + case 'checkbox': + case 'radio': + { + return elem.checked = check ?? false; + } + + default: + return null; } } - bool? _isChecked(InputElementBase? elem) { - if (elem is CheckboxInputElement) { - return elem.checked; - } else if (elem is RadioButtonInputElement) { - return elem.checked; - } else { - return null; + bool? _isChecked(HTMLInputElement? elem) { + if (elem == null) return null; + + switch (elem.type.toLowerCase()) { + case 'checkbox': + case 'radio': + { + return elem.checked; + } + + default: + return null; } } @@ -660,7 +671,7 @@ class UIMultiSelection extends UIComponent implements UIField> { var entriesFiltered = >[]; - var inputValue = _input!.value!; + var inputValue = _input!.value; if (inputValue.isNotEmpty) { entriesFiltered = getOptionsEntriesFiltered(inputValue); @@ -680,7 +691,7 @@ class UIMultiSelection extends UIComponent implements UIField> { String? _renderedPanelOptionsSignature; - List _renderPanelOptions() { + List _renderPanelOptions() { var entriesOrder = _optionsEntriesOrder(); var entries = entriesOrder[0]; @@ -694,10 +705,10 @@ class UIMultiSelection extends UIComponent implements UIField> { _renderedPanelOptionsSignature = optionsSignature; - _optionsPanel!.children.clear(); + _optionsPanel!.clear(); if (_options!.isEmpty) { - _optionsPanel!.append(createHTML(''' + _optionsPanel!.appendChild(createHTML(html: '''

${IntlBasicDictionary.msg('no_options')}
@@ -705,12 +716,12 @@ class UIMultiSelection extends UIComponent implements UIField> { return []; } - var checksList = []; + var checksList = []; - var table = TableElement()..style.borderCollapse = 'collapse'; + var table = HTMLTableElement()..style.borderCollapse = 'collapse'; var tbody = table.createTBody(); - _optionsPanel!.children.add(table); + _optionsPanel!.appendChild(table); if (entriesFiltered.isNotEmpty) { for (var optEntry in entriesFiltered) { @@ -718,9 +729,9 @@ class UIMultiSelection extends UIComponent implements UIField> { } if (entries.isNotEmpty) { - var tr = tbody.addRow(); - var td = tr.addCell()..colSpan = 2; - td.append(HRElement()); + var tr = tbody.appendRow(); + var td = tr.appendCell()..colSpan = 2; + td.append(HTMLHRElement()); } } @@ -743,11 +754,15 @@ class UIMultiSelection extends UIComponent implements UIField> { } void _scrollToElement(Element? parent, Element element) { - element.scrollIntoView(ScrollAlignment.CENTER); + element.scrollIntoView({ + 'behavior': 'smooth', + 'block': 'center', + 'inline': 'center', + }.toJSDeep); } - void _renderOptionsPanelEntry(TableSectionElement tbody, - List checksList, MapEntry optEntry, bool filtered) { + void _renderOptionsPanelEntry(HTMLTableSectionElement tbody, + List checksList, MapEntry optEntry, bool filtered) { var optKey = '${optEntry.key}'; var optValue = '${optEntry.value}'; @@ -755,14 +770,16 @@ class UIMultiSelection extends UIComponent implements UIField> { (_initialSelections.contains(optKey) || _initialSelections.contains(optEntry.key)); - InputElementBase checkElem; + HTMLInputElement checkElem; if (multiSelection!) { - var input = CheckboxInputElement(); + var input = HTMLInputElement()..type = 'checkbox'; input.checked = check; checkElem = input; } else { - var input = RadioButtonInputElement()..name = '__MultiSelection__'; + var input = HTMLInputElement() + ..type = 'radio' + ..name = '__MultiSelection__'; input.checked = check; checkElem = input; } @@ -770,17 +787,17 @@ class UIMultiSelection extends UIComponent implements UIField> { checkElem.value = optKey; checkElem.setAttribute('opt_label', optValue); - var row = tbody.addRow(); + var row = tbody.appendRow(); - var cell1 = row.addCell() + var cell1 = row.appendCell() ..style.padding = '2px 6px 2px 2px' ..style.verticalAlign = 'top'; - cell1.children.add(checkElem); + cell1.appendChild(checkElem); checksList.add(checkElem); - var label = LabelElement(); + var label = HTMLLabelElement(); setElementInnerHTML(label, optValue); checkElem.onClick.listen((e) { @@ -794,10 +811,10 @@ class UIMultiSelection extends UIComponent implements UIField> { _checkByIDImpl(optKey, _isChecked(checkElem), true, true); }); - var cell2 = row.addCell()..style.textAlign = 'left'; + var cell2 = row.appendCell()..style.textAlign = 'left'; - cell2.children.add(SpanElement()..innerHtml = ' '); - cell2.children.add(label); + cell2.appendChild(HTMLSpanElement()..innerHTML = ' '.toJS); + cell2.appendChild(label); } void _onOptionsPanelMouseMove(MouseEvent e) { diff --git a/lib/src/component/svg.dart b/lib/src/component/svg.dart index 84bc832..1055371 100644 --- a/lib/src/component/svg.dart +++ b/lib/src/component/svg.dart @@ -1,11 +1,10 @@ import 'dart:async'; -import 'dart:html'; -import 'dart:svg' as dart_svg; import 'package:collection/collection.dart'; import 'package:dom_builder/dom_builder.dart'; import 'package:dom_tools/dom_tools.dart'; import 'package:swiss_knife/swiss_knife.dart'; +import 'package:web_utils/web_utils.dart'; import '../bones_ui_component.dart'; import '../bones_ui_generator.dart'; @@ -55,7 +54,7 @@ class UISVG extends UIComponent { (parent, attributes, contentHolder, contentNodes) => UISVG( parent, src: attributes['src']?.value, - svgContent: contentHolder?.text, + svgContent: contentHolder?.textContent, width: attributes['width']?.value ?? '20px', height: attributes['height']?.value ?? '20px', color: attributes['color']?.value, @@ -152,11 +151,11 @@ class UISVG extends UIComponent { /// Returns the [Element] rendered. Element? get renderedElement => _renderedElement; - /// Returns [true] if it was rendered as an [ImageElement]. - bool get isRenderedAsImage => _renderedElement is ImageElement; + /// Returns [true] if it was rendered as an [HTMLImageElement]. + bool get isRenderedAsImage => _renderedElement.isA(); /// Returns [true] if it was rendered as an [SvgElement]. - bool get isRenderedAsSVG => _renderedElement is dart_svg.SvgElement; + bool get isRenderedAsSVG => _renderedElement.isA(); @override dynamic render() { @@ -193,22 +192,19 @@ class UISVG extends UIComponent { return element; } - static final NodeValidatorBuilder _svgNodeValidator = - createStandardNodeValidator(svg: true, allowSvgForeignObject: true); - - dart_svg.SvgElement? buildSVGElement([String? content]) { + SVGElement? buildSVGElement([String? content]) { content ??= svgContent; if (content == null || content.isEmpty) { return null; } - var svg = createHTML(content, _svgNodeValidator) as dart_svg.SvgElement; + var svg = createHTML(html: content) as SVGElement; _applyDimension(svg); if (color != null && color!.isNotEmpty) { - svg.style.cssText = '${svg.style.cssText ?? ''}fill: $color'; + svg.style.cssText = '${svg.style.cssText}fill: $color'; } if (title != null && title!.isNotEmpty) { @@ -219,19 +215,20 @@ class UISVG extends UIComponent { return svg; } - ImageElement buildSVGImg() { - ImageElement img; + HTMLImageElement buildSVGImg() { + HTMLImageElement img; if (isEmptyObject(src) && isNotEmptyObject(svgContent)) { var svgDataURL = 'data:image/svg+xml;base64,${Base64.encode(svgContent!)}'; - img = ImageElement(src: svgDataURL); + img = HTMLImageElement()..src = svgDataURL; } else { - img = ImageElement(src: src); + img = HTMLImageElement()..src = src ?? ''; } _applyDimension(img); - if (title != null && title!.isNotEmpty) img.title = title; + final title = this.title; + if (title != null && title.isNotEmpty) img.title = title; return img; } @@ -242,18 +239,18 @@ class UISVG extends UIComponent { if (width.isNotEmpty) { var w = widthAsCSSValue; - elem.style.width = w; + elem.style?.width = w; } if (height.isNotEmpty) { var h = heightAsCSSValue; - elem.style.height = h; + elem.style?.height = h; } } - Future buildRenderedImage() { + Future buildRenderedImage() { var svgIMG = buildSVGImg(); - var completer = Completer(); + var completer = Completer(); svgIMG.onLoad.listen((event) { var img = _drawImageToCanvas(svgIMG); @@ -263,11 +260,13 @@ class UISVG extends UIComponent { return completer.future; } - ImageElement _drawImageToCanvas(ImageElement img) { + HTMLImageElement _drawImageToCanvas(HTMLImageElement img) { var w = widthAsCSSLength!.value.toInt(); var h = heightAsCSSLength!.value.toInt(); - var canvas = CanvasElement(width: w, height: h); + var canvas = HTMLCanvasElement() + ..width = w + ..height = h; var ctx = canvas.context2D; ctx.clearRect(0, 0, w, h); @@ -275,7 +274,7 @@ class UISVG extends UIComponent { var imgDataURL = canvas.toDataUrl('image/png', 1.0); - var imgRendered = ImageElement(src: imgDataURL); + var imgRendered = HTMLImageElement()..src = imgDataURL; _applyDimension(imgRendered); return imgRendered; diff --git a/lib/src/component/template.dart b/lib/src/component/template.dart index 8655cfa..fac0120 100644 --- a/lib/src/component/template.dart +++ b/lib/src/component/template.dart @@ -1,4 +1,4 @@ -import 'dart:html'; +import 'package:web_utils/web_utils.dart'; import 'package:dom_builder/dom_builder.dart'; import 'package:dynamic_call/dynamic_call.dart'; @@ -37,7 +37,7 @@ class UITemplateElementGenerator extends ElementGeneratorBase { var domElement = domNode as DOMElement; - var element = DivElement(); + var element = HTMLDivElement(); setElementAttributes(element, attributes); @@ -69,7 +69,7 @@ class UITemplateElementGenerator extends ElementGeneratorBase { DOMGenerator domGenerator, DOMTreeMap treeMap, DOMElement domElement, - DivElement element, + HTMLDivElement element, Map attributes, DOMContext? domContext) { try { @@ -92,7 +92,7 @@ class UITemplateElementGenerator extends ElementGeneratorBase { var loadingConfig = UILoadingConfig.fromMap(attributes, 'loading-'); - DivElement? uiLoading; + HTMLDivElement? uiLoading; if (loadingConfig != null) { uiLoading = loadingConfig.asDivElement(); element.append(uiLoading); @@ -127,7 +127,7 @@ class UITemplateElementGenerator extends ElementGeneratorBase { DOMTemplateNode template, Map variables, DOMElement domElement, - DivElement element) { + HTMLDivElement element) { var templateBuiltHTML = template.buildAsString(variables, resolveDSX: false, elementProvider: (q) => treeMap.queryElement(q, @@ -151,7 +151,7 @@ class UITemplateElementGenerator extends ElementGeneratorBase { DOMTreeMap treeMap, String html, DOMElement domElement, - DivElement element) { + HTMLDivElement element) { _setElementAttributes(domElement, element); domElement.clearNodes(); @@ -164,7 +164,7 @@ class UITemplateElementGenerator extends ElementGeneratorBase { setTreeMapRoot: false); } - void _setElementAttributes(DOMElement domElement, DivElement element) { + void _setElementAttributes(DOMElement domElement, HTMLDivElement element) { for (var attr in domElement.domAttributes.entries) { var attrValue = attr.value; if (attrValue.isBoolean && !attrValue.hasValue) { @@ -293,23 +293,25 @@ class UITemplateElementGenerator extends ElementGeneratorBase { @override bool isGeneratedElement(UINode element) { - return element is DivElement && element.classes.contains(tag); + return element.isA() && + (element as HTMLDivElement).classList.contains(tag); } @override DOMElement? revert(DOMGenerator domGenerator, DOMTreeMap? treeMap, DOMElement? domParent, UINode? parent, UINode? node) { - if (node is DivElement) { + if (node.isA()) { + var div = node as HTMLDivElement; var domElement = - $tag(tag, classes: node.classes.join(' '), style: node.style.cssText); + $tag(tag, classes: div.classList.value, style: div.style.cssText); if (treeMap != null) { - var mappedDOMNode = treeMap.getMappedDOMNode(node); + var mappedDOMNode = treeMap.getMappedDOMNode(div); if (mappedDOMNode != null) { domElement.add(mappedDOMNode.content); } } else { - domElement.add(node.text); + domElement.add(div.text); } return domElement; diff --git a/pubspec.yaml b/pubspec.yaml index f26235c..6ac35a0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,14 +11,14 @@ executables: bones_ui_test: dependencies: - js_interop_utils: ^1.0.1 - web_utils: ^1.0.1 + js_interop_utils: ^1.0.5 + web_utils: ^1.0.5 web: ^1.1.0 intl_messages: ^2.3.4 - dom_tools: ^2.3.2 - dom_builder: ^2.2.7 - json_render: ^2.1.0 - json_object_mapper: ^2.0.1 + dom_tools: ^3.0.0-beta.3 + dom_builder: ^3.0.0-beta.1 + #json_render: ^2.1.0 + #json_object_mapper: ^2.0.1 swiss_knife: ^3.2.3 statistics: ^1.1.3 mercury_client: ^2.2.4 @@ -29,23 +29,22 @@ dependencies: expressions: ^0.2.5+2 html_unescape: ^2.0.0 intl: ^0.19.0 - enum_to_string: ^2.0.1 yaml: ^3.1.3 archive: ^3.6.1 - collection: ^1.19.0 + collection: ^1.19.1 args: ^2.6.0 logging: ^1.3.0 path: ^1.9.1 - test: ^1.25.14 + test: ^1.25.15 test_api: ^0.7.4 test_core: ^0.6.8 - stream_channel: ^2.1.3 + stream_channel: ^2.1.4 stack_trace: ^1.12.1 dev_dependencies: - build_web_compilers: ^4.1.0 - build_runner: ^2.4.14 + build_web_compilers: ^4.1.1 + build_runner: ^2.4.15 lints: ^5.1.1 dependency_validator: ^4.1.2 @@ -65,6 +64,10 @@ dev_dependencies: # path: ../dom_builder # dom_tools: # path: ../dom_tools +# js_interop_utils: +# path: ../js_interop_utils +# web_utils: +# path: ../web_utils # json_render: # path: ../json_render # json_object_mapper: diff --git a/test/bones_ui_test.dart b/test/bones_ui_test.dart index 362f8be..9bc9483 100644 --- a/test/bones_ui_test.dart +++ b/test/bones_ui_test.dart @@ -3,7 +3,7 @@ library; import 'package:bones_ui/bones_ui_test.dart'; import 'package:test/test.dart'; -import 'dart:html' as web; +import 'package:web_utils/web_utils.dart' as web; void main() { group('UIRoot', () { @@ -18,27 +18,38 @@ void main() { await uiRoot.callRenderAndWait(); await testUISleep(ms: 100); - expect(uiRoot.querySelector('#my-contact'), isNull); + expect(uiRoot.querySelectorNonTyped('#my-contact'), isNull); - var myHome = uiRoot.querySelector('#my-home'); - expect(myHome, isA()); + var myHome = uiRoot.querySelectorNonTyped('#my-home'); + expect(myHome.isA(), isTrue); expect(myHome?.text, contains('Hello world')); expect(myHome?.text, contains('- uiRoot: MyRoot')); expect(myHome?.text, contains('- uiRootComponent: MyRoot')); - var btn1 = uiRoot.selectExpected('*'); - expect(btn1, isA()); + var tmp = uiRoot + .selectAllNonTyped('*') + .map((e) => e as Object?) + .map((e) => e.asJSAny) + .where((e) => e.isA()) + .firstOrNull; + + print('!!! HTMLButtonElement: $tmp'); + + var btn1 = uiRoot.selectExpectedTyped( + '*', Web.HTMLButtonElement); + expect(btn1.isA(), isTrue, reason: "btn1: $btn1"); expect(btn1.text, equals('Go to: contact')); btn1.click(); await testUISleep(ms: 200); - expect(uiRoot.querySelector('#my-home'), isNull); - expect(uiRoot.querySelector('#my-contact'), isNotNull); + expect(uiRoot.querySelectorNonTyped('#my-home'), isNull); + expect(uiRoot.querySelectorNonTyped('#my-contact'), isNotNull); - var myContact1 = uiRoot.querySelector('#my-contact'); + var myContact1 = uiRoot.querySelectorTyped( + '#my-contact', Web.HTMLDivElement); expect(myContact1!.text, contains('Loading...')); expect(myContact1.text, isNot(contains('foo@mail.com'))); @@ -52,8 +63,9 @@ void main() { await testUISleep(ms: 1200); - var myContact2 = uiRoot.querySelector('#my-contact'); - expect(myContact2, isA()); + var myContact2 = uiRoot.querySelectorTyped( + '#my-contact', Web.HTMLDivElement); + expect(myContact2.isA(), isTrue); expect(myContact2?.text, contains('foo@mail.com')); expect(isComponentInDOM(myContact1), isTrue); @@ -68,19 +80,20 @@ void main() { expect(identical(uiContact1, uiContact2), isTrue); - var btn2 = uiRoot.selectExpected('*'); - expect(btn2, isA()); + var btn2 = uiRoot.selectExpectedTyped( + '*', Web.HTMLButtonElement); + expect(btn2.isA(), isTrue); expect(btn2.text, equals('Go to: home')); btn2.click(); await testUISleep(ms: 200); - expect(uiRoot.querySelector('#my-contact'), isNull); + expect(uiRoot.querySelectorNonTyped('#my-contact'), isNull); expect(isComponentInDOM(myContact1), isFalse); expect(canBeInDOM(myContact1), isTrue); - var myHome2 = uiRoot.querySelector('#my-home'); - expect(myHome2, isA()); + var myHome2 = uiRoot.querySelectorNonTyped('#my-home'); + expect(myHome2.isA(), isTrue); }); }); } From ed7da00d251fdef354b1c696e746d79e557cc78d Mon Sep 17 00:00:00 2001 From: gmpassos Date: Mon, 17 Feb 2025 21:19:28 -0300 Subject: [PATCH 03/64] v3.0.0-beta.1 --- lib/src/bones_ui.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/bones_ui.dart b/lib/src/bones_ui.dart index 86ec41f..1ecceb0 100644 --- a/lib/src/bones_ui.dart +++ b/lib/src/bones_ui.dart @@ -1,3 +1,3 @@ class BonesUI { - static const String version = '2.5.13'; + static const String version = '3.0.0-beta.1'; } From 4727d6c7d324a2409df4fce05432b0f463ee67bf Mon Sep 17 00:00:00 2001 From: gmpassos Date: Wed, 19 Feb 2025 01:59:03 -0300 Subject: [PATCH 04/64] v3.0.0-beta.2 - web_utils: ^1.0.6 - dom_tools: ^3.0.0-beta.4 - dom_builder: ^3.0.0-beta.2 --- CHANGELOG.md | 6 ++++++ lib/src/bones_ui.dart | 2 +- lib/src/bones_ui_component.dart | 2 +- lib/src/bones_ui_generator.dart | 4 ++-- pubspec.yaml | 8 ++++---- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43f0cf6..3ded364 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 3.0.0-beta.2 + +- web_utils: ^1.0.6 +- dom_tools: ^3.0.0-beta.4 +- dom_builder: ^3.0.0-beta.2 + ## 3.0.0-beta.1 - CI: test with `dart2js` and `dart2wasm` (on Chrome). diff --git a/lib/src/bones_ui.dart b/lib/src/bones_ui.dart index 1ecceb0..666a196 100644 --- a/lib/src/bones_ui.dart +++ b/lib/src/bones_ui.dart @@ -1,3 +1,3 @@ class BonesUI { - static const String version = '3.0.0-beta.1'; + static const String version = '3.0.0-beta.2'; } diff --git a/lib/src/bones_ui_component.dart b/lib/src/bones_ui_component.dart index 2449294..bd5e73d 100644 --- a/lib/src/bones_ui_component.dart +++ b/lib/src/bones_ui_component.dart @@ -1637,7 +1637,7 @@ abstract class UIComponent extends UIEventHandler { /// Renders the elements of this component. /// /// Accepted return types: - /// - `dart:html` [UINode] and [UIElement]. + /// - `web` [UINode] and [UIElement]. /// - [DIVElement], [DOMNode], [AsDOMElement] and [AsDOMNode]. /// - [Future]. /// - [UIAsyncContent]. diff --git a/lib/src/bones_ui_generator.dart b/lib/src/bones_ui_generator.dart index e1e526c..dfb9066 100644 --- a/lib/src/bones_ui_generator.dart +++ b/lib/src/bones_ui_generator.dart @@ -313,7 +313,7 @@ class UIComponentDOMContext extends DOMContext { } /// A [DOMGenerator] (from package `dom_builder`) -/// able to generate [UIElement] (from `dart:html`). +/// able to generate [UIElement] (from `web`). class UIDOMGenerator extends DOMGeneratorWebImpl { UIDOMGenerator() { registerElementGenerator(BUIElementGenerator()); @@ -494,7 +494,7 @@ class UIDOMGenerator extends DOMGeneratorWebImpl { if (ok && child2 != null && child2.isNotEmpty) { var uiRoot = UIRoot.getInstance(); - for (var element in child2.whereType()) { + for (var element in child2.whereElement()) { var uiComponent = uiRoot! .getUIComponentByContent(element, includePurgedEntries: true); if (uiComponent != null) { diff --git a/pubspec.yaml b/pubspec.yaml index 6ac35a0..30c23c0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: bones_ui description: Bones_UI - An intuitive and user-friendly Web User Interface framework for Dart. -version: 3.0.0-beta.1 +version: 3.0.0-beta.2 homepage: https://github.com/Colossus-Services/bones_ui environment: @@ -12,11 +12,11 @@ executables: dependencies: js_interop_utils: ^1.0.5 - web_utils: ^1.0.5 + web_utils: ^1.0.6 web: ^1.1.0 intl_messages: ^2.3.4 - dom_tools: ^3.0.0-beta.3 - dom_builder: ^3.0.0-beta.1 + dom_tools: ^3.0.0-beta.4 + dom_builder: ^3.0.0-beta.2 #json_render: ^2.1.0 #json_object_mapper: ^2.0.1 swiss_knife: ^3.2.3 From 740704bad7030ba4c5bcb2935996592d00040b19 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sat, 22 Feb 2025 19:24:05 -0300 Subject: [PATCH 05/64] v3.0.0-beta.3 - web_utils: ^1.0.7 - mercury_client: ^2.2.5 --- CHANGELOG.md | 5 +++++ lib/src/bones_ui.dart | 2 +- lib/src/bones_ui_test_tools.dart | 4 ++-- pubspec.yaml | 6 +++--- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ded364..61cd95d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 3.0.0-beta.3 + +- web_utils: ^1.0.7 +- mercury_client: ^2.2.5 + ## 3.0.0-beta.2 - web_utils: ^1.0.6 diff --git a/lib/src/bones_ui.dart b/lib/src/bones_ui.dart index 666a196..d504f6e 100644 --- a/lib/src/bones_ui.dart +++ b/lib/src/bones_ui.dart @@ -1,3 +1,3 @@ class BonesUI { - static const String version = '3.0.0-beta.2'; + static const String version = '3.0.0-beta.3'; } diff --git a/lib/src/bones_ui_test_tools.dart b/lib/src/bones_ui_test_tools.dart index 71f1daa..bf02b42 100644 --- a/lib/src/bones_ui_test_tools.dart +++ b/lib/src/bones_ui_test_tools.dart @@ -10,7 +10,7 @@ import 'package:test/test.dart' as pkg_test; import 'package:test/test.dart'; // ignore: implementation_imports import 'package:test_api/src/backend/invoker.dart' as pkg_test_invoker; -import 'package:web_utils/web_utils.dart' as dart_html; +import 'package:web_utils/web_utils.dart' as web; import 'bones_ui_web.dart'; @@ -695,7 +695,7 @@ abstract class UITestChain< bool get isNotNull => element != null; UITestChainNode get document => UITestChainNode( - testChainRoot, dart_html.document.documentElement!, this as T); + testChainRoot, web.document.documentElement!, this as T); T exists() => expect(element, pkg_test.isNotNull, reason: "Null element ($E)"); diff --git a/pubspec.yaml b/pubspec.yaml index 30c23c0..29b209f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: bones_ui description: Bones_UI - An intuitive and user-friendly Web User Interface framework for Dart. -version: 3.0.0-beta.2 +version: 3.0.0-beta.3 homepage: https://github.com/Colossus-Services/bones_ui environment: @@ -12,7 +12,7 @@ executables: dependencies: js_interop_utils: ^1.0.5 - web_utils: ^1.0.6 + web_utils: ^1.0.7 web: ^1.1.0 intl_messages: ^2.3.4 dom_tools: ^3.0.0-beta.4 @@ -21,7 +21,7 @@ dependencies: #json_object_mapper: ^2.0.1 swiss_knife: ^3.2.3 statistics: ^1.1.3 - mercury_client: ^2.2.4 + mercury_client: ^2.2.5 dynamic_call: ^2.0.1 project_template: ^1.1.0 resource_portable: ^3.1.0 From b45ca19f65a7f249d99bd21db01701394163a261 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sat, 22 Feb 2025 19:24:54 -0300 Subject: [PATCH 06/64] v3.0.0-beta.3 - web_utils: ^1.0.7 - mercury_client: ^2.2.5 --- lib/src/bones_ui_test_tools.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/bones_ui_test_tools.dart b/lib/src/bones_ui_test_tools.dart index bf02b42..f4e3dcd 100644 --- a/lib/src/bones_ui_test_tools.dart +++ b/lib/src/bones_ui_test_tools.dart @@ -694,8 +694,8 @@ abstract class UITestChain< bool get isNotNull => element != null; - UITestChainNode get document => UITestChainNode( - testChainRoot, web.document.documentElement!, this as T); + UITestChainNode get document => + UITestChainNode(testChainRoot, web.document.documentElement!, this as T); T exists() => expect(element, pkg_test.isNotNull, reason: "Null element ($E)"); From c18deb3aa0ec5c0643aa55050c1872c95e5b61ff Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 23 Feb 2025 18:18:29 -0300 Subject: [PATCH 07/64] v3.0.0-beta.4 - js_interop_utils: ^1.0.6 --- CHANGELOG.md | 4 ++++ lib/src/bones_ui.dart | 2 +- pubspec.yaml | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61cd95d..164156c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.0.0-beta.4 + +- js_interop_utils: ^1.0.6 + ## 3.0.0-beta.3 - web_utils: ^1.0.7 diff --git a/lib/src/bones_ui.dart b/lib/src/bones_ui.dart index d504f6e..244e725 100644 --- a/lib/src/bones_ui.dart +++ b/lib/src/bones_ui.dart @@ -1,3 +1,3 @@ class BonesUI { - static const String version = '3.0.0-beta.3'; + static const String version = '3.0.0-beta.4'; } diff --git a/pubspec.yaml b/pubspec.yaml index 29b209f..59f03de 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: bones_ui description: Bones_UI - An intuitive and user-friendly Web User Interface framework for Dart. -version: 3.0.0-beta.3 +version: 3.0.0-beta.4 homepage: https://github.com/Colossus-Services/bones_ui environment: @@ -11,7 +11,7 @@ executables: bones_ui_test: dependencies: - js_interop_utils: ^1.0.5 + js_interop_utils: ^1.0.6 web_utils: ^1.0.7 web: ^1.1.0 intl_messages: ^2.3.4 From f4d026cf34b6ed500c77f5a05b7ca88ba206af04 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Mon, 24 Feb 2025 01:08:53 -0300 Subject: [PATCH 08/64] v3.0.0-beta.5 - web_utils: ^1.0.9 - intl_messages: ^3.0.0-beta.1 - dom_tools: ^3.0.0-beta.5 - swiss_knife: ^3.3.0 - statistics: ^1.2.0 - resource_portable: ^3.1.2 - intl: ^0.20.2 --- CHANGELOG.md | 10 ++++++++++ lib/src/bones_ui.dart | 2 +- lib/src/bones_ui_log.dart | 5 +++-- pubspec.yaml | 16 ++++++++-------- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 164156c..fd15d1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## 3.0.0-beta.5 + +- web_utils: ^1.0.9 +- intl_messages: ^3.0.0-beta.1 +- dom_tools: ^3.0.0-beta.5 +- swiss_knife: ^3.3.0 +- statistics: ^1.2.0 +- resource_portable: ^3.1.2 +- intl: ^0.20.2 + ## 3.0.0-beta.4 - js_interop_utils: ^1.0.6 diff --git a/lib/src/bones_ui.dart b/lib/src/bones_ui.dart index 244e725..f8334eb 100644 --- a/lib/src/bones_ui.dart +++ b/lib/src/bones_ui.dart @@ -1,3 +1,3 @@ class BonesUI { - static const String version = '3.0.0-beta.4'; + static const String version = '3.0.0-beta.5'; } diff --git a/lib/src/bones_ui_log.dart b/lib/src/bones_ui_log.dart index 69ae04a..efde9a2 100644 --- a/lib/src/bones_ui_log.dart +++ b/lib/src/bones_ui_log.dart @@ -44,8 +44,9 @@ class _Logger { } } - void _printError(msg) { - window.console.error(msg); + void _printError(Object? msg) { + if (msg == null) return ; + window.console.error(msg.jsify()); } Object? _format(Object? msg, [Object? error]) { diff --git a/pubspec.yaml b/pubspec.yaml index 59f03de..ca1bae1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: bones_ui description: Bones_UI - An intuitive and user-friendly Web User Interface framework for Dart. -version: 3.0.0-beta.4 +version: 3.0.0-beta.5 homepage: https://github.com/Colossus-Services/bones_ui environment: @@ -12,23 +12,23 @@ executables: dependencies: js_interop_utils: ^1.0.6 - web_utils: ^1.0.7 + web_utils: ^1.0.9 web: ^1.1.0 - intl_messages: ^2.3.4 - dom_tools: ^3.0.0-beta.4 + intl_messages: ^3.0.0-beta.1 + dom_tools: ^3.0.0-beta.5 dom_builder: ^3.0.0-beta.2 #json_render: ^2.1.0 #json_object_mapper: ^2.0.1 - swiss_knife: ^3.2.3 - statistics: ^1.1.3 + swiss_knife: ^3.3.0 + statistics: ^1.2.0 mercury_client: ^2.2.5 dynamic_call: ^2.0.1 project_template: ^1.1.0 - resource_portable: ^3.1.0 + resource_portable: ^3.1.2 extended_type: ^2.1.1 expressions: ^0.2.5+2 html_unescape: ^2.0.0 - intl: ^0.19.0 + intl: ^0.20.2 yaml: ^3.1.3 archive: ^3.6.1 collection: ^1.19.1 From 1512148c13b1a2440815e3b56dca2c1a8aca3c11 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Mon, 24 Feb 2025 01:10:17 -0300 Subject: [PATCH 09/64] dart format --- lib/src/bones_ui_log.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/bones_ui_log.dart b/lib/src/bones_ui_log.dart index efde9a2..31e8c69 100644 --- a/lib/src/bones_ui_log.dart +++ b/lib/src/bones_ui_log.dart @@ -45,7 +45,7 @@ class _Logger { } void _printError(Object? msg) { - if (msg == null) return ; + if (msg == null) return; window.console.error(msg.jsify()); } From 530875bbb84135c77d808314473cae75506d828f Mon Sep 17 00:00:00 2001 From: gmpassos Date: Thu, 27 Feb 2025 19:58:10 -0300 Subject: [PATCH 10/64] web: ^1.1.1 --- lib/src/component/capture.dart | 6 +++--- lib/src/component/dialog_edit_image.dart | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/src/component/capture.dart b/lib/src/component/capture.dart index d7c82e3..4ee0d87 100644 --- a/lib/src/component/capture.dart +++ b/lib/src/component/capture.dart @@ -454,7 +454,7 @@ abstract class UICapture extends UIButtonBase implements UIField { var x = (imgW2 - imgW) ~/ 2; var y = (imgH2 - imgH) ~/ 2; - ctx.drawImageScaled( + ctx.drawImage( image, x.toDouble(), y.toDouble(), @@ -471,7 +471,7 @@ abstract class UICapture extends UIButtonBase implements UIField { var maxW = captureMaxWidth ?? imgW; var maxH = captureMaxHeight ?? imgH; - if (imgW <= maxW && imgH <= maxH && identical(imgSrc, image)) { + if (imgW <= maxW && imgH <= maxH && imgSrc == image) { return capturedData; } @@ -495,7 +495,7 @@ abstract class UICapture extends UIButtonBase implements UIField { ctx.imageSmoothingQuality = 'high'; ctx.clearRect(0, 0, canvasW, canvasH); - ctx.drawImageScaled( + ctx.drawImage( imgSrc, 0, 0, diff --git a/lib/src/component/dialog_edit_image.dart b/lib/src/component/dialog_edit_image.dart index d6ed270..25048a8 100644 --- a/lib/src/component/dialog_edit_image.dart +++ b/lib/src/component/dialog_edit_image.dart @@ -404,7 +404,7 @@ class _CanvasEditImage extends ExternalElementNode { var context2d = canvas.context2D; context2d.clearRect(0, 0, canvasWidth, canvasHeight); - context2d.drawImageScaled( + context2d.drawImage( img, renderX.toDouble(), renderY.toDouble(), @@ -463,7 +463,7 @@ class _CanvasEditImage extends ExternalElementNode { context2d.imageSmoothingEnabled = true; context2d.imageSmoothingQuality = 'high'; - context2d.drawImageScaled( + context2d.drawImage( img, x.toDouble(), y.toDouble(), From 8c927d6eadb7be71dce428befbdbe2a29bb7b653 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Thu, 27 Feb 2025 20:01:13 -0300 Subject: [PATCH 11/64] web: ^1.1.1 --- lib/src/bones_ui_log.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/bones_ui_log.dart b/lib/src/bones_ui_log.dart index 31e8c69..79dfdc3 100644 --- a/lib/src/bones_ui_log.dart +++ b/lib/src/bones_ui_log.dart @@ -172,11 +172,11 @@ class UIConsole { void _enable() { _enabled = true; - window.sessionStorage[sessionKeyUIConsoleEnabled] = '1'; + window.sessionStorage.setItem(sessionKeyUIConsoleEnabled, '1'); } static void checkAutoEnable() { - if (window.sessionStorage[sessionKeyUIConsoleEnabled] == '1') { + if (window.sessionStorage.getItem(sessionKeyUIConsoleEnabled) == '1') { displayButton(); } } @@ -188,7 +188,7 @@ class UIConsole { void _disable() { _enabled = false; - window.sessionStorage[sessionKeyUIConsoleEnabled] = '0'; + window.sessionStorage.setItem(sessionKeyUIConsoleEnabled, '0'); } static List logs() { From a96459c8248a34307af399143af626c4bf56d9e5 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Thu, 27 Feb 2025 20:01:37 -0300 Subject: [PATCH 12/64] - Avoids using `identical` with `Element` (`JSObject`) to avoid inconsistencies in Wasm. --- lib/src/bones_ui_component.dart | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/src/bones_ui_component.dart b/lib/src/bones_ui_component.dart index bd5e73d..5501a19 100644 --- a/lib/src/bones_ui_component.dart +++ b/lib/src/bones_ui_component.dart @@ -183,7 +183,7 @@ abstract class UIComponent extends UIEventHandler { void _setContent(HTMLElement content) { var prev = _content; - if (prev != null && !identical(prev, content)) { + if (prev != null && prev != content) { _contentsUIComponents[prev] = null; } @@ -227,13 +227,13 @@ abstract class UIComponent extends UIEventHandler { if (parent == null) throw StateError('Null parent'); if (_content != null) { - if (identical(_parent, parent)) { - if (!identical(_content!.parentElement, _parent)) { - _parent!.append(_content!); + if (_parent == parent) { + if (_content!.parentElement != _parent) { + _parent!.appendChild(_content!); } _resolveParentUIComponent(parentUIComponent ?? parent); return _parent; - } else if (identical(_content!.parentElement, parent)) { + } else if (_content!.parentElement == parent) { _resolveParentUIComponent(parentUIComponent ?? parent); return _parent; } else { @@ -273,7 +273,7 @@ abstract class UIComponent extends UIEventHandler { if (prevParent == null) { var content = _content; - if (identical(content?.parentElement, parentContent)) { + if (content?.parentElement == parentContent) { _parent = parentContent; } } @@ -1133,7 +1133,7 @@ abstract class UIComponent extends UIEventHandler { UIComponent? findUIComponentByContent(UIElement? content) { if (content == null) return null; - if (identical(content, _content)) return this; + if (content == _content) return this; if (_renderedElements == null || _renderedElements!.isEmpty) return null; @@ -1152,7 +1152,7 @@ abstract class UIComponent extends UIEventHandler { } for (var elem in _content!.children.toIterable()) { - if (identical(content, elem)) { + if (content == elem) { return this; } } @@ -1162,10 +1162,10 @@ abstract class UIComponent extends UIEventHandler { UIComponent? findUIComponentByChild(UIElement? child) { if (child == null) return null; - if (identical(child, _content)) return this; + if (child == _content) return this; for (var elem in _content!.children.toIterable()) { - if (identical(child, elem)) { + if (child == elem) { return this; } } @@ -1186,7 +1186,7 @@ abstract class UIComponent extends UIEventHandler { if (uiComp != null) return uiComp; } - var deepChild = findInContentChildDeep((elem) => identical(child, elem)); + var deepChild = findInContentChildDeep((elem) => child == elem); if (deepChild != null) return this; return null; @@ -1307,7 +1307,7 @@ abstract class UIComponent extends UIEventHandler { (clearParent == UIComponentClearParent.onInitialRender && _renderCount == 1)) { // content not added to parent: - if (!identical(content.parentNode, parent)) { + if (content.parentNode != parent) { parent.clear(); parent.appendChild(content); } @@ -1317,7 +1317,7 @@ abstract class UIComponent extends UIEventHandler { var containsContent = false; for (var node in nodes) { - if (identical(node, content)) { + if (node == content) { containsContent = true; } else { node.remove(); @@ -1330,7 +1330,7 @@ abstract class UIComponent extends UIEventHandler { } } } else { - var appended = identical(content.parentNode, parent); + var appended = content.parentNode == parent; if (!appended) { parent.append(content); } From 70f8e10213da739b60eb71d44687054b9147d7db Mon Sep 17 00:00:00 2001 From: gmpassos Date: Thu, 27 Feb 2025 20:02:36 -0300 Subject: [PATCH 13/64] v3.0.0-beta.6 - Avoids using `identical` with `Element` (`JSObject`) to avoid inconsistencies in Wasm. - dom_tools: ^3.0.0-beta.7 - dom_builder: ^3.0.0-beta.3 - web: ^1.1.1 - mercury_client: ^2.3.0 --- CHANGELOG.md | 9 +++++++++ pubspec.yaml | 10 +++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd15d1a..8139bc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## 3.0.0-beta.6 + +- Avoids using `identical` with `Element` (`JSObject`) to avoid inconsistencies in Wasm. + +- dom_tools: ^3.0.0-beta.7 +- dom_builder: ^3.0.0-beta.3 +- web: ^1.1.1 +- mercury_client: ^2.3.0 + ## 3.0.0-beta.5 - web_utils: ^1.0.9 diff --git a/pubspec.yaml b/pubspec.yaml index ca1bae1..18363be 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: bones_ui description: Bones_UI - An intuitive and user-friendly Web User Interface framework for Dart. -version: 3.0.0-beta.5 +version: 3.0.0-beta.6 homepage: https://github.com/Colossus-Services/bones_ui environment: @@ -13,15 +13,15 @@ executables: dependencies: js_interop_utils: ^1.0.6 web_utils: ^1.0.9 - web: ^1.1.0 + web: ^1.1.1 intl_messages: ^3.0.0-beta.1 - dom_tools: ^3.0.0-beta.5 - dom_builder: ^3.0.0-beta.2 + dom_tools: ^3.0.0-beta.7 + dom_builder: ^3.0.0-beta.3 #json_render: ^2.1.0 #json_object_mapper: ^2.0.1 swiss_knife: ^3.3.0 statistics: ^1.2.0 - mercury_client: ^2.2.5 + mercury_client: ^2.3.0 dynamic_call: ^2.0.1 project_template: ^1.1.0 resource_portable: ^3.1.2 From b1050845fffb28779cb4f2b95157c4639bf5409d Mon Sep 17 00:00:00 2001 From: gmpassos Date: Thu, 27 Feb 2025 20:48:29 -0300 Subject: [PATCH 14/64] clean code --- lib/src/bones_ui_async_content.dart | 18 +++++++++--------- lib/src/bones_ui_base.dart | 2 +- lib/src/bones_ui_explorer.dart | 19 ++++++++++++------- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/lib/src/bones_ui_async_content.dart b/lib/src/bones_ui_async_content.dart index 8861cd6..6bfdbd6 100644 --- a/lib/src/bones_ui_async_content.dart +++ b/lib/src/bones_ui_async_content.dart @@ -13,15 +13,15 @@ import 'bones_ui_utils.dart'; typedef AsyncContentProvider = Future? Function(); class _Content { - final dynamic content; + final Object? content; int status; _Content(this.content, [this.status = 0]); - dynamic _contentForDOM; + Object? _contentForDOM; - dynamic get contentForDOM { + Object? get contentForDOM { _contentForDOM ??= _ensureElementForDOM(content); return _contentForDOM; } @@ -31,11 +31,11 @@ class _Content { class UIAsyncContent { AsyncContentProvider? _asyncContentProvider; - Future? _asyncContentFuture; + Future? _asyncContentFuture; - final dynamic _loadingContent; + final Object? _loadingContent; - final dynamic _errorContent; + final Object? _errorContent; final Duration? _refreshInterval; @@ -140,7 +140,7 @@ class UIAsyncContent { if (_errorContent is Function) { final errorContent = _errorContent; Object? content; - if (errorContent is Function(dynamic e)) { + if (errorContent is Function(Object? e)) { content = errorContent(error); } else if (errorContent is Function()) { content = errorContent(); @@ -153,7 +153,7 @@ class UIAsyncContent { } } - static dynamic _normalizeContent(dynamic content) { + static dynamic _normalizeContent(Object? content) { if (content is Function) { return content; } else { @@ -365,7 +365,7 @@ class UIAsyncContent { } } -dynamic _ensureElementForDOM(Object? element) { +Object? _ensureElementForDOM(Object? element) { if (_isElementForDOM(element)) { return element; } diff --git a/lib/src/bones_ui_base.dart b/lib/src/bones_ui_base.dart index 22305f0..85d7705 100644 --- a/lib/src/bones_ui_base.dart +++ b/lib/src/bones_ui_base.dart @@ -150,7 +150,7 @@ abstract class UIFieldMap { } class TextProvider { - dynamic _object; + Object? _object; String? _text; diff --git a/lib/src/bones_ui_explorer.dart b/lib/src/bones_ui_explorer.dart index 5488621..ded6b38 100644 --- a/lib/src/bones_ui_explorer.dart +++ b/lib/src/bones_ui_explorer.dart @@ -172,7 +172,7 @@ abstract class ConfigDocument { } class YAMLConfigDocument extends ConfigDocument { - dynamic _document; + Object? _document; YAMLConfigDocument(YamlDocument document) { _document = deepCopy(document.contents.value); @@ -182,13 +182,16 @@ class YAMLConfigDocument extends ConfigDocument { @override dynamic get(String key, [dynamic def]) { - return findKeyValue(_document, [key], true) ?? def; + var document = _document; + if (document is! Map) return def; + return findKeyValue(document, [key], true) ?? def; } YamlNode asYamlNode() { - if (_document is Map) return YamlMap.wrap(_document); - if (_document is List) return YamlList.wrap(_document); - return YamlScalar.wrap(_document); + var document = _document; + if (document is Map) return YamlMap.wrap(document); + if (document is List) return YamlList.wrap(document); + return YamlScalar.wrap(document); } @override @@ -198,7 +201,7 @@ class YAMLConfigDocument extends ConfigDocument { } class JSONConfigDocument extends ConfigDocument { - dynamic _document; + Object? _document; JSONConfigDocument(this._document); @@ -209,7 +212,9 @@ class JSONConfigDocument extends ConfigDocument { @override dynamic get(String key, [dynamic def]) { - return findKeyValue(_document, [key], true) ?? def; + var document = _document; + if (document is! Map) return def; + return findKeyValue(document, [key], true) ?? def; } @override From ede55393b20eaff4429cd1bb2ff1efe1010a5177 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Fri, 28 Feb 2025 21:06:49 -0300 Subject: [PATCH 15/64] v3.0.0-beta.7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✨♻️ refactor(bones_ui_test_tools): - Standardize `selectNonTyped` and rename it to `select`. - Standardize `selectNonTypedAll` and rename it to `selectAll`. - Ensure that `selectTyped` exists where `querySelectorTyped` exists. - web_utils: ^1.0.11 --- CHANGELOG.md | 9 ++ lib/src/bones_ui.dart | 2 +- lib/src/bones_ui_test_tools.dart | 260 +++++++++++++++++++++---------- pubspec.yaml | 4 +- test/bones_ui_test.dart | 2 +- 5 files changed, 187 insertions(+), 90 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 541795b..ccb94b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## 3.0.0-beta.7 + +✨♻️ refactor(bones_ui_test_tools): + - Standardize `selectNonTyped` and rename it to `select`. + - Standardize `selectNonTypedAll` / `selectAllNonTyped` and rename it to `selectAll`. + - Ensure that `selectTyped` exists where `querySelectorTyped` exists. + +- web_utils: ^1.0.11 + ## 3.0.0-beta.6 - Avoids using `identical` with `Element` (`JSObject`) to avoid inconsistencies in Wasm. diff --git a/lib/src/bones_ui.dart b/lib/src/bones_ui.dart index eb0745d..7cf52cf 100644 --- a/lib/src/bones_ui.dart +++ b/lib/src/bones_ui.dart @@ -1,3 +1,3 @@ class BonesUI { - static const String version = '3.0.0-beta.6'; + static const String version = '3.0.0-beta.7'; } diff --git a/lib/src/bones_ui_test_tools.dart b/lib/src/bones_ui_test_tools.dart index 186851c..bee8fec 100644 --- a/lib/src/bones_ui_test_tools.dart +++ b/lib/src/bones_ui_test_tools.dart @@ -796,7 +796,12 @@ abstract class UITestChain< .thenChain((_) => this as T); /// Alias to [Element.querySelector] or [UIComponent.querySelector]. - UITestChainNode querySelector(String? selectors, + UITestChainNode select(String? selectors, + {bool expected = false}) => + querySelectorNonTyped(selectors, expected: expected); + + /// Alias to [Element.querySelector] or [UIComponent.querySelector]. + UITestChainNode querySelectorNonTyped(String? selectors, {bool expected = false}) { var e = element; @@ -819,7 +824,43 @@ abstract class UITestChain< return UITestChainNode(testChainRoot, elem, this as T); } - /// Alias to [UIComponent.querySelectorAll]. + /// Alias to [Element.querySelector] or [UIComponent.querySelector], filtered by [webType]. + UITestChainNode selectTyped( + String? selectors, web.Web webType, + {bool expected = false}) => + querySelectorTyped(selectors, webType, expected: expected); + + /// Alias to [Element.querySelector] or [UIComponent.querySelector], filtered by [webType]. + UITestChainNode querySelectorTyped( + String? selectors, web.Web webType, + {bool expected = false}) { + var e = element; + + W? elem; + if (e is UIComponent) { + elem = e.querySelectorTyped(selectors, webType); + } else if (e.isElement) { + elem = selectors != null + ? (e.asJSAny as Element).querySelectorTyped(selectors, webType) + : null; + } else { + elem = uiRoot.querySelectorTyped(selectors, webType); + } + + if (expected) { + expect(elem, pkg_test.isNotNull, + reason: "Can't find selected element: $selectors"); + } + + return UITestChainNode(testChainRoot, elem, this as T); + } + + /// Alias to [Element.querySelectorAll] or [UIComponent.querySelectorAll]. + UITestChainNode, T> selectAll(String? selectors, + {bool expected = false}) => + querySelectorAllNonTyped(selectors, expected: expected); + + /// Alias to [Element.querySelectorAll] or [UIComponent.querySelectorAll]. UITestChainNode, T> querySelectorAllNonTyped( String? selectors, {bool expected = false}) { @@ -850,6 +891,12 @@ abstract class UITestChain< return UITestChainNode(testChainRoot, elems, this as T); } + /// Alias to [UIComponent.querySelectorAll]. + UITestChainNode, T> selectAllTyped( + String? selectors, Web webType, + {bool expected = false}) => + querySelectorAllTyped(selectors, webType, expected: expected); + /// Alias to [UIComponent.querySelectorAll]. UITestChainNode, T> querySelectorAllTyped( String? selectors, Web webType, @@ -885,31 +932,22 @@ abstract class UITestChain< return UITestChainNode(testChainRoot, elems, this as T); } - /// Alias to [querySelector]. - UITestChainNode select(String? selectors, - {bool expected = false}) => - querySelector(selectors, expected: expected); + /// Same as [selectTyped] with `expected: true`, but [W] is non-null. + UITestChainNode selectTypedExpected( + String? selectors, web.Web webType) { + var o = querySelectorTyped(selectors, webType, expected: true); + return UITestChainNode(o.testChainRoot, o.element!, o.parent); + } - /// Alias to [querySelector]. + /// Alias to [select] with `expected: true`. UITestChainNode selectExpected(String? selectors) { - var o = querySelector(selectors, expected: true); + var o = select(selectors, expected: true); return UITestChainNode( o.testChainRoot, o.element!, o.parent); } - /// Alias to [querySelectorAll]. - UITestChainNode, T> selectAllNonTyped(String? selectors, - {bool expected = false}) => - querySelectorAllNonTyped(selectors, expected: expected); - - /// Alias to [querySelectorAll]. - UITestChainNode, T> selectAllTyped( - String? selectors, Web webType, - {bool expected = false}) => - querySelectorAllTyped(selectors, webType, expected: expected); - /// Alias to [querySelectorAll] + `where`. - UITestChainNode, T> selectWhereNonTyped( + UITestChainNode, T> selectWhere( String? selectors, bool Function(Element element) test, {bool expected = false}) { var sel = querySelectorAllNonTyped(selectors); @@ -939,7 +977,7 @@ abstract class UITestChain< } /// Alias to [querySelectorAll] + `firstWhereOrNull`. - UITestChainNode selectFirstWhereNonTyped( + UITestChainNode selectFirstWhere( String? selectors, bool Function(Element element) test, {bool expected = false}) { var sel = querySelectorAllNonTyped(selectors); @@ -969,7 +1007,7 @@ abstract class UITestChain< } /// Alias to [sleepUntilElement] + [querySelectorAll] + `where`. - Future, T>> selectWhereUntilNonTyped( + Future, T>> selectWhereUntil( String? selectors, bool Function(Element element) test, {int? timeoutMs, int? intervalMs, @@ -982,13 +1020,11 @@ abstract class UITestChain< minMs: minMs, mapper: mapper, validator: (elems) => elems.any(test)) - .selectWhereNonTyped(selectors, test) + .selectWhere(selectors, test) .thenChain((o) { if (expected) { - var sel = selectAllNonTyped(selectors) - .element - .map((e) => e.simplify()) - .toList(); + var sel = + selectAll(selectors).element.map((e) => e.simplify()).toList(); expect(o.element, pkg_test.isNotEmpty, reason: "Can't find any selected element: $selectors -> $sel"); @@ -1027,7 +1063,7 @@ abstract class UITestChain< }); /// Alias to [sleepUntilElement] + [querySelectorAll] + `firstWhereOrNull`. - Future> selectFirstWhereUntilNonTyped( + Future> selectFirstWhereUntil( String? selectors, bool Function(Element element) test, {int? timeoutMs, int? intervalMs, @@ -1039,14 +1075,12 @@ abstract class UITestChain< minMs: minMs, mapper: mapper, validator: (elems) => elems.any(test)) - .selectFirstWhereNonTyped(selectors, test) + .selectFirstWhere(selectors, test) .thenChain((o) { var elem = o.element; if (elem == null) { - var sel = selectAllNonTyped(selectors) - .element - .map((e) => e.simplify()) - .toList(); + var sel = + selectAll(selectors).element.map((e) => e.simplify()).toList(); expect(elem, pkg_test.isNotNull, reason: "Can't find selected element: $selectors -> $sel"); @@ -1086,7 +1120,7 @@ abstract class UITestChain< }); /// Alias to [sleepUntilElement] + [querySelectorAll] - Future> selectUntilNonTyped(String? selectors, + Future> selectUntil(String? selectors, {int? timeoutMs, int? intervalMs, int? minMs, @@ -1569,9 +1603,15 @@ extension FutureUITestChainExtension< Future sleep({int? frames, int? ms}) => then((o) => o.sleep(frames: frames, ms: ms)); - Future> querySelector(String? selectors, + Future> querySelectorNonTyped( + String? selectors, {bool expected = false}) => - thenChain((o) => o.querySelector(selectors, expected: expected)); + thenChain((o) => o.querySelectorNonTyped(selectors, expected: expected)); + + Future> querySelectorTyped( + String? selectors, web.Web webType, {bool expected = false}) => + thenChain( + (o) => o.querySelectorTyped(selectors, webType, expected: expected)); Future, T>> querySelectorAllNonTyped( String? selectors, @@ -1589,25 +1629,31 @@ extension FutureUITestChainExtension< {bool expected = false}) => thenChain((o) => o.select(selectors, expected: expected)); + Future> selectTyped( + String? selectors, web.Web webType, {bool expected = false}) => + thenChain( + (o) => o.selectTyped(selectors, webType, expected: expected)); + Future> selectExpected(String? selectors) => thenChain((o) => o.selectExpected(selectors)); - Future, T>> selectAllNonTyped( - String? selectors, + Future> selectTypedExpected( + String? selectors, web.Web webType) => + thenChain((o) => o.selectTypedExpected(selectors, webType)); + + Future, T>> selectAll(String? selectors, {bool expected = false}) => - thenChain((o) => o.selectAllNonTyped(selectors, expected: expected)); + thenChain((o) => o.selectAll(selectors, expected: expected)); Future, T>> selectAllTyped( String? selectors, Web webType, {bool expected = false}) => thenChain( (o) => o.selectAllTyped(selectors, webType, expected: expected)); - Future, T>> - selectWhereNonTyped( - String? selectors, bool Function(Element element) test, - {bool expected = false}) => - thenChain((o) => - o.selectWhereNonTyped(selectors, test, expected: expected)); + Future, T>> selectWhere( + String? selectors, bool Function(Element element) test, + {bool expected = false}) => + thenChain((o) => o.selectWhere(selectors, test, expected: expected)); Future, T>> selectWhereTyped( String? selectors, @@ -1617,11 +1663,10 @@ extension FutureUITestChainExtension< thenChain((o) => o.selectWhereTyped(selectors, webType, test, expected: expected)); - Future> selectFirstWhereNonTyped( + Future> selectFirstWhere( String? selectors, bool Function(Element element) test, {bool expected = false}) => - thenChain((o) => - o.selectFirstWhereNonTyped(selectors, test, expected: expected)); + thenChain((o) => o.selectFirstWhere(selectors, test, expected: expected)); Future> selectFirstWhereTyped( String? selectors, @@ -1631,14 +1676,14 @@ extension FutureUITestChainExtension< thenChain((o) => o.selectFirstWhereTyped(selectors, webType, test, expected: expected)); - Future, T>> selectWhereUntilNonTyped( + Future, T>> selectWhereUntil( String? selectors, bool Function(Element element) test, {int? timeoutMs, int? intervalMs, int? minMs, Iterable Function(List elems)? mapper, bool expected = false}) => - thenChain((o) => o.selectWhereUntilNonTyped(selectors, test, + thenChain((o) => o.selectWhereUntil(selectors, test, timeoutMs: timeoutMs, intervalMs: intervalMs, minMs: minMs, @@ -1660,13 +1705,13 @@ extension FutureUITestChainExtension< mapper: mapper, expected: expected)); - Future> selectFirstWhereUntilNonTyped( + Future> selectFirstWhereUntil( String? selectors, bool Function(Element element) test, {int? timeoutMs, int? intervalMs, int? minMs, Iterable Function(List elems)? mapper}) => - thenChain((o) => o.selectFirstWhereUntilNonTyped(selectors, test, + thenChain((o) => o.selectFirstWhereUntil(selectors, test, timeoutMs: timeoutMs, intervalMs: intervalMs, minMs: minMs, @@ -1763,11 +1808,17 @@ extension FutureUITestChainNodeExtension< Future> elementAs() => thenChain((o) => o.elementAs()); - Future> querySelector(String? selectors, + Future> querySelectorNonTyped( + String? selectors, {bool expected = false}) => - thenChain((o) => o.querySelector(selectors, expected: expected) + thenChain((o) => o.querySelectorNonTyped(selectors, expected: expected) as UITestChainNode); + Future> querySelectorTyped( + String? selectors, web.Web webType, {bool expected = false}) => + thenChain((o) => o.querySelectorTyped(selectors, webType, + expected: expected) as UITestChainNode); + Future, T>> querySelectorAllNonTyped( String? selectors, {bool expected = false}) => @@ -1785,14 +1836,23 @@ extension FutureUITestChainNodeExtension< thenChain((o) => o.select(selectors, expected: expected) as UITestChainNode); + Future> selectTyped( + String? selectors, web.Web webType, {bool expected = false}) => + thenChain((o) => o.selectTyped(selectors, webType, expected: expected) + as UITestChainNode); + Future> selectExpected(String? selectors) => thenChain( (o) => o.selectExpected(selectors) as UITestChainNode); - Future, T>> selectAllNonTyped( - String? selectors, + Future> selectTypedExpected( + String? selectors, web.Web webType) => + thenChain((o) => o.selectTypedExpected(selectors, webType) + as UITestChainNode); + + Future, T>> selectAll(String? selectors, {bool expected = false}) => - thenChain((o) => o.selectAllNonTyped(selectors, expected: expected) + thenChain((o) => o.selectAll(selectors, expected: expected) as UITestChainNode, T>); Future, T>> selectAllTyped( @@ -1800,11 +1860,11 @@ extension FutureUITestChainNodeExtension< thenChain((o) => o.selectAllTyped(selectors, webType, expected: expected) as UITestChainNode, T>); - Future, T>> selectWhereNonTyped( + Future, T>> selectWhere( String? selectors, bool Function(Element element) test, {bool expected = false}) => - thenChain((o) => o.selectWhereNonTyped(selectors, test, - expected: expected) as UITestChainNode, T>); + thenChain((o) => o.selectWhere(selectors, test, expected: expected) + as UITestChainNode, T>); Future, T>> selectWhereTyped( String? selectors, @@ -1814,11 +1874,11 @@ extension FutureUITestChainNodeExtension< thenChain((o) => o.selectWhereTyped(selectors, webType, test, expected: expected) as UITestChainNode, T>); - Future> selectFirstWhereNonTyped( + Future> selectFirstWhere( String? selectors, bool Function(Element element) test, {bool expected = false}) => - thenChain((o) => o.selectFirstWhereNonTyped(selectors, test, - expected: expected) as UITestChainNode); + thenChain((o) => o.selectFirstWhere(selectors, test, expected: expected) + as UITestChainNode); Future> selectFirstWhereTyped( String? selectors, @@ -1828,14 +1888,14 @@ extension FutureUITestChainNodeExtension< thenChain((o) => o.selectFirstWhereTyped(selectors, webType, test, expected: expected) as UITestChainNode); - Future, T>> selectWhereUntilNonTyped( + Future, T>> selectWhereUntil( String? selectors, bool Function(Element element) test, {int? timeoutMs, int? intervalMs, int? minMs, Iterable Function(List elems)? mapper, bool expected = false}) => - thenChain((o) => o.selectWhereUntilNonTyped(selectors, test, + thenChain((o) => o.selectWhereUntil(selectors, test, timeoutMs: timeoutMs, intervalMs: intervalMs, minMs: minMs, @@ -1857,14 +1917,14 @@ extension FutureUITestChainNodeExtension< mapper: mapper, expected: expected) as UITestChainNode, T>); - Future> selectFirstWhereUntilNonTyped( + Future> selectFirstWhereUntil( String? selectors, bool Function(Element element) test, {int? timeoutMs, int? intervalMs, int? minMs, Iterable Function(List elems)? mapper}) => thenChain((o) => o - .selectFirstWhereUntilNonTyped(selectors, test, + .selectFirstWhereUntil(selectors, test, timeoutMs: timeoutMs, intervalMs: intervalMs, minMs: minMs, @@ -1886,13 +1946,13 @@ extension FutureUITestChainNodeExtension< mapper: mapper) .then((o) => o as UITestChainNode)); - Future> selectUntilNonTyped(String? selectors, + Future> selectUntil(String? selectors, {int? timeoutMs, int? intervalMs, int? minMs, Iterable Function(List elems)? mapper}) => thenChain((o) => o - .selectUntilNonTyped(selectors, + .selectUntil(selectors, timeoutMs: timeoutMs, intervalMs: intervalMs, minMs: minMs, @@ -1987,12 +2047,24 @@ extension TestFutureExtension on Future { } extension TestElementExtension on Element? { - Element? select(String? selectors) { + Element? querySelectorNonTyped(String? selectors) { var self = this; if (self == null || selectors == null || selectors.isEmpty) return null; return self.querySelector(selectors); } + W? querySelectorTyped( + String? selectors, web.Web webType) { + var self = this; + if (self == null || selectors == null || selectors.isEmpty) return null; + return self.querySelectorTyped(selectors, webType); + } + + Element? select(String? selectors) => querySelectorNonTyped(selectors); + + W? selectTyped(String? selectors, web.Web webType) => + querySelectorTyped(selectors, webType); + Element selectExpected(String? selectors) { var self = this; var e = selectors != null && selectors.isNotEmpty @@ -2004,7 +2076,19 @@ extension TestElementExtension on Element? { return e; } - List selectAllNonTyped(String? selectors) { + W selectTypedExpected( + String? selectors, web.Web webType) { + var self = this; + var e = selectors != null && selectors.isNotEmpty + ? self?.querySelectorTyped(selectors, webType) + : null; + if (e == null) { + throw TestFailure("Can't find element: `$selectors` ($webType)"); + } + return e; + } + + List selectAll(String? selectors) { var self = this; if (self == null || selectors == null || selectors.isEmpty) return []; return self.querySelectorAll(selectors).toElements(); @@ -2024,13 +2108,17 @@ extension TestFutureElementExtension on Future { }); Future select(String? selectors) => - thenChain((e) => e.selectExpected(selectors)); + thenChain((e) => e.select(selectors)); + + Future selectTyped( + String? selectors, web.Web webType) => + thenChain((e) => e.selectTyped(selectors, webType)); Future selectExpected(String? selectors) => thenChain((e) => e.selectExpected(selectors)); - Future> selectAllNonTyped(String? selectors) => - then((e) => e.selectAllNonTyped(selectors)); + Future> selectAll(String? selectors) => + then((e) => e.selectAll(selectors)); Future> selectAllTyped( String? selectors, Web webType) => @@ -2038,7 +2126,7 @@ extension TestFutureElementExtension on Future { } extension TestUIComponentNullableExtension on UIComponent? { - Element? selectNonTyped(String? selectors) { + Element? select(String? selectors) { var self = this; if (self == null || selectors == null || selectors.isEmpty) return null; return self.querySelectorNonTyped(selectors); @@ -2050,7 +2138,7 @@ extension TestUIComponentNullableExtension on UIComponent? { return self.querySelectorTyped(selectors, webType); } - Element selectExpectedNonTyped(String? selectors) { + Element selectExpected(String? selectors) { var self = this; var e = selectors != null && selectors.isNotEmpty ? self?.querySelectorNonTyped(selectors) @@ -2073,7 +2161,7 @@ extension TestUIComponentNullableExtension on UIComponent? { return e; } - List selectAllNonTyped(String? selectors) { + List selectAll(String? selectors) { var self = this; if (self == null || selectors == null || selectors.isEmpty) return []; return self.querySelectorAllNonTyped(selectors); @@ -2114,22 +2202,22 @@ extension TestFutureUIComponentExtension on Future { return elem; }); - Future selectNonTyped(String? selectors) => - then((e) => e.selectNonTyped(selectors)); + Future select(String? selectors) => + then((e) => e.select(selectors)); Future selectTyped( String? selectors, Web webType) => then((e) => e.selectTyped(selectors, webType)); - Future selectExpectedNonTyped(String? selectors) => - thenChain((e) => e.selectExpectedNonTyped(selectors)); + Future selectExpected(String? selectors) => + thenChain((e) => e.selectExpected(selectors)); Future selectExpectedTyped( String? selectors, Web webType) => thenChain((e) => e.selectExpectedTyped(selectors, webType)); - Future> selectAllNonTyped(String? selectors) => - then((e) => e.selectAllNonTyped(selectors)); + Future> selectAll(String? selectors) => + then((e) => e.selectAll(selectors)); Future> selectAllTyped( String? selectors, Web webType) => @@ -2456,10 +2544,10 @@ void _checkbox(Object root, Object? o, bool checked, } Element? _querySelect(Object? elem, String selectors) { - if (elem.isElement) { - return (elem as Element).querySelector(selectors); - } else if (elem is UIComponent) { + if (elem is UIComponent) { return elem.querySelectorNonTyped(selectors); + } else if (elem.isElement) { + return (elem as Element).querySelector(selectors); } else { return null; } diff --git a/pubspec.yaml b/pubspec.yaml index 2d83819..74a9439 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: bones_ui description: Bones_UI - An intuitive and user-friendly Web User Interface framework for Dart. -version: 3.0.0-beta.6 +version: 3.0.0-beta.7 homepage: https://github.com/Colossus-Services/bones_ui environment: @@ -12,7 +12,7 @@ executables: dependencies: js_interop_utils: ^1.0.6 - web_utils: ^1.0.9 + web_utils: ^1.0.11 web: ^1.1.1 intl_messages: ^3.0.0-beta.1 dom_tools: ^3.0.0-beta.7 diff --git a/test/bones_ui_test.dart b/test/bones_ui_test.dart index 9bc9483..9696760 100644 --- a/test/bones_ui_test.dart +++ b/test/bones_ui_test.dart @@ -29,7 +29,7 @@ void main() { expect(myHome?.text, contains('- uiRootComponent: MyRoot')); var tmp = uiRoot - .selectAllNonTyped('*') + .selectAll('*') .map((e) => e as Object?) .map((e) => e.asJSAny) .where((e) => e.isA()) From 5f3f494d04e20b4da77cb8b2937a422a0c466901 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Fri, 28 Feb 2025 21:08:38 -0300 Subject: [PATCH 16/64] DART_VERSION: "latest" --- .github/workflows/dart.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index e6a2882..ce1475c 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -1,7 +1,7 @@ name: Dart CI env: - DART_VERSION: "3.6.2" + DART_VERSION: "latest" on: push: From 6b4dd89dfb4cb96bdb709ced5305f78badc9f3cf Mon Sep 17 00:00:00 2001 From: gmpassos Date: Fri, 28 Feb 2025 21:11:32 -0300 Subject: [PATCH 17/64] =?UTF-8?q?=E2=9C=A8=F0=9F=9A=A7=20lib(bones=5Fui=5F?= =?UTF-8?q?test=5Ftools):=20refactor:=20update=20select=20methods=20for=20?= =?UTF-8?q?better=20accuracy=20and=20clarity.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/bones_ui_test_tools.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/bones_ui_test_tools.dart b/lib/src/bones_ui_test_tools.dart index bee8fec..2e761cf 100644 --- a/lib/src/bones_ui_test_tools.dart +++ b/lib/src/bones_ui_test_tools.dart @@ -2108,11 +2108,11 @@ extension TestFutureElementExtension on Future { }); Future select(String? selectors) => - thenChain((e) => e.select(selectors)); + thenChain((e) => e.querySelectorNonTyped(selectors)); Future selectTyped( String? selectors, web.Web webType) => - thenChain((e) => e.selectTyped(selectors, webType)); + thenChain((e) => e.querySelectorTyped(selectors, webType)); Future selectExpected(String? selectors) => thenChain((e) => e.selectExpected(selectors)); From 5df71af5553f6e13d5e551f557f0000d7b88231a Mon Sep 17 00:00:00 2001 From: gmpassos Date: Fri, 28 Feb 2025 21:13:26 -0300 Subject: [PATCH 18/64] optimize --- lib/src/bones_ui_test_tools.dart | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/src/bones_ui_test_tools.dart b/lib/src/bones_ui_test_tools.dart index 2e761cf..264b073 100644 --- a/lib/src/bones_ui_test_tools.dart +++ b/lib/src/bones_ui_test_tools.dart @@ -2107,12 +2107,16 @@ extension TestFutureElementExtension on Future { return elem; }); - Future select(String? selectors) => - thenChain((e) => e.querySelectorNonTyped(selectors)); + Future select(String? selectors) { + if (selectors == null) return Future.value(null); + return thenChain((e) => e.select(selectors)); + } Future selectTyped( - String? selectors, web.Web webType) => - thenChain((e) => e.querySelectorTyped(selectors, webType)); + String? selectors, web.Web webType) { + if (selectors == null) return Future.value(null); + return thenChain((e) => e.selectTyped(selectors, webType)); + } Future selectExpected(String? selectors) => thenChain((e) => e.selectExpected(selectors)); From d18c6b96914c845d974a298a98643a4e895fc065 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Fri, 28 Feb 2025 21:14:56 -0300 Subject: [PATCH 19/64] optimize --- lib/src/bones_ui_test_tools.dart | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/src/bones_ui_test_tools.dart b/lib/src/bones_ui_test_tools.dart index 264b073..15e9f77 100644 --- a/lib/src/bones_ui_test_tools.dart +++ b/lib/src/bones_ui_test_tools.dart @@ -2121,12 +2121,16 @@ extension TestFutureElementExtension on Future { Future selectExpected(String? selectors) => thenChain((e) => e.selectExpected(selectors)); - Future> selectAll(String? selectors) => - then((e) => e.selectAll(selectors)); + Future> selectAll(String? selectors) { + if (selectors == null) return Future.value([]); + return then((e) => e.selectAll(selectors)); + } Future> selectAllTyped( - String? selectors, Web webType) => - then((e) => e.selectAllTyped(selectors, webType)); + String? selectors, Web webType) { + if (selectors == null) return Future.value([]); + return then((e) => e.selectAllTyped(selectors, webType)); + } } extension TestUIComponentNullableExtension on UIComponent? { From cabc224c35a678d794b6a1bc8f31fb069e331e34 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Thu, 6 Mar 2025 04:47:17 -0300 Subject: [PATCH 20/64] v3.0.0-beta.8 - web_utils: ^1.0.12 - dom_tools: ^3.0.0-beta.8 - project_template: ^1.1.1 - archive: ^4.0.4 - dependency_validator: ^5.0.2 --- CHANGELOG.md | 9 +++++++++ lib/src/bones_ui.dart | 2 +- lib/src/bones_ui_test_tools.dart | 8 +++----- pubspec.yaml | 12 ++++++------ 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccb94b3..ace958f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## 3.0.0-beta.8 + +- web_utils: ^1.0.12 +- dom_tools: ^3.0.0-beta.8 +- project_template: ^1.1.1 +- archive: ^4.0.4 + +- dependency_validator: ^5.0.2 + ## 3.0.0-beta.7 ✨♻️ refactor(bones_ui_test_tools): diff --git a/lib/src/bones_ui.dart b/lib/src/bones_ui.dart index 7cf52cf..0f33619 100644 --- a/lib/src/bones_ui.dart +++ b/lib/src/bones_ui.dart @@ -1,3 +1,3 @@ class BonesUI { - static const String version = '3.0.0-beta.7'; + static const String version = '3.0.0-beta.8'; } diff --git a/lib/src/bones_ui_test_tools.dart b/lib/src/bones_ui_test_tools.dart index 15e9f77..ffe5652 100644 --- a/lib/src/bones_ui_test_tools.dart +++ b/lib/src/bones_ui_test_tools.dart @@ -1233,11 +1233,9 @@ abstract class UITestChain< var gZipEncoder = GZipEncoder(); var compressed = gZipEncoder.encode(bytes); - if (compressed != null) { - var base64 = dart_convert.base64.encode(compressed); - msg = - '[$id]<<<<<<(GZIP: ${compressed.length}/${bytes.length})\n$base64\n>>>>>>$timeMs'; - } + var base64 = dart_convert.base64.encode(compressed); + msg = + '[$id]<<<<<<(GZIP: ${compressed.length}/${bytes.length})\n$base64\n>>>>>>$timeMs'; } msg ??= '[$id]<<<<<<\n$outerHtml\n>>>>>>$timeMs'; diff --git a/pubspec.yaml b/pubspec.yaml index 74a9439..8c4415e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: bones_ui description: Bones_UI - An intuitive and user-friendly Web User Interface framework for Dart. -version: 3.0.0-beta.7 +version: 3.0.0-beta.8 homepage: https://github.com/Colossus-Services/bones_ui environment: @@ -12,23 +12,23 @@ executables: dependencies: js_interop_utils: ^1.0.6 - web_utils: ^1.0.11 + web_utils: ^1.0.12 web: ^1.1.1 intl_messages: ^3.0.0-beta.1 - dom_tools: ^3.0.0-beta.7 + dom_tools: ^3.0.0-beta.8 dom_builder: ^3.0.0-beta.3 swiss_knife: ^3.3.0 statistics: ^1.2.0 mercury_client: ^2.3.0 dynamic_call: ^2.0.1 - project_template: ^1.1.0 + project_template: ^1.1.1 resource_portable: ^3.1.2 extended_type: ^2.1.1 expressions: ^0.2.5+2 html_unescape: ^2.0.0 intl: ^0.20.2 yaml: ^3.1.3 - archive: ^3.6.1 + archive: ^4.0.4 collection: ^1.19.0 args: ^2.6.0 logging: ^1.3.0 @@ -44,7 +44,7 @@ dev_dependencies: build_web_compilers: ^4.1.1 build_runner: ^2.4.15 lints: ^5.1.1 - dependency_validator: ^4.1.2 + dependency_validator: ^5.0.2 #dependency_overrides: From a5ed6edaaf067bde29c150f3d57d57f4fa032b76 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Thu, 17 Apr 2025 02:22:01 -0300 Subject: [PATCH 21/64] v3.0.0-beta.9 - Merge v2.5.15 - `UIComponent`: - Added `renderedElementsAsync`. - `UIDialogBase`: - Added `configureButtons` to configure the buttons `onClick`. - `posRender` and `posAsyncRender` calls `configureButtons`. - js_interop_utils: ^1.0.7 - web_utils: ^1.0.14 - dom_tools: ^3.0.0-beta.9 - dom_builder: ^3.0.0-beta.4 - archive: ^4.0.5 --- CHANGELOG.md | 15 +++++++++++++++ pubspec.yaml | 12 ++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e66f5eb..d26408d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +## 3.0.0-beta.9 + +- Merge v2.5.15 + - `UIComponent`: + - Added `renderedElementsAsync`. + - `UIDialogBase`: + - Added `configureButtons` to configure the buttons `onClick`. + - `posRender` and `posAsyncRender` calls `configureButtons`. + +- js_interop_utils: ^1.0.7 +- web_utils: ^1.0.14 +- dom_tools: ^3.0.0-beta.9 +- dom_builder: ^3.0.0-beta.4 +- archive: ^4.0.5 + ## 3.0.0-beta.8 - web_utils: ^1.0.12 diff --git a/pubspec.yaml b/pubspec.yaml index affe9b5..7066ce5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: bones_ui description: Bones_UI - An intuitive and user-friendly Web User Interface framework for Dart. -version: 3.0.0-beta.8 +version: 3.0.0-beta.9 homepage: https://github.com/Colossus-Services/bones_ui environment: @@ -11,12 +11,12 @@ executables: bones_ui_test: dependencies: - js_interop_utils: ^1.0.6 - web_utils: ^1.0.12 + js_interop_utils: ^1.0.7 + web_utils: ^1.0.14 web: ^1.1.1 intl_messages: ^3.0.0-beta.1 - dom_tools: ^3.0.0-beta.8 - dom_builder: ^3.0.0-beta.3 + dom_tools: ^3.0.0-beta.9 + dom_builder: ^3.0.0-beta.4 swiss_knife: ^3.3.0 statistics: ^1.2.0 mercury_client: ^2.3.0 @@ -28,7 +28,7 @@ dependencies: html_unescape: ^2.0.0 intl: ^0.20.2 yaml: ^3.1.3 - archive: ^4.0.4 + archive: ^4.0.5 collection: ^1.19.0 args: ^2.7.0 logging: ^1.3.0 From 344a3784ff1b7743ad1c001a6b40fdb265bf4cc6 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 1 Jun 2025 19:04:19 -0300 Subject: [PATCH 22/64] - `UISVG`: - replace `createHTML` with `createElement` for SVG element creation. --- lib/src/component/svg.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/component/svg.dart b/lib/src/component/svg.dart index 1055371..e1c93f3 100644 --- a/lib/src/component/svg.dart +++ b/lib/src/component/svg.dart @@ -199,7 +199,7 @@ class UISVG extends UIComponent { return null; } - var svg = createHTML(html: content) as SVGElement; + var svg = createElement(html: content) as SVGElement; _applyDimension(svg); From c90ce6cf60be73d751860116576299550ef5b13c Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 1 Jun 2025 19:04:55 -0300 Subject: [PATCH 23/64] - `UIComponent`: - replace `append` with `appendChild` for DOM consistency. --- lib/src/bones_ui_component.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/bones_ui_component.dart b/lib/src/bones_ui_component.dart index 32d8979..c8d9b7e 100644 --- a/lib/src/bones_ui_component.dart +++ b/lib/src/bones_ui_component.dart @@ -2054,11 +2054,11 @@ abstract class UIComponent extends UIEventHandler { var idx = content.childNodes.indexOf(element); if (idx < 0) { - content.append(element); + content.appendChild(element); idx = content.childNodes.indexOf(element); } else if (idx < prevElemIndex) { element.remove(); - content.append(element); + content.appendChild(element); idx = content.childNodes.indexOf(element); } From e0651349baa101d47d6a30c9faa553aaaad7ad25 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 1 Jun 2025 19:05:13 -0300 Subject: [PATCH 24/64] v3.0.0-beta.10 - `UISVG`: - replace `createHTML` with `createElement` for SVG element creation. - `UIComponent`: - replace `append` with `appendChild` for DOM consistency. - js_interop_utils: ^1.0.8 - web_utils: ^1.0.15 - dom_tools: ^3.0.0-beta.10 - archive: ^4.0.7 - test: ^1.26.2 - test_api: ^0.7.6 - test_core: ^0.6.11 - build_web_compilers: ^4.1.5 --- CHANGELOG.md | 18 ++++++++++++++++++ pubspec.yaml | 18 +++++++++--------- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d26408d..144e3fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +## 3.0.0-beta.10 + +- `UISVG`: + - replace `createHTML` with `createElement` for SVG element creation. + +- `UIComponent`: + - replace `append` with `appendChild` for DOM consistency. + +- js_interop_utils: ^1.0.8 +- web_utils: ^1.0.15 +- dom_tools: ^3.0.0-beta.10 +- archive: ^4.0.7 +- test: ^1.26.2 +- test_api: ^0.7.6 +- test_core: ^0.6.11 + +- build_web_compilers: ^4.1.5 + ## 3.0.0-beta.9 - Merge v2.5.15 diff --git a/pubspec.yaml b/pubspec.yaml index 7066ce5..85cea92 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: bones_ui description: Bones_UI - An intuitive and user-friendly Web User Interface framework for Dart. -version: 3.0.0-beta.9 +version: 3.0.0-beta.10 homepage: https://github.com/Colossus-Services/bones_ui environment: @@ -11,11 +11,11 @@ executables: bones_ui_test: dependencies: - js_interop_utils: ^1.0.7 - web_utils: ^1.0.14 + js_interop_utils: ^1.0.8 + web_utils: ^1.0.15 web: ^1.1.1 intl_messages: ^3.0.0-beta.1 - dom_tools: ^3.0.0-beta.9 + dom_tools: ^3.0.0-beta.10 dom_builder: ^3.0.0-beta.4 swiss_knife: ^3.3.0 statistics: ^1.2.0 @@ -28,20 +28,20 @@ dependencies: html_unescape: ^2.0.0 intl: ^0.20.2 yaml: ^3.1.3 - archive: ^4.0.5 + archive: ^4.0.7 collection: ^1.19.0 args: ^2.7.0 logging: ^1.3.0 path: ^1.9.1 - test: ^1.25.15 - test_api: ^0.7.4 - test_core: ^0.6.8 + test: ^1.26.2 + test_api: ^0.7.6 + test_core: ^0.6.11 stream_channel: ^2.1.4 stack_trace: ^1.12.1 dev_dependencies: - build_web_compilers: ^4.1.1 + build_web_compilers: ^4.1.5 build_runner: ^2.4.15 lints: ^5.1.1 dependency_validator: ^5.0.2 From 8289c5501074d7dc285a2f168d7092f4c1709c37 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 1 Jun 2025 19:07:52 -0300 Subject: [PATCH 25/64] v3.0.0-beta.10 --- lib/src/bones_ui.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/bones_ui.dart b/lib/src/bones_ui.dart index 4506425..f04ad94 100644 --- a/lib/src/bones_ui.dart +++ b/lib/src/bones_ui.dart @@ -1,3 +1,3 @@ class BonesUI { - static const String version = '3.0.0-beta.9'; + static const String version = '3.0.0-beta.10'; } From 6e4ed32af8c718491101935200d2d6e8c4e54e8b Mon Sep 17 00:00:00 2001 From: gmpassos Date: Wed, 18 Jun 2025 01:17:23 -0300 Subject: [PATCH 26/64] v3.0.0-beta.11 --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69f1cdd..f45a399 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +## 3.0.0-beta.11 + +- Merge 2.5.16: + - `UIComponent`: + - Optimized `subUIComponentsDeeply` and `getRenderedUIComponents` + - Both now return `Iterable`. + - `subUIComponentsDeeply` is optimized as a lazy `sync*` generator and recursively calls `subElem.subUIComponentsDeeply`. + - Improve `_resolveUIRootComponent`. + - `_callRenderImpl`: fix issue when `parent` is passed on constructor but it's already appended in a sub-element. + + - test: ^1.26.2 + - test_api: ^0.7.6 + - test_core: ^0.6.11 + + - dependency_validator: ^5.0.2 + ## 3.0.0-beta.10 - `UISVG`: From 801ce68543c24b19938fc746f0eb25df57a747c9 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Mon, 14 Jul 2025 17:06:25 -0300 Subject: [PATCH 27/64] v3.0.0-beta.12 - Merge 2.5.17: - `UIDialog`: - Added properties `autoScrollY` and `autoScrollX`. - dom_tools: ^3.0.0-beta.11 - swiss_knife: ^3.3.1 - build_web_compilers: ^4.2.0 - build_runner: ^2.5.4 --- CHANGELOG.md | 11 +++++++++-- pubspec.yaml | 8 ++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57fe86a..933c812 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,14 @@ ## 3.0.0-beta.12 -- `UIDialog`: - - Added properties `autoScrollY` and `autoScrollX`. +- Merge 2.5.17: + - `UIDialog`: + - Added properties `autoScrollY` and `autoScrollX`. + +- dom_tools: ^3.0.0-beta.11 +- swiss_knife: ^3.3.1 + +- build_web_compilers: ^4.2.0 +- build_runner: ^2.5.4 ## 3.0.0-beta.11 diff --git a/pubspec.yaml b/pubspec.yaml index 4fbf80d..2abe507 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,9 +15,9 @@ dependencies: web_utils: ^1.0.15 web: ^1.1.1 intl_messages: ^3.0.0-beta.1 - dom_tools: ^3.0.0-beta.10 + dom_tools: ^3.0.0-beta.11 dom_builder: ^3.0.0-beta.4 - swiss_knife: ^3.3.0 + swiss_knife: ^3.3.1 statistics: ^1.2.0 mercury_client: ^2.3.0 dynamic_call: ^2.0.1 @@ -41,8 +41,8 @@ dependencies: dev_dependencies: - build_web_compilers: ^4.1.5 - build_runner: ^2.4.15 + build_web_compilers: ^4.2.0 + build_runner: ^2.5.4 lints: ^5.1.1 dependency_validator: ^5.0.2 From e7578de76693450e4c86ecb87e309531969acd01 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Thu, 24 Jul 2025 23:55:47 -0300 Subject: [PATCH 28/64] swiss_knife: ^3.3.3 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index c77afed..a5f8b0f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ dependencies: intl_messages: ^3.0.0-beta.1 dom_tools: ^3.0.0-beta.11 dom_builder: ^3.0.0-beta.4 - swiss_knife: ^3.3.1 + swiss_knife: ^3.3.3 statistics: ^1.2.0 mercury_client: ^2.3.0 dynamic_call: ^2.0.1 From 59381ef9d80ee8b171e25139823f386547f2c635 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Fri, 25 Jul 2025 00:05:46 -0300 Subject: [PATCH 29/64] clean code --- lib/src/bones_ui_component.dart | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/src/bones_ui_component.dart b/lib/src/bones_ui_component.dart index b74e24d..190b4f8 100644 --- a/lib/src/bones_ui_component.dart +++ b/lib/src/bones_ui_component.dart @@ -708,9 +708,9 @@ abstract class UIComponent extends UIEventHandler { dst.add(elem); } - var children = elem.children.toList(); + var children = elem.children; if (children.isNotEmpty) { - queue.addAll(children); + queue.addAll(children.toIterable()); } } } @@ -729,9 +729,9 @@ abstract class UIComponent extends UIEventHandler { final elem = queue.removeFirst(); if (filter(elem)) return elem; - var children = elem.children.toList(); + var children = elem.children; if (children.isNotEmpty) { - queue.addAll(children); + queue.addAll(children.toIterable()); } } @@ -761,7 +761,8 @@ abstract class UIComponent extends UIEventHandler { MapEntry? findChildrenDeep(String fieldName, {bool resolveUIComponents = true}) => - _findChildrenDeepImpl(_content!.children.toList(), fieldName, resolveUIComponents); + _findChildrenDeepImpl( + _content!.children.toList(), fieldName, resolveUIComponents); MapEntry? _findChildrenDeepImpl( List list, String fieldName, bool resolveUIComponents) { @@ -776,9 +777,9 @@ abstract class UIComponent extends UIEventHandler { _resolveElementField(elem, resolveUIComponents: resolveUIComponents); if (ret != null && ret.key == fieldName) return ret; - final children = elem.children.toList(); + final children = elem.children; if (children.isNotEmpty) { - queue.addAll(children); + queue.addAll(children.toIterable()); } } @@ -817,9 +818,9 @@ abstract class UIComponent extends UIEventHandler { if (entry != null) { result.add(entry); } else { - final children = elem.children.toList(); + final children = elem.children; if (children.isNotEmpty) { - queue.addAll(children); + queue.addAll(children.toIterable()); } } } From de3816ec3061d9c943549c3b03b0bcdec922d53e Mon Sep 17 00:00:00 2001 From: gmpassos Date: Fri, 25 Jul 2025 00:38:43 -0300 Subject: [PATCH 30/64] 3.0.0-beta.14 - Merge 2.5.20: - `UIComponent`: - Optimize `findChildDeep` with a BFS queue. --- lib/src/bones_ui_component.dart | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/src/bones_ui_component.dart b/lib/src/bones_ui_component.dart index 190b4f8..c7c8c7a 100644 --- a/lib/src/bones_ui_component.dart +++ b/lib/src/bones_ui_component.dart @@ -748,14 +748,19 @@ abstract class UIComponent extends UIEventHandler { List list, FilterElement filter, List dst) { if (list.isEmpty) return; - for (var elem in list) { + final queue = Queue.from(list); + + while (queue.isNotEmpty) { + final elem = queue.removeFirst(); + if (filter(elem)) { dst.add(elem); } - } - for (var elem in list) { - _findChildDeepImpl(elem.children.toList(), filter, dst); + var children = elem.children; + if (children.isNotEmpty) { + queue.addAll(children.toIterable()); + } } } From 258236ecf376608f189e28c0d79507f0ddd28e0c Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sat, 2 Aug 2025 18:57:05 -0300 Subject: [PATCH 31/64] v3.0.0-beta.15 - `InputConfig`: - Added field `precision`. - Added support for type `decimal`. --- lib/src/component/input_config.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/component/input_config.dart b/lib/src/component/input_config.dart index 9f18656..ef019fb 100644 --- a/lib/src/component/input_config.dart +++ b/lib/src/component/input_config.dart @@ -410,10 +410,10 @@ class InputConfig { return textArea; } - InputElement _renderDecimal(Object? inputValue) { + Element _renderDecimal(Object? inputValue) { var valText = _resolveValueText(inputValue); - var input = InputElement() + var input = HTMLInputElement() ..type = 'number' ..value = valText ?? '' ..style.width = '100%'; From 2f7e2397ef319dff8badad1596dcec817c07d4ce Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sat, 22 Nov 2025 18:14:16 -0300 Subject: [PATCH 32/64] clean code --- lib/src/bones_ui_async_content.dart | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/src/bones_ui_async_content.dart b/lib/src/bones_ui_async_content.dart index 6bfdbd6..88e9a68 100644 --- a/lib/src/bones_ui_async_content.dart +++ b/lib/src/bones_ui_async_content.dart @@ -21,10 +21,7 @@ class _Content { Object? _contentForDOM; - Object? get contentForDOM { - _contentForDOM ??= _ensureElementForDOM(content); - return _contentForDOM; - } + Object? get contentForDOM => _contentForDOM ??= _ensureElementForDOM(content); } /// An asynchronous content. @@ -335,10 +332,11 @@ class UIAsyncContent { /// Returns the already loaded content. dynamic get content { - if (_loadedContent == null) { + var loadedContent = _loadedContent; + if (loadedContent == null) { return loadingContent; - } else if (_loadedContent!.status == 200) { - return _loadedContent!.contentForDOM; + } else if (loadedContent.status == 200) { + return loadedContent.contentForDOM; } else { return errorContent ?? loadingContent; } From 2ecf5ac2734776cfcd6cdc18afd93c741b6be888 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sat, 22 Nov 2025 18:15:07 -0300 Subject: [PATCH 33/64] - New `KeyboardEventExtension`: - getter `keyCodeSafe`. --- lib/src/bones_ui_extension.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/src/bones_ui_extension.dart b/lib/src/bones_ui_extension.dart index 44517a6..164d96f 100644 --- a/lib/src/bones_ui_extension.dart +++ b/lib/src/bones_ui_extension.dart @@ -105,3 +105,13 @@ extension UIIterableElementExtension on Iterable { /// Returns a [List] of values of this [Iterable] of [UIElement]s. List get elementsValues => map((e) => e.elementValue).toList(); } + +extension KeyboardEventExtension on KeyboardEvent { + int? get keyCodeSafe { + try { + return keyCode; + } catch (_) { + return null; + } + } +} From 22593149745888e6f5fe2645d13953fd284257f4 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sat, 22 Nov 2025 18:15:31 -0300 Subject: [PATCH 34/64] use keyCodeSafe --- lib/src/bones_ui_component.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/bones_ui_component.dart b/lib/src/bones_ui_component.dart index c7c8c7a..19d4b51 100644 --- a/lib/src/bones_ui_component.dart +++ b/lib/src/bones_ui_component.dart @@ -3134,7 +3134,7 @@ abstract class UIComponent extends UIEventHandler { }); } else { elem.onKeyPress.listen((e) { - if (e.key == key || e.keyCode.toString() == key) { + if (e.key == key || e.keyCodeSafe.toString() == key) { action(actionType); } }); From 1c71df1fbc13772a6fbe07d3a5f331f1e0dac595 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sat, 22 Nov 2025 18:23:23 -0300 Subject: [PATCH 35/64] - `UIDOMGenerator`: - `addExternalElementToElement`: improve external element resolution. - `attachFutureElement`: improve element resolution. --- lib/src/bones_ui_generator.dart | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/src/bones_ui_generator.dart b/lib/src/bones_ui_generator.dart index dfb9066..65e0c3f 100644 --- a/lib/src/bones_ui_generator.dart +++ b/lib/src/bones_ui_generator.dart @@ -394,13 +394,23 @@ class UIDOMGenerator extends DOMGeneratorWebImpl { } @override - List? addExternalElementToElement(UINode element, externalElement) { + List? addExternalElementToElement( + UINode element, Object? externalElement) { + if (externalElement == null) return null; + if (externalElement is List) { if (externalElement.isEmpty) return null; + + if (externalElement.length == 1) { + return addExternalElementToElement(element, externalElement.first); + } + var children = []; for (var elem in externalElement) { - var child = addExternalElementToElement(element, elem)!; - children.addAll(child); + var child = addExternalElementToElement(element, elem); + if (child != null) { + children.addAll(child); + } } return children; } else if (externalElement is UIComponent) { @@ -447,6 +457,9 @@ class UIDOMGenerator extends DOMGeneratorWebImpl { Object? futureElementResolved, DOMTreeMap treeMap, DOMContext? context) { + futureElementResolved = resolveElements(futureElementResolved); + if (futureElementResolved == null) return; + super.attachFutureElement(domParent, parent, domElement, templateElement, futureElementResolved, treeMap, context); From 58ed2e27b1856e3dfb93df7361352f7df76bc0f9 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sat, 22 Nov 2025 18:24:01 -0300 Subject: [PATCH 36/64] use keyCodeSafe --- lib/src/component/input_config.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/src/component/input_config.dart b/lib/src/component/input_config.dart index ef019fb..cef1ea9 100644 --- a/lib/src/component/input_config.dart +++ b/lib/src/component/input_config.dart @@ -8,6 +8,7 @@ import 'package:web_utils/web_utils.dart'; import '../bones_ui_component.dart'; import '../bones_ui_utils.dart'; +import '../bones_ui_extension.dart'; import 'capture.dart'; import 'color_picker.dart'; @@ -988,9 +989,10 @@ class UIInputTable extends UIComponent { } else if (type == 'date') { interactionCompleter.onComplete.listen(onActionListener); } else { - elem.onKeyUp - .where((evt) => evt.keyCode == 9 || evt.keyCode == 13) - .listen(onActionListener); + elem.onKeyUp.where((evt) { + var keyCode = evt.keyCodeSafe; + return keyCode == 9 || keyCode == 13; + }).listen(onActionListener); if (type != 'password') { interactionCompleter.onComplete.listen(onActionListener); From 27208da73f5cdbb85d450ae97dc3b6f2a89a7789 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sat, 22 Nov 2025 18:24:42 -0300 Subject: [PATCH 37/64] v3.0.0-beta.16 - New `KeyboardEventExtension`: - getter `keyCodeSafe`. - `UIDOMGenerator`: - `addExternalElementToElement`: improve external element resolution. - `attachFutureElement`: improve element resolution. - js_interop_utils: ^1.0.9 - web_utils: ^1.0.16 - dom_tools: ^3.0.0-beta.12 - expressions: ^0.2.5+3 - collection: ^1.19.1 - test: ^1.27.0 - test_api: ^0.7.8 - test_core: ^0.6.13 - build_web_compilers: ^4.4.2 - build_runner: ^2.10.4 - dependency_validator: ^5.0.3 --- CHANGELOG.md | 22 ++++++++++++++++++++++ bones_ui.iml | 1 - pubspec.yaml | 24 ++++++++++++------------ 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 215dd22..f89c160 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ +## 3.0.0-beta.16 + +- New `KeyboardEventExtension`: + - getter `keyCodeSafe`. + +- `UIDOMGenerator`: + - `addExternalElementToElement`: improve external element resolution. + - `attachFutureElement`: improve element resolution. + +- js_interop_utils: ^1.0.9 +- web_utils: ^1.0.16 +- dom_tools: ^3.0.0-beta.12 +- expressions: ^0.2.5+3 +- collection: ^1.19.1 +- test: ^1.27.0 +- test_api: ^0.7.8 +- test_core: ^0.6.13 + +- build_web_compilers: ^4.4.2 +- build_runner: ^2.10.4 +- dependency_validator: ^5.0.3 + ## 3.0.0-beta.15 - Merge 2.5.21 diff --git a/bones_ui.iml b/bones_ui.iml index b6003bd..ae9af97 100644 --- a/bones_ui.iml +++ b/bones_ui.iml @@ -6,7 +6,6 @@ - diff --git a/pubspec.yaml b/pubspec.yaml index 8760968..0335ec1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: bones_ui description: Bones_UI - An intuitive and user-friendly Web User Interface framework for Dart. -version: 3.0.0-beta.15 +version: 3.0.0-beta.16 homepage: https://github.com/Colossus-Services/bones_ui environment: @@ -11,11 +11,11 @@ executables: bones_ui_test: dependencies: - js_interop_utils: ^1.0.8 - web_utils: ^1.0.15 + js_interop_utils: ^1.0.9 + web_utils: ^1.0.16 web: ^1.1.1 intl_messages: ^3.0.0-beta.1 - dom_tools: ^3.0.0-beta.11 + dom_tools: ^3.0.0-beta.12 dom_builder: ^3.0.0-beta.4 swiss_knife: ^3.3.3 statistics: ^1.2.0 @@ -24,27 +24,27 @@ dependencies: project_template: ^1.1.1 resource_portable: ^3.1.2 extended_type: ^2.1.1 - expressions: ^0.2.5+2 + expressions: ^0.2.5+3 html_unescape: ^2.0.0 intl: ^0.20.2 yaml: ^3.1.3 archive: ^4.0.7 - collection: ^1.19.0 + collection: ^1.19.1 args: ^2.7.0 logging: ^1.3.0 path: ^1.9.1 - test: ^1.26.3 - test_api: ^0.7.7 - test_core: ^0.6.12 + test: ^1.27.0 + test_api: ^0.7.8 + test_core: ^0.6.13 stream_channel: ^2.1.4 stack_trace: ^1.12.1 dev_dependencies: - build_web_compilers: ^4.2.0 - build_runner: ^2.5.4 + build_web_compilers: ^4.4.2 + build_runner: ^2.10.4 lints: ^5.1.1 - dependency_validator: ^5.0.2 + dependency_validator: ^5.0.3 #dependency_overrides: From fa80e2847dc4d541eccc642fc2903f7904aecc41 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 23 Nov 2025 17:14:01 -0300 Subject: [PATCH 38/64] v3.0.0-beta.16 - New `KeyboardEventExtension`: - getter `keyCodeSafe`. - `UIDOMGenerator`: - `addExternalElementToElement`: improve external element resolution. - `attachFutureElement`: improve element resolution. - js_interop_utils: ^1.0.9 - web_utils: ^1.0.16 - dom_tools: ^3.0.0-beta.12 - dom_builder: ^3.0.0-beta.5 - expressions: ^0.2.5+3 - collection: ^1.19.1 - test: ^1.28.0 - test_api: ^0.7.8 - test_core: ^0.6.14 - build_web_compilers: ^4.4.3 - build_runner: ^2.10.4 - dependency_validator: ^5.0.3 --- CHANGELOG.md | 7 ++++--- lib/src/bones_ui.dart | 2 +- pubspec.yaml | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f89c160..16bfdc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,13 +10,14 @@ - js_interop_utils: ^1.0.9 - web_utils: ^1.0.16 - dom_tools: ^3.0.0-beta.12 +- dom_builder: ^3.0.0-beta.5 - expressions: ^0.2.5+3 - collection: ^1.19.1 -- test: ^1.27.0 +- test: ^1.28.0 - test_api: ^0.7.8 -- test_core: ^0.6.13 +- test_core: ^0.6.14 -- build_web_compilers: ^4.4.2 +- build_web_compilers: ^4.4.3 - build_runner: ^2.10.4 - dependency_validator: ^5.0.3 diff --git a/lib/src/bones_ui.dart b/lib/src/bones_ui.dart index 095aeb0..04b5509 100644 --- a/lib/src/bones_ui.dart +++ b/lib/src/bones_ui.dart @@ -1,3 +1,3 @@ class BonesUI { - static const String version = '3.0.0-beta.15'; + static const String version = '3.0.0-beta.16'; } diff --git a/pubspec.yaml b/pubspec.yaml index 0335ec1..5234792 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ dependencies: web: ^1.1.1 intl_messages: ^3.0.0-beta.1 dom_tools: ^3.0.0-beta.12 - dom_builder: ^3.0.0-beta.4 + dom_builder: ^3.0.0-beta.5 swiss_knife: ^3.3.3 statistics: ^1.2.0 mercury_client: ^2.3.0 @@ -33,15 +33,15 @@ dependencies: args: ^2.7.0 logging: ^1.3.0 path: ^1.9.1 - test: ^1.27.0 + test: ^1.28.0 test_api: ^0.7.8 - test_core: ^0.6.13 + test_core: ^0.6.14 stream_channel: ^2.1.4 stack_trace: ^1.12.1 dev_dependencies: - build_web_compilers: ^4.4.2 + build_web_compilers: ^4.4.3 build_runner: ^2.10.4 lints: ^5.1.1 dependency_validator: ^5.0.3 From fc588a337a986702108f2bf11bead5e5247c7d54 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 23 Nov 2025 18:17:01 -0300 Subject: [PATCH 39/64] Moved KeyboardEventExtension to package web_utils --- CHANGELOG.md | 5 +---- lib/src/bones_ui_extension.dart | 10 ---------- pubspec.yaml | 2 +- 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16bfdc6..acf7f83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,11 @@ ## 3.0.0-beta.16 -- New `KeyboardEventExtension`: - - getter `keyCodeSafe`. - - `UIDOMGenerator`: - `addExternalElementToElement`: improve external element resolution. - `attachFutureElement`: improve element resolution. - js_interop_utils: ^1.0.9 -- web_utils: ^1.0.16 +- web_utils: ^1.0.17 - dom_tools: ^3.0.0-beta.12 - dom_builder: ^3.0.0-beta.5 - expressions: ^0.2.5+3 diff --git a/lib/src/bones_ui_extension.dart b/lib/src/bones_ui_extension.dart index 164d96f..44517a6 100644 --- a/lib/src/bones_ui_extension.dart +++ b/lib/src/bones_ui_extension.dart @@ -105,13 +105,3 @@ extension UIIterableElementExtension on Iterable { /// Returns a [List] of values of this [Iterable] of [UIElement]s. List get elementsValues => map((e) => e.elementValue).toList(); } - -extension KeyboardEventExtension on KeyboardEvent { - int? get keyCodeSafe { - try { - return keyCode; - } catch (_) { - return null; - } - } -} diff --git a/pubspec.yaml b/pubspec.yaml index 5234792..c91964e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,7 +12,7 @@ executables: dependencies: js_interop_utils: ^1.0.9 - web_utils: ^1.0.16 + web_utils: ^1.0.17 web: ^1.1.1 intl_messages: ^3.0.0-beta.1 dom_tools: ^3.0.0-beta.12 From e25bd49776f5dd03fb44214f23f538b139abc5a7 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 23 Nov 2025 18:18:03 -0300 Subject: [PATCH 40/64] Update bones_ui_component.dart and input_config.dart with new collection.dart function and key event handling logic. --- lib/src/bones_ui_component.dart | 6 ++++-- lib/src/component/input_config.dart | 8 +++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/src/bones_ui_component.dart b/lib/src/bones_ui_component.dart index 19d4b51..bd59735 100644 --- a/lib/src/bones_ui_component.dart +++ b/lib/src/bones_ui_component.dart @@ -1,7 +1,8 @@ import 'dart:async'; import 'dart:collection'; -import 'package:collection/collection.dart' show IterableExtension; +import 'package:collection/collection.dart' + show IterableExtension, equalsIgnoreAsciiCase; import 'package:dom_builder/dom_builder.dart'; import 'package:dom_tools/dom_tools.dart'; import 'package:dynamic_call/dynamic_call.dart'; @@ -3134,7 +3135,8 @@ abstract class UIComponent extends UIEventHandler { }); } else { elem.onKeyPress.listen((e) { - if (e.key == key || e.keyCodeSafe.toString() == key) { + if (equalsIgnoreAsciiCase(e.key, key) || + equalsIgnoreAsciiCase(e.keyCodeSafe.toString(), key)) { action(actionType); } }); diff --git a/lib/src/component/input_config.dart b/lib/src/component/input_config.dart index cef1ea9..5a2eec2 100644 --- a/lib/src/component/input_config.dart +++ b/lib/src/component/input_config.dart @@ -8,7 +8,6 @@ import 'package:web_utils/web_utils.dart'; import '../bones_ui_component.dart'; import '../bones_ui_utils.dart'; -import '../bones_ui_extension.dart'; import 'capture.dart'; import 'color_picker.dart'; @@ -989,10 +988,9 @@ class UIInputTable extends UIComponent { } else if (type == 'date') { interactionCompleter.onComplete.listen(onActionListener); } else { - elem.onKeyUp.where((evt) { - var keyCode = evt.keyCodeSafe; - return keyCode == 9 || keyCode == 13; - }).listen(onActionListener); + elem.onKeyUp + .where((evt) => evt.isKeyTabOrEnter) + .listen(onActionListener); if (type != 'password') { interactionCompleter.onComplete.listen(onActionListener); From e52cc49feb0fa4dfffa71b032fba946dfc986c19 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Mon, 24 Nov 2025 13:45:58 -0300 Subject: [PATCH 41/64] v3.0.0-beta.17 - `UIComponent`: - `getFieldElementTyped`: use `asElementOfNullable`. - web_utils: ^1.0.18 --- CHANGELOG.md | 7 +++++++ lib/src/bones_ui.dart | 2 +- lib/src/bones_ui_component.dart | 2 +- pubspec.yaml | 4 ++-- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acf7f83..bb89037 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 3.0.0-beta.17 + +- `UIComponent`: + - `getFieldElementTyped`: use `asElementOfNullable`. + +- web_utils: ^1.0.18 + ## 3.0.0-beta.16 - `UIDOMGenerator`: diff --git a/lib/src/bones_ui.dart b/lib/src/bones_ui.dart index 04b5509..879ecc8 100644 --- a/lib/src/bones_ui.dart +++ b/lib/src/bones_ui.dart @@ -1,3 +1,3 @@ class BonesUI { - static const String version = '3.0.0-beta.16'; + static const String version = '3.0.0-beta.17'; } diff --git a/lib/src/bones_ui_component.dart b/lib/src/bones_ui_component.dart index bd59735..3a8c579 100644 --- a/lib/src/bones_ui_component.dart +++ b/lib/src/bones_ui_component.dart @@ -2462,7 +2462,7 @@ abstract class UIComponent extends UIEventHandler { E? getFieldElementTyped( String? fieldName, Web webType) { var elem = getFieldElementNonTyped(fieldName); - return elem.asElementOf(webType); + return elem.asElementOfNullable(webType); } Object? getFieldComponent(String? fieldName, diff --git a/pubspec.yaml b/pubspec.yaml index c91964e..56828c0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: bones_ui description: Bones_UI - An intuitive and user-friendly Web User Interface framework for Dart. -version: 3.0.0-beta.16 +version: 3.0.0-beta.17 homepage: https://github.com/Colossus-Services/bones_ui environment: @@ -12,7 +12,7 @@ executables: dependencies: js_interop_utils: ^1.0.9 - web_utils: ^1.0.17 + web_utils: ^1.0.18 web: ^1.1.1 intl_messages: ^3.0.0-beta.1 dom_tools: ^3.0.0-beta.12 From 0927af6114d6a89d62c1b6f7448dd53b781f03c9 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Mon, 24 Nov 2025 17:48:19 -0300 Subject: [PATCH 42/64] v3.0.0-beta.18 - `UIDOMGenerator`: - `toElements`: - Fix resolution of `UIComponent`, calling `UIComponent.ensureRendered` before return `UIComponent.content`. - dom_builder: ^3.0.0-beta.6 --- CHANGELOG.md | 8 ++++++++ lib/src/bones_ui.dart | 2 +- lib/src/bones_ui_generator.dart | 3 ++- pubspec.yaml | 4 ++-- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb89037..eb7b57d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## 3.0.0-beta.18 + +- `UIDOMGenerator`: + - `toElements`: + - Fix resolution of `UIComponent`, calling `UIComponent.ensureRendered` before return `UIComponent.content`. + +- dom_builder: ^3.0.0-beta.6 + ## 3.0.0-beta.17 - `UIComponent`: diff --git a/lib/src/bones_ui.dart b/lib/src/bones_ui.dart index 879ecc8..ade710f 100644 --- a/lib/src/bones_ui.dart +++ b/lib/src/bones_ui.dart @@ -1,3 +1,3 @@ class BonesUI { - static const String version = '3.0.0-beta.17'; + static const String version = '3.0.0-beta.18'; } diff --git a/lib/src/bones_ui_generator.dart b/lib/src/bones_ui_generator.dart index 65e0c3f..20cf6df 100644 --- a/lib/src/bones_ui_generator.dart +++ b/lib/src/bones_ui_generator.dart @@ -487,8 +487,9 @@ class UIDOMGenerator extends DOMGeneratorWebImpl { } @override - List? toElements(elements) { + List? toElements(Object? elements) { if (elements is UIComponent) { + elements.ensureRendered(); var content = elements.content; return content != null ? [content] : null; } else if (elements is UIAsyncContent) { diff --git a/pubspec.yaml b/pubspec.yaml index 56828c0..fbc1165 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: bones_ui description: Bones_UI - An intuitive and user-friendly Web User Interface framework for Dart. -version: 3.0.0-beta.17 +version: 3.0.0-beta.18 homepage: https://github.com/Colossus-Services/bones_ui environment: @@ -16,7 +16,7 @@ dependencies: web: ^1.1.1 intl_messages: ^3.0.0-beta.1 dom_tools: ^3.0.0-beta.12 - dom_builder: ^3.0.0-beta.5 + dom_builder: ^3.0.0-beta.6 swiss_knife: ^3.3.3 statistics: ^1.2.0 mercury_client: ^2.3.0 From 1f04e39324248e4412b53be45308b64f7c01f068 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Mon, 24 Nov 2025 19:54:02 -0300 Subject: [PATCH 43/64] v3.0.0-beta.19 - dom_tools: ^3.0.0-beta.13 --- CHANGELOG.md | 4 ++++ lib/src/bones_ui.dart | 2 +- pubspec.yaml | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb7b57d..86aa41b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.0.0-beta.19 + +- dom_tools: ^3.0.0-beta.13 + ## 3.0.0-beta.18 - `UIDOMGenerator`: diff --git a/lib/src/bones_ui.dart b/lib/src/bones_ui.dart index ade710f..bebe8bb 100644 --- a/lib/src/bones_ui.dart +++ b/lib/src/bones_ui.dart @@ -1,3 +1,3 @@ class BonesUI { - static const String version = '3.0.0-beta.18'; + static const String version = '3.0.0-beta.19'; } diff --git a/pubspec.yaml b/pubspec.yaml index fbc1165..9ccb733 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: bones_ui description: Bones_UI - An intuitive and user-friendly Web User Interface framework for Dart. -version: 3.0.0-beta.18 +version: 3.0.0-beta.19 homepage: https://github.com/Colossus-Services/bones_ui environment: @@ -15,7 +15,7 @@ dependencies: web_utils: ^1.0.18 web: ^1.1.1 intl_messages: ^3.0.0-beta.1 - dom_tools: ^3.0.0-beta.12 + dom_tools: ^3.0.0-beta.13 dom_builder: ^3.0.0-beta.6 swiss_knife: ^3.3.3 statistics: ^1.2.0 From dd5649c564f5ecb95c091ce21bbe58f1931c7739 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Mon, 24 Nov 2025 21:56:09 -0300 Subject: [PATCH 44/64] v3.0.0-beta.19 - dom_tools: ^3.0.0-beta.13 - dom_builder: ^3.0.0-beta.7 - web_utils: ^1.0.19 --- CHANGELOG.md | 2 ++ pubspec.yaml | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86aa41b..5afc17d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ ## 3.0.0-beta.19 - dom_tools: ^3.0.0-beta.13 +- dom_builder: ^3.0.0-beta.7 +- web_utils: ^1.0.19 ## 3.0.0-beta.18 diff --git a/pubspec.yaml b/pubspec.yaml index 9ccb733..6c1645c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,11 +12,11 @@ executables: dependencies: js_interop_utils: ^1.0.9 - web_utils: ^1.0.18 + web_utils: ^1.0.19 web: ^1.1.1 intl_messages: ^3.0.0-beta.1 dom_tools: ^3.0.0-beta.13 - dom_builder: ^3.0.0-beta.6 + dom_builder: ^3.0.0-beta.7 swiss_knife: ^3.3.3 statistics: ^1.2.0 mercury_client: ^2.3.0 From 2172ac9ab9707768f1e902105b33602ad0dcce4e Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 15 Feb 2026 16:48:35 -0300 Subject: [PATCH 45/64] - `UIButtonBase` and `UIButtonLoader`: - Updated event listener attachment to use `addEventListenerTyped`. - Fixed null safety and type checks for button elements. --- CHANGELOG.md | 4 ++ lib/src/component/button.dart | 74 +++++++++++++++++++++-------------- 2 files changed, 49 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5afc17d..96a6d08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +- `UIButtonBase` and `UIButtonLoader`: + - Updated event listener attachment to use `addEventListenerTyped`. + - Fixed null safety and type checks for button elements. + ## 3.0.0-beta.19 - dom_tools: ^3.0.0-beta.13 diff --git a/lib/src/component/button.dart b/lib/src/component/button.dart index 5bda28b..4e03862 100644 --- a/lib/src/component/button.dart +++ b/lib/src/component/button.dart @@ -1,10 +1,10 @@ import 'dart:async'; import 'dart:math'; -import 'package:web_utils/web_utils.dart' hide CSS; import 'package:dom_builder/dom_builder.dart'; import 'package:dom_tools/dom_tools.dart'; import 'package:swiss_knife/swiss_knife.dart'; +import 'package:web_utils/web_utils.dart' hide CSS; import '../bones_ui_base.dart'; import '../bones_ui_component.dart'; @@ -118,13 +118,14 @@ abstract class UIButtonBase extends UIComponent { for (var elem in renderedElements) { if (elem.isElement) { - (elem as Element).onClick.listen((e) => fireClickEvent(e)); + (elem as Element) + .addEventListenerTyped(EventType.click, (e) => fireClickEvent(e)); clickSet = true; } } if (!clickSet && !_content_onClick_listening) { - content!.onClick.listen((e) => fireClickEvent(e)); + content!.addEventListenerTyped(EventType.click, (e) => fireClickEvent(e)); _content_onClick_listening = true; } } @@ -416,7 +417,7 @@ class UIButtonLoader extends UIButtonBase { } } - dynamic _button; + Object? _button; HTMLDivElement? _loadingDiv; @@ -493,21 +494,25 @@ class UIButtonLoader extends UIButtonBase { } void startLoading() { - if (_loadingDiv == null) return; + final loadingDiv = _loadingDiv; + if (loadingDiv == null) return; - _loadingDiv!.style.display = 'inline-block'; + loadingDiv.style.display = 'inline-block'; var button = _button; - var buttonElement = - (button is DOMElement ? button.runtimeNode : button) as HTMLElement; - buttonElement.style.display = 'none'; + var buttonElement = (button is DOMElement + ? button.runtimeNode + : (button.isHTMLElement ? button : null)) as HTMLElement?; - _loadedMessage!.style.display = 'none'; + buttonElement?.style.display = 'none'; + + _loadedMessage?.style.display = 'none'; } void stopLoading(bool? loadOK, {String? okMessage, String? errorMessage}) { - if (_loadingDiv == null) return; + final loadingDiv = _loadingDiv; + if (loadingDiv == null) return; if (okMessage != null) { okMessage = resolveTextIntl(okMessage); @@ -519,51 +524,62 @@ class UIButtonLoader extends UIButtonBase { var button = _button; - var buttonElement = - (button is DOMElement ? button.runtimeNode : button) as HTMLElement?; + var buttonElement = (button is DOMElement + ? button.runtimeNode + : (button.isElement ? button : null)) as HTMLElement?; + + var loadedMessage = _loadedMessage; if (loadOK == null) { _setLoadedMessageStyle(); - _loadingDiv!.style.display = 'none'; - buttonElement!.style.display = ''; - _loadedMessage!.style.display = 'none'; + loadingDiv.style.display = 'none'; + buttonElement?.style.display = ''; + loadedMessage?.style.display = 'none'; } else if (loadOK) { _setLoadedMessageStyle(); - _loadingDiv!.style.display = 'none'; - buttonElement!.style.display = 'none'; + loadingDiv.style.display = 'none'; + buttonElement?.style.display = 'none'; var okMsg = okMessage ?? _loadedTextOK?.text ?? 'OK'; - setElementInnerHTML(_loadedMessage!, okMsg); - _loadedMessage!.style.display = ''; + + if (loadedMessage != null) { + setElementInnerHTML(loadedMessage, okMsg); + loadedMessage.style.display = ''; + } _disabled = true; } else { _setLoadedMessageStyle(error: true); - _loadingDiv!.style.display = 'none'; - buttonElement!.style.display = ''; + loadingDiv.style.display = 'none'; + buttonElement?.style.display = ''; var errorMsg = errorMessage ?? _loadedTextError?.text; if (isNotEmptyString(errorMsg)) { - setElementInnerHTML(_loadedMessage!, errorMsg!); - _loadedMessage!.style.display = ''; + if (loadedMessage != null) { + setElementInnerHTML(loadedMessage, errorMsg!); + loadedMessage.style.display = ''; + } } else { - _loadedMessage!.style.display = 'none'; + loadedMessage?.style.display = 'none'; } } } /// Sets the progress of loading. void setProgress(double? ratio) { - var progressDiv = _loadingDiv!.querySelector('.ui-loading-progress'); + final loadingDiv = _loadingDiv; + if (loadingDiv == null) return; + + var progressDiv = loadingDiv.querySelector('.ui-loading-progress'); + if (progressDiv == null) { progressDiv = HTMLDivElement()..classList.add('ui-loading-progress'); - var loadingDiv = _loadingDiv!.querySelector('.ui-loading')!; - loadingDiv.append(progressDiv); - return; + var loadingDiv2 = loadingDiv.querySelector('.ui-loading'); + loadingDiv2?.append(progressDiv); } if (ratio == null) { From c5862ffa91866d14342aac32cb6b38dc91a34f18 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 15 Feb 2026 16:49:24 -0300 Subject: [PATCH 46/64] - `UIDialogBase`: - Fixed background color style to use `rgb` when alpha is 1.0. --- CHANGELOG.md | 3 +++ lib/src/component/dialog.dart | 9 ++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96a6d08..0a0a8c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ - Updated event listener attachment to use `addEventListenerTyped`. - Fixed null safety and type checks for button elements. +- `UIDialogBase`: + - Fixed background color style to use `rgb` when alpha is 1.0. + ## 3.0.0-beta.19 - dom_tools: ^3.0.0-beta.13 diff --git a/lib/src/component/dialog.dart b/lib/src/component/dialog.dart index b123cf3..d6cbceb 100644 --- a/lib/src/component/dialog.dart +++ b/lib/src/component/dialog.dart @@ -1,9 +1,9 @@ import 'dart:async'; -import 'package:web_utils/web_utils.dart'; import 'package:dom_builder/dom_builder.dart'; import 'package:dom_tools/dom_tools.dart'; import 'package:swiss_knife/swiss_knife.dart'; +import 'package:web_utils/web_utils.dart'; import '../bones_ui_component.dart'; import '../bones_ui_generator.dart'; @@ -45,8 +45,6 @@ abstract class UIDialogBase extends UIRootComponent { componentClass: 'ui-dialog', ) { _myConfigure(style); - - initializeUIComponentsTree(); } @override @@ -62,8 +60,9 @@ abstract class UIDialogBase extends UIRootComponent { ..clear = 'both' ..padding = padding ..color = '#ffffff' - ..backgroundColor = - 'rgba($backgroundGrey,$backgroundGrey,$backgroundGrey, $backgroundAlpha)' + ..backgroundColor = backgroundAlpha == 1.0 + ? 'rgb($backgroundGrey,$backgroundGrey,$backgroundGrey)' + : 'rgba($backgroundGrey,$backgroundGrey,$backgroundGrey, $backgroundAlpha)' ..zIndex = '999999999'; if (fullScreen) { From f378eaf639376ed4757acf9d71fe2648e518724e Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 15 Feb 2026 16:50:35 -0300 Subject: [PATCH 47/64] - `bones_ui_extension.dart`: - Added `ElementStreamExtension` and `StreamSubscriptionExtension` to track subscriptions in `DOMTreeMap`. --- CHANGELOG.md | 3 +++ lib/src/bones_ui_extension.dart | 37 +++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a0a8c3..894b841 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +- `bones_ui_extension.dart`: + - Added `ElementStreamExtension` and `StreamSubscriptionExtension` to track subscriptions in `DOMTreeMap`. + - `UIButtonBase` and `UIButtonLoader`: - Updated event listener attachment to use `addEventListenerTyped`. - Fixed null safety and type checks for button elements. diff --git a/lib/src/bones_ui_extension.dart b/lib/src/bones_ui_extension.dart index 44517a6..42d3516 100644 --- a/lib/src/bones_ui_extension.dart +++ b/lib/src/bones_ui_extension.dart @@ -1,3 +1,6 @@ +import 'dart:async'; + +import 'package:dom_builder/dom_builder.dart'; import 'package:swiss_knife/swiss_knife.dart'; import 'package:web_utils/web_utils.dart'; @@ -105,3 +108,37 @@ extension UIIterableElementExtension on Iterable { /// Returns a [List] of values of this [Iterable] of [UIElement]s. List get elementsValues => map((e) => e.elementValue).toList(); } + +extension ElementStreamExtension on ElementStream { + StreamSubscription listenAndTrackSubscription( + void Function(E event)? onData, { + UIComponent? component, + DOMTreeMap? treeMap, + Element? element, + Function? onError, + void Function()? onDone, + bool? cancelOnError, + }) { + var subscription = listen(onData, + onError: onError, onDone: onDone, cancelOnError: cancelOnError); + + if (element != null) { + treeMap ??= component?.domTreeMap; + + if (treeMap != null) { + treeMap.mapSubscriptions(element, [subscription]); + } + } + + return subscription; + } +} + +extension StreamSubscriptionExtension on StreamSubscription { + StreamSubscription trackSubscription( + UIComponent component, Element element) { + var domTreeMap = component.domTreeMap; + domTreeMap.mapSubscriptions(element, [this]); + return this; + } +} From 3cf212d6c8df6af5824f94d30baa353fe4534643 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 15 Feb 2026 16:53:51 -0300 Subject: [PATCH 48/64] - `UIEventHandler`: - Added `unregisterEventListener` and `clearEventListeners` methods. - `EventHandlerPrivate`: - Changed `_eventListeners` to nullable and lazily initialized. - Added `_unregisterEventListener` and `_clearEventListeners` implementations. --- CHANGELOG.md | 8 ++++++++ lib/src/bones_ui_base.dart | 26 ++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 894b841..d484def 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ + +- `UIEventHandler`: + - Added `unregisterEventListener` and `clearEventListeners` methods. + +- `EventHandlerPrivate`: + - Changed `_eventListeners` to nullable and lazily initialized. + - Added `_unregisterEventListener` and `_clearEventListeners` implementations. + - `bones_ui_extension.dart`: - Added `ElementStreamExtension` and `StreamSubscriptionExtension` to track subscriptions in `DOMTreeMap`. diff --git a/lib/src/bones_ui_base.dart b/lib/src/bones_ui_base.dart index 85d7705..f9641eb 100644 --- a/lib/src/bones_ui_base.dart +++ b/lib/src/bones_ui_base.dart @@ -15,22 +15,40 @@ abstract class UIEventHandler extends EventHandlerPrivate { _registerEventListener(type, listener); } + void unregisterEventListener(String type) { + _unregisterEventListener(type); + } + + void clearEventListeners() { + _clearEventListeners(); + } + void fireEvent(String type, dynamic event, [List? params]) { _fireEvent(type, event, params); } } abstract class EventHandlerPrivate { - final Map> _eventListeners = {}; + Map>? _eventListeners; void _registerEventListener(String type, UIEventListener listener) { - var events = _eventListeners[type]; - if (events == null) _eventListeners[type] = events = []; + var eventListeners = _eventListeners ??= {}; + var events = eventListeners[type] ??= []; events.add(listener); } + void _unregisterEventListener(String type) { + var events = _eventListeners?.remove(type); + events?.clear(); + } + + void _clearEventListeners() { + _eventListeners?.clear(); + _eventListeners = null; + } + void _fireEvent(String type, dynamic event, [List? params]) { - var eventListeners = _eventListeners[type]; + var eventListeners = _eventListeners?[type]; if (eventListeners != null) { try { From a5cb563c567623c0a293af6aa5f5c120dbb544ad Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 15 Feb 2026 17:18:20 -0300 Subject: [PATCH 49/64] - `_CanvasEditImage`: - Added delayed `_configure` call after construction. - Updated `_updateCanvasDimension` to return bool indicating if dimension changed. - Improved zoom and translate setters to avoid unnecessary renders. --- CHANGELOG.md | 5 +++ lib/src/component/dialog_edit_image.dart | 41 ++++++++++++++++++------ 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d484def..c9f9d2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,11 @@ - `UIDialogBase`: - Fixed background color style to use `rgb` when alpha is 1.0. +- `_CanvasEditImage`: + - Added delayed `_configure` call after construction. + - Updated `_updateCanvasDimension` to return bool indicating if dimension changed. + - Improved zoom and translate setters to avoid unnecessary renders. + ## 3.0.0-beta.19 - dom_tools: ^3.0.0-beta.13 diff --git a/lib/src/component/dialog_edit_image.dart b/lib/src/component/dialog_edit_image.dart index 25048a8..a17e5fa 100644 --- a/lib/src/component/dialog_edit_image.dart +++ b/lib/src/component/dialog_edit_image.dart @@ -93,9 +93,13 @@ class _CanvasEditImage extends ExternalElementNode { marginHorizontal, marginVertical)) { _elementResize = TrackElementResize(); - _resetZoom(); + _resetZoom(requestRender: false); render(); + Future.delayed(Duration(milliseconds: 100), _configure); + } + + void _configure() { var canvas = this.canvas; _elementResize.track(canvas, (_) => _onResize(false)); @@ -132,9 +136,9 @@ class _CanvasEditImage extends ExternalElementNode { //print('!!! _onResize[$window]> $wR x $hR'); - _updateCanvasDimension(); + var updated = _updateCanvasDimension(); - if (r > 0.15) { + if (updated && r > 0.15) { _resetZoom(); } } @@ -275,12 +279,19 @@ class _CanvasEditImage extends ExternalElementNode { return [w, h]; } - void _updateCanvasDimension() { + bool _updateCanvasDimension() { var d = _calcCanvasDimension( imgNaturalWidth, imgNaturalHeight, marginHorizontal, marginVertical); var w = d[0]; var h = d[1]; + var w0 = canvas.width; + var h0 = canvas.height; + + if (w0 == w && h0 == h) { + return false; + } + canvas ..width = w ..height = h; @@ -288,22 +299,29 @@ class _CanvasEditImage extends ExternalElementNode { //print('!!! canvas dimension> $w x $h'); requestRender(); + + return true; } double _fitZoom = 1.0; double _zoom = 1.0; - void _resetZoom() { + void _resetZoom({bool requestRender = true}) { _fitZoom = _zoom = _calcZoom(imgNaturalWidth, imgNaturalHeight, canvasWidth, canvasHeight); var t = _translate; if (t != null) { - translate = Point(t.x + 1, t.y); + _translateImpl( + Point(t.x + 1, t.y), + requestRender: false, + ); } - requestRender(); + if (requestRender) { + this.requestRender(); + } } double get zoom => _zoom; @@ -341,7 +359,9 @@ class _CanvasEditImage extends ExternalElementNode { Point? get translate => _translate; - set translate(Point? translate) { + set translate(Point? translate) => _translateImpl(translate); + + void _translateImpl(Point? translate, {bool requestRender = true}) { if (_translate == translate) return; if (translate == null) { @@ -365,7 +385,10 @@ class _CanvasEditImage extends ExternalElementNode { } _translate = translate; - requestRender(); + + if (requestRender) { + this.requestRender(); + } } HTMLCanvasElement get canvas => externalElement as HTMLCanvasElement; From d2c441885458bde87661827a730ae6817d7a2b71 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 15 Feb 2026 17:23:34 -0300 Subject: [PATCH 50/64] - `ElementProvider` and `CSSProvider`: - Updated calls to `buildDOM` to include `treeMap` and `setTreeMapRoot` parameters. --- CHANGELOG.md | 3 +++ lib/src/bones_ui_base.dart | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9f9d2e..7e8120a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ - Changed `_eventListeners` to nullable and lazily initialized. - Added `_unregisterEventListener` and `_clearEventListeners` implementations. +- `ElementProvider` and `CSSProvider`: + - Updated calls to `buildDOM` to include `treeMap` and `setTreeMapRoot` parameters. + - `bones_ui_extension.dart`: - Added `ElementStreamExtension` and `StreamSubscriptionExtension` to track subscriptions in `DOMTreeMap`. diff --git a/lib/src/bones_ui_base.dart b/lib/src/bones_ui_base.dart index f9641eb..eb6797e 100644 --- a/lib/src/bones_ui_base.dart +++ b/lib/src/bones_ui_base.dart @@ -327,8 +327,10 @@ class ElementProvider { if (runtime.exists) { return runtime.node as UIElement?; } else { - return _domNode!.buildDOM(generator: UIComponent.domGenerator) - as UIElement?; + return _domNode!.buildDOM( + generator: UIComponent.domGenerator, + treeMap: UIComponent.domTreeMapDummy, + setTreeMapRoot: false) as UIElement?; } } From 8d803752364ec5809d19ab1bd8c7272322f51f54 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 15 Feb 2026 17:25:39 -0300 Subject: [PATCH 51/64] - `UIDOMGenerator`: - Updated `addExternalElementToElement` and `toElements` to accept `treeMap` and `context` parameters. - Updated calls to `buildDOM` to pass `treeMap` and `setTreeMapRoot`. --- CHANGELOG.md | 4 +++ lib/src/bones_ui_generator.dart | 46 ++++++++++++++++++++------------- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e8120a..b6794b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ - `bones_ui_extension.dart`: - Added `ElementStreamExtension` and `StreamSubscriptionExtension` to track subscriptions in `DOMTreeMap`. +- `UIDOMGenerator`: + - Updated `addExternalElementToElement` and `toElements` to accept `treeMap` and `context` parameters. + - Updated calls to `buildDOM` to pass `treeMap` and `setTreeMapRoot`. + - `UIButtonBase` and `UIButtonLoader`: - Updated event listener attachment to use `addEventListenerTyped`. - Fixed null safety and type checks for button elements. diff --git a/lib/src/bones_ui_generator.dart b/lib/src/bones_ui_generator.dart index 20cf6df..cb86d54 100644 --- a/lib/src/bones_ui_generator.dart +++ b/lib/src/bones_ui_generator.dart @@ -395,19 +395,22 @@ class UIDOMGenerator extends DOMGeneratorWebImpl { @override List? addExternalElementToElement( - UINode element, Object? externalElement) { + UINode element, Object? externalElement, + {DOMTreeMap? treeMap, DOMContext? context}) { if (externalElement == null) return null; if (externalElement is List) { if (externalElement.isEmpty) return null; if (externalElement.length == 1) { - return addExternalElementToElement(element, externalElement.first); + return addExternalElementToElement(element, externalElement.first, + treeMap: treeMap, context: context); } var children = []; for (var elem in externalElement) { - var child = addExternalElementToElement(element, elem); + var child = addExternalElementToElement(element, elem, + treeMap: treeMap, context: context); if (child != null) { children.addAll(child); } @@ -417,18 +420,20 @@ class UIDOMGenerator extends DOMGeneratorWebImpl { var component = externalElement; var componentContent = component.content; - final element2 = element.asElementChecked; - if (element2 != null) { - element2.appendChild(componentContent!); - component.setParent(element2); - _resolveParentUIComponent(element2, component.content, - childUIComponent: component); - component.ensureRendered(); - return [componentContent]; - } else { - _resolveParentUIComponent(element2, component.content, - childUIComponent: component); - return null; + if (componentContent != null) { + final element2 = element.asElementChecked; + if (element2 != null) { + element2.appendChild(componentContent); + component.setParent(element2); + _resolveParentUIComponent(element2, component.content, + childUIComponent: component); + component.ensureRendered(); + return [componentContent]; + } else { + _resolveParentUIComponent(element2, component.content, + childUIComponent: component); + return null; + } } } else if (externalElement is MessageBuilder) { var text = externalElement.build(); @@ -438,7 +443,8 @@ class UIDOMGenerator extends DOMGeneratorWebImpl { return [span]; } - return super.addExternalElementToElement(element, externalElement); + return super.addExternalElementToElement(element, externalElement, + treeMap: treeMap, context: context); } @override @@ -487,7 +493,10 @@ class UIDOMGenerator extends DOMGeneratorWebImpl { } @override - List? toElements(Object? elements) { + List? toElements(Object? elements, + {DOMTreeMap? treeMap, + DOMContext? context, + bool setTreeMapRoot = true}) { if (elements is UIComponent) { elements.ensureRendered(); var content = elements.content; @@ -496,7 +505,8 @@ class UIDOMGenerator extends DOMGeneratorWebImpl { var content = elements.content; return content != null ? [content] : null; } else { - return super.toElements(elements); + return super.toElements(elements, + treeMap: treeMap, context: context, setTreeMapRoot: setTreeMapRoot); } } From 8cd7c892478f1c7261c1669c84e06512794b9754 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 15 Feb 2026 17:30:59 -0300 Subject: [PATCH 52/64] - `UICapture`: - Added new `CaptureDataFormat.urlOrBlobUrl` format. - Added `selectedFileDataAsURLOrDataURLBase64` getter. - Updated `_readFile` and `_filterCapturedData` to support new format and improved async flow with `_yeld` helper. - Updated `_CapturedData` class to store optional MIME type and support new format. - Improved data format conversions to preserve MIME type. - Added helper `_yeld` function for async yielding. --- CHANGELOG.md | 8 + lib/src/component/capture.dart | 280 +++++++++++++++++++++++---------- 2 files changed, 209 insertions(+), 79 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6794b2..6077ce5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,14 @@ - Updated event listener attachment to use `addEventListenerTyped`. - Fixed null safety and type checks for button elements. +- `UICapture`: + - Added new `CaptureDataFormat.urlOrBlobUrl` format. + - Added `selectedFileDataAsURLOrDataURLBase64` getter. + - Updated `_readFile` and `_filterCapturedData` to support new format and improved async flow with `_yeld` helper. + - Updated `_CapturedData` class to store optional MIME type and support new format. + - Improved data format conversions to preserve MIME type. + - Added helper `_yeld` function for async yielding. + - `UIDialogBase`: - Fixed background color style to use `rgb` when alpha is 1.0. diff --git a/lib/src/component/capture.dart b/lib/src/component/capture.dart index 4ee0d87..f8de8bc 100644 --- a/lib/src/component/capture.dart +++ b/lib/src/component/capture.dart @@ -60,6 +60,9 @@ enum CaptureDataFormat { /// The data is stored as a URL [String] (including `DataURL`). url, + + /// The data is stored as a URL [String] or [Blob] URL. + urlOrBlobUrl, } typedef CapturePhotoEditor = FutureOr Function( @@ -225,8 +228,12 @@ abstract class UICapture extends UIButtonBase implements UIField { final EventStream onCapture = EventStream(); void _callOnCapture(HTMLInputElement input, Event event) async { + await _yeld(); + await _readFile(input); + await _yeld(); + onCaptureFile(input, event); onCapture.add(this); } @@ -243,7 +250,7 @@ abstract class UICapture extends UIButtonBase implements UIField { @override String? getFieldValue() { - return selectedFileDataAsDataURLBase64; + return selectedFileDataAsURLOrDataURLBase64; } @override @@ -317,6 +324,20 @@ abstract class UICapture extends UIButtonBase implements UIField { } } + String? get selectedFileDataAsURLOrDataURLBase64 { + var selectedFileData = _selectedFileData; + if (selectedFileData == null) return null; + + if (selectedFileData.dataFormat == CaptureDataFormat.url || + selectedFileData.dataFormat == CaptureDataFormat.dataUrlBase64) { + return selectedFileData.dataAsURLOrDataURL(); + } else { + return selectedFileData + .dataAsDataUrlBase64(dataEncoding: _dataEncoding) + .asDataURLString(); + } + } + /// The maximum width of the captured photo. /// See [captureType]. int? captureMaxWidth; @@ -341,26 +362,37 @@ abstract class UICapture extends UIButtonBase implements UIField { bool removeExifFromImage = true; Future _readFile(HTMLInputElement input) async { - if (input.files!.isNotEmpty) { - var file = input.files!.item(0); + var files = input.files; + if (files == null || files.isEmpty) return; - _selectedFile = file; + var file = files.item(0); + if (file == null) return; - var data = await _readFileInput(input); + _selectedFile = file; - if (data == null) { - throw StateError("Can't capture data as format: $captureDataFormat"); - } + await _yeld(); - var capturedData = _CapturedData.from(captureDataFormat, data); + var data = await _readFileInput(input); - capturedData = await _filterCapturedData(capturedData); + if (data == null) { + throw StateError("Can't capture data as format: $captureDataFormat"); + } - _selectedFileData = capturedData; + await _yeld(); - onCaptureData.add(this); - onChange.add(this); - } + var mimeType = getFileMimeType(file); + + var capturedData = _CapturedData.from(captureDataFormat, data, + mimeType: mimeType?.toString()); + + await _yeld(); + + capturedData = await _filterCapturedData(capturedData); + + _selectedFileData = capturedData; + + onCaptureData.add(this); + onChange.add(this); } Future<_CapturedData> _filterCapturedData(_CapturedData capturedData) async { @@ -372,7 +404,7 @@ abstract class UICapture extends UIButtonBase implements UIField { } if (captureType.isPhoto) { - var fileURL = capturedData.dataAsURL(); + var fileURL = capturedData.dataAsURLOrDataURL(); var imageElement = HTMLImageElement()..src = fileURL; @@ -403,6 +435,8 @@ abstract class UICapture extends UIButtonBase implements UIField { Future<_CapturedData> _filterCapturedPhoto( _CapturedData capturedData, HTMLImageElement image) async { if (editCapture) { + await _yeld(); + var photoEditor = this.photoEditor; if (photoEditor != null) { var imageEdited = await photoEditor(image); @@ -411,6 +445,7 @@ abstract class UICapture extends UIButtonBase implements UIField { } } else { var dialogEdit = UIDialogEditImage(image); + await _yeld(); var edited = await dialogEdit.showAndWait(); if (edited) { image = dialogEdit.editedImage ?? image; @@ -515,7 +550,8 @@ abstract class UICapture extends UIButtonBase implements UIField { photoScaleMimeType ??= "image/jpeg"; var canvasDataURL = canvas.toDataUrl(photoScaleMimeType, photoScaleQuality); - var capturedData2 = _CapturedData.fromURL(canvasDataURL); + var capturedData2 = + _CapturedData.fromURL(canvasDataURL, photoScaleMimeType); capturedData2 = capturedData2.withDataFormat(capturedData.dataFormat); return capturedData2; @@ -540,6 +576,9 @@ abstract class UICapture extends UIButtonBase implements UIField { case CaptureDataFormat.url: return await readFileInputElementAsDataURLBase64( input, removeExifFromImage); + + case CaptureDataFormat.urlOrBlobUrl: + return await readFileInputElementAsBlobUrl(input, removeExifFromImage); } } @@ -600,61 +639,68 @@ class _CapturedData { final Object data; - _CapturedData._(this.dataFormat, this.data); + String? _mimeTypeStr; - factory _CapturedData.fromArrayBuffer(List data) { + _CapturedData._(this.dataFormat, this.data, this._mimeTypeStr); + + factory _CapturedData.fromArrayBuffer(List data, String? mimeType) { var bs = data is Uint8List ? data : Uint8List.fromList(data); - return _CapturedData._(CaptureDataFormat.arrayBuffer, bs); + return _CapturedData._(CaptureDataFormat.arrayBuffer, bs, mimeType); } - factory _CapturedData.fromBase64(String base64) { - return _CapturedData._(CaptureDataFormat.base64, base64.trim()); + factory _CapturedData.fromBase64(String base64, String? mimeType) { + return _CapturedData._(CaptureDataFormat.base64, base64.trim(), mimeType); } factory _CapturedData.fromDataUrlBase64(DataURLBase64 dataUrlBase64) { - return _CapturedData._(CaptureDataFormat.dataUrlBase64, dataUrlBase64); + return _CapturedData._(CaptureDataFormat.dataUrlBase64, dataUrlBase64, + dataUrlBase64.mimeTypeAsString); } - factory _CapturedData.fromString(String string) { - return _CapturedData._(CaptureDataFormat.string, string); + factory _CapturedData.fromString(String string, String? mimeType) { + return _CapturedData._(CaptureDataFormat.string, string, mimeType); } - factory _CapturedData.fromURL(String url) { - return _CapturedData._(CaptureDataFormat.url, url); + factory _CapturedData.fromURL(String url, String? mimeType) { + return _CapturedData._(CaptureDataFormat.url, url, mimeType); } factory _CapturedData.from(CaptureDataFormat dataFormat, Object data, - {data_convert.Encoding? dataEncoding}) { + {String? mimeType, data_convert.Encoding? dataEncoding}) { switch (dataFormat) { case CaptureDataFormat.arrayBuffer: { if (data is List) { - return _CapturedData.fromArrayBuffer(data); + return _CapturedData.fromArrayBuffer(data, mimeType); } else if (data is DataURLBase64) { - return _CapturedData.fromArrayBuffer(data.payloadArrayBuffer); + return _CapturedData.fromArrayBuffer( + data.payloadArrayBuffer, mimeType); } else { var s = data.toString(); var dataUrl = DataURLBase64.parse(s); if (dataUrl != null) { - return _CapturedData.fromArrayBuffer(dataUrl.payloadArrayBuffer); + return _CapturedData.fromArrayBuffer( + dataUrl.payloadArrayBuffer, dataUrl.mimeTypeAsString); } else { var bs = _decodeBase64(s); if (bs != null) { - return _CapturedData.fromArrayBuffer(bs); + return _CapturedData.fromArrayBuffer(bs, mimeType); } dataEncoding ??= data_convert.utf8; - return _CapturedData.fromArrayBuffer(dataEncoding.encode(s)); + return _CapturedData.fromArrayBuffer( + dataEncoding.encode(s), mimeType); } } } case CaptureDataFormat.dataUrlBase64: { if (data is List) { - return _CapturedData.fromDataUrlBase64( - DataURLBase64(data_convert.base64.encode(data))); + var dataURLBase64 = + DataURLBase64(data_convert.base64.encode(data), mimeType); + return _CapturedData.fromDataUrlBase64(dataURLBase64); } else if (data is DataURLBase64) { return _CapturedData.fromDataUrlBase64(data); } else { @@ -666,39 +712,44 @@ class _CapturedData { } else { List? bs = _decodeBase64(s); if (bs != null) { - return _CapturedData.fromDataUrlBase64(DataURLBase64(s)); + var dataURLBase64 = DataURLBase64(s, mimeType); + return _CapturedData.fromDataUrlBase64(dataURLBase64); } dataEncoding ??= data_convert.utf8; - bs = dataEncoding.encode(s); - return _CapturedData.fromDataUrlBase64( - DataURLBase64(data_convert.base64.encode(bs), 'text/plain')); + + var dataURLBase64 = DataURLBase64( + data_convert.base64.encode(bs), mimeType ?? 'text/plain'); + return _CapturedData.fromDataUrlBase64(dataURLBase64); } } } case CaptureDataFormat.base64: { if (data is List) { - return _CapturedData.fromBase64(data_convert.base64.encode(data)); + return _CapturedData.fromBase64( + data_convert.base64.encode(data), mimeType); } else if (data is DataURLBase64) { - return _CapturedData.fromBase64(data.payload); + return _CapturedData.fromBase64( + data.payload, data.mimeTypeAsString); } else { var s = data.toString(); var dataUrl = DataURLBase64.parse(s); if (dataUrl != null) { - return _CapturedData.fromBase64(dataUrl.payloadBase64); + return _CapturedData.fromBase64(dataUrl.payloadBase64, mimeType); } else { List? bs = _decodeBase64(s); if (bs != null) { - return _CapturedData.fromBase64(s); + return _CapturedData.fromBase64(s, mimeType); } dataEncoding ??= data_convert.utf8; bs = dataEncoding.encode(s); - return _CapturedData.fromBase64(data_convert.base64.encode(bs)); + return _CapturedData.fromBase64( + data_convert.base64.encode(bs), mimeType); } } } @@ -706,48 +757,94 @@ class _CapturedData { { if (data is List) { var s = _decodeAsString(data, dataEncoding); - return _CapturedData.fromString(s); + return _CapturedData.fromString(s, mimeType); } else if (data is DataURLBase64) { - return _CapturedData.fromString(data.payload); + return _CapturedData.fromString(data.payload, mimeType); } else { var s = data.toString(); var dataUrl = DataURLBase64.parse(s); if (dataUrl != null) { - return _CapturedData.fromString(dataUrl.payload); + return _CapturedData.fromString(dataUrl.payload, mimeType); } else { - List? bs = _decodeBase64(s); + var bs = _decodeBase64(s); if (bs != null) { return _CapturedData.fromString( - _decodeAsString(bs, dataEncoding)); + _decodeAsString(bs, dataEncoding), mimeType); } - - return _CapturedData.fromString(s); + return _CapturedData.fromString(s, mimeType); } } } case CaptureDataFormat.url: { if (data is List) { - return _CapturedData.fromURL( - DataURLBase64(data_convert.base64.encode(data)) - .asDataURLString()); + var dataUrl = DataURLBase64.from( + data, mimeType ?? MimeType.applicationOctetStream); + return _CapturedData.fromDataUrlBase64(dataUrl); } else if (data is DataURLBase64) { - return _CapturedData.fromURL(data.asDataURLString()); + return _CapturedData.fromDataUrlBase64(data); } else { var s = data.toString().trim(); + + // Identify URL and avoid extra parsing: + if (s.startsWith("http://") || + s.startsWith("https://") || + s.startsWith("blob:http")) { + return _CapturedData.fromURL(s, mimeType); + } + var dataUrl = DataURLBase64.parse(s); if (dataUrl != null) { - return _CapturedData.fromURL(dataUrl.asDataURLString()); + return _CapturedData.fromDataUrlBase64(dataUrl); } else { - List? bs = _decodeBase64(s); + var bs = _decodeBase64(s); if (bs != null) { - return _CapturedData.fromURL( - DataURLBase64(s).asDataURLString()); + var dataUrl = DataURLBase64(s, mimeType); + return _CapturedData.fromDataUrlBase64(dataUrl); } - return _CapturedData.fromURL(s); + return _CapturedData.fromURL(s, mimeType); + } + } + } + case CaptureDataFormat.urlOrBlobUrl: + { + if (data is List) { + var bs = data is Uint8List ? data : Uint8List.fromList(data); + var url = + createBlobURL(bs, mimeType ?? MimeType.applicationOctetStream); + return _CapturedData.fromURL(url, mimeType); + } else if (data is DataURLBase64) { + var url = + createBlobURL(data.payloadArrayBuffer, data.mimeTypeAsString); + return _CapturedData.fromURL(url, data.mimeTypeAsString); + } else { + var s = data.toString().trim(); + + // Identify URL and avoid extra parsing: + if (s.startsWith("http://") || + s.startsWith("https://") || + s.startsWith("blob:http")) { + return _CapturedData.fromURL(s, mimeType); + } + + var dataUrl = DataURLBase64.parse(s); + + if (dataUrl != null) { + var url = createBlobURL( + dataUrl.payloadArrayBuffer, dataUrl.mimeTypeAsString); + return _CapturedData.fromURL(url, mimeType); + } else { + var bs = _decodeBase64(s); + if (bs != null) { + var url = createBlobURL( + bs, mimeType ?? MimeType.applicationOctetStream); + return _CapturedData.fromURL(url, mimeType); + } + + return _CapturedData.fromURL(s, mimeType); } } } @@ -759,7 +856,7 @@ class _CapturedData { return data as Uint8List; } else { return _CapturedData.from(CaptureDataFormat.arrayBuffer, data, - dataEncoding: dataEncoding) + mimeType: _mimeTypeStr, dataEncoding: dataEncoding) .data as Uint8List; } } @@ -769,7 +866,7 @@ class _CapturedData { return data as String; } else { return _CapturedData.from(CaptureDataFormat.base64, data, - dataEncoding: dataEncoding) + mimeType: _mimeTypeStr, dataEncoding: dataEncoding) .data as String; } } @@ -779,7 +876,7 @@ class _CapturedData { return data as DataURLBase64; } else { return _CapturedData.from(CaptureDataFormat.dataUrlBase64, data, - dataEncoding: dataEncoding) + mimeType: _mimeTypeStr, dataEncoding: dataEncoding) .data as DataURLBase64; } } @@ -789,7 +886,7 @@ class _CapturedData { return data as String; } else { return _CapturedData.from(CaptureDataFormat.string, data, - dataEncoding: dataEncoding) + mimeType: _mimeTypeStr, dataEncoding: dataEncoding) .data as String; } } @@ -799,35 +896,58 @@ class _CapturedData { return data as String; } else { return _CapturedData.from(CaptureDataFormat.url, data, - dataEncoding: dataEncoding) + mimeType: _mimeTypeStr, dataEncoding: dataEncoding) + .data as String; + } + } + + String dataAsURLOrDataURL({data_convert.Encoding? dataEncoding}) { + if (dataFormat == CaptureDataFormat.url) { + return data as String; + } else if (dataFormat == CaptureDataFormat.dataUrlBase64) { + return (data as DataURLBase64).toString(); + } else { + return _CapturedData.from(CaptureDataFormat.url, data, + mimeType: _mimeTypeStr, dataEncoding: dataEncoding) .data as String; } } MimeType? get mimeType { - DataURLBase64? dataUrl; + var mimeTypeStr = _mimeTypeStr; + if (mimeTypeStr != null && mimeTypeStr.isNotEmpty) { + return MimeType.parse(mimeTypeStr); + } if (dataFormat == CaptureDataFormat.dataUrlBase64) { - dataUrl = dataAsDataUrlBase64(); - } else if (dataFormat == CaptureDataFormat.url && - dataAsURL().startsWith('data:')) { - dataUrl = dataAsDataUrlBase64(); - } else { - try { - dataUrl = dataAsDataUrlBase64(); - } catch (_) {} + var dataUrl = data as DataURLBase64; + var mimeType = dataUrl.mimeType; + _mimeTypeStr = mimeType?.toString(); + return mimeType; + } else if (dataFormat == CaptureDataFormat.url) { + var dataUrl = dataAsURL(); + if (dataUrl.startsWith('data:')) { + var mimeType = DataURLBase64.parseMimeType(dataUrl); + _mimeTypeStr = mimeType?.toString(); + return mimeType; + } else { + return null; + } } - if (dataUrl != null) { - return dataUrl.mimeType; - } + try { + var dataUrl = dataAsDataUrlBase64(); + var mimeType = dataUrl.mimeType; + _mimeTypeStr = mimeType?.toString(); + return mimeType; + } catch (_) {} return null; } _CapturedData withDataFormat(CaptureDataFormat targetDataFormat) { if (dataFormat != targetDataFormat) { - return _CapturedData.from(targetDataFormat, data); + return _CapturedData.from(targetDataFormat, data, mimeType: _mimeTypeStr); } else { return this; } @@ -1049,7 +1169,7 @@ class UIButtonCapturePhoto extends UICapture { final List _selectedImageElements = []; void showSelectedImage() { - var dataURL = selectedFileDataAsDataURLBase64; + var dataURL = selectedFileDataAsURLOrDataURLBase64; if (dataURL == null) return; var content = this.content!; @@ -1174,7 +1294,7 @@ class UIButtonCapture extends UICapture { } void showSelectedFile() { - var dataURL = selectedFileDataAsDataURLBase64; + var dataURL = selectedFileDataAsURLOrDataURLBase64; if (dataURL == null) return; final content = this.content; @@ -1198,3 +1318,5 @@ class UIButtonCapture extends UICapture { content!.style.width = ''; } } + +Future _yeld({int ms = 1}) => Future.delayed(Duration(milliseconds: ms)); From d07bc0e98c4d7b51668e1ee6caeb0a6e24a41737 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 15 Feb 2026 17:34:09 -0300 Subject: [PATCH 53/64] - `UILoading`: - Updated `asDIVElement` buildDOM calls to pass `treeMap` and `setTreeMapRoot`. --- CHANGELOG.md | 3 +++ lib/src/component/loading.dart | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6077ce5..ef90bb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,9 @@ - Updated `_updateCanvasDimension` to return bool indicating if dimension changed. - Improved zoom and translate setters to avoid unnecessary renders. +- `UILoading`: + - Updated `asDIVElement` buildDOM calls to pass `treeMap` and `setTreeMapRoot`. + ## 3.0.0-beta.19 - dom_tools: ^3.0.0-beta.13 diff --git a/lib/src/component/loading.dart b/lib/src/component/loading.dart index a8a2d51..4080c2c 100644 --- a/lib/src/component/loading.dart +++ b/lib/src/component/loading.dart @@ -597,7 +597,10 @@ abstract class UILoading { cssContext: cssContext, withProgress: withProgress, config: config); - return div.buildDOM(generator: UIComponent.domGenerator) as HTMLDivElement; + return div.buildDOM( + generator: UIComponent.domGenerator, + treeMap: UIComponent.domTreeMapDummy, + setTreeMapRoot: false) as HTMLDivElement; } } From 8b31076f6da6d62e7a7bfc835142afe1c95f1b48 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 15 Feb 2026 17:36:02 -0300 Subject: [PATCH 54/64] - `MasonryItem` and `_MasonryRenderItem`: - Updated DOM generation calls to pass `treeMap` and `setTreeMapRoot`. --- CHANGELOG.md | 2 ++ lib/src/component/masonry.dart | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef90bb7..9f67368 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,8 @@ - `UILoading`: - Updated `asDIVElement` buildDOM calls to pass `treeMap` and `setTreeMapRoot`. +- `MasonryItem` and `_MasonryRenderItem`: + - Updated DOM generation calls to pass `treeMap` and `setTreeMapRoot`. ## 3.0.0-beta.19 - dom_tools: ^3.0.0-beta.13 diff --git a/lib/src/component/masonry.dart b/lib/src/component/masonry.dart index f218ad4..1337885 100644 --- a/lib/src/component/masonry.dart +++ b/lib/src/component/masonry.dart @@ -31,7 +31,8 @@ class MasonryItem { } factory MasonryItem.fromDOMElement(DOMElement element) { - var domElement = UIComponent.domGenerator.generate(element); + var domElement = UIComponent.domGenerator.generate(element, + treeMap: UIComponent.domTreeMapDummy, setTreeMapRoot: false); return MasonryItem.fromElement(domElement as Element?); } @@ -1009,12 +1010,18 @@ class _MasonryRenderItem extends _MasonryRenderable { element.ensureRendered(true); element = element.content; } else if (element is DOMElement) { - element = - element.buildDOM(generator: UIComponent.domGenerator, parent: div4); + element = element.buildDOM( + generator: UIComponent.domGenerator, + treeMap: masonry.domTreeMap, + parent: div4, + setTreeMapRoot: false); } else { var htmlRoot = $htmlRoot(element); - element = - htmlRoot?.buildDOM(generator: UIComponent.domGenerator, parent: div4); + element = htmlRoot?.buildDOM( + generator: UIComponent.domGenerator, + treeMap: masonry.domTreeMap, + parent: div4, + setTreeMapRoot: false); } if (element.isElement) { From 81e5e221e2144702744a4b94d9d9c14986dd47f8 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 15 Feb 2026 17:36:49 -0300 Subject: [PATCH 55/64] - `UITemplateElementGenerator`: - Updated DOM generation calls to pass `treeMap`, `context`, and `setTreeMapRoot`. - Updated template building to use `dsxResolution` enum instead of boolean flags. --- CHANGELOG.md | 5 +++++ lib/src/component/template.dart | 20 ++++++++++++-------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f67368..a583fb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,11 @@ - `MasonryItem` and `_MasonryRenderItem`: - Updated DOM generation calls to pass `treeMap` and `setTreeMapRoot`. + +- `UITemplateElementGenerator`: + - Updated DOM generation calls to pass `treeMap`, `context`, and `setTreeMapRoot`. + - Updated template building to use `dsxResolution` enum instead of boolean flags. + ## 3.0.0-beta.19 - dom_tools: ^3.0.0-beta.13 diff --git a/lib/src/component/template.dart b/lib/src/component/template.dart index fac0120..f2bbac5 100644 --- a/lib/src/component/template.dart +++ b/lib/src/component/template.dart @@ -1,8 +1,7 @@ -import 'package:web_utils/web_utils.dart'; - import 'package:dom_builder/dom_builder.dart'; import 'package:dynamic_call/dynamic_call.dart'; import 'package:swiss_knife/swiss_knife.dart'; +import 'package:web_utils/web_utils.dart'; import '../bones_ui_generator.dart'; import '../bones_ui_navigator.dart'; @@ -51,7 +50,8 @@ class UITemplateElementGenerator extends ElementGeneratorBase { _generateFromTemplateHTML(htmlUnresolved, domGenerator, treeMap, domElement, element, attributes, context); } else { - domGenerator.generateWithRoot(domElement, element, contentNodes); + domGenerator.generateWithRoot(domElement, element, contentNodes, + treeMap: treeMap, context: context, setTreeMapRoot: false); } return element; @@ -60,7 +60,9 @@ class UITemplateElementGenerator extends ElementGeneratorBase { String _nodesToHTMLUnresolved(List contentNodes) { return contentNodes .map((e) => e.buildHTML( - withIndent: true, buildTemplates: false, resolveDSX: false)) + withIndent: true, + buildTemplates: false, + dsxResolution: DSXResolution.skipDSX)) .join(''); } @@ -129,9 +131,11 @@ class UITemplateElementGenerator extends ElementGeneratorBase { DOMElement domElement, HTMLDivElement element) { var templateBuiltHTML = template.buildAsString(variables, - resolveDSX: false, - elementProvider: (q) => treeMap.queryElement(q, - domContext: domContext, buildTemplates: true), + dsxResolution: DSXResolution.skipDSX, + elementProvider: (q) => treeMap.queryElementAsHTML(q, + domContext: domContext, + buildTemplates: true, + dsxResolution: DSXResolution.skipDSX), intlMessageResolver: domContext?.intlMessageResolver); var nodes = DOMNode.parseNodes(templateBuiltHTML); @@ -267,7 +271,7 @@ class UITemplateElementGenerator extends ElementGeneratorBase { }, (c, k, v) { var template = DOMTemplate.parse(v as String); var v2 = template.build(variables, - resolveDSX: false, + dsxResolution: DSXResolution.skipDSX, intlMessageResolver: domContext?.intlMessageResolver); return v2; }); From 46f0aa86f29c8c06cbdbc3d48fbaf15679141fb9 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 15 Feb 2026 17:37:38 -0300 Subject: [PATCH 56/64] =?UTF-8?q?=F0=9F=90=9B=20fix(lib/src/bones=5Fui=5Fn?= =?UTF-8?q?avigator.dart):=20use=20children.asListViewFixed=20instead=20of?= =?UTF-8?q?=20toList=20to=20avoid=20extra=20copying?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/bones_ui_navigator.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/bones_ui_navigator.dart b/lib/src/bones_ui_navigator.dart index 79b0fe8..477df9d 100644 --- a/lib/src/bones_ui_navigator.dart +++ b/lib/src/bones_ui_navigator.dart @@ -523,7 +523,7 @@ class UINavigator { !routes.contains(navigateRoute)) { routes.add(navigateRoute); } - _findElementNavigableRoutes(elem.children.toList(), routes); + _findElementNavigableRoutes(elem.children.asListViewFixed, routes); } } From 0face63a716b76fd333d1da44fce0dfa8420ffab Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 15 Feb 2026 17:38:59 -0300 Subject: [PATCH 57/64] - `InputConfig`: - Added optional `parent` parameter to rendering methods. - Updated DOM building calls to pass `treeMap` and `setTreeMapRoot`. - Updated `_resolveValueText` to use `parent` for intl message resolution. - `UIInputTable`: - Updated rendering of inputs and labels to pass `domTreeMap` and `setTreeMapRoot`. --- CHANGELOG.md | 8 +++ lib/src/component/input_config.dart | 82 ++++++++++++++++++----------- 2 files changed, 60 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a583fb2..5dd5f81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,14 @@ - Updated `_updateCanvasDimension` to return bool indicating if dimension changed. - Improved zoom and translate setters to avoid unnecessary renders. +- `InputConfig`: + - Added optional `parent` parameter to rendering methods. + - Updated DOM building calls to pass `treeMap` and `setTreeMapRoot`. + - Updated `_resolveValueText` to use `parent` for intl message resolution. + +- `UIInputTable`: + - Updated rendering of inputs and labels to pass `domTreeMap` and `setTreeMapRoot`. + - `UILoading`: - Updated `asDIVElement` buildDOM calls to pass `treeMap` and `setTreeMapRoot`. diff --git a/lib/src/component/input_config.dart b/lib/src/component/input_config.dart index 5a2eec2..50568e8 100644 --- a/lib/src/component/input_config.dart +++ b/lib/src/component/input_config.dart @@ -229,7 +229,8 @@ class InputConfig { } } - dynamic renderInput([FieldValueProvider? fieldValueProvider]) { + dynamic renderInput( + {UIComponent? parent, FieldValueProvider? fieldValueProvider}) { var inputID = id; var inputType = type; var inputValue = fieldValueProvider != null @@ -260,11 +261,11 @@ class InputConfig { "Can't handle input rendered object type: ${obj.runtimeType} > $obj"); } } else if (inputType == 'textarea') { - inputElement = _renderTextArea(inputValue); + inputElement = _renderTextArea(parent, inputValue); } else if (inputType == 'decimal') { - inputElement = _renderDecimal(inputValue); + inputElement = _renderDecimal(parent, inputValue); } else if (inputType == 'select') { - inputElement = _renderSelect(inputValue); + inputElement = _renderSelect(parent, inputValue); } else if (inputType == 'image') { var capture = UIButtonCapturePhoto(null, text: label, fieldName: inputID); inputComponent = capture; @@ -277,7 +278,7 @@ class InputConfig { pickerHeight: 100); inputComponent = picker; } else if (inputType == 'path') { - element = _renderInputPath(inputID, inputValue); + element = _renderInputPath(parent, inputID, inputValue); } else if (inputType == 'html') { inputElement = createHTML(html: inputValue); inputElement.onClick.listen((event) { @@ -287,7 +288,7 @@ class InputConfig { } }); } else { - inputElement = _renderGenericInput(inputType, inputValue); + inputElement = _renderGenericInput(parent, inputType, inputValue); } if (inputElement != null) { @@ -320,7 +321,7 @@ class InputConfig { return inputValue; } - String? _resolveValueText(Object? inputValue) { + String? _resolveValueText(UIComponent? parent, Object? inputValue) { var val = _resolveValue(inputValue); if (val == null) return null; @@ -328,8 +329,12 @@ class InputConfig { if (containsIntlMessage(valText)) { var domSpan = $span(content: valText); - var dom = domSpan.buildDOM(generator: UIComponent.domGenerator); + var dom = domSpan.buildDOM( + generator: UIComponent.domGenerator, + treeMap: parent?.domTreeMap ?? UIComponent.domTreeMapDummy, + setTreeMapRoot: false); var text = dom?.textContent; + return text; } @@ -370,7 +375,8 @@ class InputConfig { } } - HTMLDivElement? _renderInputPath(String fieldName, String? inputValue) { + HTMLDivElement? _renderInputPath( + UIComponent? parent, String fieldName, String? inputValue) { var input = $input(style: 'width: auto', value: inputValue); DOMElement? button; @@ -397,21 +403,23 @@ class InputConfig { }); } - var div = $div(content: [input, button]) - .buildDOM(generator: UIComponent.domGenerator); + var div = $div(content: [input, button]).buildDOM( + generator: UIComponent.domGenerator, + treeMap: parent?.domTreeMap ?? UIComponent.domTreeMapDummy, + setTreeMapRoot: false); return div as HTMLDivElement?; } - HTMLTextAreaElement _renderTextArea(Object? inputValue) { - var valText = _resolveValueText(inputValue); + HTMLTextAreaElement _renderTextArea(UIComponent? parent, Object? inputValue) { + var valText = _resolveValueText(parent, inputValue); var textArea = HTMLTextAreaElement()..style.width = '100%'; textArea.value = valText ?? ''; return textArea; } - Element _renderDecimal(Object? inputValue) { - var valText = _resolveValueText(inputValue); + Element _renderDecimal(UIComponent? parent, Object? inputValue) { + var valText = _resolveValueText(parent, inputValue); var input = HTMLInputElement() ..type = 'number' @@ -438,8 +446,9 @@ class InputConfig { return input; } - Element _renderGenericInput(String? inputType, Object? inputValue) { - var valText = _resolveValueText(inputValue); + Element _renderGenericInput( + UIComponent? parent, String? inputType, Object? inputValue) { + var valText = _resolveValueText(parent, inputValue); var input = HTMLInputElement() ..type = inputType ?? 'text' @@ -457,7 +466,7 @@ class InputConfig { return input; } - HTMLSelectElement _renderSelect(Object? inputValue) { + HTMLSelectElement _renderSelect(UIComponent? parent, Object? inputValue) { var select = HTMLSelectElement()..style.maxWidth = '100%'; if (options != null && options!.isNotEmpty) { @@ -474,7 +483,7 @@ class InputConfig { selected = true; } - optVal = _resolveValueText(optVal); + optVal = _resolveValueText(parent, optVal); if (optVal == null || optVal.isEmpty) { optVal = optKey; @@ -491,7 +500,7 @@ class InputConfig { select.add(optionElement, null); } } else if (inputValue != null) { - var s = _resolveValueText(inputValue); + var s = _resolveValueText(parent, inputValue); if (s != null && s.isNotEmpty) { select.innerHTML = s.toJS; } @@ -716,8 +725,10 @@ class UIInputTable extends UIComponent { forID: input.id, style: 'font-weight: bold', content: [label, ':', ' ']); - var dom = domLabel.buildDOM(generator: UIComponent.domGenerator) - as HTMLLabelElement; + var dom = domLabel.buildDOM( + generator: UIComponent.domGenerator, + treeMap: domTreeMap, + setTreeMapRoot: false) as HTMLLabelElement; cell.appendChild(dom); } else { cell.appendHTML( @@ -728,8 +739,9 @@ class UIInputTable extends UIComponent { var celInput = row.appendCell()..style.textAlign = 'left'; - var inputRendered = - input.renderInput(getPreviousRenderedFieldValue) as Object?; + var inputRendered = input.renderInput( + parent: this, + fieldValueProvider: getPreviousRenderedFieldValue) as Object?; if (inputRendered.isElement) { var inputRenderedElement = inputRendered as Element; @@ -757,7 +769,10 @@ class UIInputTable extends UIComponent { } } else if (inputRendered is DOMElement) { inputRendered.buildDOM( - generator: UIComponent.domGenerator, parent: celInput); + generator: UIComponent.domGenerator, + treeMap: domTreeMap, + parent: celInput, + setTreeMapRoot: false); } if (showInvalidMessages) { @@ -874,16 +889,21 @@ class UIInputTable extends UIComponent { } if (table != null) { - var dom = table.buildDOM(generator: UIComponent.domGenerator) - as HTMLTableElement; + var dom = table.buildDOM( + generator: UIComponent.domGenerator, + treeMap: domTreeMap, + setTreeMapRoot: false) as HTMLTableElement; var trs = dom.rows.toList(); if (trs.isEmpty) return null; return trs.length == 1 ? trs.first : trs; } var div = $div(content: nodes); - var dom = - div.buildDOM(generator: UIComponent.domGenerator) as HTMLDivElement; + var dom = div.buildDOM( + generator: UIComponent.domGenerator, + treeMap: domTreeMap, + setTreeMapRoot: false) as HTMLDivElement; + return dom.children.toList(); } @@ -916,7 +936,9 @@ class UIInputTable extends UIComponent { _onChangeTriggerDelay = value; } - EventStream onInputFocus = EventStream(); + EventStream? _onInputFocus; + + EventStream get onInputFocus => _onInputFocus ??= EventStream(); @override void posRender() { From e7df94ceef3420ab4323a44825d554c3bf2e6aed Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 15 Feb 2026 17:39:30 -0300 Subject: [PATCH 58/64] =?UTF-8?q?=F0=9F=90=9B=20fix(lib/src/bones=5Fui=5Fb?= =?UTF-8?q?ase.dart):=20provide=20domTreeMapDummy=20and=20disable=20setTre?= =?UTF-8?q?eMapRoot=20when=20building=20DOM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/bones_ui_base.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/src/bones_ui_base.dart b/lib/src/bones_ui_base.dart index eb6797e..c6bb815 100644 --- a/lib/src/bones_ui_base.dart +++ b/lib/src/bones_ui_base.dart @@ -403,8 +403,10 @@ class CSSProvider { if (runtime.exists) { return cssFromElement(runtime.node as UIElement); } else { - var element = _domNode!.buildDOM(generator: UIComponent.domGenerator) - as UIElement; + var element = _domNode!.buildDOM( + generator: UIComponent.domGenerator, + treeMap: UIComponent.domTreeMapDummy, + setTreeMapRoot: false) as UIElement; return cssFromElement(element); } } From 17fefc2094d0f4b11da78b20bf06ec86ea195560 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 15 Feb 2026 20:07:16 -0300 Subject: [PATCH 59/64] - `UIComponent`: - Replaced `Expando` with `DualWeakMap` for static field `_asyncRenderingZoneComponent`. - Added `_domTreeMap` field and `domTreeMap` getter for lazy DOM tree mapping. - Added `domTreeMapIfInitialized` getter and static `domTreeMapDummy`. - Replaced `Expando` with `DualWeakMap` for `_contentsUIComponents`. - Updated parent setting methods to use named parameter `addToParent`. - Added `_resolveParentFromParentUIComponent` to better resolve `_parent`. - Updated many list conversions from `.toList()` to `.asListViewFixed` for performance. - Added `_onRender` and `_onChange` lazy event streams. - Added `_renderedUIRoots` and `_renderedUIComponents` sets to track rendering. - Improved `_markRenderTime` and rendering finish notification logic. - Added `dispose` and `_recycle` methods for lifecycle management. - Added static `purgeGlobals` to clear global UI state. - Updated rendering and async content handling to use nullable collections. - Updated event listener attachment to use `addEventListenerTyped`. --- lib/src/bones_ui_component.dart | 310 ++++++++++++++++++++++++-------- 1 file changed, 235 insertions(+), 75 deletions(-) diff --git a/lib/src/bones_ui_component.dart b/lib/src/bones_ui_component.dart index 3a8c579..f015a4a 100644 --- a/lib/src/bones_ui_component.dart +++ b/lib/src/bones_ui_component.dart @@ -42,6 +42,28 @@ abstract class UIComponent extends UIEventHandler { return domGenerator.registerElementGenerator(generator); } + DOMTreeMap? _domTreeMap; + + /// [DOMTreeMap] for this component (lazily-created). + /// + /// Maps [DOMNode]s and [UINode]s in the DOM tree generated by [domGenerator]. + /// Also handles event subscriptions and purging (see [DOMTreeMap.purge]). + DOMTreeMap get domTreeMap => _domTreeMap ??= DOMTreeMap(domGenerator) + ..setRoot(ExternalElementNode(this), content); + + /// Returns the [DOMTreeMap] if it has already been initialized. + /// + /// Unlike [domTreeMap], this getter does not create a new instance and + /// returns `null` if [domTreeMap] has not been accessed yet. + DOMTreeMap? get domTreeMapIfInitialized => _domTreeMap; + + /// Global dummy [DOMTreeMap] instance. + /// + /// Used when a non-null [DOMTreeMap] is required but no real DOM + /// mapping or event handling is needed. + static final DOMTreeMap domTreeMapDummy = + DOMTreeMapDummy(domGenerator); + final UIComponentGenerator? _generator; static int _globalIDCount = 0; @@ -176,8 +198,8 @@ abstract class UIComponent extends UIEventHandler { HTMLElement? _getContent() => _content; - static final Expando _contentsUIComponents = - Expando('_content:UIComponent'); + static final DualWeakMap _contentsUIComponents = + DualWeakMap(); static UIComponent? getContentUIComponent(UIElement content) => _contentsUIComponents[content]; @@ -185,7 +207,7 @@ abstract class UIComponent extends UIEventHandler { void _setContent(HTMLElement content) { var prev = _content; if (prev != null && prev != content) { - _contentsUIComponents[prev] = null; + _contentsUIComponents.remove(prev); } _content = content; @@ -193,11 +215,11 @@ abstract class UIComponent extends UIEventHandler { } void addTo(UIElement parent, {UIComponent? parentUIComponent}) { - _setParentImpl(parent, parentUIComponent, true); + _setParentImpl(parent, parentUIComponent, addToParent: true); } void insertTo(int index, UIElement parent, {UIComponent? parentUIComponent}) { - _setParentImpl(parent, parentUIComponent, true); + _setParentImpl(parent, parentUIComponent, addToParent: true); parent.insertChild(index, content!); } @@ -220,33 +242,34 @@ abstract class UIComponent extends UIEventHandler { /// Sets the [parent] [UIElement]. UIElement? setParent(UIElement parent, {UIComponent? parentUIComponent}) { - return _setParentImpl(parent, parentUIComponent, true); + return _setParentImpl(parent, parentUIComponent, addToParent: true); } - UIElement? _setParentImpl( - UIElement? parent, UIComponent? parentUIComponent, bool addToParent) { + UIElement? _setParentImpl(UIElement? parent, UIComponent? parentUIComponent, + {required bool addToParent}) { if (parent == null) throw StateError('Null parent'); - if (_content != null) { + final content = _content; + if (content != null) { if (_parent == parent) { - if (_content!.parentElement != _parent) { - _parent!.appendChild(_content!); + if (content.parentElement != _parent) { + _parent!.appendChild(content); } _resolveParentUIComponent(parentUIComponent ?? parent); return _parent; - } else if (_content!.parentElement == parent) { + } else if (content.parentElement == parent) { _resolveParentUIComponent(parentUIComponent ?? parent); return _parent; } else { - _content!.remove(); + content.remove(); } } _parentUIComponent = parentUIComponent; _parent = parent; - if (_content != null && addToParent) { - _parent!.append(_content!); + if (content != null && addToParent) { + _parent!.append(content); clear(); } @@ -274,21 +297,37 @@ abstract class UIComponent extends UIEventHandler { } } + void _resolveParentFromParentUIComponent() { + if (_parent != null) return; + + var content = this.content; + if (content == null) return; + + final parentUIComponent = _parentUIComponent; + if (parentUIComponent == null) return; + + var parentUIComponentContent = parentUIComponent.content; + if (parentUIComponentContent == null) return; + + var contentParent = content.parentElement; + + if (contentParent == null) { + // print("** not attached: $this"); + return; + } + + if (contentParent == parentUIComponentContent) { + _parent = parentUIComponentContent; + } else if (parentUIComponentContent.contains(content)) { + _parent = parentUIComponentContent; + } + } + void _setParentUIComponent(UIComponent? uiParent) { if (uiParent != null) { _parentUIComponent = uiParent; - var parentContent = uiParent.content; - if (parentContent != null) { - var prevParent = _parent; - - if (prevParent == null) { - var content = _content; - if (content?.parentElement == parentContent) { - _parent = parentContent; - } - } - } + _resolveParentFromParentUIComponent(); _uiRoot = uiParent._uiRoot; _uiRootComponent = uiParent._uiRootComponent; @@ -359,6 +398,7 @@ abstract class UIComponent extends UIEventHandler { } if (elementUIComponent._parentUIComponent != null) { + elementUIComponent._resolveParentFromParentUIComponent(); return; } @@ -655,6 +695,7 @@ abstract class UIComponent extends UIEventHandler { set refreshOnNavigate(bool refresh) { if (refreshOnNavigate != refresh) { if (refresh) { + _refreshOnNavigateListener?.cancel(); _refreshOnNavigateListener = UINavigator.onNavigate.listen((_) => this.refresh()); } else { @@ -675,7 +716,7 @@ abstract class UIComponent extends UIEventHandler { List getContentChildren( {FilterElement? filter, bool deep = true}) { return _getContentChildrenImpl( - _content!.children.toList(), [], deep, filter); + _content!.children.asListViewFixed, [], deep, filter); } List _getContentChildrenImpl(List list, @@ -718,7 +759,7 @@ abstract class UIComponent extends UIEventHandler { UIElement? findInContentChildDeep(FilterElement filter) => _findInContentChildDeepImpl( - _content?.children.toList() ?? [], filter); + _content?.children.asListViewFixed ?? [], filter); UIElement? _findInContentChildDeepImpl( List list, FilterElement filter) { @@ -741,7 +782,7 @@ abstract class UIComponent extends UIEventHandler { List findChildDeep(FilterElement filter) { var list = []; - _findChildDeepImpl(_content!.children.toList(), filter, list); + _findChildDeepImpl(_content!.children.asListViewFixed, filter, list); return list; } @@ -768,7 +809,7 @@ abstract class UIComponent extends UIEventHandler { MapEntry? findChildrenDeep(String fieldName, {bool resolveUIComponents = true}) => _findChildrenDeepImpl( - _content!.children.toList(), fieldName, resolveUIComponents); + _content!.children.asListViewFixed, fieldName, resolveUIComponents); MapEntry? _findChildrenDeepImpl( List list, String fieldName, bool resolveUIComponents) { @@ -842,7 +883,7 @@ abstract class UIComponent extends UIEventHandler { List? _renderedElements; - List? get renderedElements => + List? get renderedElements => _renderedElements != null ? List.unmodifiable(_renderedElements!) : null; dynamic getRenderedElement(FilterRendered filter, [bool? deep]) { @@ -972,7 +1013,8 @@ abstract class UIComponent extends UIEventHandler { } } - _renderedAsyncContents.clear(); + _renderedAsyncContents?.clear(); + _renderedAsyncContents = null; var content = _content; @@ -1310,7 +1352,8 @@ abstract class UIComponent extends UIEventHandler { return _catchRenderingZone(); } - static final Expando _asyncRenderingZoneComponent = Expando(); + static final WeakKeyMap _asyncRenderingZoneComponent = + DualWeakMap(); void _setRenderingZoneUIComponent(Zone renderingZone) { _renderingZone = renderingZone; @@ -1368,7 +1411,16 @@ abstract class UIComponent extends UIEventHandler { _renderCount++; _requestRefresh = null; - var content = this.content!; + var content = this.content; + // Prevent render of not initialized components: + if (content == null) { + return; + } + + // Recycle disposed component: + if (_disposed) { + _recycle(); + } final parent = _parent; if (parent != null) { @@ -1519,7 +1571,7 @@ abstract class UIComponent extends UIEventHandler { rendered = render(); } - _renderedAsyncContents = {}; + _renderedAsyncContents = null; var renderedElements = toContentElements(rendered); @@ -1535,14 +1587,14 @@ abstract class UIComponent extends UIEventHandler { _finalizeRender(); try { - _parseAttributesPosRender(content!.children.toList()); + _parseAttributesPosRender(content!.children.asListViewFixed); } catch (e, s) { UIConsole.error('$this _parseAttributesPosRender(...) error', e, s); } _callPosRender(); - _markRenderTime(); + _markRenderTime(this, _uiRoot); return rendered; } @@ -1560,7 +1612,8 @@ abstract class UIComponent extends UIEventHandler { } else if (e.isElement) { var subElements = []; - for (var child in (e as Element).children.toList()) { + var children = (e as Element).children.toList(); + for (var child in children) { var uiComponent = _getUIComponentByContent(child); if (uiComponent != null) { @@ -1575,7 +1628,9 @@ abstract class UIComponent extends UIEventHandler { } } - final EventStream onRender = EventStream(); + EventStream? _onRender; + + EventStream get onRender => _onRender ??= EventStream(); void _notifyRendered() { var waitingRender = _waitingRender; @@ -1584,7 +1639,8 @@ abstract class UIComponent extends UIEventHandler { _waitingRender = null; } - if (onRender.isUsed) { + final onRender = _onRender; + if (onRender != null && onRender.isUsed) { Future.delayed(Duration(milliseconds: 1), () => onRender.add(this)); } } @@ -1625,15 +1681,27 @@ abstract class UIComponent extends UIEventHandler { void onChildRendered(UIComponent child) {} - EventStream onChange = EventStream(); + EventStream? _onChange; + + EventStream get onChange => _onChange ??= EventStream(); static int _lastRenderTime = DateTime.now().millisecondsSinceEpoch; static bool _renderFinished = true; - static void _markRenderTime() { + static final Set _renderedUIRoots = {}; + static final Set _renderedUIComponents = {}; + + static void _markRenderTime(UIComponent uiComponent, UIRoot? uiRoot) { _lastRenderTime = DateTime.now().millisecondsSinceEpoch; _renderFinished = false; + + _renderedUIComponents.add(uiComponent); + + if (uiRoot != null) { + _renderedUIRoots.add(uiRoot); + } + _scheduleCheckFinishedRendered(); } @@ -1646,7 +1714,7 @@ abstract class UIComponent extends UIEventHandler { future = _scheduleCheckFinishedRenderedFuture = Future.delayed(Duration(milliseconds: 300), _checkFinishedRendered); - future.then((_) { + future.whenComplete(() { if (identical(future, _scheduleCheckFinishedRenderedFuture)) { _scheduleCheckFinishedRenderedFuture = null; } @@ -1677,7 +1745,28 @@ abstract class UIComponent extends UIEventHandler { UILayout.checkInstances(); } - UIRoot.getInstance()?.notifyFinishRender(); + var mainUIRoot = UIRoot.getInstance(); + + var uiComponents = _renderedUIComponents.toList(); + var uiRoots = { + ..._renderedUIRoots, + if (mainUIRoot != null) mainUIRoot, + }; + + _renderedUIComponents.clear(); + _renderedUIRoots.clear(); + + for (var uiComponent in uiComponents) { + uiComponent._onFinishRender(); + } + + for (var uiRoot in uiRoots) { + uiRoot.notifyFinishRender(); + } + } + + void _onFinishRender() { + _domTreeMap?.purge(); } /// If [true] will preserve last render in next calls to [render]. @@ -1751,7 +1840,7 @@ abstract class UIComponent extends UIEventHandler { var list = _toContentElementsImpl(rendered, append, domContext); if (parseAttributes) { - _parseAttributes(content!.children.toList()); + _parseAttributes(content!.children.asListViewFixed); } return list; @@ -1882,7 +1971,11 @@ abstract class UIComponent extends UIEventHandler { UIElement? content, Object? value, DOMContext? domContext) { if (value is DOMNode) { return value.buildDOM( - generator: domGenerator, parent: content, context: domContext); + generator: domGenerator, + treeMap: domTreeMap, + parent: content, + context: domContext, + setTreeMapRoot: false); } else if (value is String) { var nodes = $html(value); return nodes; @@ -1912,11 +2005,19 @@ abstract class UIComponent extends UIEventHandler { } else if (value is AsDOMElement) { var element = value.asDOMElement; return element.buildDOM( - generator: domGenerator, parent: content, context: domContext); + generator: domGenerator, + treeMap: domTreeMap, + parent: content, + context: domContext, + setTreeMapRoot: false); } else if (value is AsDOMNode) { var node = value.asDOMNode; return node.buildDOM( - generator: domGenerator, parent: content, context: domContext); + generator: domGenerator, + treeMap: domTreeMap, + parent: content, + context: domContext, + setTreeMapRoot: false); } else if (value is Map || (value is List && listMatchesAll(value, (dynamic e) => e is Map))) { return UIJsonRender(null, json: value); @@ -1928,7 +2029,7 @@ abstract class UIComponent extends UIEventHandler { int _buildRenderList(Object? value, List renderedList, int prevElemIndex, DOMContext? domContext) { if (value == null) return prevElemIndex; - var content = this.content; + final content = this.content; value = _normalizeRenderListValue(content, value, domContext); @@ -1937,7 +2038,7 @@ abstract class UIComponent extends UIEventHandler { value, value as Node, renderedList, prevElemIndex); } else if (value is UIComponent) { if (value.parent != content) { - value._setParentImpl(content, this, false); + value._setParentImpl(content, this, addToParent: false); } prevElemIndex = _addElementToRenderList( value, value.content!, renderedList, prevElemIndex); @@ -1986,20 +2087,21 @@ abstract class UIComponent extends UIEventHandler { void posAsyncRender() {} - Map _renderedAsyncContents = {}; - final Set _loadingAsyncContents = {}; + Map? _renderedAsyncContents; + Set? _loadingAsyncContents; - bool get isLoadingUIAsyncContent => _loadingAsyncContents.isNotEmpty; + bool get isLoadingUIAsyncContent => + _loadingAsyncContents?.isNotEmpty ?? false; void _resolveUIAsyncContentLoaded( UIAsyncContent asyncContent, DOMContext? domContext) { if (!asyncContent.isLoaded) return; if (asyncContent.isLoaded && !asyncContent.hasAutoRefresh) { - _loadingAsyncContents.remove(asyncContent); + _loadingAsyncContents?.remove(asyncContent); } - var prevRendered = _renderedAsyncContents[asyncContent]; + var prevRendered = _renderedAsyncContents?[asyncContent]; if (prevRendered == null) return; final renderedElements = _renderedElements; @@ -2063,8 +2165,10 @@ abstract class UIComponent extends UIEventHandler { .map((e) => e.content) .toList(); - tail.removeWhere( - (e) => renderedList.contains(e) || renderedComponents.contains(e)); + tail.removeWhere((e) => + renderedList.contains(e) || + renderedComponents.contains(e) || + content.contains(e)); content.appendAll(tail); } @@ -2076,13 +2180,14 @@ abstract class UIComponent extends UIEventHandler { _ensureAllRendered(renderedList); try { - _parseAttributes(content.children.toList()); - _parseAttributesPosRender(content.children.toList()); + _parseAttributes(content.children.asListViewFixed); + _parseAttributesPosRender(content.children.asListViewFixed); } catch (e, s) { UIConsole.error('$this _parseAttributesPosRender(...) error', e, s); } - _renderedAsyncContents[asyncContent] = renderedList; + final renderedAsyncContents = _renderedAsyncContents ??= {}; + renderedAsyncContents[asyncContent] = renderedList; _callPosAsyncRender(); } @@ -2092,7 +2197,9 @@ abstract class UIComponent extends UIEventHandler { if (!asyncContent.isLoaded || asyncContent.isExpired || asyncContent.hasAutoRefresh) { - _loadingAsyncContents.add(asyncContent); + final loadingAsyncContents = _loadingAsyncContents ??= {}; + loadingAsyncContents.add(asyncContent); + asyncContent.onLoadContent.listen((c) { _resolveUIAsyncContentLoaded(asyncContent, domContext); }, singletonIdentifier: this); @@ -2113,7 +2220,8 @@ abstract class UIComponent extends UIEventHandler { var rendered = renderedList.sublist(renderIdx).toList(); - _renderedAsyncContents[asyncContent] = rendered; + final renderedAsyncContents = _renderedAsyncContents ??= {}; + renderedAsyncContents[asyncContent] = rendered; return prevElemIndex; } @@ -2168,7 +2276,7 @@ abstract class UIComponent extends UIEventHandler { _parseDataSource(elem); try { - _parseAttributes(elem.children.toList()); + _parseAttributes(elem.children.asListViewFixed); } catch (e) { UIConsole.error('Error parsing attributes for element: $elem', e); } @@ -2183,13 +2291,13 @@ abstract class UIComponent extends UIEventHandler { elem = elem as HTMLElement; try { - _parseUiLayout(elem); + _parseUILayout(elem); } catch (e) { UIConsole.error('Error parsing attributes for element: $elem', e); } try { - _parseAttributesPosRender(elem.children.toList()); + _parseAttributesPosRender(elem.children.asListViewFixed); } catch (e) { UIConsole.error('Error parsing attributes for element: $elem', e); } @@ -2649,7 +2757,7 @@ abstract class UIComponent extends UIEventHandler { } List getFieldsElements() => _getContentChildrenImpl( - content!.children.toList(), + content!.children.asListViewFixed, [], true, (e) => getElementFieldName(e) != null); @@ -3100,7 +3208,7 @@ abstract class UIComponent extends UIEventHandler { var actionValue = getElementAttribute(elem, 'action'); if (actionValue != null && actionValue.isNotEmpty) { - elem.onClick.listen((e) => action(actionValue)); + elem.addEventListenerTyped(EventType.click, (e) => action(actionValue)); } } @@ -3108,7 +3216,7 @@ abstract class UIComponent extends UIEventHandler { UIConsole.log('action: $action'); } - void _parseUiLayout(HTMLElement elem) { + void _parseUILayout(HTMLElement elem) { var uiLayout = getElementAttribute(elem, 'uiLayout'); if (uiLayout != null) { @@ -3130,11 +3238,10 @@ abstract class UIComponent extends UIEventHandler { var actionType = parts[1]; if (key == '*') { - elem.onKeyPress.listen((e) { - action(actionType); - }); + elem.addEventListenerTyped( + EventType.keyPress, (e) => action(actionType)); } else { - elem.onKeyPress.listen((e) { + elem.addEventListenerTyped(EventType.keyPress, (e) { if (equalsIgnoreAsciiCase(e.key, key) || equalsIgnoreAsciiCase(e.keyCodeSafe.toString(), key)) { action(actionType); @@ -3148,9 +3255,62 @@ abstract class UIComponent extends UIEventHandler { var click = getElementAttribute(elem, 'onEventClick'); if (click != null && click.isNotEmpty) { - elem.onClick.listen((e) { - action(click); - }); + elem.addEventListenerTyped(EventType.click, (e) => action(click)); } } + + int _disposeCount = 0; + + int get disposeCount => _disposeCount; + + bool _disposed = false; + + /// Whether this instance has been disposed. + /// + /// A disposed instance should no longer be used. + bool get isDisposed => _disposed; + + /// Disposes this instance and releases all associated resources. + /// + /// Marks the object as disposed, clears cached rendering state, + /// and removes all registered event listeners. + void dispose() { + ++_disposeCount; + _disposed = true; + + if (!preserveRender) { + final domTreeMap = _domTreeMap; + if (domTreeMap != null) { + domTreeMap.dispose(); + _domTreeMap = null; + } + + _renderedElements = null; + _renderedFieldsValues = null; + } + } + + void _recycle() { + if (!_disposed) return; + _disposed = false; + + var content = _content; + if (content != null) { + _contentsUIComponents[content] = this; + } else { + _setContent(createContentElement(true)); + } + + // Ensure instantiated: + domTreeMap; + } + + /// Purges all global UI state and resources. + /// + /// Clears globally managed components, mappings, and caches. + static void purgeGlobals() { + _contentsUIComponents.purge(); + _asyncRenderingZoneComponent.purge(); + DSX.purge(); + } } From 034d5fc23ede6b16f61da9189c41eba812501273 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 15 Feb 2026 20:07:34 -0300 Subject: [PATCH 60/64] - `UIComponent`: - Replaced `Expando` with `DualWeakMap` for static field `_asyncRenderingZoneComponent`. - Added `_domTreeMap` field and `domTreeMap` getter for lazy DOM tree mapping. - Added `domTreeMapIfInitialized` getter and static `domTreeMapDummy`. - Replaced `Expando` with `DualWeakMap` for `_contentsUIComponents`. - Updated parent setting methods to use named parameter `addToParent`. - Added `_resolveParentFromParentUIComponent` to better resolve `_parent`. - Updated many list conversions from `.toList()` to `.asListViewFixed` for performance. - Added `_onRender` and `_onChange` lazy event streams. - Added `_renderedUIRoots` and `_renderedUIComponents` sets to track rendering. - Improved `_markRenderTime` and rendering finish notification logic. - Added `dispose` and `_recycle` methods for lifecycle management. - Added static `purgeGlobals` to clear global UI state. - Updated rendering and async content handling to use nullable collections. - Updated event listener attachment to use `addEventListenerTyped`. --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dd5f81..113c7dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,22 @@ - `ElementProvider` and `CSSProvider`: - Updated calls to `buildDOM` to include `treeMap` and `setTreeMapRoot` parameters. +- `UIComponent`: + - Replaced `Expando` with `DualWeakMap` for static field `_asyncRenderingZoneComponent`. + - Added `_domTreeMap` field and `domTreeMap` getter for lazy DOM tree mapping. + - Added `domTreeMapIfInitialized` getter and static `domTreeMapDummy`. + - Replaced `Expando` with `DualWeakMap` for `_contentsUIComponents`. + - Updated parent setting methods to use named parameter `addToParent`. + - Added `_resolveParentFromParentUIComponent` to better resolve `_parent`. + - Updated many list conversions from `.toList()` to `.asListViewFixed` for performance. + - Added `_onRender` and `_onChange` lazy event streams. + - Added `_renderedUIRoots` and `_renderedUIComponents` sets to track rendering. + - Improved `_markRenderTime` and rendering finish notification logic. + - Added `dispose` and `_recycle` methods for lifecycle management. + - Added static `purgeGlobals` to clear global UI state. + - Updated rendering and async content handling to use nullable collections. + - Updated event listener attachment to use `addEventListenerTyped`. + - `bones_ui_extension.dart`: - Added `ElementStreamExtension` and `StreamSubscriptionExtension` to track subscriptions in `DOMTreeMap`. From 4e66a2623a44b8516c2f51b0234f0d130c153e08 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 15 Feb 2026 20:07:53 -0300 Subject: [PATCH 61/64] - `UIRootComponent`: - Changed `_uiComponentsTree` to nullable and lazily initialized. - Added `_onPurgedUIComponents` callback to dispose purged components. - Updated methods to safely handle nullable `_uiComponentsTree`. - Added `purgeRoot` method to purge tree and global UI state. - Added `_UIDOMTreeReferenceMap` subclass to prevent premature purging of components not fully initialized or disposed. --- CHANGELOG.md | 8 ++++ lib/src/bones_ui_root.dart | 83 ++++++++++++++++++++++++++++++-------- 2 files changed, 74 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 113c7dc..4aa0c44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,14 @@ - Updated `addExternalElementToElement` and `toElements` to accept `treeMap` and `context` parameters. - Updated calls to `buildDOM` to pass `treeMap` and `setTreeMapRoot`. +- `UIRootComponent`: + - Changed `_uiComponentsTree` to nullable and lazily initialized. + - Added `_onPurgedUIComponents` callback to dispose purged components. + - Updated methods to safely handle nullable `_uiComponentsTree`. + - Added `purgeRoot` method to purge tree and global UI state. + +- Added `_UIDOMTreeReferenceMap` subclass to prevent premature purging of components not fully initialized or disposed. + - `UIButtonBase` and `UIButtonLoader`: - Updated event listener attachment to use `addEventListenerTyped`. - Fixed null safety and type checks for button elements. diff --git a/lib/src/bones_ui_root.dart b/lib/src/bones_ui_root.dart index dc78c1d..ffeb290 100644 --- a/lib/src/bones_ui_root.dart +++ b/lib/src/bones_ui_root.dart @@ -69,13 +69,22 @@ abstract class UIRootComponent extends UIComponent { _rootComponentInstances.add(WeakReference(this)); } - late DOMTreeReferenceMap _uiComponentsTree; + DOMTreeReferenceMap? _uiComponentsTree; - void initializeUIComponentsTree() { - _uiComponentsTree = DOMTreeReferenceMap(content!, - keepPurgedKeys: true, - maxPurgedEntries: 1000, - purgedEntriesTimeout: Duration(minutes: 1)); + void initializeUIComponentsTree() => _getUIComponentsTree(); + + DOMTreeReferenceMap _getUIComponentsTree() { + return _uiComponentsTree ??= _UIDOMTreeReferenceMap( + this, + onPurgedEntries: _onPurgedUIComponents, + ); + } + + void _onPurgedUIComponents(Map purgedEntries) { + for (var e in purgedEntries.entries) { + final component = e.value; + component.dispose(); + } } @override @@ -84,52 +93,62 @@ abstract class UIRootComponent extends UIComponent { @override UIRootComponent get uiRootComponent; - bool get isAnyComponentRendering => _uiComponentsTree.validEntries - .where((e) => e.value.isRendering) - .isNotEmpty; + bool get isAnyComponentRendering => + _uiComponentsTree?.validEntries.any((e) => e.value.isRendering) ?? false; UIComponent? getUIComponentByContent(UIElement? uiComponentContent, {bool includePurgedEntries = false}) { if (uiComponentContent == null) return null; + if (includePurgedEntries) { - return _uiComponentsTree.getAlsoFromPurgedEntries(uiComponentContent); + return _uiComponentsTree?.getAlsoFromPurgedEntries(uiComponentContent); } else { - return _uiComponentsTree.get(uiComponentContent); + return _uiComponentsTree?.get(uiComponentContent); } } UIComponent? getUIComponentByChild(UIElement? child, {bool includePurgedEntries = false}) { - return _uiComponentsTree.getParentValue(child, + return _uiComponentsTree?.getParentValue(child, includePurgedEntries: includePurgedEntries); } List? getSubUIComponentsByElement(UIElement? element, {bool includePurgedEntries = false}) { if (element == null || - (!_uiComponentsTree.isInTree(element) && !(includePurgedEntries))) { + (!includePurgedEntries && + !(_uiComponentsTree?.isInTree(element) ?? false))) { return null; } - return _uiComponentsTree.getSubValues(element, + return _uiComponentsTree?.getSubValues(element, includePurgedEntries: includePurgedEntries); } void registerUIComponentInTree(UIComponent uiComponent) { - _uiComponentsTree.put(uiComponent.content!, uiComponent); + _getUIComponentsTree().put(uiComponent.content!, uiComponent); //print('_uiComponentsTree> $_uiComponentsTree'); } - void purgeUIComponentsTree() => _uiComponentsTree.purge(); + void purgeUIComponentsTree() => _uiComponentsTree?.purge(); /// [EventStream] for when this [UIRoot] finishes to render UI. final EventStream onFinishRender = EventStream(); void notifyFinishRender() { + purgeRoot(); + onFinishRender.add(this); - _uiComponentsTree.purge(); //print('FINISH RENDER> _uiComponentsTree: $_uiComponentsTree'); } + + void purgeRoot() { + _uiComponentsTree?.purge(); + + domTreeMapIfInitialized?.purge(); + + UIComponent.purgeGlobals(); + } } /// The root for `Bones_UI` component tree. @@ -472,3 +491,33 @@ void _registerAllComponents() { UIDialog.register(); UISVG.register(); } + +class _UIDOMTreeReferenceMap extends DOMTreeReferenceMap { + final UIRootComponent rootComponent; + + _UIDOMTreeReferenceMap(this.rootComponent, {super.onPurgedEntries}) + : super( + rootComponent.content!, + autoPurge: false, + keepPurgedKeys: true, + purgedEntriesTimeout: Duration(minutes: 1), + ); + + @override + bool isValidEntry(Node key, UIComponent value) { + var valid = super.isValidEntry(key, value); + + if (!valid && !value.isDisposed) { + var content = value.content; + var parent = value.parent; + + // Component still in the initial rendering: + if (content == null || parent == null) { + //print('!!! Prevent purge> $value >> $content ; $parent'); + return true; + } + } + + return valid; + } +} From 34f34bffd353b54bce65c29656abe283506a844a Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 15 Feb 2026 20:08:26 -0300 Subject: [PATCH 62/64] v3.0.0-beta.20 - `UIEventHandler`: - Added `unregisterEventListener` and `clearEventListeners` methods. - `EventHandlerPrivate`: - Changed `_eventListeners` to nullable and lazily initialized. - Added `_unregisterEventListener` and `_clearEventListeners` implementations. - `ElementProvider` and `CSSProvider`: - Updated calls to `buildDOM` to include `treeMap` and `setTreeMapRoot` parameters. - `UIComponent`: - Replaced `Expando` with `DualWeakMap` for static field `_asyncRenderingZoneComponent`. - Added `_domTreeMap` field and `domTreeMap` getter for lazy DOM tree mapping. - Added `domTreeMapIfInitialized` getter and static `domTreeMapDummy`. - Replaced `Expando` with `DualWeakMap` for `_contentsUIComponents`. - Updated parent setting methods to use named parameter `addToParent`. - Added `_resolveParentFromParentUIComponent` to better resolve `_parent`. - Updated many list conversions from `.toList()` to `.asListViewFixed` for performance. - Added `_onRender` and `_onChange` lazy event streams. - Added `_renderedUIRoots` and `_renderedUIComponents` sets to track rendering. - Improved `_markRenderTime` and rendering finish notification logic. - Added `dispose` and `_recycle` methods for lifecycle management. - Added static `purgeGlobals` to clear global UI state. - Updated rendering and async content handling to use nullable collections. - Updated event listener attachment to use `addEventListenerTyped`. - `bones_ui_extension.dart`: - Added `ElementStreamExtension` and `StreamSubscriptionExtension` to track subscriptions in `DOMTreeMap`. - `UIDOMGenerator`: - Updated `addExternalElementToElement` and `toElements` to accept `treeMap` and `context` parameters. - Updated calls to `buildDOM` to pass `treeMap` and `setTreeMapRoot`. - `UIRootComponent`: - Changed `_uiComponentsTree` to nullable and lazily initialized. - Added `_onPurgedUIComponents` callback to dispose purged components. - Updated methods to safely handle nullable `_uiComponentsTree`. - Added `purgeRoot` method to purge tree and global UI state. - Added `_UIDOMTreeReferenceMap` subclass to prevent premature purging of components not fully initialized or disposed. - `UIButtonBase` and `UIButtonLoader`: - Updated event listener attachment to use `addEventListenerTyped`. - Fixed null safety and type checks for button elements. - `UICapture`: - Added new `CaptureDataFormat.urlOrBlobUrl` format. - Added `selectedFileDataAsURLOrDataURLBase64` getter. - Updated `_readFile` and `_filterCapturedData` to support new format and improved async flow with `_yeld` helper. - Updated `_CapturedData` class to store optional MIME type and support new format. - Improved data format conversions to preserve MIME type. - Added helper `_yeld` function for async yielding. - `UIDialogBase`: - Fixed background color style to use `rgb` when alpha is 1.0. - `_CanvasEditImage`: - Added delayed `_configure` call after construction. - Updated `_updateCanvasDimension` to return bool indicating if dimension changed. - Improved zoom and translate setters to avoid unnecessary renders. - `InputConfig`: - Added optional `parent` parameter to rendering methods. - Updated DOM building calls to pass `treeMap` and `setTreeMapRoot`. - Updated `_resolveValueText` to use `parent` for intl message resolution. - `UIInputTable`: - Updated rendering of inputs and labels to pass `domTreeMap` and `setTreeMapRoot`. - `UILoading`: - Updated `asDIVElement` buildDOM calls to pass `treeMap` and `setTreeMapRoot`. - `MasonryItem` and `_MasonryRenderItem`: - Updated DOM generation calls to pass `treeMap` and `setTreeMapRoot`. - `UITemplateElementGenerator`: - Updated DOM generation calls to pass `treeMap`, `context`, and `setTreeMapRoot`. - Updated template building to use `dsxResolution` enum instead of boolean flags. - `pubspec.yaml`: - Updated dependencies: - `web_utils`: ^1.0.21 - `dom_tools`: ^3.0.0-beta.19 - `dom_builder`: ^3.0.0-beta.11 - `swiss_knife`: ^3.3.5 - `statistics`: ^1.2.1 - `test`: ^1.29.0 - `test_api`: ^0.7.9 - `test_core`: ^0.6.15 - `build_web_compilers`: ^4.4.11 - `build_runner`: ^2.11.1 - `dependency_validator`: ^5.0.4 --- CHANGELOG.md | 15 +++++++++++++++ lib/src/bones_ui.dart | 2 +- pubspec.yaml | 24 ++++++++++++------------ 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4aa0c44..be0ee53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +## 3.0.0-beta.20 - `UIEventHandler`: - Added `unregisterEventListener` and `clearEventListeners` methods. @@ -78,6 +79,20 @@ - Updated DOM generation calls to pass `treeMap`, `context`, and `setTreeMapRoot`. - Updated template building to use `dsxResolution` enum instead of boolean flags. +- `pubspec.yaml`: + - Updated dependencies: + - `web_utils`: ^1.0.21 + - `dom_tools`: ^3.0.0-beta.19 + - `dom_builder`: ^3.0.0-beta.11 + - `swiss_knife`: ^3.3.5 + - `statistics`: ^1.2.1 + - `test`: ^1.29.0 + - `test_api`: ^0.7.9 + - `test_core`: ^0.6.15 + - `build_web_compilers`: ^4.4.11 + - `build_runner`: ^2.11.1 + - `dependency_validator`: ^5.0.4 + ## 3.0.0-beta.19 - dom_tools: ^3.0.0-beta.13 diff --git a/lib/src/bones_ui.dart b/lib/src/bones_ui.dart index bebe8bb..5e655f8 100644 --- a/lib/src/bones_ui.dart +++ b/lib/src/bones_ui.dart @@ -1,3 +1,3 @@ class BonesUI { - static const String version = '3.0.0-beta.19'; + static const String version = '3.0.0-beta.20'; } diff --git a/pubspec.yaml b/pubspec.yaml index 6c1645c..2bc31f0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: bones_ui description: Bones_UI - An intuitive and user-friendly Web User Interface framework for Dart. -version: 3.0.0-beta.19 +version: 3.0.0-beta.20 homepage: https://github.com/Colossus-Services/bones_ui environment: @@ -12,13 +12,13 @@ executables: dependencies: js_interop_utils: ^1.0.9 - web_utils: ^1.0.19 + web_utils: ^1.0.21 web: ^1.1.1 intl_messages: ^3.0.0-beta.1 - dom_tools: ^3.0.0-beta.13 - dom_builder: ^3.0.0-beta.7 - swiss_knife: ^3.3.3 - statistics: ^1.2.0 + dom_tools: ^3.0.0-beta.19 + dom_builder: ^3.0.0-beta.11 + swiss_knife: ^3.3.5 + statistics: ^1.2.1 mercury_client: ^2.3.0 dynamic_call: ^2.0.1 project_template: ^1.1.1 @@ -33,18 +33,18 @@ dependencies: args: ^2.7.0 logging: ^1.3.0 path: ^1.9.1 - test: ^1.28.0 - test_api: ^0.7.8 - test_core: ^0.6.14 + test: ^1.29.0 + test_api: ^0.7.9 + test_core: ^0.6.15 stream_channel: ^2.1.4 stack_trace: ^1.12.1 dev_dependencies: - build_web_compilers: ^4.4.3 - build_runner: ^2.10.4 + build_web_compilers: ^4.4.11 + build_runner: ^2.11.1 lints: ^5.1.1 - dependency_validator: ^5.0.3 + dependency_validator: ^5.0.4 #dependency_overrides: From 7e47af538fad32163164416d7ab56ae52b570c5c Mon Sep 17 00:00:00 2001 From: gmpassos Date: Sun, 15 Feb 2026 20:10:01 -0300 Subject: [PATCH 63/64] bump.sh --- bump.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100755 bump.sh diff --git a/bump.sh b/bump.sh new file mode 100755 index 0000000..7ff7af5 --- /dev/null +++ b/bump.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +APIKEY=$1 +shift # remove the first argument (API key) from "$@" + +## dart pub global activate dart_bump + +dart_bump . \ + --extra-file "lib/src/bones_ui.dart=static\\s+const\\s+String\\s+version\\s+=\\s+['\"]([\\w.\\-]+)['\"]" \ + --api-key $APIKEY \ + "$@" From d6827ff85797a554b58485b29f93bb94507a25d4 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Tue, 17 Feb 2026 17:07:49 -0300 Subject: [PATCH 64/64] v3.0.0 - Release v3.0.0 - Dependencies: - Updated `dom_tools` from `^3.0.0-beta.19` to `^3.0.0`. - Updated `dom_builder` from `^3.0.0-beta.11` to `^3.0.0`. --- CHANGELOG.md | 8 ++++++++ lib/src/bones_ui.dart | 2 +- pubspec.yaml | 6 +++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be0ee53..f1ea2fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## 3.0.0 + +- Release v3.0.0 + +- Dependencies: + - Updated `dom_tools` from `^3.0.0-beta.19` to `^3.0.0`. + - Updated `dom_builder` from `^3.0.0-beta.11` to `^3.0.0`. + ## 3.0.0-beta.20 - `UIEventHandler`: diff --git a/lib/src/bones_ui.dart b/lib/src/bones_ui.dart index 5e655f8..9475dbc 100644 --- a/lib/src/bones_ui.dart +++ b/lib/src/bones_ui.dart @@ -1,3 +1,3 @@ class BonesUI { - static const String version = '3.0.0-beta.20'; + static const String version = '3.0.0'; } diff --git a/pubspec.yaml b/pubspec.yaml index 2bc31f0..9125d5f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: bones_ui description: Bones_UI - An intuitive and user-friendly Web User Interface framework for Dart. -version: 3.0.0-beta.20 +version: 3.0.0 homepage: https://github.com/Colossus-Services/bones_ui environment: @@ -15,8 +15,8 @@ dependencies: web_utils: ^1.0.21 web: ^1.1.1 intl_messages: ^3.0.0-beta.1 - dom_tools: ^3.0.0-beta.19 - dom_builder: ^3.0.0-beta.11 + dom_tools: ^3.0.0 + dom_builder: ^3.0.0 swiss_knife: ^3.3.5 statistics: ^1.2.1 mercury_client: ^2.3.0