Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions web/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
- Added `childNodesAsList` to `Node` and `childrenAsList` to `Element` via
extensions to support mutable operations on node lists.
- Added `asList` to `NodeList` via extension.
- Added `data` to `HTMLElement`, `SVGElement` and `MathMLElement` via
extension. It provides nullable interface for `dataset` allowing to check if
data attribute is set.

## 1.1.1

Expand Down
1 change: 1 addition & 0 deletions web/lib/src/helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
library;

export 'helpers/cross_origin.dart' show CrossOriginLocation, CrossOriginWindow;
export 'helpers/dataset.dart';
export 'helpers/enums.dart';
export 'helpers/events/events.dart';
export 'helpers/events/providers.dart';
Expand Down
62 changes: 62 additions & 0 deletions web/lib/src/helpers/dataset.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:js_interop';
import 'dart:js_interop_unsafe';

import '../dom/html.dart';
import '../dom/mathml_core.dart';
import '../dom/svg.dart';

/// Provides nullable api on `DOMStringMap`.
///
/// Native `DOMStringMap` returns non nullable `DOMString` from getter and
/// returns `undefined` when given key does not exist. As in Dart there is no
/// `undefined`, this wrapper will return null in such case.
/// This extension also allows removing `dataset` elements.
extension type NullableDOMStringMap._(JSObject _) implements JSObject {
/// Retrieves `dataset` element.
///
/// When it is not set (`data-*` attribute is missing) returns `null`.
external String? operator [](String name);

/// Sets `dataset` element (and corresponding `data-*` attribute).
///
/// Removes attribute if `value` is `null`.
void operator []=(
Comment thread
fsw marked this conversation as resolved.
String name,
String? value,
) {
if (value != null) {
setProperty(name.toJS, value.toJS);
} else {
remove(name);
}
}

/// Removes `dataset` element (and corresponding `data-*` attribute)
String? remove(String name) {
final ret = this[name];
delete(name.toJS);
return ret;
}
}

Comment thread
fsw marked this conversation as resolved.
extension HTMLElementDatasetExtension on HTMLElement {
/// Wrapper for nullable [dataset]. See [NullableDOMStringMap] for details.
@JS('dataset')
external NullableDOMStringMap get data;
}

extension SVGElementDatasetExtension on SVGElement {
/// Wrapper for nullable [dataset]. See [NullableDOMStringMap] for details.
@JS('dataset')
external NullableDOMStringMap get data;
}

extension MathMLElementDatasetExtension on MathMLElement {
/// Wrapper for nullable [dataset]. See [NullableDOMStringMap] for details.
@JS('dataset')
external NullableDOMStringMap get data;
}
29 changes: 29 additions & 0 deletions web/test/helpers_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -289,4 +289,33 @@ void main() {
test('Uri.toJS throws an ArgumentError for a relative URL', () {
expect(() => Uri.parse('/path').toJS, throwsArgumentError);
});

test('nullable dataset extension', () {
final elements = [SVGSVGElement(), HTMLDivElement(), MathMLElement.mi()];

for (var element in elements) {
element.setAttribute('data-foo', 'bar');
element.setAttribute('data-foo-camel', 'bar');
final data = element.isA<SVGElement>()
? (element as SVGElement).data
: element.isA<MathMLElement>()
? (element as MathMLElement).data
: (element as HTMLElement).data;
//read existing and not existing data
expect(data['foo'], equals('bar'));
expect(data['fooCamel'], equals('bar'));
expect(data['nonexisting'], isNull);

//update data
data['foo'] = data['fooCamel'] = 'bar2';
expect(data['foo'], equals('bar2'));
expect(data['fooCamel'], equals('bar2'));

//unset data
data['foo'] = null;
expect(data.remove('fooCamel'), equals('bar2'));
expect(data['foo'], isNull);
expect(element.getAttribute('data-foo-camel'), isNull);
}
});
Comment thread
fsw marked this conversation as resolved.
}