diff --git a/web/CHANGELOG.md b/web/CHANGELOG.md index 78374285..b854ecdf 100644 --- a/web/CHANGELOG.md +++ b/web/CHANGELOG.md @@ -5,6 +5,8 @@ `dart:io` and `dart:html`. - Added `JSImmutableListWrapper` which helps create a dart list from a JS list. - Deprecated `TouchListWrapper` and `TouchListConvert` in favor of `JSImmutableListWrapper`. +- Added `[]` and `[]=` overloaded operators to types which define unnamed + `getter`s and `setter`s, respectively. ## 1.0.0 diff --git a/web/lib/src/dom/css_animations.dart b/web/lib/src/dom/css_animations.dart index aa15467b..23efacd2 100644 --- a/web/lib/src/dom/css_animations.dart +++ b/web/lib/src/dom/css_animations.dart @@ -106,6 +106,8 @@ extension type CSSKeyframeRule._(JSObject _) implements CSSRule, JSObject { /// API documentation sourced from /// [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/CSSKeyframesRule). extension type CSSKeyframesRule._(JSObject _) implements CSSRule, JSObject { + external CSSKeyframeRule operator [](int index); + /// The **`appendRule()`** method of the [CSSKeyframeRule] interface appends a /// [CSSKeyFrameRule] to the end of the rules. external void appendRule(String rule); diff --git a/web/lib/src/dom/css_typed_om.dart b/web/lib/src/dom/css_typed_om.dart index 4779c727..93521e0b 100644 --- a/web/lib/src/dom/css_typed_om.dart +++ b/web/lib/src/dom/css_typed_om.dart @@ -139,6 +139,12 @@ extension type CSSUnparsedValue._(JSObject _) implements CSSStyleValue, JSObject { external factory CSSUnparsedValue(JSArray members); + external CSSUnparsedSegment operator [](int index); + external void operator []=( + int index, + CSSUnparsedSegment val, + ); + /// The **`length`** read-only property of the /// [CSSUnparsedValue] interface returns the number of items in the object. external int get length; @@ -468,6 +474,8 @@ extension type CSSMathClamp._(JSObject _) implements CSSMathValue, JSObject { /// API documentation sourced from /// [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/CSSNumericArray). extension type CSSNumericArray._(JSObject _) implements JSObject { + external CSSNumericValue operator [](int index); + /// The read-only **`length`** property of the /// [CSSNumericArray] interface returns the number of /// [CSSNumericValue] objects in the list. @@ -486,6 +494,12 @@ extension type CSSTransformValue._(JSObject _) implements CSSStyleValue, JSObject { external factory CSSTransformValue(JSArray transforms); + external CSSTransformComponent operator [](int index); + external void operator []=( + int index, + CSSTransformComponent val, + ); + /// The **`toMatrix()`** method of the /// [CSSTransformValue] interface returns a [DOMMatrix] object. external DOMMatrix toMatrix(); diff --git a/web/lib/src/dom/dom.dart b/web/lib/src/dom/dom.dart index 38530e0f..b0d3ee8c 100644 --- a/web/lib/src/dom/dom.dart +++ b/web/lib/src/dom/dom.dart @@ -1568,6 +1568,7 @@ extension type Document._(JSObject _) implements Node, JSObject { /// fullscreen mode, restoring the previous state of the screen. This usually /// reverses the effects of a previous call to [Element.requestFullscreen]. external JSPromise exitFullscreen(); + external JSObject operator [](String name); /// The **`getElementsByName()`** method /// of the [Document] object returns a [NodeList] Collection of diff --git a/web/lib/src/dom/html.dart b/web/lib/src/dom/html.dart index f6c25c7e..7fd8de04 100644 --- a/web/lib/src/dom/html.dart +++ b/web/lib/src/dom/html.dart @@ -121,6 +121,8 @@ typedef WorkerType = String; /// API documentation sourced from /// [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/HTMLAllCollection). extension type HTMLAllCollection._(JSObject _) implements JSObject { + external Element operator [](int index); + /// The **`namedItem()`** method of the [HTMLAllCollection] interface returns /// the first [Element] in the collection whose `id` or `name` attribute /// matches the specified name, or `null` if no element matches. @@ -197,6 +199,10 @@ extension type RadioNodeList._(JSObject _) implements NodeList, JSObject { /// [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/HTMLOptionsCollection). extension type HTMLOptionsCollection._(JSObject _) implements HTMLCollection, JSObject { + external void operator []=( + int index, + HTMLOptionElement? option, + ); external void add( JSObject element, [ JSAny? before, @@ -1271,7 +1277,13 @@ extension type HTMLUnknownElement._(JSObject _) /// /// API documentation sourced from /// [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/DOMStringMap). -extension type DOMStringMap._(JSObject _) implements JSObject {} +extension type DOMStringMap._(JSObject _) implements JSObject { + external String operator [](String name); + external void operator []=( + String name, + String value, + ); +} /// The **`HTMLHtmlElement`** interface serves as the root node for a given HTML /// document. This object inherits the properties and methods described in the @@ -3728,6 +3740,8 @@ extension type MediaError._(JSObject _) implements JSObject { /// API documentation sourced from /// [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/AudioTrackList). extension type AudioTrackList._(JSObject _) implements EventTarget, JSObject { + external AudioTrack operator [](int index); + /// The **[AudioTrackList]** method **`getTrackById()`** returns the first /// [AudioTrack] object from the track list whose [AudioTrack.id] matches the /// specified string. @@ -3829,6 +3843,8 @@ extension type AudioTrack._(JSObject _) implements JSObject { /// API documentation sourced from /// [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/VideoTrackList). extension type VideoTrackList._(JSObject _) implements EventTarget, JSObject { + external VideoTrack operator [](int index); + /// The **[VideoTrackList]** method /// **`getTrackById()`** returns the first /// [VideoTrack] object from the track list whose [VideoTrack.id] matches the @@ -3949,6 +3965,8 @@ extension type VideoTrack._(JSObject _) implements JSObject { /// API documentation sourced from /// [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/TextTrackList). extension type TextTrackList._(JSObject _) implements EventTarget, JSObject { + external TextTrack operator [](int index); + /// The **[TextTrackList]** method /// **`getTrackById()`** returns the first /// [TextTrack] object from the track list whose @@ -4096,6 +4114,8 @@ extension type TextTrack._(JSObject _) implements EventTarget, JSObject { /// API documentation sourced from /// [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/TextTrackCueList). extension type TextTrackCueList._(JSObject _) implements JSObject { + external TextTrackCue operator [](int index); + /// The **`getCueById()`** method of the [TextTrackCueList] interface returns /// the first [VTTCue] in the list represented by the `TextTrackCueList` /// object whose identifier matches the value of `id`. @@ -5045,6 +5065,8 @@ extension type HTMLFormElement._(JSObject _) implements HTMLElement, JSObject { /// Creates an [HTMLFormElement] using the tag 'form'. HTMLFormElement() : _ = document.createElement('form'); + external JSObject operator [](JSAny indexOrName); + /// The **`HTMLFormElement.submit()`** method submits a given /// `form`. /// @@ -5900,6 +5922,10 @@ extension type HTMLSelectElement._(JSObject _) /// at the specified index from the options collection for this select /// element. external void remove([int index]); + external void operator []=( + int index, + HTMLOptionElement? option, + ); /// The **`HTMLSelectElement.checkValidity()`** method checks /// whether the element has any constraints and whether it satisfies them. If @@ -9823,6 +9849,8 @@ extension type DataTransfer._(JSObject _) implements JSObject { /// API documentation sourced from /// [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItemList). extension type DataTransferItemList._(JSObject _) implements JSObject { + external DataTransferItem operator [](int index); + /// The **`DataTransferItemList.add()`** method creates a new /// [DataTransferItem] using the specified data and adds it to the drag data /// list. The item may be a [File] or a string of a @@ -10136,6 +10164,7 @@ extension type Window._(JSObject _) implements EventTarget, JSObject { String target, String features, ]); + external JSObject operator [](String name); /// `window.alert()` instructs the browser to display a dialog with an /// optional message, and to wait until the user dismisses the dialog. diff --git a/web/lib/src/dom/media_source.dart b/web/lib/src/dom/media_source.dart index b030a7c4..460c9cc9 100644 --- a/web/lib/src/dom/media_source.dart +++ b/web/lib/src/dom/media_source.dart @@ -323,6 +323,8 @@ extension type SourceBuffer._(JSObject _) implements EventTarget, JSObject { /// API documentation sourced from /// [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/SourceBufferList). extension type SourceBufferList._(JSObject _) implements EventTarget, JSObject { + external SourceBuffer operator [](int index); + /// The **`length`** read-only property of the /// [SourceBufferList] interface returns the number of /// [SourceBuffer] objects in the list. diff --git a/web/lib/src/dom/svg.dart b/web/lib/src/dom/svg.dart index 82b6c5b1..c2be1228 100644 --- a/web/lib/src/dom/svg.dart +++ b/web/lib/src/dom/svg.dart @@ -510,6 +510,10 @@ extension type SVGNumberList._(JSObject _) implements JSObject { ); external SVGNumber removeItem(int index); external SVGNumber appendItem(SVGNumber newItem); + external void operator []=( + int index, + SVGNumber newItem, + ); external int get length; external int get numberOfItems; } @@ -534,6 +538,10 @@ extension type SVGLengthList._(JSObject _) implements JSObject { ); external SVGLength removeItem(int index); external SVGLength appendItem(SVGLength newItem); + external void operator []=( + int index, + SVGLength newItem, + ); external int get length; external int get numberOfItems; } @@ -558,6 +566,10 @@ extension type SVGStringList._(JSObject _) implements JSObject { ); external String removeItem(int index); external String appendItem(String newItem); + external void operator []=( + int index, + String newItem, + ); external int get numberOfItems; } @@ -1093,6 +1105,10 @@ extension type SVGTransformList._(JSObject _) implements JSObject { ); external SVGTransform removeItem(int index); external SVGTransform appendItem(SVGTransform newItem); + external void operator []=( + int index, + SVGTransform newItem, + ); external SVGTransform createSVGTransformFromMatrix([DOMMatrix2DInit matrix]); external SVGTransform? consolidate(); external int get numberOfItems; @@ -1317,6 +1333,10 @@ extension type SVGPointList._(JSObject _) implements JSObject { /// The **`appendItem()`** method of the [SVGPointList] interface adds a /// [SVGPoint] to the end of the list. external DOMPoint appendItem(DOMPoint newItem); + external void operator []=( + int index, + DOMPoint newItem, + ); /// The **`length`** read-only property of the [SVGPointList] interface /// returns the number of items in the list. diff --git a/web/test/smoke_test.dart b/web/test/smoke_test.dart index 41e04498..41990fe6 100644 --- a/web/test/smoke_test.dart +++ b/web/test/smoke_test.dart @@ -51,4 +51,15 @@ void main() { expect(div.style.textOverflow, equals('ellipsis')); expect(div.style.getPropertyValue('text-overflow'), equals('ellipsis')); }); + + test('External [] and []= operators work as expected.', () { + // [] + expect(window['document'], window.document); + expect(window.document['body'], window.document.body); + // []= + final select = HTMLSelectElement(); + final option = HTMLOptionElement(); + select[0] = option; + expect(select.item(0), option); + }); } diff --git a/web_generator/lib/src/translator.dart b/web_generator/lib/src/translator.dart index 7bedf781..63511712 100644 --- a/web_generator/lib/src/translator.dart +++ b/web_generator/lib/src/translator.dart @@ -264,6 +264,10 @@ class _RawType { nullable = union.nullable; typeParameter = union.typeParameter; } + + @override + String toString() => '_RawType(type: $type, nullable: $nullable, ' + 'typeParameter: $typeParameter)'; } class _Parameter { @@ -366,19 +370,21 @@ class _OverridableOperation extends _OverridableMember { bool _finalized = false; _MemberName _name; - final bool isStatic; + final String special; final _RawType returnType; final MdnProperty? mdnProperty; late final _MemberName name = _generateName(); - _OverridableOperation._(this._name, this.isStatic, this.returnType, + _OverridableOperation._(this._name, this.special, this.returnType, this.mdnProperty, super.parameters); - factory _OverridableOperation(idl.Operation operation, _MemberName name, + factory _OverridableOperation(idl.Operation operation, _MemberName memberName, MdnProperty? mdnProperty) => - _OverridableOperation._(name, operation.special == 'static', + _OverridableOperation._(memberName, operation.special, _getRawType(operation.idlType), mdnProperty, operation.arguments); + bool get isStatic => special == 'static'; + _MemberName _generateName() { // The name is determined after all updates are done, so finalize the // operation. @@ -404,7 +410,8 @@ class _OverridableOperation extends _OverridableMember { 'finalized.'); final jsOverride = _name.jsOverride; final thisName = jsOverride.isNotEmpty ? jsOverride : _name.name; - assert(thisName == that.name && isStatic == (that.special == 'static')); + assert((that.name.isEmpty || thisName == that.name) && + special == that.special); returnType.update(that.idlType); _processParameters(that.arguments); } @@ -449,7 +456,7 @@ class _PartialInterfacelike { case 'constructor': if (!_shouldGenerateMember(name)) break; final idlConstructor = member as idl.Constructor; - if (_hasHTMLConstructorAttribute(idlConstructor)) continue; + if (_hasHTMLConstructorAttribute(idlConstructor)) break; if (constructor == null) { constructor = _OverridableConstructor(idlConstructor); } else { @@ -487,16 +494,39 @@ class _PartialInterfacelike { break; case 'operation': final operation = member as idl.Operation; - final operationName = operation.name; - if (operationName.isEmpty) { - // TODO(joshualitt): We may be able to handle some unnamed - // operations. - continue; + final special = operation.special; + var operationName = operation.name; + // Some special operations may not have any MDN data and may be given + // a name that is irrelevant to the IDL, so avoid querying in that + // case and always emit. + var shouldQueryMDN = true; + switch (special) { + case 'getter': + if (operationName.isEmpty) { + operationName = 'operator []'; + shouldQueryMDN = false; + } + break; + case 'setter': + if (operationName.isEmpty) { + operationName = 'operator []='; + shouldQueryMDN = false; + } + break; + case 'static': + break; + default: + // TODO(srujzs): Should we handle other special operations, + // unnamed or otherwise? For now, don't emit the unnamed ones and + // do nothing special for the named ones. + if (operationName.isEmpty) break; } final isStatic = operation.special == 'static'; - if (!_shouldGenerateMember(operationName, isStatic: isStatic)) break; - final docs = - mdnInterface?.propertyFor(operationName, isStatic: isStatic); + if (shouldQueryMDN && + !_shouldGenerateMember(operationName, isStatic: isStatic)) break; + final docs = shouldQueryMDN + ? mdnInterface?.propertyFor(operationName, isStatic: isStatic) + : null; // Static member may have the same name as instance members in the // IDL, but not in Dart. Rename the static member if so. if (isStatic) { @@ -986,13 +1016,18 @@ class Translator { code.Method _operation(_OverridableOperation operation) { final memberName = operation.name; + // The IDL may return the value that is set. Dart doesn't let us use any + // type besides `void` for `[]=`, so we ignore the return value. + final returnType = memberName.name == 'operator []=' + ? code.TypeReference((b) => b..symbol = 'void') + : _typeReference(operation.returnType, returnType: true); return _overridableMember( operation, (requiredParameters, optionalParameters) => code.Method((b) => b ..annotations.addAll(_jsOverride(memberName.jsOverride)) ..external = true ..static = operation.isStatic - ..returns = _typeReference(operation.returnType, returnType: true) + ..returns = returnType ..name = memberName.name ..docs.addAll(operation.mdnProperty?.formattedDocs ?? []) ..requiredParameters.addAll(requiredParameters)